Update
@@ -0,0 +1,454 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>160</width>
|
||||
<height>560</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>560</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="titleFrame">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="mainFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="iconTitleFrame">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="iconFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="iconFrame2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="thumbnailLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="thumbnailFrame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>150</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="formFrame">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_14">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="customWidgetFrame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="previewButtons">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<property name="spacing">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="acceptButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>35</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>35</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
<pointsize>12</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectionSetButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>35</width>
|
||||
<height>35</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>35</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>icons/selectionSet2.png</normaloff>icons/selectionSet2.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="labelSpacer">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>2</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<slots>
|
||||
<slot>snapshot()</slot>
|
||||
<slot>apply()</slot>
|
||||
<slot>edit()</slot>
|
||||
<slot>showContextMenu()</slot>
|
||||
</slots>
|
||||
</ui>
|
||||
@@ -0,0 +1,304 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>167</width>
|
||||
<height>473</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Create Item</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="titleFrame">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="thumbnailLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="thumbnailFrame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>150</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>150</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="optionsFrame">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="previewButtons">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="acceptButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>35</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>35</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
<pointsize>12</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectionSetButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>35</width>
|
||||
<height>35</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>icons/selectionSet2.png</normaloff>icons/selectionSet2.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>2</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<slots>
|
||||
<slot>snapshot()</slot>
|
||||
<slot>save()</slot>
|
||||
</slots>
|
||||
</ui>
|
||||
@@ -0,0 +1,109 @@
|
||||
# Studio Library Items
|
||||
|
||||
Items are used for loading and saving data.
|
||||
|
||||
|
||||
### Pose Item
|
||||
|
||||
Saving and loading a pose items
|
||||
|
||||
```python
|
||||
from studiolibrarymaya import poseitem
|
||||
|
||||
path = "/AnimLibrary/Characters/Malcolm/malcolm.pose"
|
||||
objects = maya.cmds.ls(selection=True) or []
|
||||
namespaces = []
|
||||
|
||||
# Saving a pose item
|
||||
poseitem.save(path, objects=objects)
|
||||
|
||||
# Loading a pose item
|
||||
poseitem.load(path, objects=objects, namespaces=namespaces, key=True, mirror=False)
|
||||
```
|
||||
|
||||
### Animation Item
|
||||
|
||||
Saving and loading animation items
|
||||
|
||||
```python
|
||||
from studiolibrarymaya import animitem
|
||||
|
||||
path = "/AnimLibrary/Characters/Malcolm/malcolm.anim"
|
||||
objects = maya.cmds.ls(selection=True) or []
|
||||
|
||||
# Saving an animation item
|
||||
animitem.save(path, objects=objects, frameRange=(0, 200), bakeConnected=False)
|
||||
|
||||
# Loading an animation item
|
||||
animitem.load(path, objects=objects, option="replace all", connect=False, currentTime=False)
|
||||
```
|
||||
|
||||
Loading an animation to multiple namespaces
|
||||
|
||||
```python
|
||||
from studiolibrarymaya import animitem
|
||||
animitem.load(path, namespaces=["character1", "character2"], option="replace all")
|
||||
```
|
||||
|
||||
### Mirror Table Item
|
||||
|
||||
Saving and loading mirror tables
|
||||
|
||||
```python
|
||||
from studiolibrarymaya import mirroritem
|
||||
|
||||
path = "/AnimLibrary/Characters/Malcolm/malcolm.mirror"
|
||||
objects = maya.cmds.ls(selection=True) or []
|
||||
|
||||
# Saving a mirror table item
|
||||
mirroritem.save(path, objects=objects, leftSide="Lf", rightSide="Rf")
|
||||
|
||||
# Loading a mirror table item
|
||||
mirroritem.load(path, objects=objects, namespaces=[], option="swap", animation=True, time=None)
|
||||
```
|
||||
|
||||
### Selection Set Item
|
||||
|
||||
Saving and loading selection sets
|
||||
|
||||
```python
|
||||
from studiolibrarymaya import setsitem
|
||||
|
||||
path = "/AnimLibrary/Characters/Malcolm/malcolm.set"
|
||||
objects = maya.cmds.ls(selection=True) or []
|
||||
|
||||
# Saving a selection sets item
|
||||
setsitem.save(path, objects=objects)
|
||||
|
||||
# Loading a selection sets item
|
||||
setsitem.load(path, objects=objects, namespaces=[])
|
||||
```
|
||||
|
||||
|
||||
### Maya File Item (Development)
|
||||
|
||||
Saving and loading a Maya file item
|
||||
|
||||
This item can be used to load and save any Maya nodes. For example:
|
||||
locators and geometry.
|
||||
|
||||
```python
|
||||
from studiolibrarymaya import mayafileitem
|
||||
|
||||
path = "/AnimLibrary/Characters/Malcolm/malcolm.mayafile"
|
||||
objects = maya.cmds.ls(selection=True) or []
|
||||
|
||||
# Saving the item to disc
|
||||
mayafileitem.save(path, objects=objects)
|
||||
|
||||
# Loading the item from disc
|
||||
mayafileitem.load(path)
|
||||
```
|
||||
|
||||
### Example Item
|
||||
|
||||
If you would like to create a custom item for saving and loading different data types, then please have a look at the [exampleitem.py](exampleitem.py)
|
||||
|
||||
When developing a new item you can "Shift + Click" on the shelf icon which will reload all Studio Library modules including your changes to the item.
|
||||
|
||||
Make sure you register any new items using either the "itemRegistry" key in the [config file](../studiolibrary/config/default.json) or by calling `studiolibrary.registerItem(cls)`.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
@@ -0,0 +1,273 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from studiolibrarymaya import baseitem
|
||||
|
||||
try:
|
||||
import mutils
|
||||
import mutils.gui
|
||||
import maya.cmds
|
||||
except ImportError as error:
|
||||
print(error)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def save(path, *args, **kwargs):
|
||||
"""Convenience function for saving an AnimItem."""
|
||||
AnimItem(path).safeSave(*args, **kwargs)
|
||||
|
||||
|
||||
def load(path, *args, **kwargs):
|
||||
"""Convenience function for loading an AnimItem."""
|
||||
AnimItem(path).load(*args, **kwargs)
|
||||
|
||||
|
||||
class AnimItem(baseitem.BaseItem):
|
||||
|
||||
NAME = "Animation"
|
||||
EXTENSION = ".anim"
|
||||
ICON_PATH = os.path.join(os.path.dirname(__file__), "icons", "animation.png")
|
||||
TRANSFER_CLASS = mutils.Animation
|
||||
|
||||
def imageSequencePath(self):
|
||||
"""
|
||||
Return the image sequence location for playing the animation preview.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self.path() + "/sequence"
|
||||
|
||||
def loadSchema(self):
|
||||
"""
|
||||
Get schema used to load the animation item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
schema = super(AnimItem, self).loadSchema()
|
||||
|
||||
anim = mutils.Animation.fromPath(self.path())
|
||||
|
||||
startFrame = anim.startFrame() or 0
|
||||
endFrame = anim.endFrame() or 0
|
||||
|
||||
value = "{0} - {1}".format(startFrame, endFrame)
|
||||
schema.insert(3, {"name": "Range", "value": value})
|
||||
|
||||
schema.extend([
|
||||
{
|
||||
"name": "optionsGroup",
|
||||
"title": "Options",
|
||||
"type": "group",
|
||||
"order": 2,
|
||||
},
|
||||
{
|
||||
"name": "connect",
|
||||
"type": "bool",
|
||||
"inline": True,
|
||||
"default": False,
|
||||
"persistent": True,
|
||||
"label": {"name": ""}
|
||||
},
|
||||
{
|
||||
"name": "currentTime",
|
||||
"type": "bool",
|
||||
"inline": True,
|
||||
"default": True,
|
||||
"persistent": True,
|
||||
"label": {"name": ""}
|
||||
},
|
||||
{
|
||||
"name": "sourceTime",
|
||||
"title": "source",
|
||||
"type": "range",
|
||||
"default": [startFrame, endFrame],
|
||||
},
|
||||
{
|
||||
"name": "option",
|
||||
"type": "enum",
|
||||
"default": "replace all",
|
||||
"items": ["replace", "replace all", "insert", "merge"],
|
||||
"persistent": True,
|
||||
},
|
||||
])
|
||||
|
||||
return schema
|
||||
|
||||
def load(self, **kwargs):
|
||||
"""
|
||||
Load the animation for the given objects and options.
|
||||
|
||||
:type kwargs: dict
|
||||
"""
|
||||
anim = mutils.Animation.fromPath(self.path())
|
||||
anim.load(
|
||||
objects=kwargs.get("objects"),
|
||||
namespaces=kwargs.get("namespaces"),
|
||||
attrs=kwargs.get("attrs"),
|
||||
startFrame=kwargs.get("startFrame"),
|
||||
sourceTime=kwargs.get("sourceTime"),
|
||||
option=kwargs.get("option"),
|
||||
connect=kwargs.get("connect"),
|
||||
mirrorTable=kwargs.get("mirrorTable"),
|
||||
currentTime=kwargs.get("currentTime")
|
||||
)
|
||||
|
||||
def saveSchema(self):
|
||||
"""
|
||||
Get the schema for saving an animation item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
start, end = (1, 100)
|
||||
|
||||
try:
|
||||
start, end = mutils.currentFrameRange()
|
||||
except NameError as error:
|
||||
logger.exception(error)
|
||||
|
||||
return [
|
||||
{
|
||||
"name": "folder",
|
||||
"type": "path",
|
||||
"layout": "vertical",
|
||||
"visible": False,
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"layout": "vertical"
|
||||
},
|
||||
{
|
||||
"name": "fileType",
|
||||
"type": "enum",
|
||||
"layout": "vertical",
|
||||
"default": "mayaAscii",
|
||||
"items": ["mayaAscii", "mayaBinary"],
|
||||
"persistent": True
|
||||
},
|
||||
{
|
||||
"name": "frameRange",
|
||||
"type": "range",
|
||||
"layout": "vertical",
|
||||
"default": [start, end],
|
||||
"actions": [
|
||||
{
|
||||
"name": "From Timeline",
|
||||
"callback": mutils.playbackFrameRange
|
||||
},
|
||||
{
|
||||
"name": "From Selected Timeline",
|
||||
"callback": mutils.selectedFrameRange
|
||||
},
|
||||
{
|
||||
"name": "From Selected Objects",
|
||||
"callback": mutils.selectedObjectsFrameRange
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "byFrame",
|
||||
"type": "int",
|
||||
"default": 1,
|
||||
"layout": "vertical",
|
||||
"persistent": True
|
||||
},
|
||||
{
|
||||
"name": "comment",
|
||||
"type": "text",
|
||||
"layout": "vertical"
|
||||
},
|
||||
{
|
||||
"name": "bakeConnected",
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
"persistent": True,
|
||||
"inline": True,
|
||||
"label": {"visible": False}
|
||||
},
|
||||
{
|
||||
"name": "objects",
|
||||
"type": "objects",
|
||||
"label": {
|
||||
"visible": False
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
def saveValidator(self, **kwargs):
|
||||
"""
|
||||
The save validator is called when an input field has changed.
|
||||
|
||||
:type kwargs: dict
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
fields = super(AnimItem, self).saveValidator(**kwargs)
|
||||
|
||||
# Validate the by frame field
|
||||
if kwargs.get("byFrame") == '' or kwargs.get("byFrame", 1) < 1:
|
||||
fields.extend([
|
||||
{
|
||||
"name": "byFrame",
|
||||
"error": "The by frame value cannot be less than 1!"
|
||||
}
|
||||
])
|
||||
|
||||
# Validate the frame range field
|
||||
start, end = kwargs.get("frameRange", (0, 1))
|
||||
if start >= end:
|
||||
fields.extend([
|
||||
{
|
||||
"name": "frameRange",
|
||||
"error": "The start frame cannot be greater "
|
||||
"than or equal to the end frame!"
|
||||
}
|
||||
])
|
||||
|
||||
# Validate the current selection field
|
||||
objects = kwargs.get("objects")
|
||||
if objects and mutils.getDurationFromNodes(objects, time=[start, end]) <= 0:
|
||||
fields.extend([
|
||||
{
|
||||
"name": "objects",
|
||||
"error": "No animation was found on the selected object/s!"
|
||||
"Please create a pose instead!",
|
||||
}
|
||||
])
|
||||
|
||||
return fields
|
||||
|
||||
def save(self, objects, sequencePath="", **kwargs):
|
||||
"""
|
||||
Save the animation from the given objects to the item path.
|
||||
|
||||
:type objects: list[str]
|
||||
:type sequencePath: str
|
||||
:type kwargs: dict
|
||||
"""
|
||||
super(AnimItem, self).save(**kwargs)
|
||||
|
||||
# Save the animation to the given path location on disc
|
||||
mutils.saveAnim(
|
||||
objects,
|
||||
self.path(),
|
||||
time=kwargs.get("frameRange"),
|
||||
fileType=kwargs.get("fileType"),
|
||||
iconPath=kwargs.get("thumbnail"),
|
||||
metadata={"description": kwargs.get("comment", "")},
|
||||
sequencePath=sequencePath,
|
||||
bakeConnected=kwargs.get("bakeConnected")
|
||||
)
|
||||
@@ -0,0 +1,445 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
from studiovendor.Qt import QtGui
|
||||
from studiovendor.Qt import QtCore
|
||||
|
||||
import studiolibrary
|
||||
|
||||
from studiolibrarymaya import basesavewidget
|
||||
from studiolibrarymaya import baseloadwidget
|
||||
|
||||
try:
|
||||
import mutils
|
||||
import mutils.gui
|
||||
import maya.cmds
|
||||
except ImportError as error:
|
||||
print(error)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseItemSignals(QtCore.QObject):
|
||||
""""""
|
||||
loadValueChanged = QtCore.Signal(object, object)
|
||||
|
||||
|
||||
class BaseItem(studiolibrary.LibraryItem):
|
||||
|
||||
_baseItemSignals = BaseItemSignals()
|
||||
|
||||
loadValueChanged = _baseItemSignals.loadValueChanged
|
||||
|
||||
"""Base class for anim, pose, mirror and sets transfer items."""
|
||||
SAVE_WIDGET_CLASS = basesavewidget.BaseSaveWidget
|
||||
LOAD_WIDGET_CLASS = baseloadwidget.BaseLoadWidget
|
||||
|
||||
TRANSFER_CLASS = None
|
||||
TRANSFER_BASENAME = ""
|
||||
|
||||
def createLoadWidget(self, parent=None):
|
||||
widget = self.LOAD_WIDGET_CLASS(item=self, parent=parent)
|
||||
return widget
|
||||
|
||||
@classmethod
|
||||
def createSaveWidget(cls, parent=None, item=None):
|
||||
item = item or cls()
|
||||
widget = cls.SAVE_WIDGET_CLASS(item=item, parent=parent)
|
||||
return widget
|
||||
|
||||
@classmethod
|
||||
def showSaveWidget(cls, libraryWindow=None, item=None):
|
||||
"""
|
||||
Overriding this method to set the destination location
|
||||
for the save widget.
|
||||
|
||||
Triggered when the user clicks the item action in the new item menu.
|
||||
|
||||
:type libraryWindow: studiolibrary.LibraryWindow
|
||||
:type item: studiolibrary.LibraryItem or None
|
||||
"""
|
||||
item = item or cls()
|
||||
widget = cls.SAVE_WIDGET_CLASS(item=item, parent=libraryWindow)
|
||||
|
||||
if libraryWindow:
|
||||
path = libraryWindow.selectedFolderPath()
|
||||
|
||||
widget.setFolderPath(path)
|
||||
widget.setLibraryWindow(libraryWindow)
|
||||
|
||||
libraryWindow.setCreateWidget(widget)
|
||||
libraryWindow.folderSelectionChanged.connect(widget.setFolderPath)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialise a new instance for the given path.
|
||||
|
||||
:type path: str
|
||||
:type args: list
|
||||
:type kwargs: dict
|
||||
"""
|
||||
self._transferObject = None
|
||||
self._currentLoadValues = {}
|
||||
|
||||
studiolibrary.LibraryItem.__init__(self, *args, **kwargs)
|
||||
|
||||
def emitLoadValueChanged(self, field, value):
|
||||
"""
|
||||
Emit the load value changed to be validated.
|
||||
|
||||
:type field: str
|
||||
:type value: object
|
||||
"""
|
||||
self.loadValueChanged.emit(field, value)
|
||||
|
||||
def namespaces(self):
|
||||
"""
|
||||
Return the namesapces for this item depending on the namespace option.
|
||||
|
||||
:rtype: list[str] or None
|
||||
"""
|
||||
return self.currentLoadValue("namespaces")
|
||||
|
||||
def namespaceOption(self):
|
||||
"""
|
||||
Return the namespace option for this item.
|
||||
|
||||
:rtype: NamespaceOption or None
|
||||
"""
|
||||
return self.currentLoadValue("namespaceOption")
|
||||
|
||||
def doubleClicked(self):
|
||||
"""
|
||||
This method is called when the user double clicks the item.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
self.loadFromCurrentValues()
|
||||
|
||||
def transferPath(self):
|
||||
"""
|
||||
Return the disc location to transfer path.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
if self.TRANSFER_BASENAME:
|
||||
return os.path.join(self.path(), self.TRANSFER_BASENAME)
|
||||
else:
|
||||
return self.path()
|
||||
|
||||
def transferObject(self):
|
||||
"""
|
||||
Return the transfer object used to read and write the data.
|
||||
|
||||
:rtype: mutils.TransferObject
|
||||
"""
|
||||
if not self._transferObject:
|
||||
path = self.transferPath()
|
||||
self._transferObject = self.TRANSFER_CLASS.fromPath(path)
|
||||
return self._transferObject
|
||||
|
||||
def currentLoadValue(self, name):
|
||||
"""
|
||||
Get the current field value for the given name.
|
||||
|
||||
:type name: str
|
||||
:rtype: object
|
||||
"""
|
||||
return self._currentLoadValues.get(name)
|
||||
|
||||
def setCurrentLoadValues(self, values):
|
||||
"""
|
||||
Set the current field values for the the item.
|
||||
|
||||
:type values: dict
|
||||
"""
|
||||
self._currentLoadValues = values
|
||||
|
||||
def loadFromCurrentValues(self):
|
||||
"""Load the mirror table using the settings for this item."""
|
||||
kwargs = self._currentLoadValues
|
||||
objects = maya.cmds.ls(selection=True) or []
|
||||
|
||||
try:
|
||||
self.load(objects=objects, **kwargs)
|
||||
except Exception as error:
|
||||
self.showErrorDialog("Item Error", str(error))
|
||||
raise
|
||||
|
||||
def contextMenu(self, menu, items=None):
|
||||
"""
|
||||
This method is called when the user right clicks on this item.
|
||||
|
||||
:type menu: QtWidgets.QMenu
|
||||
:type items: list[BaseItem]
|
||||
:rtype: None
|
||||
"""
|
||||
from studiolibrarymaya import setsmenu
|
||||
|
||||
action = setsmenu.selectContentAction(self, parent=menu)
|
||||
menu.addAction(action)
|
||||
menu.addSeparator()
|
||||
|
||||
subMenu = self.createSelectionSetsMenu(menu, enableSelectContent=False)
|
||||
menu.addMenu(subMenu)
|
||||
menu.addSeparator()
|
||||
|
||||
studiolibrary.LibraryItem.contextMenu(self, menu, items=items)
|
||||
|
||||
def showSelectionSetsMenu(self, **kwargs):
|
||||
"""
|
||||
Show the selection sets menu for this item at the cursor position.
|
||||
|
||||
:rtype: QtWidgets.QAction
|
||||
"""
|
||||
menu = self.createSelectionSetsMenu(**kwargs)
|
||||
position = QtGui.QCursor().pos()
|
||||
action = menu.exec_(position)
|
||||
return action
|
||||
|
||||
def createSelectionSetsMenu(self, parent=None, enableSelectContent=True):
|
||||
"""
|
||||
Get a new instance of the selection sets menu.
|
||||
|
||||
:type parent: QtWidgets.QWidget
|
||||
:type enableSelectContent: bool
|
||||
:rtype: QtWidgets.QMenu
|
||||
"""
|
||||
from . import setsmenu
|
||||
|
||||
parent = parent or self.libraryWindow()
|
||||
|
||||
namespaces = self.namespaces()
|
||||
|
||||
menu = setsmenu.SetsMenu(
|
||||
item=self,
|
||||
parent=parent,
|
||||
namespaces=namespaces,
|
||||
enableSelectContent=enableSelectContent,
|
||||
)
|
||||
|
||||
return menu
|
||||
|
||||
def selectContent(self, namespaces=None, **kwargs):
|
||||
"""
|
||||
Select the contents of this item in the Maya scene.
|
||||
|
||||
:type namespaces: list[str]
|
||||
"""
|
||||
namespaces = namespaces or self.namespaces()
|
||||
|
||||
kwargs = kwargs or mutils.selectionModifiers()
|
||||
|
||||
msg = "Select content: Item.selectContent(namespacea={0}, kwargs={1})"
|
||||
msg = msg.format(namespaces, kwargs)
|
||||
logger.debug(msg)
|
||||
|
||||
try:
|
||||
self.transferObject().select(namespaces=namespaces, **kwargs)
|
||||
except Exception as error:
|
||||
self.showErrorDialog("Item Error", str(error))
|
||||
raise
|
||||
|
||||
def loadSchema(self):
|
||||
"""
|
||||
Get schema used to load the item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
modified = self.itemData().get("modified")
|
||||
if modified:
|
||||
modified = studiolibrary.timeAgo(modified)
|
||||
|
||||
count = self.transferObject().objectCount()
|
||||
plural = "s" if count > 1 else ""
|
||||
contains = str(count) + " Object" + plural
|
||||
|
||||
return [
|
||||
{
|
||||
"name": "infoGroup",
|
||||
"title": "Info",
|
||||
"type": "group",
|
||||
"order": 1,
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"value": self.name(),
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"value": self.transferObject().owner(),
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"value": modified,
|
||||
},
|
||||
{
|
||||
"name": "contains",
|
||||
"value": contains,
|
||||
},
|
||||
{
|
||||
"name": "comment",
|
||||
"value": self.transferObject().description() or "No comment",
|
||||
},
|
||||
{
|
||||
"name": "namespaceGroup",
|
||||
"title": "Namespace",
|
||||
"type": "group",
|
||||
"order": 10,
|
||||
},
|
||||
{
|
||||
"name": "namespaceOption",
|
||||
"title": "",
|
||||
"type": "radio",
|
||||
"value": "From file",
|
||||
"items": ["From file", "From selection", "Use custom"],
|
||||
"persistent": True,
|
||||
"persistentKey": "BaseItem",
|
||||
},
|
||||
{
|
||||
"name": "namespaces",
|
||||
"title": "",
|
||||
"type": "tags",
|
||||
"value": [],
|
||||
"items": mutils.namespace.getAll(),
|
||||
"persistent": True,
|
||||
"label": {"visible": False},
|
||||
"persistentKey": "BaseItem",
|
||||
},
|
||||
]
|
||||
|
||||
def loadValidator(self, **values):
|
||||
"""
|
||||
Called when the load fields change.
|
||||
|
||||
:type values: dict
|
||||
"""
|
||||
namespaces = values.get("namespaces")
|
||||
namespaceOption = values.get("namespaceOption")
|
||||
|
||||
if namespaceOption == "From file":
|
||||
namespaces = self.transferObject().namespaces()
|
||||
elif namespaceOption == "From selection":
|
||||
namespaces = mutils.namespace.getFromSelection()
|
||||
|
||||
fieldChanged = values.get("fieldChanged")
|
||||
if fieldChanged == "namespaces":
|
||||
values["namespaceOption"] = "Use custom"
|
||||
else:
|
||||
values["namespaces"] = namespaces
|
||||
|
||||
self._currentLoadValues = values
|
||||
|
||||
return [
|
||||
{
|
||||
"name": "namespaces",
|
||||
"value": values.get("namespaces"),
|
||||
},
|
||||
{
|
||||
"name": "namespaceOption",
|
||||
"value": values.get("namespaceOption"),
|
||||
},
|
||||
]
|
||||
|
||||
def load(self, **kwargs):
|
||||
"""
|
||||
Load the data from the transfer object.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
logger.debug(u'Loading: {0}'.format(self.transferPath()))
|
||||
|
||||
self.transferObject().load(**kwargs)
|
||||
|
||||
logger.debug(u'Loading: {0}'.format(self.transferPath()))
|
||||
|
||||
def saveSchema(self):
|
||||
"""
|
||||
The base save schema.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"name": "folder",
|
||||
"type": "path",
|
||||
"layout": "vertical",
|
||||
"visible": False,
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"layout": "vertical",
|
||||
},
|
||||
{
|
||||
"name": "comment",
|
||||
"type": "text",
|
||||
"layout": "vertical"
|
||||
},
|
||||
{
|
||||
"name": "objects",
|
||||
"type": "objects",
|
||||
"label": {"visible": False}
|
||||
},
|
||||
]
|
||||
|
||||
def saveValidator(self, **kwargs):
|
||||
"""
|
||||
The save validator is called when an input field has changed.
|
||||
|
||||
:type kwargs: dict
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
fields = []
|
||||
|
||||
if not kwargs.get("folder"):
|
||||
fields.append({
|
||||
"name": "folder",
|
||||
"error": "No folder selected. Please select a destination folder.",
|
||||
})
|
||||
|
||||
if not kwargs.get("name"):
|
||||
fields.append({
|
||||
"name": "name",
|
||||
"error": "No name specified. Please set a name before saving.",
|
||||
})
|
||||
|
||||
selection = maya.cmds.ls(selection=True) or []
|
||||
msg = ""
|
||||
if not selection:
|
||||
msg = "No objects selected. Please select at least one object."
|
||||
|
||||
fields.append({
|
||||
"name": "objects",
|
||||
"value": selection,
|
||||
"error": msg,
|
||||
},
|
||||
)
|
||||
|
||||
return fields
|
||||
|
||||
def save(self, thumbnail="", **kwargs):
|
||||
"""
|
||||
Save all the given object data to the item path on disc.
|
||||
|
||||
:type thumbnail: str
|
||||
:type kwargs: dict
|
||||
"""
|
||||
# Copy the icon path to the given path
|
||||
if thumbnail:
|
||||
basename = os.path.basename(thumbnail)
|
||||
shutil.copyfile(thumbnail, self.path() + "/" + basename)
|
||||
@@ -0,0 +1,255 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from studiovendor.Qt import QtGui
|
||||
from studiovendor.Qt import QtCore
|
||||
from studiovendor.Qt import QtWidgets
|
||||
|
||||
import studioqt
|
||||
import studiolibrary
|
||||
import studiolibrary.widgets
|
||||
|
||||
try:
|
||||
import mutils
|
||||
import mutils.gui
|
||||
import maya.cmds
|
||||
except ImportError as error:
|
||||
print(error)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseLoadWidget(QtWidgets.QWidget):
|
||||
|
||||
"""Base widget for loading items."""
|
||||
|
||||
def __init__(self, item, parent=None):
|
||||
"""
|
||||
:type item: studiolibrarymaya.BaseItem
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
"""
|
||||
QtWidgets.QWidget.__init__(self, parent)
|
||||
|
||||
self.setObjectName("studioLibraryBaseLoadWidget")
|
||||
self.setWindowTitle("Load Item")
|
||||
|
||||
self.loadUi()
|
||||
|
||||
self._item = item
|
||||
self._scriptJob = None
|
||||
self._formWidget = None
|
||||
|
||||
widget = self.createTitleWidget()
|
||||
widget.ui.menuButton.clicked.connect(self.showMenu)
|
||||
|
||||
self.ui.titleFrame.layout().addWidget(widget)
|
||||
|
||||
# Create the icon group box
|
||||
groupBox = studiolibrary.widgets.GroupBoxWidget("Icon", self.ui.iconFrame)
|
||||
groupBox.setObjectName("iconGroupBoxWidget")
|
||||
groupBox.setPersistent(True)
|
||||
self.ui.iconTitleFrame.layout().addWidget(groupBox)
|
||||
|
||||
# Create the thumbnail widget and set the image
|
||||
self.ui.thumbnailButton = studiolibrary.widgets.ImageSequenceWidget(self)
|
||||
self.ui.thumbnailButton.setObjectName("thumbnailButton")
|
||||
self.ui.thumbnailFrame.layout().insertWidget(0, self.ui.thumbnailButton)
|
||||
|
||||
if os.path.exists(item.imageSequencePath()):
|
||||
self.ui.thumbnailButton.setPath(item.imageSequencePath())
|
||||
|
||||
elif os.path.exists(item.thumbnailPath()):
|
||||
self.ui.thumbnailButton.setPath(item.thumbnailPath())
|
||||
|
||||
# Create the load widget and set the load schema
|
||||
self._formWidget = studiolibrary.widgets.FormWidget(self)
|
||||
self._formWidget.setObjectName(item.__class__.__name__ + "Form")
|
||||
self._formWidget.setSchema(item.loadSchema())
|
||||
self._formWidget.setValidator(self.loadValidator)
|
||||
self._formWidget.validate()
|
||||
|
||||
self.ui.formFrame.layout().addWidget(self._formWidget)
|
||||
|
||||
try:
|
||||
self.selectionChanged()
|
||||
self.setScriptJobEnabled(True)
|
||||
except NameError as error:
|
||||
logger.exception(error)
|
||||
|
||||
self.updateThumbnailSize()
|
||||
|
||||
self._item.loadValueChanged.connect(self._itemValueChanged)
|
||||
self.ui.acceptButton.clicked.connect(self.accept)
|
||||
self.ui.selectionSetButton.clicked.connect(self.showSelectionSetsMenu)
|
||||
|
||||
def loadValidator(self, *args, **kwargs):
|
||||
return self.item().loadValidator(*args, **kwargs)
|
||||
|
||||
def createTitleWidget(self):
|
||||
"""
|
||||
Create a new instance of the title bar widget.
|
||||
|
||||
:rtype: QtWidgets.QFrame
|
||||
"""
|
||||
class UI(object):
|
||||
"""Proxy class for attaching ui widgets as properties."""
|
||||
pass
|
||||
|
||||
titleWidget = QtWidgets.QFrame(self)
|
||||
titleWidget.setObjectName("titleWidget")
|
||||
titleWidget.ui = UI()
|
||||
|
||||
vlayout = QtWidgets.QVBoxLayout()
|
||||
vlayout.setSpacing(0)
|
||||
vlayout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
hlayout = QtWidgets.QHBoxLayout()
|
||||
hlayout.setSpacing(0)
|
||||
hlayout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
vlayout.addLayout(hlayout)
|
||||
|
||||
titleButton = QtWidgets.QLabel(self)
|
||||
titleButton.setText(self.item().NAME)
|
||||
titleButton.setObjectName("titleButton")
|
||||
titleWidget.ui.titleButton = titleButton
|
||||
|
||||
hlayout.addWidget(titleButton)
|
||||
|
||||
menuButton = QtWidgets.QPushButton(self)
|
||||
menuButton.setText("...")
|
||||
menuButton.setObjectName("menuButton")
|
||||
titleWidget.ui.menuButton = menuButton
|
||||
|
||||
hlayout.addWidget(menuButton)
|
||||
|
||||
titleWidget.setLayout(vlayout)
|
||||
|
||||
return titleWidget
|
||||
|
||||
def _itemValueChanged(self, field, value):
|
||||
"""
|
||||
Triggered when the a field value has changed.
|
||||
|
||||
:type field: str
|
||||
:type value: object
|
||||
"""
|
||||
self._formWidget.setValue(field, value)
|
||||
|
||||
def showMenu(self):
|
||||
"""
|
||||
Show the edit menu at the current cursor position.
|
||||
|
||||
:rtype: QtWidgets.QAction
|
||||
"""
|
||||
menu = QtWidgets.QMenu(self)
|
||||
|
||||
self.item().contextEditMenu(menu)
|
||||
|
||||
point = QtGui.QCursor.pos()
|
||||
point.setX(point.x() + 3)
|
||||
point.setY(point.y() + 3)
|
||||
|
||||
return menu.exec_(point)
|
||||
|
||||
def loadUi(self):
|
||||
"""Convenience method for loading the .ui file."""
|
||||
studioqt.loadUi(self, cls=BaseLoadWidget)
|
||||
|
||||
def formWidget(self):
|
||||
"""
|
||||
Get the form widget instance.
|
||||
|
||||
:rtype: studiolibrary.widgets.formwidget.FormWidget
|
||||
"""
|
||||
return self._formWidget
|
||||
|
||||
def setCustomWidget(self, widget):
|
||||
"""Convenience method for adding a custom widget when loading."""
|
||||
self.ui.customWidgetFrame.layout().addWidget(widget)
|
||||
|
||||
def item(self):
|
||||
"""
|
||||
Get the library item to be created.
|
||||
|
||||
:rtype: studiolibrarymaya.BaseItem
|
||||
"""
|
||||
return self._item
|
||||
|
||||
def showSelectionSetsMenu(self):
|
||||
"""Show the selection sets menu."""
|
||||
item = self.item()
|
||||
item.showSelectionSetsMenu()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""
|
||||
Overriding to adjust the image size when the widget changes size.
|
||||
|
||||
:type event: QtCore.QSizeEvent
|
||||
"""
|
||||
self.updateThumbnailSize()
|
||||
|
||||
def updateThumbnailSize(self):
|
||||
"""Update the thumbnail button to the size of the widget."""
|
||||
width = self.width() - 10
|
||||
if width > 250:
|
||||
width = 250
|
||||
|
||||
size = QtCore.QSize(width, width)
|
||||
self.ui.thumbnailButton.setIconSize(size)
|
||||
self.ui.thumbnailButton.setMaximumSize(size)
|
||||
self.ui.thumbnailFrame.setMaximumSize(size)
|
||||
|
||||
def close(self):
|
||||
"""Overriding this method to disable the script job when closed."""
|
||||
self.setScriptJobEnabled(False)
|
||||
|
||||
if self.formWidget():
|
||||
self.formWidget().savePersistentValues()
|
||||
|
||||
QtWidgets.QWidget.close(self)
|
||||
|
||||
def scriptJob(self):
|
||||
"""
|
||||
Get the script job object used when the users selection changes.
|
||||
|
||||
:rtype: mutils.ScriptJob
|
||||
"""
|
||||
return self._scriptJob
|
||||
|
||||
def setScriptJobEnabled(self, enabled):
|
||||
"""
|
||||
Enable the script job used when the users selection changes.
|
||||
|
||||
:type enabled: bool
|
||||
"""
|
||||
if enabled:
|
||||
if not self._scriptJob:
|
||||
event = ['SelectionChanged', self.selectionChanged]
|
||||
self._scriptJob = mutils.ScriptJob(event=event)
|
||||
else:
|
||||
sj = self.scriptJob()
|
||||
if sj:
|
||||
sj.kill()
|
||||
self._scriptJob = None
|
||||
|
||||
def selectionChanged(self):
|
||||
"""Triggered when the users Maya selection has changed."""
|
||||
self.formWidget().validate()
|
||||
|
||||
def accept(self):
|
||||
"""Called when the user clicks the apply button."""
|
||||
self.item().loadFromCurrentValues()
|
||||
@@ -0,0 +1,488 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from studiovendor.Qt import QtGui
|
||||
from studiovendor.Qt import QtCore
|
||||
from studiovendor.Qt import QtWidgets
|
||||
|
||||
import studioqt
|
||||
import studiolibrary.widgets
|
||||
|
||||
try:
|
||||
import mutils
|
||||
import mutils.gui
|
||||
import maya.cmds
|
||||
except ImportError as error:
|
||||
print(error)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseSaveWidget(QtWidgets.QWidget):
|
||||
|
||||
"""Base widget for saving new items."""
|
||||
|
||||
def __init__(self, item, parent=None):
|
||||
"""
|
||||
:type item: studiolibrarymaya.BaseItem
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
"""
|
||||
QtWidgets.QWidget.__init__(self, parent)
|
||||
|
||||
self.setObjectName("studioLibraryBaseSaveWidget")
|
||||
self.setWindowTitle("Save Item")
|
||||
|
||||
studioqt.loadUi(self)
|
||||
|
||||
self._item = item
|
||||
self._scriptJob = None
|
||||
self._formWidget = None
|
||||
|
||||
widget = self.createTitleWidget()
|
||||
widget.ui.menuButton.hide()
|
||||
self.ui.titleFrame.layout().addWidget(widget)
|
||||
|
||||
self.ui.acceptButton.clicked.connect(self.accept)
|
||||
self.ui.selectionSetButton.clicked.connect(self.showSelectionSetsMenu)
|
||||
|
||||
try:
|
||||
self.setScriptJobEnabled(True)
|
||||
except NameError as error:
|
||||
logger.exception(error)
|
||||
|
||||
self.createSequenceWidget()
|
||||
self.updateThumbnailSize()
|
||||
self.setItem(item)
|
||||
|
||||
def showMenu(self):
|
||||
"""
|
||||
Show the edit menu at the current cursor position.
|
||||
|
||||
:rtype: QtWidgets.QAction
|
||||
"""
|
||||
raise NotImplementedError("The title menu is not implemented")
|
||||
|
||||
def createTitleWidget(self):
|
||||
"""
|
||||
Create a new instance of the title bar widget.
|
||||
|
||||
:rtype: QtWidgets.QFrame
|
||||
"""
|
||||
|
||||
class UI(object):
|
||||
"""Proxy class for attaching ui widgets as properties."""
|
||||
pass
|
||||
|
||||
titleWidget = QtWidgets.QFrame(self)
|
||||
titleWidget.setObjectName("titleWidget")
|
||||
titleWidget.ui = UI()
|
||||
|
||||
vlayout = QtWidgets.QVBoxLayout()
|
||||
vlayout.setSpacing(0)
|
||||
vlayout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
hlayout = QtWidgets.QHBoxLayout()
|
||||
hlayout.setSpacing(0)
|
||||
hlayout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
vlayout.addLayout(hlayout)
|
||||
|
||||
titleButton = QtWidgets.QLabel(self)
|
||||
titleButton.setText(self.item().NAME)
|
||||
titleButton.setObjectName("titleButton")
|
||||
titleWidget.ui.titleButton = titleButton
|
||||
|
||||
hlayout.addWidget(titleButton)
|
||||
|
||||
menuButton = QtWidgets.QPushButton(self)
|
||||
menuButton.setText("...")
|
||||
menuButton.setObjectName("menuButton")
|
||||
titleWidget.ui.menuButton = menuButton
|
||||
|
||||
hlayout.addWidget(menuButton)
|
||||
|
||||
titleWidget.setLayout(vlayout)
|
||||
|
||||
return titleWidget
|
||||
|
||||
def createSequenceWidget(self):
|
||||
"""Create a sequence widget to replace the static thumbnail widget."""
|
||||
theme = None
|
||||
if self.parent():
|
||||
try:
|
||||
theme = self.parent().theme()
|
||||
except AttributeError as error:
|
||||
logger.debug("Cannot find theme for parent.")
|
||||
|
||||
self.ui.thumbnailButton = studiolibrary.widgets.ImageSequenceWidget(self, theme=theme)
|
||||
self.ui.thumbnailButton.setObjectName("thumbnailButton")
|
||||
self.ui.thumbnailFrame.layout().insertWidget(0, self.ui.thumbnailButton)
|
||||
self.ui.thumbnailButton.clicked.connect(self.thumbnailCapture)
|
||||
|
||||
text = "Click to capture a thumbnail from the current model panel.\n" \
|
||||
"CTRL + Click to show the capture window for better framing."
|
||||
|
||||
self.ui.thumbnailButton.setToolTip(text)
|
||||
|
||||
path = studiolibrary.resource.get("icons", "camera.svg")
|
||||
self.ui.thumbnailButton.addAction(
|
||||
path,
|
||||
"Capture new image",
|
||||
"Capture new image",
|
||||
self.thumbnailCapture
|
||||
)
|
||||
|
||||
path = studiolibrary.resource.get("icons", "expand.svg")
|
||||
self.ui.thumbnailButton.addAction(
|
||||
path,
|
||||
"Show Capture window",
|
||||
"Show Capture window",
|
||||
self.showCaptureWindow
|
||||
)
|
||||
|
||||
path = studiolibrary.resource.get("icons", "folder.svg")
|
||||
self.ui.thumbnailButton.addAction(
|
||||
path,
|
||||
"Load image from disk",
|
||||
"Load image from disk",
|
||||
self.showBrowseImageDialog
|
||||
)
|
||||
|
||||
icon = studiolibrary.resource.icon("thumbnail_solid.png")
|
||||
self.ui.thumbnailButton.setIcon(icon)
|
||||
|
||||
def setLibraryWindow(self, libraryWindow):
|
||||
"""
|
||||
Set the library widget for the item.
|
||||
|
||||
:type libraryWindow: studiolibrary.LibraryWindow
|
||||
:rtype: None
|
||||
"""
|
||||
self.item().setLibraryWindow(libraryWindow)
|
||||
|
||||
def libraryWindow(self):
|
||||
"""
|
||||
Get the library widget for the item.
|
||||
|
||||
:rtype: libraryWindow: studiolibrary.LibraryWindow
|
||||
"""
|
||||
return self.item().libraryWindow()
|
||||
|
||||
def formWidget(self):
|
||||
"""
|
||||
Get the form widget instance.
|
||||
|
||||
:rtype: studiolibrary.widgets.formwidget.FormWidget
|
||||
"""
|
||||
return self._formWidget
|
||||
|
||||
def item(self):
|
||||
"""
|
||||
Get the library item to be created.
|
||||
|
||||
:rtype: studiolibrarymaya.BaseItem
|
||||
"""
|
||||
return self._item
|
||||
|
||||
def setItem(self, item):
|
||||
"""
|
||||
Set the item to be created.
|
||||
|
||||
:type item: studiolibrarymaya.BaseItem
|
||||
"""
|
||||
self._item = item
|
||||
|
||||
if os.path.exists(item.imageSequencePath()):
|
||||
self.setThumbnailPath(item.imageSequencePath())
|
||||
elif not item.isTHUMBNAIL_PATH():
|
||||
self.setThumbnailPath(item.thumbnailPath())
|
||||
|
||||
schema = item.saveSchema()
|
||||
if schema:
|
||||
formWidget = studiolibrary.widgets.FormWidget(self)
|
||||
formWidget.setSchema(schema)
|
||||
formWidget.setValidator(item.saveValidator)
|
||||
|
||||
# Used when overriding the item
|
||||
name = os.path.basename(item.path())
|
||||
formWidget.setValues({"name": name})
|
||||
|
||||
self.ui.optionsFrame.layout().addWidget(formWidget)
|
||||
self._formWidget = formWidget
|
||||
|
||||
formWidget.validate()
|
||||
else:
|
||||
self.ui.optionsFrame.setVisible(False)
|
||||
|
||||
def showSelectionSetsMenu(self):
|
||||
"""
|
||||
Show the selection sets menu for the current folder path.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
from studiolibrarymaya import setsmenu
|
||||
|
||||
path = self.folderPath()
|
||||
position = QtGui.QCursor().pos()
|
||||
libraryWindow = self.libraryWindow()
|
||||
|
||||
menu = setsmenu.SetsMenu.fromPath(path, libraryWindow=libraryWindow)
|
||||
menu.exec_(position)
|
||||
|
||||
def close(self):
|
||||
"""Overriding the close method to disable the script job on close."""
|
||||
self._formWidget.savePersistentValues()
|
||||
self.setScriptJobEnabled(False)
|
||||
QtWidgets.QWidget.close(self)
|
||||
|
||||
def scriptJob(self):
|
||||
"""
|
||||
Get the script job object used when the users selection changes.
|
||||
|
||||
:rtype: mutils.ScriptJob
|
||||
"""
|
||||
return self._scriptJob
|
||||
|
||||
def setScriptJobEnabled(self, enabled):
|
||||
"""Set the script job used when the users selection changes."""
|
||||
if enabled:
|
||||
if not self._scriptJob:
|
||||
event = ['SelectionChanged', self.selectionChanged]
|
||||
self._scriptJob = mutils.ScriptJob(event=event)
|
||||
else:
|
||||
sj = self.scriptJob()
|
||||
if sj:
|
||||
sj.kill()
|
||||
self._scriptJob = None
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""
|
||||
Overriding to adjust the image size when the widget changes size.
|
||||
|
||||
:type event: QtCore.QSizeEvent
|
||||
"""
|
||||
self.updateThumbnailSize()
|
||||
|
||||
def updateThumbnailSize(self):
|
||||
"""Update the thumbnail button to the size of the widget."""
|
||||
width = self.width() - 10
|
||||
if width > 250:
|
||||
width = 250
|
||||
|
||||
size = QtCore.QSize(width, width)
|
||||
self.ui.thumbnailButton.setIconSize(size)
|
||||
self.ui.thumbnailButton.setMaximumSize(size)
|
||||
self.ui.thumbnailFrame.setMaximumSize(size)
|
||||
|
||||
def setFolderPath(self, path):
|
||||
"""
|
||||
Set the destination folder path.
|
||||
|
||||
:type path: str
|
||||
"""
|
||||
self.formWidget().setValue("folder", path)
|
||||
|
||||
def folderPath(self):
|
||||
"""
|
||||
Return the folder path.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self.formWidget().value("folder")
|
||||
|
||||
def selectionChanged(self):
|
||||
"""Triggered when the Maya selection changes."""
|
||||
if self.formWidget():
|
||||
self.formWidget().validate()
|
||||
|
||||
def showByFrameDialog(self):
|
||||
"""
|
||||
Show the by frame dialog.
|
||||
|
||||
:rtype: None or QtWidgets.QDialogButtonBox.StandardButton
|
||||
"""
|
||||
result = None
|
||||
text = 'To help speed up the playblast you can set the "by frame" ' \
|
||||
'to a number greater than 1. For example if the "by frame" ' \
|
||||
'is set to 2 it will playblast every second frame.'
|
||||
|
||||
options = self.formWidget().values()
|
||||
byFrame = options.get("byFrame", 1)
|
||||
startFrame, endFrame = options.get("frameRange", [None, None])
|
||||
|
||||
duration = 1
|
||||
if startFrame is not None and endFrame is not None:
|
||||
duration = endFrame - startFrame
|
||||
|
||||
if duration > 100 and byFrame == 1:
|
||||
|
||||
buttons = [
|
||||
QtWidgets.QDialogButtonBox.Ok,
|
||||
QtWidgets.QDialogButtonBox.Cancel
|
||||
]
|
||||
|
||||
result = studiolibrary.widgets.MessageBox.question(
|
||||
self.libraryWindow(),
|
||||
title="Playblast Tip",
|
||||
text=text,
|
||||
buttons=buttons,
|
||||
enableDontShowCheckBox=True,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def showBrowseImageDialog(self):
|
||||
"""Show a file dialog for choosing an image from disc."""
|
||||
fileDialog = QtWidgets.QFileDialog(
|
||||
self,
|
||||
caption="Open Image",
|
||||
filter="Image Files (*.png *.jpg)"
|
||||
)
|
||||
|
||||
fileDialog.fileSelected.connect(self.setThumbnailPath)
|
||||
fileDialog.exec_()
|
||||
|
||||
def showCaptureWindow(self):
|
||||
"""Show the capture window for framing."""
|
||||
self.thumbnailCapture(show=True)
|
||||
|
||||
def setThumbnailPath(self, path):
|
||||
"""
|
||||
Set the path to the thumbnail image or the image sequence directory.
|
||||
|
||||
:type path: str
|
||||
"""
|
||||
filename, extension = os.path.splitext(path)
|
||||
dst = studiolibrary.tempPath("thumbnail" + extension)
|
||||
|
||||
studiolibrary.copyPath(path, dst, force=True)
|
||||
|
||||
self.ui.thumbnailButton.setPath(dst)
|
||||
|
||||
def _capturedCallback(self, src):
|
||||
"""
|
||||
Triggered when capturing a thumbnail snapshot.
|
||||
|
||||
:type src: str
|
||||
"""
|
||||
path = os.path.dirname(src)
|
||||
self.setThumbnailPath(path)
|
||||
|
||||
def thumbnailCapture(self, show=False):
|
||||
"""Capture a playblast and save it to the temp thumbnail path."""
|
||||
options = self.formWidget().values()
|
||||
startFrame, endFrame = options.get("frameRange", [None, None])
|
||||
step = options.get("byFrame", 1)
|
||||
|
||||
# Ignore the by frame dialog when the control modifier is pressed.
|
||||
if not studioqt.isControlModifier():
|
||||
result = self.showByFrameDialog()
|
||||
if result == QtWidgets.QDialogButtonBox.Cancel:
|
||||
return
|
||||
|
||||
try:
|
||||
path = studiolibrary.tempPath("sequence", "thumbnail.jpg")
|
||||
mutils.gui.thumbnailCapture(
|
||||
show=show,
|
||||
path=path,
|
||||
startFrame=startFrame,
|
||||
endFrame=endFrame,
|
||||
step=step,
|
||||
clearCache=True,
|
||||
captured=self._capturedCallback,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
title = "Error while capturing thumbnail"
|
||||
studiolibrary.widgets.MessageBox.critical(self.libraryWindow(), title, str(e))
|
||||
raise
|
||||
|
||||
def showThumbnailCaptureDialog(self):
|
||||
"""
|
||||
Ask the user if they would like to capture a thumbnail.
|
||||
|
||||
:rtype: int
|
||||
"""
|
||||
title = "Create a thumbnail"
|
||||
text = "Would you like to capture a thumbnail?"
|
||||
|
||||
buttons = [
|
||||
QtWidgets.QDialogButtonBox.Yes,
|
||||
QtWidgets.QDialogButtonBox.Ignore,
|
||||
QtWidgets.QDialogButtonBox.Cancel
|
||||
]
|
||||
|
||||
|
||||
parent = self.item().libraryWindow()
|
||||
button = studiolibrary.widgets.MessageBox.question(
|
||||
parent,
|
||||
title,
|
||||
text,
|
||||
buttons=buttons
|
||||
)
|
||||
|
||||
if button == QtWidgets.QDialogButtonBox.Yes:
|
||||
self.thumbnailCapture()
|
||||
|
||||
return button
|
||||
|
||||
def accept(self):
|
||||
"""Triggered when the user clicks the save button."""
|
||||
try:
|
||||
self.formWidget().validate()
|
||||
|
||||
if self.formWidget().hasErrors():
|
||||
raise Exception("\n".join(self.formWidget().errors()))
|
||||
|
||||
hasFrames = self.ui.thumbnailButton.hasFrames()
|
||||
if not hasFrames:
|
||||
button = self.showThumbnailCaptureDialog()
|
||||
if button == QtWidgets.QDialogButtonBox.Cancel:
|
||||
return
|
||||
|
||||
name = self.formWidget().value("name")
|
||||
folder = self.formWidget().value("folder")
|
||||
path = folder + "/" + name
|
||||
thumbnail = self.ui.thumbnailButton.firstFrame()
|
||||
|
||||
self.save(path=path, thumbnail=thumbnail)
|
||||
|
||||
except Exception as e:
|
||||
studiolibrary.widgets.MessageBox.critical(
|
||||
self.libraryWindow(),
|
||||
"Error while saving",
|
||||
str(e),
|
||||
)
|
||||
raise
|
||||
|
||||
def save(self, path, thumbnail):
|
||||
"""
|
||||
Save the item with the given objects to the given disc location path.
|
||||
|
||||
:type path: str
|
||||
:type thumbnail: str
|
||||
"""
|
||||
kwargs = self.formWidget().values()
|
||||
sequencePath = self.ui.thumbnailButton.dirname()
|
||||
|
||||
item = self.item()
|
||||
item.setPath(path)
|
||||
item.safeSave(
|
||||
thumbnail=thumbnail,
|
||||
sequencePath=sequencePath,
|
||||
**kwargs
|
||||
)
|
||||
self.close()
|
||||
@@ -0,0 +1,92 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
NOTE: Make sure you register this item in the config.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from studiolibrarymaya import baseitem
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExampleItem(baseitem.BaseItem):
|
||||
|
||||
NAME = "Example"
|
||||
EXTENSION = ".example"
|
||||
ICON_PATH = os.path.join(os.path.dirname(__file__), "icons", "pose.png")
|
||||
|
||||
def loadSchema(self, **kwargs):
|
||||
"""
|
||||
Get the schema used for loading the example item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"name": "option",
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
"persistent": True,
|
||||
},
|
||||
]
|
||||
|
||||
def load(self, **kwargs):
|
||||
"""
|
||||
The load method is called with the user values from the load schema.
|
||||
|
||||
:type kwargs: dict
|
||||
"""
|
||||
logger.info("Loading %s %s", self.path(), kwargs)
|
||||
raise NotImplementedError("The load method is not implemented!")
|
||||
|
||||
def saveSchema(self, **kwargs):
|
||||
"""
|
||||
Get the schema used for saving the example item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return [
|
||||
# The 'name' field and the 'folder' field are both required by
|
||||
# the BaseItem. How this is handled may change in the future.
|
||||
{
|
||||
"name": "folder",
|
||||
"type": "path",
|
||||
"layout": "vertical",
|
||||
"visible": False,
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"layout": "vertical"
|
||||
},
|
||||
{
|
||||
"name": "fileType",
|
||||
"type": "enum",
|
||||
"layout": "vertical",
|
||||
"default": "mayaAscii",
|
||||
"items": ["mayaAscii", "mayaBinary"],
|
||||
"persistent": True
|
||||
},
|
||||
]
|
||||
|
||||
def save(self, **kwargs):
|
||||
"""
|
||||
The save method is called with the user values from the save schema.
|
||||
|
||||
:type kwargs: dict
|
||||
"""
|
||||
logger.info("Saving %s %s", self.path(), kwargs)
|
||||
raise NotImplementedError("The save method is not implemented!")
|
||||
|
After Width: | Height: | Size: 171 B |
|
After Width: | Height: | Size: 260 B |
|
After Width: | Height: | Size: 652 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 348 B |
|
After Width: | Height: | Size: 275 B |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 572 B |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,105 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
NOTE: Make sure you register this item in the config.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
import maya.cmds
|
||||
|
||||
from studiolibrarymaya import baseitem
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MayaFileItem(baseitem.BaseItem):
|
||||
|
||||
NAME = "Maya File"
|
||||
TYPE = NAME
|
||||
EXTENSION = ".mayafile"
|
||||
ICON_PATH = os.path.join(os.path.dirname(__file__), "icons", "file.png")
|
||||
|
||||
def transferPath(self):
|
||||
return self.path() + "/mayafile.ma"
|
||||
|
||||
def loadSchema(self, **kwargs):
|
||||
"""
|
||||
Get the schema used for loading the example item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return []
|
||||
|
||||
def load(self, **kwargs):
|
||||
"""
|
||||
The load method is called with the user values from the load schema.
|
||||
|
||||
:type kwargs: dict
|
||||
"""
|
||||
logger.info("Loading %s %s", self.path(), kwargs)
|
||||
|
||||
maya.cmds.file(
|
||||
self.transferPath(),
|
||||
i=True,
|
||||
type="mayaAscii",
|
||||
options="v=0;",
|
||||
preserveReferences=True,
|
||||
mergeNamespacesOnClash=False,
|
||||
)
|
||||
|
||||
def saveSchema(self, **kwargs):
|
||||
"""
|
||||
Get the schema used for saving the example item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return [
|
||||
# The 'name' field and the 'folder' field are both required by
|
||||
# the BaseItem. How this is handled may change in the future.
|
||||
{
|
||||
"name": "folder",
|
||||
"type": "path",
|
||||
"layout": "vertical",
|
||||
"visible": False,
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"layout": "vertical"
|
||||
},
|
||||
{
|
||||
"name": "objects",
|
||||
"type": "objects",
|
||||
"layout": "vertical"
|
||||
},
|
||||
]
|
||||
|
||||
def save(self, **kwargs):
|
||||
"""
|
||||
The save method is called with the user values from the save schema.
|
||||
|
||||
:type kwargs: dict
|
||||
"""
|
||||
logger.info("Saving %s %s", self.path(), kwargs)
|
||||
|
||||
super(MayaFileItem, self).save(**kwargs)
|
||||
|
||||
maya.cmds.file(
|
||||
self.transferPath(),
|
||||
type="mayaAscii",
|
||||
options="v=0;",
|
||||
preserveReferences=True,
|
||||
exportSelected=True
|
||||
)
|
||||
@@ -0,0 +1,195 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import uuid
|
||||
import logging
|
||||
|
||||
import maya.cmds
|
||||
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
|
||||
|
||||
import studiolibrary
|
||||
from studiolibrary import librarywindow
|
||||
|
||||
import mutils
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_mayaCloseScriptJob = None
|
||||
|
||||
|
||||
def enableMayaClosedEvent():
|
||||
"""
|
||||
Create a Maya script job to trigger on the event "quitApplication".
|
||||
|
||||
Enable the Maya closed event to save the library settings on close
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
global _mayaCloseScriptJob
|
||||
|
||||
if not _mayaCloseScriptJob:
|
||||
event = ['quitApplication', mayaClosedEvent]
|
||||
try:
|
||||
_mayaCloseScriptJob = mutils.ScriptJob(event=event)
|
||||
logger.debug("Maya close event enabled")
|
||||
except NameError as error:
|
||||
logging.exception(error)
|
||||
|
||||
|
||||
def disableMayaClosedEvent():
|
||||
"""Disable the maya closed event."""
|
||||
global _mayaCloseScriptJob
|
||||
|
||||
if _mayaCloseScriptJob:
|
||||
_mayaCloseScriptJob.kill()
|
||||
_mayaCloseScriptJob = None
|
||||
logger.debug("Maya close event disabled")
|
||||
|
||||
|
||||
def mayaClosedEvent():
|
||||
"""
|
||||
Create a Maya script job to trigger on the event "quitApplication".
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
for libraryWindow in librarywindow.LibraryWindow.instances():
|
||||
libraryWindow.saveSettings()
|
||||
|
||||
|
||||
class MayaLibraryWindow(MayaQWidgetDockableMixin, librarywindow.LibraryWindow):
|
||||
|
||||
def destroy(self):
|
||||
"""
|
||||
Overriding this method to avoid multiple script jobs when developing.
|
||||
"""
|
||||
disableMayaClosedEvent()
|
||||
librarywindow.LibraryWindow.destroy(self)
|
||||
|
||||
def setObjectName(self, name):
|
||||
"""
|
||||
Overriding to ensure the widget has a unique name for Maya.
|
||||
|
||||
:type name: str
|
||||
:rtype: None
|
||||
"""
|
||||
name = '{0}_{1}'.format(name, uuid.uuid4())
|
||||
|
||||
librarywindow.LibraryWindow.setObjectName(self, name)
|
||||
|
||||
def tabWidget(self):
|
||||
"""
|
||||
Return the tab widget for the library widget.
|
||||
|
||||
:rtype: QtWidgets.QTabWidget or None
|
||||
"""
|
||||
if self.isDockable():
|
||||
return self.parent().parent().parent()
|
||||
else:
|
||||
return None
|
||||
|
||||
def workspaceControlName(self):
|
||||
"""
|
||||
Return the workspaceControl name for the widget.
|
||||
|
||||
:rtype: str or None
|
||||
"""
|
||||
if self.isDockable() and self.parent():
|
||||
return self.parent().objectName()
|
||||
else:
|
||||
return None
|
||||
|
||||
def isDocked(self):
|
||||
"""
|
||||
Convenience method to return if the widget is docked.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
return not self.isFloating()
|
||||
|
||||
def isFloating(self):
|
||||
"""
|
||||
Return True if the widget is a floating window.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
name = self.workspaceControlName()
|
||||
if name:
|
||||
try:
|
||||
return maya.cmds.workspaceControl(name, q=True, floating=True)
|
||||
except AttributeError:
|
||||
msg = 'The "maya.cmds.workspaceControl" ' \
|
||||
'command is not supported!'
|
||||
|
||||
logger.warning(msg)
|
||||
|
||||
return True
|
||||
|
||||
def window(self):
|
||||
"""
|
||||
Overriding this method to return itself when docked.
|
||||
|
||||
This is used for saving the correct window position and size settings.
|
||||
|
||||
:rtype: QWidgets.QWidget
|
||||
"""
|
||||
if self.isDocked():
|
||||
return self
|
||||
else:
|
||||
return librarywindow.LibraryWindow.window(self)
|
||||
|
||||
def show(self, **kwargs):
|
||||
"""
|
||||
Show the library widget as a dockable window.
|
||||
|
||||
Set dockable=False in kwargs if you want to show the widget as a floating window.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
dockable = kwargs.get('dockable', True)
|
||||
MayaQWidgetDockableMixin.show(self, dockable=dockable)
|
||||
self.raise_()
|
||||
self.fixBorder()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""
|
||||
Override method to remove the border when the window size has changed.
|
||||
|
||||
:type event: QtCore.QEvent
|
||||
:rtype: None
|
||||
"""
|
||||
if event.isAccepted():
|
||||
if not self.isLoaded():
|
||||
self.fixBorder()
|
||||
|
||||
def floatingChanged(self, isFloating):
|
||||
"""
|
||||
Override method to remove the grey border when the parent has changed.
|
||||
|
||||
Only supported/triggered in Maya 2018
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
self.fixBorder()
|
||||
|
||||
def fixBorder(self):
|
||||
"""
|
||||
Remove the grey border around the tab widget.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
if self.tabWidget():
|
||||
self.tabWidget().setStyleSheet("border:0px;")
|
||||
|
||||
|
||||
enableMayaClosedEvent()
|
||||
@@ -0,0 +1,231 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from studiolibrarymaya import baseitem
|
||||
|
||||
try:
|
||||
import mutils
|
||||
import maya.cmds
|
||||
except ImportError as error:
|
||||
print(error)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def save(path, *args, **kwargs):
|
||||
"""Convenience function for saving a MirrorItem."""
|
||||
MirrorItem(path).safeSave(*args, **kwargs)
|
||||
|
||||
|
||||
def load(path, *args, **kwargs):
|
||||
"""Convenience function for loading a MirrorItem."""
|
||||
MirrorItem(path).load(*args, **kwargs)
|
||||
|
||||
|
||||
class MirrorItem(baseitem.BaseItem):
|
||||
|
||||
NAME = "Mirror Table"
|
||||
EXTENSION = ".mirror"
|
||||
ICON_PATH = os.path.join(os.path.dirname(__file__), "icons", "mirrortable.png")
|
||||
TRANSFER_CLASS = mutils.MirrorTable
|
||||
TRANSFER_BASENAME = "mirrortable.json"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
:type args: list
|
||||
:type kwargs: dict
|
||||
"""
|
||||
super(MirrorItem, self).__init__(*args, **kwargs)
|
||||
|
||||
self._validatedObjects = []
|
||||
|
||||
def loadSchema(self):
|
||||
"""
|
||||
Get schema used to load the mirror table item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
schema = super(MirrorItem, self).loadSchema()
|
||||
|
||||
mt = self.transferObject()
|
||||
|
||||
schema.insert(2, {"name": "Left", "value": mt.leftSide()})
|
||||
schema.insert(3, {"name": "Right", "value": mt.rightSide()})
|
||||
|
||||
schema.extend([
|
||||
{
|
||||
"name": "optionsGroup",
|
||||
"title": "Options",
|
||||
"type": "group",
|
||||
"order": 2,
|
||||
},
|
||||
{
|
||||
"name": "keysOption",
|
||||
"title": "Keys",
|
||||
"type": "radio",
|
||||
"value": "Selected Range",
|
||||
"items": ["All Keys", "Selected Range"],
|
||||
"persistent": True,
|
||||
},
|
||||
{
|
||||
"name": "option",
|
||||
"type": "enum",
|
||||
"default": "swap",
|
||||
"items": ["swap", "left to right", "right to left"],
|
||||
"persistent": True
|
||||
},
|
||||
])
|
||||
|
||||
return schema
|
||||
|
||||
def load(self, **kwargs):
|
||||
"""
|
||||
Load the current mirror table to the given objects.
|
||||
|
||||
:type kwargs: dict
|
||||
"""
|
||||
mt = mutils.MirrorTable.fromPath(self.path() + "/mirrortable.json")
|
||||
mt.load(
|
||||
objects=kwargs.get("objects"),
|
||||
namespaces=kwargs.get("namespaces"),
|
||||
option=kwargs.get("option"),
|
||||
keysOption=kwargs.get("keysOption"),
|
||||
time=kwargs.get("time")
|
||||
)
|
||||
|
||||
def saveSchema(self):
|
||||
"""
|
||||
Get the fields used to save the item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"name": "folder",
|
||||
"type": "path",
|
||||
"layout": "vertical",
|
||||
"visible": False,
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"layout": "vertical"
|
||||
},
|
||||
{
|
||||
"name": "mirrorPlane",
|
||||
"type": "buttonGroup",
|
||||
"default": "YZ",
|
||||
"layout": "vertical",
|
||||
"items": ["YZ", "XY", "XZ"],
|
||||
},
|
||||
{
|
||||
"name": "leftSide",
|
||||
"type": "string",
|
||||
"layout": "vertical",
|
||||
"menu": {
|
||||
"name": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "rightSide",
|
||||
"type": "string",
|
||||
"layout": "vertical",
|
||||
"menu": {
|
||||
"name": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "comment",
|
||||
"type": "text",
|
||||
"layout": "vertical"
|
||||
},
|
||||
{
|
||||
"name": "objects",
|
||||
"type": "objects",
|
||||
"label": {
|
||||
"visible": False
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
def saveValidator(self, **kwargs):
|
||||
"""
|
||||
The save validator is called when an input field has changed.
|
||||
|
||||
:type kwargs: dict
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
results = super(MirrorItem, self).saveValidator(**kwargs)
|
||||
|
||||
objects = maya.cmds.ls(selection=True) or []
|
||||
|
||||
dirty = kwargs.get("fieldChanged") in ["leftSide", "rightSide"]
|
||||
dirty = dirty or self._validatedObjects != objects
|
||||
|
||||
if dirty:
|
||||
self._validatedObjects = objects
|
||||
|
||||
leftSide = kwargs.get("leftSide", "")
|
||||
if not leftSide:
|
||||
leftSide = mutils.MirrorTable.findLeftSide(objects)
|
||||
|
||||
rightSide = kwargs.get("rightSide", "")
|
||||
if not rightSide:
|
||||
rightSide = mutils.MirrorTable.findRightSide(objects)
|
||||
|
||||
mt = mutils.MirrorTable.fromObjects(
|
||||
[],
|
||||
leftSide=leftSide,
|
||||
rightSide=rightSide
|
||||
)
|
||||
|
||||
results.extend([
|
||||
{
|
||||
"name": "leftSide",
|
||||
"value": leftSide,
|
||||
"menu": {
|
||||
"name": str(mt.leftCount(objects))
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "rightSide",
|
||||
"value": rightSide,
|
||||
"menu": {
|
||||
"name": str(mt.rightCount(objects))
|
||||
}
|
||||
},
|
||||
])
|
||||
|
||||
return results
|
||||
|
||||
def save(self, objects, **kwargs):
|
||||
"""
|
||||
Save the given objects to the item path on disc.
|
||||
|
||||
:type objects: list[str]
|
||||
:type kwargs: dict
|
||||
"""
|
||||
super(MirrorItem, self).save(**kwargs)
|
||||
|
||||
# Save the mirror table to the given location
|
||||
mutils.saveMirrorTable(
|
||||
self.path() + "/mirrortable.json",
|
||||
objects,
|
||||
metadata={"description": kwargs.get("comment", "")},
|
||||
leftSide=kwargs.get("leftSide"),
|
||||
rightSide=kwargs.get("rightSide"),
|
||||
mirrorPlane=kwargs.get("mirrorPlane"),
|
||||
)
|
||||
@@ -0,0 +1,392 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
import studiolibrary
|
||||
|
||||
from studiovendor.Qt import QtGui
|
||||
from studiovendor.Qt import QtCore
|
||||
from studiovendor.Qt import QtWidgets
|
||||
|
||||
from studiolibrarymaya import baseitem
|
||||
from studiolibrarymaya import baseloadwidget
|
||||
|
||||
try:
|
||||
import mutils
|
||||
import maya.cmds
|
||||
except ImportError as error:
|
||||
print(error)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def save(path, *args, **kwargs):
|
||||
"""Convenience function for saving a PoseItem."""
|
||||
PoseItem(path).safeSave(*args, **kwargs)
|
||||
|
||||
|
||||
def load(path, *args, **kwargs):
|
||||
"""Convenience function for loading a PoseItem."""
|
||||
PoseItem(path).load(*args, **kwargs)
|
||||
|
||||
|
||||
class PoseLoadWidget(baseloadwidget.BaseLoadWidget):
|
||||
|
||||
@classmethod
|
||||
def createFromPath(cls, path, theme=None):
|
||||
|
||||
item = PoseItem(path)
|
||||
widget = cls(item)
|
||||
|
||||
if not theme:
|
||||
import studiolibrary.widgets
|
||||
theme = studiolibrary.widgets.Theme()
|
||||
widget.setStyleSheet(theme.styleSheet())
|
||||
|
||||
widget.show()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PoseLoadWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
self._options = None
|
||||
self._pose = mutils.Pose.fromPath(self.item().transferPath())
|
||||
|
||||
self.ui.blendFrame = QtWidgets.QFrame(self)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
self.ui.blendFrame.setLayout(layout)
|
||||
|
||||
if self.item().libraryWindow():
|
||||
self.item().libraryWindow().itemsWidget().itemSliderMoved.connect(self._sliderMoved)
|
||||
self.item().libraryWindow().itemsWidget().itemSliderReleased.connect(self._sliderReleased)
|
||||
self.item().libraryWindow().itemsWidget().itemDoubleClicked.connect(self._itemDoubleClicked)
|
||||
|
||||
self.ui.blendSlider = QtWidgets.QSlider(self)
|
||||
self.ui.blendSlider.setObjectName("blendSlider")
|
||||
self.ui.blendSlider.setMinimum(-30)
|
||||
self.ui.blendSlider.setMaximum(130)
|
||||
self.ui.blendSlider.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.ui.blendSlider.sliderMoved.connect(self._sliderMoved)
|
||||
self.ui.blendSlider.sliderReleased.connect(self._sliderReleased)
|
||||
|
||||
self.ui.blendEdit = QtWidgets.QLineEdit(self)
|
||||
self.ui.blendEdit.setObjectName("blendEdit")
|
||||
self.ui.blendEdit.setText("0")
|
||||
self.ui.blendEdit.editingFinished.connect(self._blendEditChanged)
|
||||
|
||||
validator = QtGui.QIntValidator(-200, 200, self)
|
||||
self.ui.blendEdit.setValidator(validator)
|
||||
|
||||
layout.addWidget(self.ui.blendSlider)
|
||||
layout.addWidget(self.ui.blendEdit)
|
||||
|
||||
self.setCustomWidget(self.ui.blendFrame)
|
||||
|
||||
def _itemDoubleClicked(self):
|
||||
"""Triggered when the user double-clicks a pose."""
|
||||
self.accept()
|
||||
|
||||
def _sliderMoved(self, value):
|
||||
"""Triggered when the user moves the slider handle."""
|
||||
self.load(blend=value, batchMode=True)
|
||||
|
||||
def _sliderReleased(self):
|
||||
"""Triggered when the user releases the slider handle."""
|
||||
try:
|
||||
self.load(blend=self.ui.blendSlider.value(), refresh=False)
|
||||
self._options = {}
|
||||
except Exception as error:
|
||||
self.item().showErrorDialog("Item Error", str(error))
|
||||
raise
|
||||
|
||||
def _blendEditChanged(self, *args):
|
||||
"""Triggered when the user changes the blend edit value."""
|
||||
try:
|
||||
self._options = {}
|
||||
self.load(blend=int(self.ui.blendEdit.text()), clearSelection=False)
|
||||
except Exception as error:
|
||||
self.item().showErrorDialog("Item Error", str(error))
|
||||
raise
|
||||
|
||||
def loadValidator(self, *args, **kwargs):
|
||||
self._options = {}
|
||||
return super(PoseLoadWidget, self).loadValidator(*args, **kwargs)
|
||||
|
||||
def accept(self):
|
||||
"""Triggered when the user clicks the apply button."""
|
||||
try:
|
||||
self._options = {}
|
||||
self.load(clearCache=True, clearSelection=False)
|
||||
except Exception as error:
|
||||
self.item().showErrorDialog("Item Error", str(error))
|
||||
raise
|
||||
|
||||
def load(
|
||||
self,
|
||||
blend=100.0,
|
||||
refresh=True,
|
||||
batchMode=False,
|
||||
clearCache=False,
|
||||
clearSelection=True,
|
||||
):
|
||||
"""
|
||||
Load the pose item with the current user settings from disc.
|
||||
|
||||
:type blend: float
|
||||
:type refresh: bool
|
||||
:type batchMode: bool
|
||||
:type clearCache: bool
|
||||
:type clearSelection: bool
|
||||
"""
|
||||
if batchMode:
|
||||
self.formWidget().setValidatorEnabled(False)
|
||||
else:
|
||||
self.formWidget().setValidatorEnabled(True)
|
||||
|
||||
if not self._options:
|
||||
|
||||
self._options = self.formWidget().values()
|
||||
self._options['mirrorTable'] = self.item().mirrorTable()
|
||||
self._options['objects'] = maya.cmds.ls(selection=True) or []
|
||||
|
||||
if not self._options["searchAndReplaceEnabled"]:
|
||||
self._options["searchAndReplace"] = None
|
||||
|
||||
del self._options["namespaceOption"]
|
||||
del self._options["searchAndReplaceEnabled"]
|
||||
|
||||
self.ui.blendEdit.blockSignals(True)
|
||||
self.ui.blendSlider.setValue(blend)
|
||||
self.ui.blendEdit.setText(str(int(blend)))
|
||||
self.ui.blendEdit.blockSignals(False)
|
||||
|
||||
if self.item().libraryWindow():
|
||||
|
||||
self.item().libraryWindow().itemsWidget().blockSignals(True)
|
||||
self.item().setSliderValue(blend)
|
||||
self.item().libraryWindow().itemsWidget().blockSignals(False)
|
||||
|
||||
if batchMode:
|
||||
self.item().libraryWindow().showToastMessage("Blend: {0}%".format(blend))
|
||||
|
||||
try:
|
||||
self._pose.load(
|
||||
blend=blend,
|
||||
refresh=refresh,
|
||||
batchMode=batchMode,
|
||||
clearCache=clearCache,
|
||||
clearSelection=clearSelection,
|
||||
**self._options
|
||||
)
|
||||
finally:
|
||||
self.item().setSliderDown(batchMode)
|
||||
|
||||
|
||||
def findMirrorTable(path):
|
||||
"""
|
||||
Get the mirror table object for this item.
|
||||
|
||||
:rtype: mutils.MirrorTable or None
|
||||
"""
|
||||
mirrorTable = None
|
||||
|
||||
mirrorTablePaths = list(studiolibrary.walkup(
|
||||
path,
|
||||
match=lambda path: path.endswith(".mirror"),
|
||||
depth=10,
|
||||
)
|
||||
)
|
||||
|
||||
if mirrorTablePaths:
|
||||
mirrorTablePath = mirrorTablePaths[0]
|
||||
|
||||
path = os.path.join(mirrorTablePath, "mirrortable.json")
|
||||
|
||||
if path:
|
||||
mirrorTable = mutils.MirrorTable.fromPath(path)
|
||||
|
||||
return mirrorTable
|
||||
|
||||
|
||||
class PoseItem(baseitem.BaseItem):
|
||||
|
||||
NAME = "Pose"
|
||||
EXTENSION = ".pose"
|
||||
ICON_PATH = os.path.join(os.path.dirname(__file__), "icons", "pose.png")
|
||||
LOAD_WIDGET_CLASS = PoseLoadWidget
|
||||
TRANSFER_CLASS = mutils.Pose
|
||||
TRANSFER_BASENAME = "pose.json"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Create a new instance of the pose item from the given path.
|
||||
|
||||
:type path: str
|
||||
:type args: list
|
||||
:type kwargs: dict
|
||||
"""
|
||||
super(PoseItem, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setSliderEnabled(True)
|
||||
|
||||
def mirrorTableSearchAndReplace(self):
|
||||
"""
|
||||
Get the values for search and replace from the mirror table.
|
||||
|
||||
:rtype: (str, str)
|
||||
"""
|
||||
mirrorTable = findMirrorTable(self.path())
|
||||
|
||||
return mirrorTable.leftSide(), mirrorTable.rightSide()
|
||||
|
||||
def switchSearchAndReplace(self):
|
||||
"""
|
||||
Switch the values of the search and replace field.
|
||||
|
||||
:rtype: (str, str)
|
||||
"""
|
||||
values = self.currentLoadValue("searchAndReplace")
|
||||
return values[1], values[0]
|
||||
|
||||
def clearSearchAndReplace(self):
|
||||
"""
|
||||
Clear the search and replace field.
|
||||
|
||||
:rtype: (str, str)
|
||||
"""
|
||||
return '', ''
|
||||
|
||||
def doubleClicked(self):
|
||||
"""Triggered when the user double-click the item."""
|
||||
pass
|
||||
|
||||
def loadSchema(self):
|
||||
"""
|
||||
Get schema used to load the pose item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
schema = [
|
||||
{
|
||||
"name": "optionsGroup",
|
||||
"title": "Options",
|
||||
"type": "group",
|
||||
"order": 2,
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"type": "bool",
|
||||
"inline": True,
|
||||
"default": False,
|
||||
"persistent": True,
|
||||
},
|
||||
{
|
||||
"name": "mirror",
|
||||
"type": "bool",
|
||||
"inline": True,
|
||||
"default": False,
|
||||
"persistent": True,
|
||||
},
|
||||
{
|
||||
"name": "additive",
|
||||
"type": "bool",
|
||||
"inline": True,
|
||||
"default": False,
|
||||
"persistent": True,
|
||||
},
|
||||
{
|
||||
"name": "searchAndReplaceEnabled",
|
||||
"title": "Search and Replace",
|
||||
"type": "bool",
|
||||
"inline": True,
|
||||
"default": False,
|
||||
"persistent": True,
|
||||
},
|
||||
{
|
||||
"name": "searchAndReplace",
|
||||
"title": "",
|
||||
"type": "stringDouble",
|
||||
"default": ("", ""),
|
||||
"placeholder": ("search", "replace"),
|
||||
"persistent": True,
|
||||
"actions": [
|
||||
{
|
||||
"name": "Switch",
|
||||
"callback": self.switchSearchAndReplace,
|
||||
},
|
||||
{
|
||||
"name": "Clear",
|
||||
"callback": self.clearSearchAndReplace,
|
||||
},
|
||||
{
|
||||
"name": "From Mirror Table",
|
||||
"enabled": bool(findMirrorTable(self.path())),
|
||||
"callback": self.mirrorTableSearchAndReplace,
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
schema.extend(super(PoseItem, self).loadSchema())
|
||||
|
||||
return schema
|
||||
|
||||
def mirrorTable(self):
|
||||
return findMirrorTable(self.path())
|
||||
|
||||
def loadValidator(self, **values):
|
||||
"""
|
||||
Using the validator to change the state of the mirror option.
|
||||
|
||||
:type values: dict
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
# Mirror check box
|
||||
mirrorTip = "Cannot find a mirror table!"
|
||||
mirrorTable = findMirrorTable(self.path())
|
||||
if mirrorTable:
|
||||
mirrorTip = "Using mirror table: %s" % mirrorTable.path()
|
||||
|
||||
fields = [
|
||||
{
|
||||
"name": "mirror",
|
||||
"toolTip": mirrorTip,
|
||||
"enabled": mirrorTable is not None,
|
||||
},
|
||||
{
|
||||
"name": "searchAndReplace",
|
||||
"visible": values.get("searchAndReplaceEnabled")
|
||||
},
|
||||
]
|
||||
|
||||
fields.extend(super(PoseItem, self).loadValidator(**values))
|
||||
|
||||
return fields
|
||||
|
||||
def save(self, objects, **kwargs):
|
||||
"""
|
||||
Save all the given object data to the item path on disc.
|
||||
|
||||
:type objects: list[str]
|
||||
:type kwargs: dict
|
||||
"""
|
||||
super(PoseItem, self).save(**kwargs)
|
||||
|
||||
# Save the pose to the temp location
|
||||
mutils.savePose(
|
||||
self.transferPath(),
|
||||
objects,
|
||||
metadata={"description": kwargs.get("comment", "")}
|
||||
)
|
||||
@@ -0,0 +1,65 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
try:
|
||||
import mutils
|
||||
except ImportError as error:
|
||||
print(error)
|
||||
|
||||
from studiolibrarymaya import baseitem
|
||||
|
||||
|
||||
def save(path, *args, **kwargs):
|
||||
"""Convenience function for saving a SetsItem."""
|
||||
SetsItem(path).safeSave(*args, **kwargs)
|
||||
|
||||
|
||||
def load(path, *args, **kwargs):
|
||||
"""Convenience function for loading a SetsItem."""
|
||||
SetsItem(path).load(*args, **kwargs)
|
||||
|
||||
|
||||
class SetsItem(baseitem.BaseItem):
|
||||
|
||||
NAME = "Selection Set"
|
||||
EXTENSION = ".set"
|
||||
ICON_PATH = os.path.join(os.path.dirname(__file__), "icons", "selectionSet.png")
|
||||
TRANSFER_CLASS = mutils.SelectionSet
|
||||
TRANSFER_BASENAME = "set.json"
|
||||
|
||||
def loadFromCurrentValues(self):
|
||||
"""Load the selection set using the settings for this item."""
|
||||
self.load(namespaces=self.namespaces())
|
||||
|
||||
def load(self, namespaces=None):
|
||||
"""
|
||||
:type namespaces: list[str] | None
|
||||
"""
|
||||
self.selectContent(namespaces=namespaces)
|
||||
|
||||
def save(self, objects, **kwargs):
|
||||
"""
|
||||
Save all the given object data to the item path on disc.
|
||||
|
||||
:type objects: list[str]
|
||||
:type kwargs: dict
|
||||
"""
|
||||
super(SetsItem, self).save(**kwargs)
|
||||
|
||||
# Save the selection set to the given path
|
||||
mutils.saveSelectionSet(
|
||||
self.path() + "/set.json",
|
||||
objects,
|
||||
metadata={"description": kwargs.get("comment", "")}
|
||||
)
|
||||
@@ -0,0 +1,167 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
from studiovendor.Qt import QtGui
|
||||
from studiovendor.Qt import QtWidgets
|
||||
|
||||
import studiolibrary
|
||||
|
||||
from studiolibrarymaya import setsitem
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
DIRNAME = os.path.dirname(__file__)
|
||||
ARROW_ICON_PATH = os.path.join(DIRNAME, "icons", "arrow.png")
|
||||
|
||||
|
||||
def selectContentAction(item, parent=None):
|
||||
"""
|
||||
:param item: mayabaseitem.MayaBaseItem
|
||||
:param parent: QtWidgets.QMenu
|
||||
"""
|
||||
arrowIcon = QtGui.QIcon(ARROW_ICON_PATH)
|
||||
action = QtWidgets.QAction(arrowIcon, "Select content", parent)
|
||||
action.triggered.connect(item.selectContent)
|
||||
return action
|
||||
|
||||
|
||||
def showSetsMenu(path, **kwargs):
|
||||
"""
|
||||
Show the frame range menu at the current cursor position.
|
||||
|
||||
:type path: str
|
||||
:rtype: QtWidgets.QAction
|
||||
"""
|
||||
menu = SetsMenu.fromPath(path, **kwargs)
|
||||
position = QtGui.QCursor().pos()
|
||||
action = menu.exec_(position)
|
||||
return action
|
||||
|
||||
|
||||
class SetsMenu(QtWidgets.QMenu):
|
||||
|
||||
@classmethod
|
||||
def fromPath(cls, path, parent=None, libraryWindow=None, **kwargs):
|
||||
"""
|
||||
Return a new SetMenu instance from the given path.
|
||||
|
||||
:type path: str
|
||||
:type parent: QtWidgets.QMenu or None
|
||||
:type libraryWindow: studiolibrary.LibraryWindow or None
|
||||
:type kwargs: dict
|
||||
:rtype: QtWidgets.QAction
|
||||
"""
|
||||
item = setsitem.SetsItem(path, libraryWindow=libraryWindow)
|
||||
return cls(item, parent, enableSelectContent=False, **kwargs)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
item,
|
||||
parent=None,
|
||||
namespaces=None,
|
||||
enableSelectContent=True,
|
||||
):
|
||||
"""
|
||||
:type item: studiolibrarymaya.BaseItem
|
||||
:type parent: QtWidgets.QMenu or None
|
||||
:type namespaces: list[str] or None
|
||||
:type enableSelectContent: bool
|
||||
"""
|
||||
parent = parent or item.libraryWindow()
|
||||
QtWidgets.QMenu.__init__(self, "Selection Sets", parent)
|
||||
|
||||
icon = QtGui.QIcon(setsitem.SetsItem.ICON_PATH)
|
||||
self.setIcon(icon)
|
||||
|
||||
self._item = item
|
||||
self._namespaces = namespaces
|
||||
self._enableSelectContent = enableSelectContent
|
||||
self.reload()
|
||||
|
||||
def item(self):
|
||||
"""
|
||||
:rtype: mayabaseitem.MayaBaseItem
|
||||
"""
|
||||
return self._item
|
||||
|
||||
def namespaces(self):
|
||||
"""
|
||||
:rtype: list[str]
|
||||
"""
|
||||
return self._namespaces
|
||||
|
||||
def selectContent(self):
|
||||
"""
|
||||
:rtype: None
|
||||
"""
|
||||
self.item().selectContent(namespaces=self.namespaces())
|
||||
|
||||
def selectionSets(self):
|
||||
"""
|
||||
:rtype: list[setsitem.SetsItem]
|
||||
"""
|
||||
path = self.item().path()
|
||||
|
||||
paths = studiolibrary.walkup(
|
||||
path,
|
||||
match=lambda path: path.endswith(".set"),
|
||||
depth=10,
|
||||
)
|
||||
|
||||
items = []
|
||||
paths = list(paths)
|
||||
libraryWindow = self.item().libraryWindow()
|
||||
|
||||
for path in paths:
|
||||
item = setsitem.SetsItem(path)
|
||||
item.setLibraryWindow(libraryWindow)
|
||||
items.append(item)
|
||||
|
||||
return items
|
||||
|
||||
def reload(self):
|
||||
"""
|
||||
:rtype: None
|
||||
"""
|
||||
self.clear()
|
||||
|
||||
if self._enableSelectContent:
|
||||
action = selectContentAction(item=self.item(), parent=self)
|
||||
self.addAction(action)
|
||||
self.addSeparator()
|
||||
|
||||
selectionSets = self.selectionSets()
|
||||
|
||||
if selectionSets:
|
||||
for selectionSet in selectionSets:
|
||||
|
||||
dirname = os.path.basename(os.path.dirname(selectionSet.path()))
|
||||
|
||||
basename = os.path.basename(selectionSet.path())
|
||||
basename = basename.replace(selectionSet.EXTENSION, "")
|
||||
|
||||
nicename = dirname + ": " + basename
|
||||
|
||||
action = QtWidgets.QAction(nicename, self)
|
||||
callback = partial(selectionSet.load, namespaces=self.namespaces())
|
||||
action.triggered.connect(callback)
|
||||
self.addAction(action)
|
||||
else:
|
||||
action = QtWidgets.QAction("No selection sets found!", self)
|
||||
action.setEnabled(False)
|
||||
self.addAction(action)
|
||||