├── .gitignore
├── LICENSE.txt
├── README.md
├── RenderMan
├── RendermanButton.qml
├── RendermanPrefsPanel.qml
├── RendermanTextField.qml
├── icons
│ ├── PxrDisney_hover.svg
│ ├── PxrDisney_idle.svg
│ ├── PxrSurface_hover.svg
│ ├── PxrSurface_idle.svg
│ ├── R_logo.svg
│ ├── R_logo_white.svg
│ ├── folder.svg
│ └── icons.qrc
├── main.qml
├── plugin.json
├── plugintoolbar.qml
├── renderman.js
├── rmanAssetsSubstancePainter.py
├── rules.json
└── toolbar.qml
├── img
├── RfSP_v0.gif
├── configure_dialog.jpg
├── open_configure_dialog.jpg
└── shelf_buttons.jpg
├── renderman.rcc
├── renderman_for_sp.py
├── renderman_rules.json
├── resources
├── PxrDisney.svg
└── PxrSurface.svg
└── shaders
└── .gitignore
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *,cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # history dir
101 | .history
102 |
103 | log.txt
104 | rfsp_log.txt
105 | settings.ini
106 | .vscode
107 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Philippe Leprince
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RfSP
2 |
3 | :warning: This repo is obsolete ! Go to https://github.com/prman-pixar/RfSP for the latest version. :warning:
4 |
5 | ## RenderMan for Substance Painter 2.x
6 |
7 | This plugin exports your [Substance Painter](https://www.allegorithmic.com/products/substance-painter) project as one or more RenderManAsset.
8 |
9 | RenderManAsset is the format used by the preset browser that was introduced in [RenderMan For Maya](https://rmanwiki.pixar.com/display/REN/RenderMan+for+Maya) 21.0. It allows for easy material setup interchange and includes dependencies like textures or OSL shaders.
10 |
11 | 
12 |
13 | [Full demo video of 0.1.1](https://youtu.be/ZEyT95aPFYk)
14 |
15 | ## Features
16 |
17 | ###  : Export to PxrDisney-based material
18 |
19 | The asset will use the PxrDisney bxdf to re-create the Substance Painter material. There are limitations though:
20 |
21 | * The Substance Painter project MUST use the pbr-metal-rough shader.
22 | * normals MUST be in directx format.
23 | * Opacity is not supported.
24 |
25 | ###  : Export to PxrSurface-based material
26 |
27 | * The Substance Painter project MUST use the pbr-metal-rough shader.
28 | * normals MUST be in directx format.
29 | * Opacity is not supported.
30 |
31 | ## Requirements
32 |
33 | This plugin will NOT work without the following software:
34 |
35 | * Substance Painter 2.3+
36 | * RenderMan Pro Server 21.0+
37 | * RenderMan For Maya 21.0+
38 | * [Python 2.7+](https://www.python.org/downloads/release/python-2712/) (but not Python 3.x)
39 |
40 | ## Install
41 |
42 | * Download a zip archive from the github page
43 | * Un-zip the archive
44 | * Copy the RenderMan folder inside Substance Painter's plugin folder.
45 | > OSX: `/Users/yourlogin/Documents/Substance Painter 2/plugins`
46 |
47 | ## Known Issues
48 |
49 | * No progress indication during export: be patient !
50 | * It takes time to export the maps and turn them into textures. The plugin will print a message in the log when done.
51 |
52 | ## Usage
53 |
54 | 1. On first use, open the "configure" dialog.
55 |
56 | 
57 |
58 | 1. Fill ALL fields of the dialog and click "Save", otherwise the export will fail.
59 |
60 | 
61 |
62 | 1. Once this initial configuration is done, the settings will be remembered even if you close Substance Painter.
63 |
64 | 1. Open a SP project and click one of the pixar buttons in the shelf.
65 |
66 | Hint: _Only the first one (PxrDisney) works for now._
67 |
68 | 
69 |
70 | ## Release notes
71 |
72 | ### 0.3.0
73 |
74 | * **new**
75 | * UDIM support
76 | * The export code converts SP images to RenderMan textures
77 |
78 | * **code**
79 | * Refactored javascript json export.
80 |
81 | ### 0.2.2
82 |
83 | * new
84 | * Added metadata to the asset files: user, description, resolution.
85 | * Compatibility chunk holds Substance Painter version.
86 |
87 | * Fixed
88 | * better match with PxrSurface. Updated rules.json to fix a number of issues:
89 | * roughness should not be linearized
90 | * metallicity should not be linearized
91 | * normal map should use directx orientation
92 | * exported normal map contains mesh normals + bump + normal map.
93 |
94 | * code
95 | * Added a new "settings" section to rules.json to set node param values in the shading graph.
96 | * Some refactoring
97 |
98 | ### 0.2.1
99 |
100 | * Fixed
101 | * Fix ui error in Substance Painter 2018 (was ok in SP 2.x)
102 |
103 | ### 0.2.0
104 |
105 | * New
106 | * Added support for RenderMan 22.x.
107 | * REMEMBER to change the SP prefs to point to RenderMan 22.x
108 | * The assets are now named after the SP project's name.
109 | * Fixed
110 | * The specular was incorrect when outputing for PxrSurface.
111 | * The correct renderman version number is saved in the asset file.
112 | * code
113 | * Refactored file path management.
114 | * Used logging module instead of my own.
115 | * Doc strings and PEP-8 fixes
116 |
117 | ### 0.1.4
118 |
119 | * Initial support for PxrSurface
120 | * Simply use the rough metal workflow in Substance Painter and it will be converted to work with PxrSurface.
121 | * Also fixed Issue #2, reported by dayelov. Thanks !
122 |
123 | ### 0.1.2
124 |
125 | * windows file paths were not correctly serialized to json.
126 | * mention python dependency in README
127 | * Final cleanup was inadvertantly disabled
128 | * Fixed incorrect path on windows.
129 |
130 | ### 0.1.1
131 |
132 | * Added license text to all source files.
133 |
134 | ### 0.1.0
135 |
136 | * Initial Release
137 | * Implemented configure dialog to specify the path to RenderMan Pro Server and RenderMan For Maya. This is necessary because SP uses javascript, a sandboxed language that can not get access to environment variables.
138 | * Export all channels from all textureSets to png files.
139 | * Implemented basic export to PxrDisney-based asset.
140 | * Each TextureSet will be exported as a RenderManAsset directory.
141 |
--------------------------------------------------------------------------------
/RenderMan/RendermanButton.qml:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // MIT License
3 | //
4 | // Copyright (c) 2016 Philippe Leprince
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 | // ----------------------------------------------------------------------------
24 |
25 |
26 |
27 | import QtQuick 2.3
28 | import QtQuick.Controls 1.4
29 | import QtQuick.Controls.Styles 1.4
30 |
31 | Button {
32 | property bool isDefaultButton
33 | isDefaultButton: false
34 | property color rmanblue: "#1e94e6"
35 | property color rmanbluebg: "#303A3F"
36 |
37 | style: ButtonStyle {
38 | background: Rectangle {
39 | implicitWidth: 50
40 | implicitHeight: 24
41 | border.width: isDefaultButton ? 2 : 1
42 | border.color: control.pressed ? rmanblue : isDefaultButton ? "#ccc" : "#222"
43 | radius: 4
44 | color: control.pressed ? "#323232" : control.hovered ? rmanbluebg : "#333"
45 | }
46 | label: Component {
47 | Text {
48 | text: control.text
49 | font.bold: isDefaultButton
50 | clip: true
51 | wrapMode: Text.WordWrap
52 | verticalAlignment: Text.AlignVCenter
53 | horizontalAlignment: Text.AlignHCenter
54 | anchors.fill: parent
55 | color: control.pressed ? "#FFFFFF" : control.hovered ? rmanblue : "#C8C8C8"
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/RenderMan/RendermanPrefsPanel.qml:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // MIT License
3 | //
4 | // Copyright (c) 2016 Philippe Leprince
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 | // ----------------------------------------------------------------------------
24 |
25 |
26 |
27 | import QtQuick 2.3
28 | import QtQuick.Window 2.2
29 | import QtQuick.Dialogs 1.2
30 | import QtQuick.Controls 1.4
31 | import QtQuick.Layouts 1.3
32 | import QtQuick.Controls.Styles 1.4
33 |
34 |
35 | Dialog
36 | {
37 | signal accepted()
38 | visible: false
39 | title: "RenderMan Export Preferences"
40 |
41 | function accept()
42 | {
43 | if (rmanPathField.text != "")
44 | {
45 | alg.settings.setValue("RMANTREE", rmanPathField.text)
46 | // alg.log.info("RMANTREE = "+rmanPathField.text)
47 | }
48 | if (rmsPathField.text != "")
49 | {
50 | alg.settings.setValue("RMSTREE", rmsPathField.text)
51 | // alg.log.info("RMSTREE = "+rmsPathField.text)
52 | }
53 | if (exportPathField.text != "")
54 | {
55 | alg.settings.setValue("saveTo", exportPathField.text)
56 | // alg.log.info("saveTo = "+exportPathField.text)
57 | }
58 |
59 | accepted()
60 | close()
61 | }
62 |
63 | function readPrefs()
64 | {
65 | rmanPathField.readPrefs()
66 | rmsPathField.readPrefs()
67 | exportPathField.readPrefs()
68 | }
69 |
70 | FocusScope
71 | {
72 | focus: true
73 | Keys.onPressed: {
74 | if (event.key === Qt.Key_Escape)
75 | {
76 | close()
77 | }
78 | else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
79 | {
80 | accept()
81 | }
82 | }
83 | }
84 |
85 | contentItem: Rectangle
86 | {
87 | color: "#292929"
88 | implicitWidth: 600
89 | implicitHeight: 180
90 | anchors.fill: parent
91 | property var text_color: "#c0c0c0"
92 |
93 | Text
94 | {
95 | x:10; y:10
96 | height: 30
97 | text: "Please enter the location of RenderMan products."
98 | color: parent.text_color
99 | Layout.fillWidth: true
100 | Layout.fillHeight: true
101 | // anchors.margins: 10
102 | }
103 |
104 | RowLayout
105 | {
106 | id: rmanlayout
107 | x:10; y:40
108 | height: 30
109 | width: parent.width
110 | spacing: 6
111 | Layout.fillWidth: true
112 |
113 | Text
114 | {
115 | id: rmanPathLabel
116 | text: "RenderMan Pro Server:"
117 | horizontalAlignment: Text.AlignRight
118 | color: "#e6e6e6"
119 | Layout.minimumWidth: 150
120 | Layout.maximumWidth: 150
121 | }
122 | RendermanTextField
123 | {
124 | id: rmanPathField
125 | placeholderText: "path to renderman pro server directory"
126 | anchors.left: rmanPathLabel.right
127 | anchors.right: rmanPathButton.left
128 | anchors.leftMargin: 4
129 | anchors.rightMargin: 4
130 |
131 | function readPrefs()
132 | {
133 | text = alg.settings.value("RMANTREE")
134 | }
135 |
136 | Component.onCompleted: {
137 | readPrefs()
138 | }
139 |
140 | }
141 | RendermanButton
142 | {
143 | id:rmanPathButton
144 | text: "pick"
145 | onClicked: {
146 | // alg.log.info("rman: click")
147 | folderPickerDialog.fieldid = rmanPathField
148 | folderPickerDialog.open()
149 | }
150 | width: 40
151 | height: 10
152 | anchors.right: rmanlayout.right
153 | anchors.rightMargin: 20
154 | }
155 | }
156 |
157 | RowLayout {
158 | id: rmslayout
159 | x:10; y:70
160 | height: 30
161 | width: parent.width
162 | spacing: 6
163 | Layout.fillWidth: true
164 |
165 | Text {
166 | id: rmsPathLabel
167 | text: "RenderMan For Maya:"
168 | horizontalAlignment: Text.AlignRight
169 | color: "#e6e6e6"
170 | Layout.minimumWidth: 150
171 | Layout.maximumWidth: 150
172 | }
173 | RendermanTextField {
174 | id: rmsPathField
175 | placeholderText: "path to renderman for maya directory"
176 | anchors.left: rmsPathLabel.right
177 | anchors.right: rmsPathButton.left
178 | anchors.leftMargin: 4
179 | anchors.rightMargin: 4
180 |
181 | function readPrefs()
182 | {
183 | text = alg.settings.value("RMSTREE")
184 | }
185 |
186 | Component.onCompleted: {
187 | readPrefs()
188 | }
189 | }
190 | RendermanButton
191 | {
192 | id: rmsPathButton
193 | text: "pick"
194 | width: 40
195 | height: 10
196 | anchors.right: rmslayout.right
197 | anchors.rightMargin: 20
198 | onClicked: {
199 | folderPickerDialog.fieldid = rmsPathField
200 | folderPickerDialog.setVisible(true)
201 | }
202 | }
203 | }
204 |
205 | RowLayout {
206 | id: exportlayout
207 | x:10; y:100
208 | height: 30
209 | width: parent.width
210 | spacing: 6
211 | Layout.fillWidth: true
212 |
213 | Text {
214 | id: exportPathLabel
215 | text: "Export to:"
216 | horizontalAlignment: Text.AlignRight
217 | color: "#e6e6e6"
218 | Layout.minimumWidth: 150
219 | Layout.maximumWidth: 150
220 | }
221 | RendermanTextField {
222 | id: exportPathField
223 | placeholderText: "where the asset(s) will be saved"
224 | anchors.left: exportPathLabel.right
225 | anchors.right: exportPathButton.left
226 | anchors.leftMargin: 4
227 | anchors.rightMargin: 4
228 |
229 | function readPrefs()
230 | {
231 | text = alg.settings.value("saveTo")
232 | }
233 |
234 | Component.onCompleted: {
235 | readPrefs()
236 | }
237 | }
238 | RendermanButton
239 | {
240 | id: exportPathButton
241 | text: "pick"
242 | width: 40
243 | height: 10
244 | anchors.right: exportlayout.right
245 | anchors.rightMargin: 20
246 | onClicked: {
247 | folderPickerDialog.fieldid = exportPathField
248 | folderPickerDialog.setVisible(true)
249 | }
250 | }
251 | }
252 |
253 |
254 | RendermanButton
255 | {
256 | id: okbutton
257 | text: "Save"
258 | onClicked: accept()
259 | isDefaultButton: true
260 | anchors.bottom: parent.bottom
261 | anchors.bottomMargin: 10
262 | anchors.right: parent.right
263 | anchors.rightMargin: 10
264 | width: 100
265 | }
266 | RendermanButton
267 | {
268 | text: "Cancel"
269 | onClicked: close()
270 | anchors.bottom: parent.bottom
271 | anchors.bottomMargin: 10
272 | anchors.right: okbutton.left
273 | anchors.rightMargin: 10
274 | width: 100
275 | }
276 |
277 | FileDialog
278 | {
279 | id: folderPickerDialog
280 | visible: false
281 | title: "Choose the directory ..."
282 | nameFilters: [ "All files (*)" ]
283 | selectedNameFilter: "Executable files (*)"
284 | selectFolder: true
285 | selectExisting: true
286 | property var fieldid
287 |
288 | onAccepted:
289 | {
290 | fieldid.text = alg.fileIO.urlToLocalFile(fileUrl.toString())
291 | }
292 |
293 | onVisibleChanged:
294 | {
295 | if (visible == false && parent != null )
296 | {
297 | // alg.log.info("vis OFF")
298 | parent.active()
299 | }
300 | else
301 | {
302 | // alg.log.info("vis ON")
303 | }
304 | }
305 | }
306 | }
307 |
308 |
309 | }
--------------------------------------------------------------------------------
/RenderMan/RendermanTextField.qml:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // MIT License
3 | //
4 | // Copyright (c) 2016 Philippe Leprince
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 | // ----------------------------------------------------------------------------
24 |
25 |
26 |
27 | import QtQuick 2.3
28 | import QtQuick.Controls 1.4
29 | import QtQuick.Controls.Styles 1.4
30 |
31 | TextField {
32 | style: TextFieldStyle {
33 | textColor: "#c0c0c0"
34 | placeholderTextColor: "#333"
35 | background: Rectangle {
36 | radius: 2
37 | implicitWidth: 100
38 | implicitHeight: 24
39 | border.color: "#333"
40 | border.width: 1
41 | color: "#222"
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/RenderMan/icons/PxrDisney_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
41 |
--------------------------------------------------------------------------------
/RenderMan/icons/PxrDisney_idle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
41 |
--------------------------------------------------------------------------------
/RenderMan/icons/PxrSurface_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
29 |
--------------------------------------------------------------------------------
/RenderMan/icons/PxrSurface_idle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
29 |
--------------------------------------------------------------------------------
/RenderMan/icons/R_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
54 |
--------------------------------------------------------------------------------
/RenderMan/icons/R_logo_white.svg:
--------------------------------------------------------------------------------
1 |
2 |
54 |
--------------------------------------------------------------------------------
/RenderMan/icons/folder.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
81 |
--------------------------------------------------------------------------------
/RenderMan/icons/icons.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PxrDisney_hover.svg
5 | PxrDisney_idle.svg
6 | PxrSurface_hover.svg
7 | PxrSurface_idle.svg
8 | folder.svg
9 | R_logo.svg
10 | R_logo_white.svg
11 |
12 |
--------------------------------------------------------------------------------
/RenderMan/main.qml:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // MIT License
3 | //
4 | // Copyright (c) 2016 Philippe Leprince
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 | // ----------------------------------------------------------------------------
24 |
25 |
26 |
27 | import QtQuick 2.2
28 | import Painter 1.0
29 |
30 | PainterPlugin
31 | {
32 | tickIntervalMS: -1 // Disabled, no need for Tick
33 | jsonServerPort: -1 // Disabled, no need for JSON server
34 |
35 | Component.onCompleted:
36 | {
37 | if(alg.version.painter <= "2017.4.2"){
38 | // create a toolbar button for releases prior to 2018.1.0
39 | alg.ui.addToolBarWidget("toolbar.qml");
40 | }
41 |
42 | else{
43 | // create a plugintoolbar button for releases after 2018.1.0
44 | alg.ui.addWidgetToPluginToolBar("plugintoolbar.qml");
45 | }
46 | }
47 |
48 | onConfigure:
49 | {
50 | // open the configuration panel
51 | rmanPrefsPanel.open()
52 | }
53 |
54 | RendermanPrefsPanel
55 | {
56 | id: rmanPrefsPanel
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/RenderMan/plugin.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "This plugin exports a painter project to RenderMan Assets.",
3 | "url": "https://github.com/pleprince/RfSP",
4 | "version": "0.3.0",
5 | "license": "MIT"
6 | }
--------------------------------------------------------------------------------
/RenderMan/plugintoolbar.qml:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // MIT License
3 | //
4 | // Copyright (c) 2016 Philippe Leprince
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 | // ----------------------------------------------------------------------------
24 |
25 |
26 |
27 | import QtQuick 2.3
28 | import QtQuick.Window 2.2
29 | import QtQuick.Layouts 1.2
30 | import QtQuick.Controls 1.4
31 | import QtQuick.Controls.Styles 1.4
32 |
33 | import "renderman.js" as Renderman
34 |
35 | Column
36 | {
37 |
38 | Button
39 | {
40 | id: disney
41 | antialiasing: true
42 | tooltip: "Export as a PxrDisney Material Preset"
43 | width: 32
44 | height: 32
45 |
46 | style: ButtonStyle {
47 | background: Rectangle {
48 | implicitWidth: control.width
49 | implicitHeight: control.height
50 | width: control.width;
51 | height: control.height
52 | color: control.hovered ?
53 | "#262626" :
54 | "transparent"
55 |
56 | Image {
57 | anchors.fill: parent
58 | anchors.margins: 7
59 | source: control.hovered && !control.loading ? "icons/PxrDisney_hover.svg" : "icons/PxrDisney_idle.svg"
60 | fillMode: Image.PreserveAspectFit
61 | width: control.width; height: control.height
62 | mipmap: true
63 | opacity: 1.0
64 | }
65 | }
66 | }
67 |
68 | onClicked:
69 | {
70 | try
71 | {
72 | alg.log.info( 'RenderMan: Export PxrDisney-based asset...')
73 | Renderman.exportAssets('PxrDisney')
74 | }
75 | catch(err)
76 | {
77 | alg.log.error( 'rman: ' + err.message )
78 | }
79 | }
80 | }
81 |
82 |
83 | Button
84 | {
85 | id: surface
86 | antialiasing: true
87 | tooltip: "Export as a PxrSurface Material Preset"
88 | width: 32
89 | height: 32
90 |
91 | style: ButtonStyle {
92 | background: Rectangle {
93 | implicitWidth: control.width
94 | implicitHeight: control.height
95 | width: control.width;
96 | height: control.height
97 | color: control.hovered ?
98 | "#262626" :
99 | "transparent"
100 |
101 | Image {
102 | anchors.fill: parent
103 | anchors.margins: 7
104 | source: control.hovered && !control.loading ? "icons/PxrSurface_hover.svg" : "icons/PxrSurface_idle.svg"
105 | fillMode: Image.PreserveAspectFit
106 | width: control.width; height: control.height
107 | mipmap: true
108 | opacity: 1.0
109 | }
110 | }
111 | }
112 |
113 | onClicked:
114 | {
115 | try
116 | {
117 | Renderman.exportAssets('PxrSurface')
118 | }
119 | catch(err)
120 | {
121 | alg.log.error( 'rman: ' + err.message )
122 | }
123 | }
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/RenderMan/renderman.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // MIT License
3 | //
4 | // Copyright (c) 2016 Philippe Leprince
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 | // ----------------------------------------------------------------------------
24 |
25 |
26 | function osPath(input_path) {
27 | var platformPath = input_path
28 | if (Qt.platform.os == "windows") {
29 | var tmp = new String(platformPath)
30 | platformPath = tmp.replace(/\//g, "\\")
31 | // alg.log.info("[WIN] platformPath = " + platformPath)
32 | }
33 | return platformPath
34 | }
35 |
36 |
37 | function jsonPath(input_path) {
38 | var jsnPath = input_path
39 | if (Qt.platform.os == "windows") {
40 | var tmp = new String(jsnPath)
41 | jsnPath = tmp.replace(/\\/g, "\\\\")
42 | // alg.log.info("[WIN] jsnPath = " + jsnPath)
43 | }
44 | return jsnPath
45 | }
46 |
47 |
48 | function checkPrefs() {
49 | var all_valid = true
50 | if (alg.settings.value("RMANTREE") == undefined) {
51 | all_valid = false
52 | alg.log.warn("RMANTREE = " + alg.settings.value("RMANTREE"))
53 | }
54 | if (alg.settings.value("RMSTREE") == undefined) {
55 | all_valid = false
56 | alg.log.warn("RMSTREE = " + alg.settings.value("RMSTREE"))
57 | }
58 | if (alg.settings.value("saveTo") == undefined) {
59 | all_valid = false
60 | alg.log.warn("saveTo = " + alg.settings.value("saveTo"))
61 | }
62 | return all_valid
63 | }
64 |
65 |
66 | function isUDIMProject(document)
67 | {
68 | var isUDIM = true
69 | var udimPat = '1[0-9]{3}'
70 | for (var matIdx = 0; matIdx < document.materials.length; matIdx++)
71 | {
72 | if (document.materials[matIdx].name.match(udimPat) == null)
73 | {
74 | isUDIM = false
75 | break
76 | }
77 | }
78 | alg.log.info('UDIM = ' + isUDIM)
79 | return isUDIM
80 | }
81 |
82 |
83 | // FIXME: the bxdf param is currently ignored.
84 |
85 | function exportAssets(bxdf) {
86 |
87 | // bail out if the prefs are not all filled
88 | //
89 | var valid = checkPrefs()
90 | if (!valid) {
91 | alg.log.error("RenderMan: Please open the configure panel and fill all fields !")
92 | return
93 | }
94 |
95 | alg.log.info("\n\n\n\n")
96 | var toks = alg.project.url().split('/')
97 | var scene_name = toks[toks.length - 1]
98 | scene_name = scene_name.split(".")[0]
99 | alg.log.info("Scene name: " + scene_name)
100 |
101 | // Some useful variables
102 | //
103 | var sep = "/"
104 | var pyBin = "python"
105 | var winOS = (Qt.platform.os == "windows")
106 | if (winOS) {
107 | sep = "\\"
108 | pyBin += ".exe"
109 | }
110 | var ext = ".png"
111 | var script = "rmanAssetsSubstancePainter.py"
112 | var exportPath = ""
113 | var jsonFilePath = ""
114 | var tab = " "
115 | var tab2 = tab + tab
116 | var tab3 = tab2 + tab
117 | var tab4 = tab3 + tab
118 |
119 | // Query export path
120 | //
121 | exportPath = osPath(alg.mapexport.exportPath())
122 | exportPath += sep + "RenderMan" + sep
123 | jsonFilePath = exportPath + "RmanExport.json"
124 |
125 | // Export masks
126 | //
127 | var matIdx = 0
128 | var channelIdx = 0
129 | var document = alg.mapexport.documentStructure()
130 |
131 | // check if this is a UDIM set
132 | var isUDIM = isUDIMProject(document)
133 |
134 | // store env vars
135 | //
136 | var obj = {
137 | scene: scene_name,
138 | sp_version: alg.version.painter,
139 | RMANTREE: jsonPath(alg.settings.value("RMANTREE")),
140 | RMSTREE: jsonPath(alg.settings.value("RMSTREE")),
141 | bxdf: bxdf,
142 | udim: isUDIM,
143 | saveTo: jsonPath(alg.settings.value("saveTo")),
144 | document: []
145 | }
146 |
147 | // Parse all materials (texture sets)
148 | //
149 | var mobj = null
150 | for (matIdx = 0; matIdx < document.materials.length; matIdx++)
151 | {
152 | var material = document.materials[matIdx].name
153 | if (!isUDIM || matIdx == 0)
154 | {
155 | mobj = {
156 | textureSet: isUDIM ? "UDIM" : material,
157 | resolution: alg.mapexport.textureSetResolution(material),
158 | channels: {}
159 | }
160 | }
161 |
162 | var numChannels = document.materials[matIdx].stacks[0].channels.length
163 | for (channelIdx = 0; channelIdx < numChannels; channelIdx++)
164 | {
165 | var thisChannel = document.materials[matIdx].stacks[0].channels[channelIdx]
166 | // alg.log.info("RenderMan: | " + thisChannel)
167 |
168 | // Skip the height channel: we prefer normal maps.
169 | if (thisChannel == "height")
170 | {
171 | // alg.log.info("RenderMan: |_ skip")
172 | continue
173 | }
174 |
175 | var output = exportPath
176 | if (isUDIM)
177 | output += thisChannel + "." + material + ext
178 | else
179 | output += material + "_" + thisChannel + ext
180 |
181 | var t0 = new Date().getTime()
182 | if (thisChannel == "normal")
183 | {
184 | // Make sure the normals are correctly configured to combine
185 | // mesh + height + normal.
186 | alg.mapexport.saveConvertedMap([material], "normal_directx", output)
187 | }
188 | else
189 | {
190 | // regular map export
191 | alg.mapexport.save([material, thisChannel], output)
192 | }
193 | var t1 = new Date().getTime()
194 | alg.log.info("RenderMan: |_ Exported in " + ((t1-t0)/1000.0).toFixed(2) + " sec.: " + output)
195 |
196 | try {
197 | mobj.channels[thisChannel].push(jsonPath(output))
198 | } catch (error) {
199 | mobj.channels[thisChannel] = [jsonPath(output)]
200 | }
201 | }
202 |
203 | if (!isUDIM || matIdx == 0)
204 | {
205 | obj.document.push(mobj)
206 | }
207 | }
208 |
209 | // Write json file and export
210 | //
211 | if (obj != null)
212 | {
213 | alg.log.info("RenderMan: Writing " + jsonFilePath + "...")
214 |
215 | // write json file needed by the python script
216 | //
217 | var jsonFile = alg.fileIO.open(jsonFilePath, "w")
218 | jsonFile.write(JSON.stringify(obj, null, 4))
219 | jsonFile.close()
220 |
221 | // Call python
222 | // FIXME: we should probably just catch exceptions and print the log
223 | // on error.
224 | //
225 | alg.log.info("RenderMan: Launching " + script + "...")
226 | try
227 | {
228 | var fpath = "\"" + jsonFilePath + "\""
229 | var result = alg.subprocess.check_output([pyBin, script, fpath])
230 | var lines = result.split(/[\r\n]+/g)
231 | for (var i in lines)
232 | {
233 | alg.log.info("RenderMan: " + lines[i])
234 | }
235 | }
236 | catch (err)
237 | {
238 | alg.log.error('ERROR: ' + err)
239 | }
240 | }
241 | else {
242 | alg.log.error("RenderMan: Nothing to export !")
243 | }
244 | alg.log.info("RenderMan: Export successful ! :)")
245 | }
246 |
247 |
--------------------------------------------------------------------------------
/RenderMan/rmanAssetsSubstancePainter.py:
--------------------------------------------------------------------------------
1 | """python 2.7 plugin for substance painter 2.3+
2 | Export substance painter maps to a RenderMan Asset package.
3 | """
4 | # -----------------------------------------------------------------------------
5 | # MIT License
6 | #
7 | # Copyright (c) 2016 Philippe Leprince
8 | #
9 | # Permission is hereby granted, free of charge, to any person obtaining a copy
10 | # of this software and associated documentation files (the "Software"), to deal
11 | # in the Software without restriction, including without limitation the rights
12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | # copies of the Software, and to permit persons to whom the Software is
14 | # furnished to do so, subject to the following conditions:
15 | #
16 | # The above copyright notice and this permission notice shall be included in all
17 | # copies or substantial portions of the Software.
18 | #
19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | # SOFTWARE.
26 | # -----------------------------------------------------------------------------
27 |
28 |
29 | # standard imports first
30 | import os
31 | import os.path
32 | import sys
33 | import re
34 | import json
35 | import shutil
36 | import logging
37 | import getpass
38 | import subprocess
39 |
40 | THIS_DIR = os.path.dirname(os.path.realpath(__file__))
41 | LOGFILE = os.path.join(THIS_DIR, 'rfsp_log.txt')
42 | logging.basicConfig(filename=LOGFILE,
43 | filemode='w',
44 | level=logging.DEBUG,
45 | format='%(levelname)-10s %(message)s')
46 | DBUG = logging.debug
47 | INFO = logging.info
48 | WARN = logging.warning
49 | ERR = logging.error
50 | XCPT = logging.exception
51 | IMG_EXTS = ['.png', '.jpg', '.exr']
52 | TEX_EXTS = ['.tex', '.tx', '.txr']
53 |
54 |
55 | class FilePath(unicode):
56 | """A class based on unicode to handle filepaths on various OS platforms.
57 |
58 | Extends:
59 | unicode
60 | """
61 |
62 | def __new__(cls, path):
63 | """Create new unicode file path in POSIX format. Windows paths will be
64 | converted.
65 |
66 | Arguments:
67 | path {str} -- a file path, in any format.
68 | """
69 | fpath = path
70 | if os.sep is not '/':
71 | fpath = fpath.replace(os.sep, '/')
72 | return unicode.__new__(cls, fpath)
73 |
74 | def osPath(self):
75 | """return the platform-specif path, i.e. convert to windows format if
76 | need be.
77 |
78 | Returns:
79 | str -- a path formatted for the current OS.
80 | """
81 | return r'%s' % os.path.normpath(self)
82 |
83 | def exists(self):
84 | """Check is the path actually exists, using os.path.
85 |
86 | Returns:
87 | bool -- True if the path exists.
88 | """
89 | return os.path.exists(self)
90 |
91 | def join(self, *args):
92 | """Combine the arguments with the current path and return a new
93 | FilePath object.
94 |
95 | Arguments:
96 | *args {list} -- a list of path segments.
97 |
98 | Returns:
99 | FilePath -- A new object containing the joined path.
100 | """
101 | return FilePath(os.path.join(self, *args))
102 |
103 | def dirname(self):
104 | """Returns the dirname of the current path (using os.path.dirname) as a
105 | FilePath object.
106 |
107 | Returns:
108 | FilePath -- the path's directory name.
109 | """
110 | return FilePath(os.path.dirname(self))
111 |
112 | def basename(self):
113 | """Return the basename, i.e. '/path/to/file.ext' -> 'file.ext'
114 |
115 | Returns:
116 | str -- The final segment of the path.
117 | """
118 | return os.path.basename(self)
119 |
120 | def isWritable(self):
121 | """Checks if the path is writable. The Write and Execute bits must
122 | be enabled.
123 |
124 | Returns:
125 | bool -- True is writable
126 | """
127 | return os.access(self, os.W_OK | os.X_OK)
128 |
129 | # functions -------------------------------------------------------------------
130 |
131 | def readJson(fpath):
132 | """Read a json file without exception handling.
133 |
134 | Arguments:
135 | fpath {str} -- full path to json file
136 |
137 | Returns:
138 | dict -- json data
139 | """
140 | with open(fpath, 'r') as fhdl:
141 | data = json.load(fhdl)
142 | return data
143 |
144 |
145 | def setup_environment(jsonDict):
146 | """make sure that RMANTREE and RMSTREE are defined in our environment and
147 | we can import our python module.
148 |
149 | Arguments:
150 | jsonDict {dict} -- json data
151 |
152 | Returns:
153 | tuple -- (rmanAssets, rman_version)
154 | """
155 | rmantree = FilePath(jsonDict['RMANTREE'])
156 | rmstree = FilePath(jsonDict['RMSTREE'])
157 |
158 | if not rmantree in os.environ:
159 | os.environ['RMANTREE'] = rmantree.osPath()
160 | if not rmstree in os.environ:
161 | os.environ['RMSTREE'] = rmstree.osPath()
162 |
163 | rman_version = float(re.search(r'RenderManProServer-(\d+\.\d+)', rmantree).group(1))
164 |
165 | rmstree_py = rmstree.join(rmstree, "scripts")
166 | if int(rman_version) == 21:
167 | if rmstree_py not in sys.path:
168 | sys.path.append(rmstree_py)
169 | else:
170 | rmantree_py = rmantree.join(rmantree, "bin")
171 | if rmstree_py not in sys.path:
172 | sys.path.insert(0, rmstree_py)
173 | if rmantree_py not in sys.path:
174 | sys.path.insert(0, rmantree_py)
175 | return rman_version
176 |
177 |
178 | def set_params(settings_dict, chan, node_name, asset):
179 | # The bxdf may need specific settings to match Substance Painter
180 | try:
181 | params = settings_dict[chan]
182 | except (KeyError, TypeError):
183 | pass
184 | else:
185 | for pname, pdict in params.iteritems():
186 | asset.addParam(node_name, pname, pdict)
187 | DBUG(' |_ param: %s %s = %s', pdict['type'], pname, pdict['value'])
188 |
189 |
190 | def add_texture_node(asset, node_name, ntype, filepath):
191 | asset.addNode(node_name, ntype, 'pattern', ntype)
192 | pdict = {'type': 'string', 'value': filepath}
193 | asset.addParam(node_name, 'filename', pdict)
194 | if '_MAPID_' in filepath:
195 | asset.addParam(node_name, 'atlasStyle', {'type': 'int', 'value': 1})
196 |
197 |
198 | def set_metadata(asset, mat_dict):
199 | meta = asset.stdMetadata()
200 | meta['author'] = getpass.getuser()
201 | meta['description'] = 'Created by RenderMan for Substance Painter 0.3.0'
202 | meta['resolution'] = '%d x %d' % (mat_dict['resolution'][0],
203 | mat_dict['resolution'][1])
204 | for k, v in meta.iteritems():
205 | asset.addMetadata(k, v)
206 |
207 |
208 | def startupInfo():
209 | """Returns a Windows-only object to make sure tasks launched through
210 | subprocess don't open a cmd window.
211 |
212 | Returns:
213 | subprocess.STARTUPINFO -- the properly configured object if we are on
214 | Windows, otherwise None
215 | """
216 | startupinfo = None
217 | if os.name is 'nt':
218 | startupinfo = subprocess.STARTUPINFO()
219 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
220 | return startupinfo
221 |
222 |
223 | def app(name):
224 | if os.name is 'nt':
225 | return (name + '.exe')
226 | return name
227 |
228 |
229 | def txmake(is_udim, asset_path, fpath_list):
230 |
231 | rmantree = FilePath(os.environ['RMANTREE'])
232 | binary = rmantree.join('bin', app('txmake')).osPath()
233 | cmd = [binary]
234 | if is_udim:
235 | cmd += ['-resize', 'round-',
236 | '-mode', 'clamp',
237 | '-format', 'pixar',
238 | '-compression', 'lossless',
239 | '-newer',
240 | 'src', 'dst']
241 | else:
242 | cmd += ['-resize', 'round-',
243 | '-mode', 'periodic',
244 | '-format', 'pixar',
245 | '-compression', 'lossless',
246 | '-newer',
247 | 'src', 'dst']
248 | for img in fpath_list:
249 | cmd[-2] = FilePath(img).osPath()
250 | dirname, filename = os.path.split(img)
251 | texfile = os.path.splitext(filename)[0] + '.tex'
252 | cmd[-1] = asset_path.join(texfile).osPath()
253 | DBUG(' |_ txmake : %s -> %s', cmd[-2], cmd[-1])
254 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
255 | stderr=subprocess.PIPE,
256 | startupinfo=startupInfo())
257 | p.wait()
258 |
259 | # return a local path to the tex file.
260 | dirname, filename = os.path.split(fpath_list[0])
261 | fname, fext = os.path.splitext(filename)
262 | asset_file_ref = FilePath(dirname).join(fname + '.tex')
263 | if is_udim:
264 | asset_file_ref = re.sub(r'1\d{3}', '_MAPID_', asset_file_ref)
265 | return asset_file_ref
266 |
267 |
268 | def export():
269 | """Export a RenderManAsset package based on a json file.
270 | """
271 | INFO('Start !')
272 |
273 | if len(sys.argv) < 2:
274 | ERR('expecting 2 arguments !')
275 | raise Exception
276 |
277 | # get the input json file
278 | jsonFile = FilePath(sys.argv[1].replace('"', ''))
279 |
280 | # import json file
281 | jsonDict = readJson(jsonFile)
282 | DBUG('OK: json read')
283 |
284 | rman_version = setup_environment(jsonDict)
285 | if int(rman_version) >= 22:
286 | import rmanAssets.core as ra # pylint: disable=import-error
287 | else:
288 | import rfm.rmanAssets as ra # pylint: disable=import-error
289 | DBUG('OK: imported rmanAssets')
290 |
291 | # constants
292 | _bump = ('height', 'normal')
293 | slotsFile = FilePath(os.path.dirname(os.path.realpath(__file__))).join('rules.json')
294 | rules = readJson(slotsFile)
295 | DBUG('OK: rules read')
296 |
297 | _bxdf = jsonDict['bxdf']
298 | bxdf_rules = rules[_bxdf]
299 | mappings = bxdf_rules['mapping']
300 | graph = bxdf_rules.get('graph', None)
301 | settings = bxdf_rules.get('settings', None)
302 | is_udim = jsonDict['udim']
303 |
304 | # we save the assets to SP's export directory, because we know it is writable.
305 | # We will move them to the requested location later.
306 | exportPath = jsonFile.dirname()
307 |
308 | # build assets
309 | assetList = []
310 | scene = jsonDict['scene']
311 | matArray = jsonDict['document']
312 | for mat in matArray:
313 | label = scene
314 | if not is_udim:
315 | label = '%s_%s' % (scene, mat['textureSet'])
316 | chans = mat['channels']
317 | DBUG('+ Exporting %s', label)
318 |
319 | assetPath = exportPath.join(label + '.rma')
320 | DBUG(' + assetPath %s', assetPath)
321 | assetJsonPath = assetPath.join('asset.json')
322 | DBUG(' + assetJsonPath %s', assetJsonPath)
323 |
324 | # create asset directory
325 | if not assetPath.exists():
326 | try:
327 | os.mkdir(assetPath.osPath())
328 | except (OSError, IOError):
329 | XCPT('Asset directory could not be created !')
330 | sys.exit(0)
331 | DBUG(' + Created dir: %s', assetPath)
332 | else:
333 | DBUG(' + dir exists: %s', assetPath)
334 |
335 | # create asset
336 | try:
337 | asset = ra.RmanAsset(assetType='nodeGraph', label=label)
338 | except Exception:
339 | XCPT('Asset creation failed')
340 | sys.exit(0)
341 |
342 | # create standard metadata
343 | #
344 | set_metadata(asset, mat)
345 |
346 | # Compatibility data
347 | # This will help other application decide if they can use this asset.
348 | #
349 | prmanVersion = str(rman_version)
350 | asset.setCompatibility(hostName='Substance Painter',
351 | hostVersion=jsonDict['sp_version'],
352 | rendererVersion=prmanVersion)
353 | DBUG(' + compatibility set')
354 |
355 | # create nodes
356 | # start by adding a root node
357 | #
358 | rootNode = label + '_Material'
359 | asset.addNode(rootNode, 'shadingEngine', 'root', 'shadingEngine')
360 | pdict = {'type': 'reference float[]', 'value': None}
361 | asset.addParam(rootNode, 'surfaceShader', pdict)
362 | DBUG(' + Root node: %s', rootNode)
363 |
364 | # add a disney or pixar bxdf
365 | #
366 | bxdfNode = label + "_Srf"
367 | asset.addNode(bxdfNode, _bxdf, 'bxdf', _bxdf)
368 | DBUG(' + BxDF node: %s (%s)', (rootNode, _bxdf))
369 |
370 | # The bxdf may need specific settings to match Substance Painter
371 | set_params(settings, 'bxdf', bxdfNode, asset)
372 |
373 | # connect surf to root node
374 | #
375 | asset.addConnection('%s.outColor' % bxdfNode,
376 | '%s.surfaceShader' % rootNode)
377 |
378 | # build additional nodes if need be.
379 | #
380 | if graph:
381 | DBUG(' + Create graph nodes...')
382 | for nname, ndict in graph['nodes'].iteritems():
383 | lname = label + nname
384 | asset.addNode(lname, ndict['nodetype'],
385 | 'pattern', ndict['nodetype'])
386 | DBUG(' |_ %s (%s)' % (lname, ndict['nodetype']))
387 | if 'params' in ndict:
388 | for pname, pdict in ndict['params'].iteritems():
389 | asset.addParam(lname, pname, pdict)
390 | DBUG(' |_ param: %s %s = %s' %
391 | (pdict['type'], pname, pdict['value']))
392 |
393 | # create texture nodes
394 | DBUG(' + Create texture nodes...')
395 | chanNodes = {}
396 | for chan, fpath_list in chans.iteritems():
397 | nodeName = "%s_%s_tex" % (label, chan)
398 | DBUG(' |_ %s' % nodeName)
399 | chanNodes[chan] = nodeName
400 | fpath = txmake(is_udim, assetPath, fpath_list)
401 | if chan == 'normal':
402 | add_texture_node(asset, nodeName, 'PxrNormalMap', fpath)
403 | elif chan == 'height':
404 | add_texture_node(asset, nodeName, 'PxrBump', fpath)
405 | else:
406 | add_texture_node(asset, nodeName, 'PxrTexture', fpath)
407 | set_params(settings, chan, nodeName, asset)
408 |
409 | # make direct connections
410 | #
411 | DBUG(' + Direct connections...')
412 | for chan in chans:
413 | src = None
414 | dstType = mappings[chan]['type']
415 | dstParam = mappings[chan]['param']
416 | if dstType == 'normal':
417 | src = '%s.resultN' % (chanNodes[chan])
418 | elif dstType == 'color':
419 | src = '%s.resultRGB' % (chanNodes[chan])
420 | elif dstType == 'float':
421 | src = '%s.resultR' % (chanNodes[chan])
422 | else:
423 | # don't create a connection
424 | if dstParam != 'graph':
425 | # connections with a graph type will be handled later, so
426 | # we don't warn in that case.
427 | print 'WARNING: Not connecting: %s' % chan
428 | continue
429 | if dstParam == 'graph':
430 | continue
431 | dst = '%s.%s' % (bxdfNode, dstParam)
432 | asset.addConnection(src, dst)
433 | DBUG(' |_ connect: %s -> %s' % (src, dst))
434 | # also tag the bxdf param as connected
435 | pdict = {'type': 'reference ' + dstType, 'value': None}
436 | asset.addParam(bxdfNode, dstParam, pdict)
437 | DBUG(' |_ param: %s %s -> %s' % (pdict['type'],
438 | dstParam,
439 | pdict['value']))
440 |
441 | # make graph connections
442 | #
443 | if graph:
444 | if 'connections' in graph:
445 | DBUG(' + Connect graph nodes...')
446 | for con in graph['connections']:
447 |
448 | src_node = con['src']['node']
449 | src_ch = None
450 | if src_node == _bxdf:
451 | src_node = bxdfNode
452 | elif src_node.startswith('ch:'):
453 | src_ch = src_node[3:]
454 | if src_ch in chanNodes:
455 | src_node = chanNodes[src_ch]
456 | else:
457 | continue
458 | if not src_node.startswith(label):
459 | src_node = label + src_node
460 | src = '%s.%s' % (src_node, con['src']['param'])
461 |
462 | dst_node = con['dst']['node']
463 | dst_ch = None
464 | if dst_node == _bxdf:
465 | dst_node = bxdfNode
466 | elif dst_node.startswith('ch:'):
467 | dst_ch = dst_node[3:]
468 | if dst_ch in chanNodes:
469 | dst_node = chanNodes[dst_ch]
470 | else:
471 | continue
472 | if not dst_node.startswith(label):
473 | dst_node = label + dst_node
474 | dst = '%s.%s' % (dst_node, con['dst']['param'])
475 | asset.addConnection(src, dst)
476 | DBUG(' |_ connect: %s -> %s' % (src, dst))
477 | # mark param as a connected
478 | dstType = con['dst']['type']
479 | pdict = {'type': 'reference %s' % dstType, 'value': None}
480 | asset.addParam(dst_node, con['dst']['param'], pdict)
481 | DBUG(' |_ param: %s %s = %s' % (pdict['type'],
482 | con['dst']['param'],
483 | pdict['value']))
484 |
485 | # save asset
486 | #
487 | DBUG(' + ready to save: %s' % assetJsonPath)
488 | try:
489 | asset.save(assetJsonPath, False)
490 | except:
491 | XCPT('Saving the asset failed !')
492 | raise
493 |
494 | # mark this asset as ready to be moved
495 | #
496 | assetList.append(assetPath)
497 |
498 | # move assets to the requested location
499 | #
500 | dst = jsonDict['saveTo']
501 | for item in assetList:
502 | # if the asset already exists in the destination
503 | # location, we need to move it first.
504 | dstAsset = os.path.join(dst, os.path.basename(item))
505 | if os.path.exists(dstAsset):
506 | try:
507 | os.rename(dstAsset, dstAsset + '_old')
508 | except (OSError, IOError):
509 | XCPT('Could not rename asset to %s_old' % dstAsset)
510 | continue
511 | else:
512 | shutil.rmtree(dstAsset + '_old', ignore_errors=False)
513 | try:
514 | shutil.move(item, dst)
515 | except (OSError, IOError):
516 | XCPT('WARNING: Could not copy asset to %s' % dst)
517 |
518 |
519 | # clean-up intermediate files
520 | for mat in matArray:
521 | for chan, fpath_list in chans.iteritems():
522 | for fpath in fpath_list:
523 | if not os.path.exists(fpath):
524 | print 'cleanup: file not found: %s' % fpath
525 | continue
526 | try:
527 | os.remove(fpath)
528 | except (OSError, IOError):
529 | XCPT('Cleanup failed: %s' % fpath)
530 | else:
531 | DBUG('Cleanup: %s' % fpath)
532 |
533 | if os.path.exists(jsonFile):
534 | try:
535 | os.remove(jsonFile)
536 | except (OSError, IOError):
537 | XCPT('Cleanup failed: %s' % jsonFile)
538 | else:
539 | DBUG('Cleanup: %s' % jsonFile)
540 |
541 | INFO('RenderMan : Done !')
542 |
543 |
544 | # main
545 |
546 | try:
547 | export()
548 | except Exception:
549 | XCPT('Export failed')
550 | sys.exit(0)
551 |
--------------------------------------------------------------------------------
/RenderMan/rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "PxrDisney": {
3 | "mapping": {
4 | "basecolor": {
5 | "param": "baseColor",
6 | "type": "color"
7 | },
8 | "specular": {
9 | "param": "specular",
10 | "type": "float"
11 | },
12 | "roughness": {
13 | "param": "roughness",
14 | "type": "float"
15 | },
16 | "metallic": {
17 | "param": "metallic",
18 | "type": "float"
19 | },
20 | "opacity": {
21 | "param": null,
22 | "type": null
23 | },
24 | "emissive": {
25 | "param": "emitColor",
26 | "type": "color"
27 | },
28 | "normal": {
29 | "param": "bumpNormal",
30 | "type": "normal"
31 | }
32 | },
33 | "settings": {
34 | "basecolor": {
35 | "linearize": {
36 | "type": "int",
37 | "value": 1
38 | }
39 | },
40 | "specular": {
41 | "linearize": {
42 | "type": "int",
43 | "value": 1
44 | }
45 | },
46 | "roughness": {
47 | "linearize": {
48 | "type": "int",
49 | "value": 0
50 | }
51 | },
52 | "metallic": {
53 | "linearize": {
54 | "type": "int",
55 | "value": 0
56 | }
57 | },
58 | "opacity": {
59 | "linearize": {
60 | "type": "int",
61 | "value": 0
62 | }
63 | },
64 | "emissive": {
65 | "linearize": {
66 | "type": "int",
67 | "value": 1
68 | }
69 | },
70 | "normal": {
71 | "orientation": {
72 | "type": "int",
73 | "value": 1
74 | },
75 | "adjustAmount": {
76 | "type": "float",
77 | "value": 1.0
78 | }
79 | }
80 | }
81 | },
82 | "PxrSurface": {
83 | "mapping": {
84 | "basecolor": {
85 | "param": "graph",
86 | "type": "color"
87 | },
88 | "specular": {
89 | "param": "graph",
90 | "type": "color"
91 | },
92 | "roughness": {
93 | "param": "specularRoughness",
94 | "type": "float"
95 | },
96 | "metallic": {
97 | "param": "graph",
98 | "type": "float"
99 | },
100 | "opacity": {
101 | "param": null,
102 | "type": null
103 | },
104 | "emissive": {
105 | "param": "glowColor",
106 | "type": "color"
107 | },
108 | "normal": {
109 | "param": "bumpNormal",
110 | "type": "normal"
111 | }
112 | },
113 | "graph": {
114 | "nodes": {
115 | "_specFaceColor": {
116 | "nodetype": "PxrBlend",
117 | "params": {
118 | "operation": {
119 | "type": "int",
120 | "value": 19
121 | },
122 | "topA": {
123 | "type": "float",
124 | "value": 0.0
125 | },
126 | "bottomRGB": {
127 | "type": "color",
128 | "value": [
129 | 0.04,
130 | 0.04,
131 | 0.04
132 | ]
133 | },
134 | "bottomA": {
135 | "type": "float",
136 | "value": 1.0
137 | }
138 | }
139 | },
140 | "_specEdgeColor": {
141 | "nodetype": "PxrBlend",
142 | "params": {
143 | "operation": {
144 | "type": "int",
145 | "value": 19
146 | },
147 | "topA": {
148 | "type": "float",
149 | "value": 0.0
150 | },
151 | "bottomRGB": {
152 | "type": "color",
153 | "value": [
154 | 1,
155 | 1,
156 | 1
157 | ]
158 | },
159 | "bottomA": {
160 | "type": "float",
161 | "value": 1.0
162 | }
163 | }
164 | },
165 | "_diffuseAtten": {
166 | "nodetype": "PxrBlend",
167 | "params": {
168 | "operation": {
169 | "type": "int",
170 | "value": 19
171 | },
172 | "topRGB": {
173 | "type": "color",
174 | "value": [
175 | 0,
176 | 0,
177 | 0
178 | ]
179 | }
180 | }
181 | }
182 | },
183 | "connections": [
184 | {
185 | "src": {
186 | "node": "_specFaceColor",
187 | "param": "resultRGB"
188 | },
189 | "dst": {
190 | "node": "PxrSurface",
191 | "param": "specularFaceColor",
192 | "type": "color"
193 | }
194 | },
195 | {
196 | "src": {
197 | "node": "_specEdgeColor",
198 | "param": "resultRGB"
199 | },
200 | "dst": {
201 | "node": "PxrSurface",
202 | "param": "specularEdgeColor",
203 | "type": "color"
204 | }
205 | },
206 | {
207 | "src": {
208 | "node": "_diffuseAtten",
209 | "param": "resultRGB"
210 | },
211 | "dst": {
212 | "node": "PxrSurface",
213 | "param": "diffuseColor",
214 | "type": "color"
215 | }
216 | },
217 | {
218 | "src": {
219 | "node": "ch:basecolor",
220 | "param": "resultRGB"
221 | },
222 | "dst": {
223 | "node": "_specEdgeColor",
224 | "param": "topRGB",
225 | "type": "color"
226 | }
227 | },
228 | {
229 | "src": {
230 | "node": "ch:basecolor",
231 | "param": "resultRGB"
232 | },
233 | "dst": {
234 | "node": "_specFaceColor",
235 | "param": "topRGB",
236 | "type": "color"
237 | }
238 | },
239 | {
240 | "src": {
241 | "node": "ch:basecolor",
242 | "param": "resultRGB"
243 | },
244 | "dst": {
245 | "node": "_diffuseAtten",
246 | "param": "bottomRGB",
247 | "type": "color"
248 | }
249 | },
250 | {
251 | "src": {
252 | "node": "ch:metallic",
253 | "param": "resultR"
254 | },
255 | "dst": {
256 | "node": "_specEdgeColor",
257 | "param": "topA",
258 | "type": "float"
259 | }
260 | },
261 | {
262 | "src": {
263 | "node": "ch:metallic",
264 | "param": "resultR"
265 | },
266 | "dst": {
267 | "node": "_specFaceColor",
268 | "param": "topA",
269 | "type": "float"
270 | }
271 | },
272 | {
273 | "src": {
274 | "node": "ch:metallic",
275 | "param": "resultR"
276 | },
277 | "dst": {
278 | "node": "_diffuseAtten",
279 | "param": "topA",
280 | "type": "float"
281 | }
282 | },
283 | {
284 | "src": {
285 | "node": "ch:specular",
286 | "param": "resultRGB "
287 | },
288 | "dst": {
289 | "node": "_specFaceColor",
290 | "param": "bottomRGB",
291 | "type": "color"
292 | }
293 | }
294 | ]
295 | },
296 | "settings": {
297 | "bxdf": {
298 | "specularModelType": {
299 | "type": "int",
300 | "value": 1
301 | }
302 | },
303 | "basecolor": {
304 | "linearize": {
305 | "type": "int",
306 | "value": 1
307 | }
308 | },
309 | "specular": {
310 | "linearize": {
311 | "type": "int",
312 | "value": 1
313 | }
314 | },
315 | "roughness": {
316 | "linearize": {
317 | "type": "int",
318 | "value": 0
319 | }
320 | },
321 | "metallic": {
322 | "linearize": {
323 | "type": "int",
324 | "value": 0
325 | }
326 | },
327 | "opacity": {
328 | "linearize": {
329 | "type": "int",
330 | "value": 0
331 | }
332 | },
333 | "emissive": {
334 | "linearize": {
335 | "type": "int",
336 | "value": 1
337 | }
338 | },
339 | "normal": {
340 | "orientation": {
341 | "type": "int",
342 | "value": 1
343 | },
344 | "adjustAmount": {
345 | "type": "float",
346 | "value": 1.0
347 | }
348 | }
349 | }
350 | }
351 | }
--------------------------------------------------------------------------------
/RenderMan/toolbar.qml:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // MIT License
3 | //
4 | // Copyright (c) 2016 Philippe Leprince
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 | // ----------------------------------------------------------------------------
24 |
25 |
26 |
27 | import QtQuick 2.3
28 | import QtQuick.Window 2.2
29 | import QtQuick.Layouts 1.2
30 | import QtQuick.Controls 1.4
31 | import QtQuick.Controls.Styles 1.4
32 |
33 | import "renderman.js" as Renderman
34 |
35 | Row
36 | {
37 |
38 | Button
39 | {
40 | id: disney
41 | antialiasing: true
42 | tooltip: "Export as a PxrDisney Material Preset"
43 | width: 32
44 | height: 32
45 |
46 | style: ButtonStyle {
47 | background: Rectangle {
48 | implicitWidth: control.width
49 | implicitHeight: control.height
50 | width: control.width;
51 | height: control.height
52 | color: control.hovered ?
53 | "#262626" :
54 | "transparent"
55 |
56 | Image {
57 | anchors.fill: parent
58 | anchors.margins: 4
59 | source: control.hovered && !control.loading ? "icons/PxrDisney_hover.svg" : "icons/PxrDisney_idle.svg"
60 | fillMode: Image.PreserveAspectFit
61 | width: control.width; height: control.height
62 | mipmap: true
63 | opacity: 1.0
64 | }
65 | }
66 | }
67 |
68 | onClicked:
69 | {
70 | try
71 | {
72 | alg.log.info( 'RenderMan: Export PxrDisney-based asset...')
73 | Renderman.exportAssets('PxrDisney')
74 | }
75 | catch(err)
76 | {
77 | alg.log.error( 'rman: ' + err.message )
78 | }
79 | }
80 | }
81 |
82 |
83 | Button
84 | {
85 | id: surface
86 | antialiasing: true
87 | tooltip: "Export as a PxrSurface Material Preset"
88 | width: 32
89 | height: 32
90 |
91 | style: ButtonStyle {
92 | background: Rectangle {
93 | implicitWidth: control.width
94 | implicitHeight: control.height
95 | width: control.width;
96 | height: control.height
97 | color: control.hovered ?
98 | "#262626" :
99 | "transparent"
100 |
101 | Image {
102 | anchors.fill: parent
103 | anchors.margins: 4
104 | source: control.hovered && !control.loading ? "icons/PxrSurface_hover.svg" : "icons/PxrSurface_idle.svg"
105 | fillMode: Image.PreserveAspectFit
106 | width: control.width; height: control.height
107 | mipmap: true
108 | opacity: 1.0
109 | }
110 | }
111 | }
112 |
113 | onClicked:
114 | {
115 | try
116 | {
117 | Renderman.exportAssets('PxrSurface')
118 | }
119 | catch(err)
120 | {
121 | alg.log.error( 'rman: ' + err.message )
122 | }
123 | }
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/img/RfSP_v0.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pleprince/RfSP/f739ded7b010a5a7b4714d0500f1664f02523d8f/img/RfSP_v0.gif
--------------------------------------------------------------------------------
/img/configure_dialog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pleprince/RfSP/f739ded7b010a5a7b4714d0500f1664f02523d8f/img/configure_dialog.jpg
--------------------------------------------------------------------------------
/img/open_configure_dialog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pleprince/RfSP/f739ded7b010a5a7b4714d0500f1664f02523d8f/img/open_configure_dialog.jpg
--------------------------------------------------------------------------------
/img/shelf_buttons.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pleprince/RfSP/f739ded7b010a5a7b4714d0500f1664f02523d8f/img/shelf_buttons.jpg
--------------------------------------------------------------------------------
/renderman.rcc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pleprince/RfSP/f739ded7b010a5a7b4714d0500f1664f02523d8f/renderman.rcc
--------------------------------------------------------------------------------
/renderman_for_sp.py:
--------------------------------------------------------------------------------
1 |
2 | """python plugin for substance painter 2020+.
3 | Export substance painter maps to a RenderMan Asset package.
4 | """
5 | # -----------------------------------------------------------------------------
6 | # MIT License
7 | #
8 | # Copyright (c) 2016 Philippe Leprince
9 | #
10 | # Permission is hereby granted, free of charge, to any person obtaining a copy
11 | # of this software and associated documentation files (the "Software"), to deal
12 | # in the Software without restriction, including without limitation the rights
13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | # copies of the Software, and to permit persons to whom the Software is
15 | # furnished to do so, subject to the following conditions:
16 | #
17 | # The above copyright notice and this permission notice shall be included in all
18 | # copies or substantial portions of the Software.
19 | #
20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | # SOFTWARE.
27 | # -----------------------------------------------------------------------------
28 |
29 | # TODO: Colorspace txmake
30 | # TODO: remove non-exportable channels for def.
31 | # TODO: export progress dialog
32 | # TODO: name textures with color spaces
33 | # pylint: disable=missing-docstring,invalid-name
34 |
35 | import os
36 | import sys
37 | import traceback
38 | import inspect
39 | import json
40 | # import logging
41 | import tempfile
42 | import getpass
43 | import re
44 | import subprocess
45 | import shutil
46 | # from PySide2 import (QtWidgets, QtGui, QtCore) # pylint: disable=import-error
47 | from PySide2.QtCore import (QResource, Qt) # pylint: disable=import-error
48 | from PySide2.QtGui import (QIcon) # pylint: disable=import-error
49 | from PySide2.QtWidgets import (
50 | QWidget,
51 | QMessageBox,
52 | QFileDialog,
53 | QFormLayout,
54 | QComboBox
55 | ) # pylint: disable=import-error
56 | import substance_painter as sp # pylint: disable=import-error
57 | import substance_painter.ui as spui # pylint: disable=import-error
58 | import substance_painter.logging as spl # pylint: disable=import-error
59 | import substance_painter.project as spp # pylint: disable=import-error
60 | import substance_painter.textureset as spts # pylint: disable=import-error
61 | # import substance_painter.resource as spr # pylint: disable=import-error
62 | import substance_painter.export as spex # pylint: disable=import-error
63 |
64 |
65 | __version__ = '24.1.0'
66 | MIN_RPS = '24.1'
67 | MIN_SP_API = '0.1.0'
68 |
69 |
70 | class Log(object):
71 | def __init__(self, loglevel=spl.ERROR):
72 | self.channel = 'RenderMan %s' % __version__
73 | self.loglevel = int(loglevel)
74 | self.info('Log Level: %s (%s)', loglevel, self.loglevel)
75 | pyv = sys.version_info
76 | self.info('SP python: %d.%d.%d', *tuple(pyv[0:3]))
77 |
78 | def debug_error(self, msg, *args):
79 | if self.loglevel >= int(spl.DBG_ERROR): # 5
80 | spl.log(spl.ERROR, self.channel, msg % args)
81 |
82 | def debug_warning(self, msg, *args):
83 | if self.loglevel >= int(spl.DBG_WARNING): # 4
84 | spl.log(spl.WARNING, self.channel, msg % args)
85 |
86 | def debug_info(self, msg, *args):
87 | if self.loglevel >= int(spl.DBG_INFO): # 3
88 | spl.log(spl.INFO, self.channel, msg % args)
89 |
90 | def error(self, msg, *args):
91 | if self.loglevel >= int(spl.ERROR): # 2
92 | spl.log(spl.ERROR, self.channel, msg % args)
93 |
94 | def warning(self, msg, *args):
95 | if self.loglevel >= int(spl.WARNING): # 1
96 | spl.log(spl.WARNING, self.channel, msg % args)
97 |
98 | def info(self, msg, *args):
99 | if self.loglevel >= int(spl.INFO): # 0
100 | spl.log(spl.INFO, self.channel, msg % args)
101 |
102 |
103 | LOG = Log(loglevel=spl.ERROR)
104 |
105 |
106 | def root_dir():
107 | """Returns the path the dir from which this plugin is executed."""
108 | try:
109 | this_file_path = __file__
110 | except NameError:
111 | this_file_path = os.path.abspath(inspect.stack()[0][1])
112 | root = os.path.dirname(this_file_path)
113 | return root
114 |
115 |
116 | def pick_directory(*args):
117 | libpath = QFileDialog.getExistingDirectory(
118 | None, 'Select a directory...')
119 | args[0].setText(libpath)
120 | LOG.info('pick_directory%s', str(args))
121 |
122 |
123 | class Prefs(object):
124 |
125 | def __init__(self):
126 | self.prefs = {}
127 | self.root = root_dir()
128 | self.file = os.path.join(self.root, 'renderman.prefs')
129 | self.load()
130 | LOG.debug_info('Prefs created')
131 |
132 | def load(self):
133 | if os.path.exists(self.file):
134 | with open(self.file, 'r') as fhdl:
135 | self.prefs = json.load(fhdl)
136 | LOG.debug_info('Loaded: %s', self.file)
137 | else:
138 | LOG.debug_warning('NOT loaded: %s', self.file)
139 |
140 | def save(self):
141 | with open(self.file, mode='w') as fhdl:
142 | json.dump(self.prefs, fhdl, sort_keys=False, indent=4)
143 | LOG.debug_info('PREFS SAVED: %s', self.file)
144 |
145 | def set(self, key, val):
146 | self.prefs[key] = val
147 |
148 | def get(self, key, default):
149 | return self.prefs.get(key, default)
150 |
151 | def __del__(self):
152 | self.save()
153 |
154 |
155 | class RenderManForSP(object):
156 |
157 | def __init__(self):
158 | # find root dir
159 | self.root = root_dir()
160 | LOG.debug_info('root = %r', self.root)
161 | # load resource file
162 | rpath = os.path.join(self.root, 'renderman.rcc')
163 | rloaded = QResource.registerResource(rpath)
164 | if not rloaded:
165 | LOG.error('Invalid Resource: %s', rpath)
166 | # init UI
167 | self.prefs = Prefs()
168 | self.widget, self.dock = self.build_panel()
169 |
170 | def cleanup(self):
171 | LOG.debug_info('cleanup')
172 | self.prefs.save()
173 | spui.delete_ui_element(self.dock)
174 |
175 | def build_panel(self):
176 | """Build the UI"""
177 | LOG.debug_info('build_panel')
178 | # Create a simple text widget
179 | root = QWidget(None, Qt.Window)
180 | root.setWindowTitle("RenderMan")
181 | logo = QIcon(':R_logo.svg')
182 | logo.addFile(':R_logo_white.svg', mode=QIcon.Normal, state=QIcon.On)
183 | root.setWindowIcon(logo)
184 | # Add this widget as a dock to the interface
185 | dock = spui.add_dock_widget(root)
186 |
187 | # preset browser
188 | rman_version_str = env_check(self.prefs)
189 | try:
190 | import rman_utils.rman_assets as ra
191 | import rman_utils.rman_assets.core as rac
192 | import rman_utils.rman_assets.ui as rui
193 | import rman_utils.rman_assets.lib as ral
194 | # from rman_utils.rman_assets.common.ui_utils import createHLayout
195 | from rman_utils.filepath import FilePath
196 | except BaseException as err:
197 | LOG.error('Failed to import: %s', err)
198 | traceback.print_exc(file=sys.stdout)
199 | else:
200 | # ra.setLogLevel(logging.DEBUG)
201 |
202 | class SPrefs(ral.HostPrefs):
203 | saved = {
204 | 'rpbConfigFile': FilePath(''), 'rpbUserLibraries': [],
205 | 'rpbSwatchSize': 64, 'rpbSelectedPreviewEnv': 0,
206 | 'rpbSelectedCategory': 'Materials',
207 | 'rpbSelectedLibrary': FilePath(''),
208 | 'rpbRenderAllHDRs': False,
209 | 'rpbHideFactoryLib': False
210 | }
211 |
212 | def __init__(self, rman_version, pref_obj):
213 | super(SPrefs, self).__init__(rman_version)
214 | self.root_dir = root_dir()
215 | self.prefsobj = pref_obj
216 | self.rules = self._load_rules()
217 | if 'host_prefs' in self.prefsobj.prefs:
218 | hprefs = self.prefsobj.prefs['host_prefs']
219 | for k in self.saved:
220 | setattr(self, k, hprefs.get(k, self.saved[k]))
221 | if k == 'rpbConfigFile':
222 | self.rpbConfigFile = FilePath(
223 | self.rpbConfigFile)
224 | elif k == 'rpbSelectedLibrary':
225 | self.rpbSelectedLibrary = FilePath(
226 | self.rpbSelectedLibrary)
227 | elif k == 'rpbUserLibraries' and self.rpbUserLibraries:
228 | self.rpbUserLibraries = [
229 | FilePath(f) for f in self.rpbUserLibraries]
230 | # export vars
231 | self.spx_exported_files = {}
232 | self.opt_bxdf = None
233 | self.opt_ocio = None
234 | self._defaultLabel = 'UNTITLED'
235 | self.ocio_config = {'config': None, 'path': None}
236 | # render previews
237 | self.hostTree = ''
238 | self.rmanTree = self.prefsobj.get('RMANTREE', '')
239 | LOG.debug_info('SPrefs object created')
240 |
241 | def getHostPref(self, pref_name, default_value):
242 | return self.prefsobj.get(pref_name, default_value)
243 |
244 | def setHostPref(self, pref_name, value):
245 | prefs = self.prefsobj.get('host_prefs', {})
246 | prefs[pref_name] = value
247 | self.prefsobj.set('host_prefs', prefs)
248 | # self._print()
249 |
250 | def saveAllPrefs(self):
251 | for k in self.saved:
252 | self.setHostPref(k, getattr(self, k))
253 | # self._print()
254 |
255 | def preExportCheck(self, mode, hdr=None):
256 | LOG.debug_info('preExportCheck: %r, hdr=%r', mode, hdr)
257 | if mode == 'material':
258 | try:
259 | self._defaultLabel = spp.name() or 'UNTITLED'
260 | except BaseException as err:
261 | LOG.error('%s', err)
262 | msg_box(str(err), '', QMessageBox.Ok, QMessageBox.Ok)
263 | return False
264 | return True
265 | LOG.warning('Not supported (%s)', mode)
266 | msg_box('This is not supported !', 'Sorry...',
267 | QMessageBox.Ok, QMessageBox.Ok)
268 | return False
269 |
270 | def exportMaterial(self, categorypath, infodict, previewtype):
271 | LOG.debug_info(
272 | 'exportMaterial: %r, %r, %r', categorypath, infodict,
273 | previewtype)
274 | #
275 | _bxdf = self.opt_bxdf.currentText()
276 | self.prefsobj.set('last bxdf', _bxdf)
277 | LOG.debug_info('chosen bxdf: %s', _bxdf)
278 | _ocio = self.opt_ocio.currentText()
279 | self.ocio_config['config'] = _ocio
280 | if _ocio == '$OCIO':
281 | self.ocio_config['path'] = FilePath(os.environ['OCIO'])
282 | elif _ocio != 'Off':
283 | self.ocio_config['path'] = FilePath(self.rmanTree).join(
284 | 'lib', 'ocio', _ocio, 'config.ocio')
285 | self.prefsobj.set('ocio config', _ocio)
286 | LOG.debug_info('chosen ocio config: %s', _ocio)
287 | # setup data
288 | bxdf_rules = self.rules['models'][_bxdf]
289 | mappings = bxdf_rules['mapping']
290 | graph = bxdf_rules.get('graph', None)
291 | settings = bxdf_rules.get('settings', None)
292 | scene = infodict['label']
293 |
294 | # we save the assets to SP's export directory, because we
295 | # know it is writable. We will move them to the requested
296 | # location later.
297 | export_path = FilePath(tempfile.mkdtemp(prefix='rfsp_export_'))
298 |
299 | # export project textures
300 | self.sp_export(export_path)
301 |
302 | # list of spts.TextureSet objects
303 | tset_list = spts.all_texture_sets()
304 |
305 | # build assets
306 | asset_list = []
307 | for mat in tset_list:
308 | label = scene
309 | is_udim = mat.has_uv_tiles()
310 | if not is_udim:
311 | label = '%s_%s' % (scene, mat.name())
312 |
313 | # chans = mat.get_stack().all_channels()
314 | chans = self.textureset_channels(mat)
315 | LOG.debug_info('+ Exporting %s', label)
316 |
317 | asset_path = export_path.join(label + '.rma')
318 | LOG.debug_info(' + asset_path %s', asset_path)
319 | asset_json_path = asset_path.join('asset.json')
320 | LOG.debug_info(' + asset_json_path %s', asset_json_path)
321 |
322 | # create asset directory
323 | create_directory(asset_path)
324 |
325 | # create asset
326 | try:
327 | asset = rac.RmanAsset(assetType='nodeGraph', label=label)
328 | except Exception:
329 | LOG.error('Asset creation failed')
330 | raise
331 |
332 | asset.ocio = self.ocio_config
333 |
334 | # create standard metadata
335 | #
336 | self.set_metadata(asset, mat)
337 |
338 | # create nodes
339 | # start by adding a root node
340 | #
341 | root_node = label + '_Material'
342 | asset.addNode(root_node, 'shadingEngine', 'root', 'shadingEngine')
343 | pdict = {'type': 'reference float[]', 'value': None}
344 | asset.addParam(root_node, 'surfaceShader', pdict)
345 | LOG.debug_info(' + Root node: %s', root_node)
346 |
347 | # add a disney, pixar or lama bxdf
348 | #
349 | bxdf_node = label + "_Srf"
350 | asset.addNode(bxdf_node, _bxdf, 'bxdf', _bxdf)
351 | LOG.debug_info(' + BxDF node: %s (%s)', root_node, _bxdf)
352 |
353 | # The bxdf may need specific settings to match Substance Painter
354 | set_params(settings, 'bxdf', bxdf_node, asset)
355 |
356 | # connect surf to root node
357 | #
358 | asset.addConnection('%s.outColor' % bxdf_node,
359 | '%s.surfaceShader' % root_node)
360 |
361 | # build additional nodes if need be.
362 | #
363 | if graph:
364 | LOG.debug_info(' + Create graph nodes...')
365 | for nname, ndict in graph['nodes'].items():
366 | lname = label + nname
367 | asset.addNode(lname, ndict['nodetype'],
368 | ndict.get('category', 'pattern'),
369 | ndict['nodetype'])
370 | LOG.debug_info(
371 | ' |_ %s (%s)', lname, ndict['nodetype'])
372 | if 'params' in ndict:
373 | for pname, pdict in ndict['params'].items():
374 | asset.addParam(lname, pname, pdict)
375 | LOG.debug_info(
376 | ' |_ param: %s %s = %s',
377 | pdict['type'], pname, pdict['value'])
378 |
379 | # create texture nodes
380 | LOG.debug_info(' + Create texture nodes...')
381 | chan_nodes = {}
382 | for ch_type in chans:
383 | fpath_list = self.spx_exported_files[mat.name()].get(ch_type, None)
384 | if fpath_list is None:
385 | LOG.debug_warning(
386 | ' |_ tex_dict[%r][%r] failed', mat.name(),
387 | ch_type)
388 | continue
389 | node_name = "%s_%s_tex" % (label, ch_type)
390 | LOG.debug_info(' |_ %s', node_name)
391 | chan_nodes[ch_type] = node_name
392 | colorspace = mappings[ch_type]['ocio']
393 | fpath = self.txmake(is_udim, asset_path, fpath_list,
394 | self.ocio_config, colorspace)
395 | if ch_type == 'Normal':
396 | add_texture_node(asset, node_name, 'PxrNormalMap', fpath)
397 | elif ch_type == 'Height':
398 | add_texture_node(asset, node_name, 'PxrBump', fpath)
399 | else:
400 | add_texture_node(asset, node_name, 'PxrTexture', fpath)
401 | set_params(settings, ch_type, node_name, asset)
402 |
403 | # print_dict(chan_nodes, msg='chan_nodes:\n')
404 |
405 | # make direct connections
406 | #
407 | LOG.debug_info(' + Direct connections...')
408 | for ch_type in chans:
409 | if not ch_type in mappings:
410 | LOG.debug_warning(' |_ skipped %r', ch_type)
411 | continue
412 | src = None
413 | dst_type = mappings[ch_type]['type']
414 | dst_param = mappings[ch_type]['param']
415 | if dst_type == 'normal':
416 | src = '%s.resultN' % (chan_nodes[ch_type])
417 | elif dst_type == 'color':
418 | src = '%s.resultRGB' % (chan_nodes[ch_type])
419 | elif dst_type == 'float':
420 | src = '%s.resultR' % (chan_nodes[ch_type])
421 | else:
422 | # don't create a connection
423 | if dst_param != 'graph':
424 | # connections with a graph type will be handled later, so
425 | # we don't warn in that case.
426 | LOG.debug_warning('WARNING: Not connecting: %s', ch_type)
427 | continue
428 | if dst_param == 'graph':
429 | continue
430 | dst = '%s.%s' % (bxdf_node, dst_param)
431 | asset.addConnection(src, dst)
432 | LOG.debug_info(' |_ connect: %s -> %s' % (src, dst))
433 | # also tag the bxdf param as connected
434 | pdict = {'type': 'reference ' + dst_type, 'value': None}
435 | asset.addParam(bxdf_node, dst_param, pdict)
436 | LOG.debug_info(
437 | ' |_ param: %s %s -> %s', pdict['type'],
438 | dst_param, pdict['value'])
439 |
440 | # make graph connections
441 | #
442 | if graph and 'connections' in graph:
443 | LOG.debug_info(' + Connect graph nodes...')
444 | for con in graph['connections']:
445 |
446 | src_node = con['src']['node']
447 | src_ch = None
448 | if src_node == _bxdf:
449 | src_node = bxdf_node
450 | elif src_node.startswith('ch:'):
451 | src_ch = src_node[3:]
452 | if src_ch in chan_nodes:
453 | src_node = chan_nodes[src_ch]
454 | else:
455 | continue
456 | if not src_node.startswith(label):
457 | src_node = label + src_node
458 | src = '%s.%s' % (src_node, con['src']['param'])
459 |
460 | dst_node = con['dst']['node']
461 | dst_ch = None
462 | if dst_node == _bxdf:
463 | dst_node = bxdf_node
464 | elif dst_node.startswith('ch:'):
465 | dst_ch = dst_node[3:]
466 | if dst_ch in chan_nodes:
467 | dst_node = chan_nodes[dst_ch]
468 | else:
469 | continue
470 | if not dst_node.startswith(label):
471 | dst_node = label + dst_node
472 | dst = '%s.%s' % (dst_node, con['dst']['param'])
473 | asset.addConnection(src, dst)
474 | LOG.debug_info(' |_ connect: %s -> %s', src, dst)
475 | # mark param as a connected
476 | dstType = con['dst']['type']
477 | pdict = {'type': 'reference %s' % dstType, 'value': None}
478 | asset.addParam(dst_node, con['dst']['param'], pdict)
479 | LOG.debug_info(
480 | ' |_ param: %s %s = %s',
481 | pdict['type'], con['dst']['param'],
482 | pdict['value'])
483 |
484 | # save asset
485 | #
486 | LOG.debug_info(' + ready to save: %s' % asset_json_path)
487 | try:
488 | asset.save(asset_json_path, False)
489 | except:
490 | LOG.error('Saving the asset failed !')
491 | raise
492 |
493 | # mark this asset as ready to be moved
494 | #
495 | asset_list.append(asset_path)
496 |
497 | # move assets to the requested location
498 | #
499 | dst = ral.getAbsCategoryPath(self.cfg, categorypath)
500 | for item in asset_list:
501 | # if the asset already exists in the destination
502 | # location, we need to move it first.
503 | dst_asset = os.path.join(dst, os.path.basename(item))
504 | if os.path.exists(dst_asset):
505 | try:
506 | os.rename(dst_asset, dst_asset + '_old')
507 | except (OSError, IOError):
508 | LOG.error('Could not rename asset to %s_old' % dst_asset)
509 | continue
510 | else:
511 | shutil.rmtree(dst_asset + '_old', ignore_errors=False)
512 | try:
513 | shutil.move(item, dst)
514 | except (OSError, IOError):
515 | LOG.error('WARNING: Could not copy asset to %s' % dst)
516 |
517 |
518 | # clean-up intermediate files
519 | for mat in tset_list:
520 | for chan, fpath_list in chans.items():
521 | for fpath in fpath_list:
522 | if not os.path.exists(fpath):
523 | LOG.warning('cleanup: file not found: %s', fpath)
524 | continue
525 | try:
526 | os.remove(fpath)
527 | except (OSError, IOError):
528 | LOG.error('Cleanup failed: %s' % fpath)
529 | else:
530 | LOG.debug_info('Cleanup: %s' % fpath)
531 |
532 | LOG.debug_info('RenderMan : Done !')
533 | return True
534 |
535 | def addUiExportOptions(self, top_layout, mode):
536 | if mode == 'material':
537 | lyt = QFormLayout()
538 | lyt.FieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
539 | lyt.setContentsMargins(5, 5, 5, 5)
540 | lyt.setSpacing(5)
541 | lyt.setLabelAlignment(Qt.AlignRight)
542 | # BXDF
543 | self.opt_bxdf = QComboBox()
544 | self.opt_bxdf.addItems(list(self.rules['models'].keys()))
545 | lyt.addRow('BxDF :', self.opt_bxdf)
546 | # color space
547 | self.opt_ocio = QComboBox()
548 | self.opt_ocio.addItems(['Off', 'ACES-1.2',
549 | 'filmic-blender', '$OCIO'])
550 | lyt.addRow('Color configuration :', self.opt_ocio)
551 | # add to parent layout
552 | top_layout.addLayout(lyt)
553 | # set last used bxdf and ocio config
554 | last_bxdf = self.prefsobj.get('last bxdf', None)
555 | if last_bxdf:
556 | self.opt_bxdf.setCurrentText(last_bxdf)
557 | ocio_config = self.prefsobj.get('ocio config', None)
558 | if ocio_config:
559 | self.opt_ocio.setCurrentText(ocio_config)
560 |
561 | def _print(self):
562 | prefs = self.prefsobj.get('host_prefs', {})
563 | loaded = ['\t%s: %s\n' % (k, prefs[k]) for k in prefs]
564 | state = ['\t%s: %s\n' % (k, getattr(self, k)) for k in self.saved]
565 | LOG.debug_info(
566 | '%r ------------------------\nLOADED:\n%s\nSTATE:\n%s', self,
567 | ''.join(loaded), ''.join(state))
568 |
569 | def _load_rules(self):
570 | fpath = FilePath(root_dir()).join('renderman_rules.json')
571 | if fpath.exists():
572 | with open(fpath, 'r') as hdl:
573 | data = json.load(hdl)
574 | return data
575 | else:
576 | LOG.error('RULES ARE MISSING: can not open %r', fpath)
577 | return {}
578 |
579 | def set_metadata(self, asset, sp_ts):
580 | meta = asset.stdMetadata()
581 | meta['author'] = getpass.getuser()
582 | meta['description'] = ('Created by RenderMan for Substance '
583 | 'Painter %s' % __version__)
584 | res = sp_ts.get_resolution()
585 | meta['resolution'] = '%d x %d' % (res.width, res.height)
586 | for k, v in meta.items():
587 | asset.addMetadata(k, v)
588 | # Compatibility data
589 | # This will help other application decide if they can use this asset.
590 | #
591 | asset.setCompatibility(
592 | hostName='Substance Painter',
593 | hostVersion=sp.__version__,
594 | rendererVersion=str(self.rman_version))
595 | LOG.debug_info(' + compatibility set')
596 |
597 | def sp_export(self, export_path):
598 | tset_names = [s.name() for s in spts.all_texture_sets()]
599 | config = dict(self.rules['export_config'])
600 | tex_path = export_path.join('exported')
601 | create_directory(tex_path)
602 | config['exportPath'] = tex_path.os_path()
603 | # config['defaultExportPreset'] = spr.ResourceID(
604 | # context='allegorithmic', name='Renderman (pxrDisney)').url()
605 | config['exportList'] = [{'rootPath': n} for n in tset_names]
606 | # print_dict(config, msg='config:\n')
607 | result = spex.export_project_textures(config)
608 | if result.status != spex.ExportStatus.Success:
609 | LOG.error(result.message)
610 | raise RuntimeError(result.message)
611 | LOG.debug_info('+ Exported --------------------------------------------')
612 | self.spx_exported_files = {}
613 | for stack, texs in result.textures.items():
614 | LOG.debug_info(' |_ Stack %s: ', stack)
615 | stck_name = stack[0]
616 | self.spx_exported_files[stck_name] = {}
617 | for t in texs:
618 | LOG.debug_info(' |_ %s', t)
619 | if t:
620 | ch_type = re.search(r'_([A-Za-z]+)(\.\d{4})*\.\w{3}$', t).group(1)
621 | if ch_type in self.spx_exported_files[stck_name]:
622 | self.spx_exported_files[stck_name][ch_type].append(t)
623 | else:
624 | self.spx_exported_files[stck_name][ch_type] = [t]
625 |
626 | def textureset_channels(self, spts_textureset):
627 | result = {}
628 | ts_name = spts_textureset.name()
629 | chans = spts_textureset.get_stack().all_channels()
630 | for chan_type in chans:
631 | if ts_name in self.spx_exported_files:
632 | ch = chan_type_str(chan_type)
633 | result[chan_type_str(chan_type)] = \
634 | self.spx_exported_files[ts_name].get(ch, [])
635 | return result
636 |
637 | def txmake(self, is_udim, asset_path, fpath_list, ocio_config,
638 | ocio_colorspace):
639 |
640 | rmantree = FilePath(os.environ['RMANTREE'])
641 | binary = rmantree.join('bin', app('txmake')).os_path()
642 | cmd = [binary]
643 | if is_udim:
644 | cmd += ['-resize', 'round-',
645 | '-mode', 'clamp',
646 | '-format', 'openexr',
647 | '-compression', 'pxr24',
648 | '-newer']
649 | else:
650 | cmd += ['-resize', 'round-',
651 | '-mode', 'periodic',
652 | '-format', 'openexr',
653 | '-compression', 'pxr24',
654 | '-newer']
655 | if ocio_config['path']:
656 | cmd += ['-ocioconfig', ocio_config['path'],
657 | '-ocioconvert', ocio_colorspace, 'rendering']
658 | cmd += ['src', 'dst']
659 | LOG.debug_info(' |_ cmd = %r', ' '.join(cmd))
660 | for img in fpath_list:
661 | img = FilePath(img)
662 | cmd[-2] = img.os_path()
663 | filename = img.basename()
664 | texfile = os.path.splitext(filename)[0] + '.tex'
665 | cmd[-1] = asset_path.join(texfile).os_path()
666 | LOG.debug_info(' |_ txmake : %s -> %s',
667 | cmd[-2], cmd[-1])
668 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
669 | stderr=subprocess.PIPE,
670 | startupinfo=startup_info())
671 | p.wait()
672 |
673 | # return a local path to the tex file.
674 | filename = FilePath(fpath_list[0]).basename()
675 | fname, _ = os.path.splitext(filename)
676 | asset_file_ref = FilePath(asset_path).join(fname + '.tex')
677 | if is_udim:
678 | asset_file_ref = re.sub(r'1\d{3}', '', asset_file_ref)
679 | return FilePath(asset_file_ref)
680 |
681 |
682 | root.setWindowFlag(Qt.SubWindow, True)
683 | try:
684 | self.aui = rui.Ui(SPrefs(rman_version_str, self.prefs), parent=root)
685 | except BaseException:
686 | traceback.print_exc(file=sys.stdout)
687 | else:
688 | root.setLayout(self.aui.topLayout)
689 |
690 | LOG.debug_info(' |_ done')
691 | return root, dock
692 |
693 |
694 | def pick_rmantree():
695 | rmantree = QFileDialog.getExistingDirectory(
696 | None,
697 | caption='Select your RenderManProServer %s+ directory' % MIN_RPS)
698 | if not 'RenderManProServer-' in rmantree:
699 | ret = msg_box(
700 | 'This is not a RenderManProServer directory !',
701 | 'This software needs RendermanProServer-%s+ to run.' % MIN_RPS,
702 | QMessageBox.Abort | QMessageBox.Retry, QMessageBox.Retry)
703 | if ret == QMessageBox.Abort:
704 | raise RuntimeError('This is not a RenderMan Pro Server directory')
705 | else:
706 | return pick_rmantree()
707 | return rmantree
708 |
709 |
710 | def env_check(prefs):
711 | rmantree = prefs.get('RMANTREE', None)
712 | if rmantree is None or not os.path.exists(rmantree):
713 | rmantree = pick_rmantree()
714 | prefs.set('RMANTREE', rmantree)
715 | # check the version
716 | rps_version = re.search(r'RenderManProServer-([\d]+)', rmantree).group(1)
717 | if rps_version < MIN_RPS:
718 | ret = msg_box(
719 | 'RenderMan version too old !',
720 | 'This software needs RendermanProServer-%s+ to run.' % MIN_RPS,
721 | QMessageBox.Abort | QMessageBox.Retry, QMessageBox.Retry)
722 | if ret == QMessageBox.Retry:
723 | return env_check(prefs)
724 | raise RuntimeError(
725 | 'This software needs RendermanProServer-%s+ to run.' % MIN_RPS)
726 |
727 | LOG.info('RMANTREE = %r', rmantree)
728 | os.environ['RMANTREE'] = rmantree
729 | rmp_path = os.path.join(rmantree, 'lib', 'python3.7', 'site-packages')
730 | if rmp_path not in sys.path:
731 | sys.path.append(rmp_path)
732 | rmu_path = os.path.join(rmantree, 'bin')
733 | if rmu_path not in sys.path:
734 | sys.path.append(rmu_path)
735 | return rps_version
736 |
737 |
738 | def create_directory(dir_path):
739 | if not dir_path.exists():
740 | try:
741 | os.mkdir(dir_path.os_path())
742 | except (OSError, IOError):
743 | LOG.error('Asset directory could not be created !')
744 | raise
745 | LOG.debug_info(' + Created dir: %s', dir_path)
746 | else:
747 | LOG.debug_info(' + dir exists: %s', dir_path)
748 |
749 |
750 | def set_params(settings_dict, chan, node_name, asset):
751 | # The bxdf may need specific settings to match Substance Painter
752 | try:
753 | params = settings_dict[chan]
754 | except (KeyError, TypeError):
755 | pass
756 | else:
757 | for pname, pdict in params.items():
758 | asset.addParam(node_name, pname, pdict)
759 | LOG.debug_info(' |_ param: %s %s = %s', pdict['type'],
760 | pname, pdict['value'])
761 |
762 |
763 | def add_texture_node(asset, node_name, ntype, filepath):
764 | asset.addNode(node_name, ntype, 'pattern', ntype)
765 | pdict = {'type': 'string', 'value': filepath.basename()}
766 | asset.addParam(node_name, 'filename', pdict)
767 |
768 |
769 | def chan_type_str(channel_type):
770 | return str(channel_type).split('.')[-1]
771 |
772 |
773 | def print_dict(some_dict, msg=''):
774 | LOG.debug_info(msg + json.dumps(some_dict, indent=4))
775 |
776 |
777 | def app(name):
778 | if os.name is 'nt':
779 | return name + '.exe'
780 | return name
781 |
782 |
783 | def startup_info():
784 | """Returns a Windows-only object to make sure tasks launched through
785 | subprocess don't open a cmd window.
786 |
787 | Returns:
788 | subprocess.STARTUPINFO -- the properly configured object if we are on
789 | Windows, otherwise None
790 | """
791 | startupinfo = None
792 | if os.name is 'nt':
793 | startupinfo = subprocess.STARTUPINFO()
794 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
795 | return startupinfo
796 |
797 |
798 | def msg_box(msg, infos, buttons, default_button):
799 | wdgt = QMessageBox()
800 | wdgt.setText(msg)
801 | wdgt.setInformativeText(infos)
802 | wdgt.setStandardButtons(buttons)
803 | wdgt.setDefaultButton(default_button)
804 | return wdgt.exec_()
805 |
806 |
807 | # -----------------------------------------------------------------------------
808 |
809 | def start_plugin():
810 | """This method is called when the plugin is started."""
811 | if sp.__version__ < MIN_SP_API:
812 | raise RuntimeError(
813 | 'RenderMan for Substance Painter requires python API %s+ !' % MIN_SP_API)
814 | setattr(start_plugin, 'obj', RenderManForSP())
815 | LOG.info('RenderMan started')
816 |
817 |
818 | def close_plugin():
819 | """This method is called when the plugin is stopped."""
820 | # We need to remove all added widgets from the UI.
821 | rman_obj = getattr(start_plugin, 'obj')
822 | rman_obj.cleanup()
823 | del rman_obj
824 | setattr(start_plugin, 'obj', None)
825 | LOG.info('RenderMan stopped')
826 |
827 |
828 | if __name__ == "__main__":
829 | start_plugin()
830 |
--------------------------------------------------------------------------------
/renderman_rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "models": {
3 | "PxrDisney": {
4 | "mapping": {
5 | "BaseColor": {
6 | "param": "baseColor",
7 | "type": "color",
8 | "ocio": "srgb_texture"
9 | },
10 | "Specular": {
11 | "param": "specular",
12 | "type": "float",
13 | "ocio": "srgb_texture"
14 | },
15 | "Roughness": {
16 | "param": "roughness",
17 | "type": "float",
18 | "ocio": "data"
19 | },
20 | "Metallic": {
21 | "param": "metallic",
22 | "type": "float",
23 | "ocio": "data"
24 | },
25 | "Opacity": {
26 | "param": null,
27 | "type": null,
28 | "ocio": "data"
29 | },
30 | "Emissive": {
31 | "param": "emitColor",
32 | "type": "color",
33 | "ocio": "srgb_texture"
34 | },
35 | "Normal": {
36 | "param": "bumpNormal",
37 | "type": "normal",
38 | "ocio": "data"
39 | },
40 | "Height": {
41 | "param": null,
42 | "type": null,
43 | "ocio": "data"
44 | }
45 | },
46 | "settings": {
47 | "BaseColor": {
48 | "linearize": {
49 | "type": "int",
50 | "value": 1
51 | }
52 | },
53 | "Specular": {
54 | "linearize": {
55 | "type": "int",
56 | "value": 1
57 | }
58 | },
59 | "Roughness": {
60 | "linearize": {
61 | "type": "int",
62 | "value": 0
63 | }
64 | },
65 | "Metallic": {
66 | "linearize": {
67 | "type": "int",
68 | "value": 0
69 | }
70 | },
71 | "Opacity": {
72 | "linearize": {
73 | "type": "int",
74 | "value": 0
75 | }
76 | },
77 | "Emissive": {
78 | "linearize": {
79 | "type": "int",
80 | "value": 1
81 | }
82 | },
83 | "Normal": {
84 | "orientation": {
85 | "type": "int",
86 | "value": 1
87 | },
88 | "adjustAmount": {
89 | "type": "float",
90 | "value": 1.0
91 | }
92 | }
93 | }
94 | },
95 | "PxrSurface": {
96 | "mapping": {
97 | "BaseColor": {
98 | "param": "graph",
99 | "type": "color",
100 | "ocio": "srgb_texture"
101 | },
102 | "Specular": {
103 | "param": "graph",
104 | "type": "color",
105 | "ocio": "srgb_texture"
106 | },
107 | "Roughness": {
108 | "param": "specularRoughness",
109 | "type": "float",
110 | "ocio": "data"
111 | },
112 | "Metallic": {
113 | "param": "graph",
114 | "type": "float",
115 | "ocio": "data"
116 | },
117 | "Opacity": {
118 | "param": "presence",
119 | "type": "float",
120 | "ocio": "data"
121 | },
122 | "Emissive": {
123 | "param": "glowColor",
124 | "type": "color",
125 | "ocio": "srgb_texture"
126 | },
127 | "Normal": {
128 | "param": "bumpNormal",
129 | "type": "normal",
130 | "ocio": "data"
131 | },
132 | "Height": {
133 | "param": null,
134 | "type": null,
135 | "ocio": "data"
136 | }
137 | },
138 | "graph": {
139 | "nodes": {
140 | "_specFaceColor": {
141 | "nodetype": "PxrBlend",
142 | "category": "pattern",
143 | "params": {
144 | "operation": {
145 | "type": "int",
146 | "value": 19
147 | },
148 | "topA": {
149 | "type": "float",
150 | "value": 0.0
151 | },
152 | "bottomRGB": {
153 | "type": "color",
154 | "value": [
155 | 0.04,
156 | 0.04,
157 | 0.04
158 | ]
159 | },
160 | "bottomA": {
161 | "type": "float",
162 | "value": 1.0
163 | }
164 | }
165 | },
166 | "_specEdgeColor": {
167 | "nodetype": "PxrBlend",
168 | "category": "pattern",
169 | "params": {
170 | "operation": {
171 | "type": "int",
172 | "value": 19
173 | },
174 | "topA": {
175 | "type": "float",
176 | "value": 0.0
177 | },
178 | "bottomRGB": {
179 | "type": "color",
180 | "value": [
181 | 1,
182 | 1,
183 | 1
184 | ]
185 | },
186 | "bottomA": {
187 | "type": "float",
188 | "value": 1.0
189 | }
190 | }
191 | },
192 | "_diffuseAtten": {
193 | "nodetype": "PxrBlend",
194 | "category": "pattern",
195 | "params": {
196 | "operation": {
197 | "type": "int",
198 | "value": 19
199 | },
200 | "topRGB": {
201 | "type": "color",
202 | "value": [
203 | 0,
204 | 0,
205 | 0
206 | ]
207 | }
208 | }
209 | }
210 | },
211 | "connections": [
212 | {
213 | "src": {
214 | "node": "_specFaceColor",
215 | "param": "resultRGB"
216 | },
217 | "dst": {
218 | "node": "PxrSurface",
219 | "param": "specularFaceColor",
220 | "type": "color"
221 | }
222 | },
223 | {
224 | "src": {
225 | "node": "_specEdgeColor",
226 | "param": "resultRGB"
227 | },
228 | "dst": {
229 | "node": "PxrSurface",
230 | "param": "specularEdgeColor",
231 | "type": "color"
232 | }
233 | },
234 | {
235 | "src": {
236 | "node": "_diffuseAtten",
237 | "param": "resultRGB"
238 | },
239 | "dst": {
240 | "node": "PxrSurface",
241 | "param": "diffuseColor",
242 | "type": "color"
243 | }
244 | },
245 | {
246 | "src": {
247 | "node": "ch:BaseColor",
248 | "param": "resultRGB"
249 | },
250 | "dst": {
251 | "node": "_specEdgeColor",
252 | "param": "topRGB",
253 | "type": "color"
254 | }
255 | },
256 | {
257 | "src": {
258 | "node": "ch:BaseColor",
259 | "param": "resultRGB"
260 | },
261 | "dst": {
262 | "node": "_specFaceColor",
263 | "param": "topRGB",
264 | "type": "color"
265 | }
266 | },
267 | {
268 | "src": {
269 | "node": "ch:BaseColor",
270 | "param": "resultRGB"
271 | },
272 | "dst": {
273 | "node": "_diffuseAtten",
274 | "param": "bottomRGB",
275 | "type": "color"
276 | }
277 | },
278 | {
279 | "src": {
280 | "node": "ch:Metallic",
281 | "param": "resultR"
282 | },
283 | "dst": {
284 | "node": "_specEdgeColor",
285 | "param": "topA",
286 | "type": "float"
287 | }
288 | },
289 | {
290 | "src": {
291 | "node": "ch:Metallic",
292 | "param": "resultR"
293 | },
294 | "dst": {
295 | "node": "_specFaceColor",
296 | "param": "topA",
297 | "type": "float"
298 | }
299 | },
300 | {
301 | "src": {
302 | "node": "ch:Metallic",
303 | "param": "resultR"
304 | },
305 | "dst": {
306 | "node": "_diffuseAtten",
307 | "param": "topA",
308 | "type": "float"
309 | }
310 | },
311 | {
312 | "src": {
313 | "node": "ch:Specular",
314 | "param": "resultRGB "
315 | },
316 | "dst": {
317 | "node": "_specFaceColor",
318 | "param": "bottomRGB",
319 | "type": "color"
320 | }
321 | }
322 | ]
323 | },
324 | "settings": {
325 | "bxdf": {
326 | "specularModelType": {
327 | "type": "int",
328 | "value": 1
329 | }
330 | },
331 | "BaseColor": {
332 | "linearize": {
333 | "type": "int",
334 | "value": 1
335 | }
336 | },
337 | "Specular": {
338 | "linearize": {
339 | "type": "int",
340 | "value": 1
341 | }
342 | },
343 | "Roughness": {
344 | "linearize": {
345 | "type": "int",
346 | "value": 0
347 | }
348 | },
349 | "Metallic": {
350 | "linearize": {
351 | "type": "int",
352 | "value": 0
353 | }
354 | },
355 | "Opacity": {
356 | "linearize": {
357 | "type": "int",
358 | "value": 0
359 | }
360 | },
361 | "Emissive": {
362 | "linearize": {
363 | "type": "int",
364 | "value": 1
365 | }
366 | },
367 | "Normal": {
368 | "orientation": {
369 | "type": "int",
370 | "value": 1
371 | },
372 | "adjustAmount": {
373 | "type": "float",
374 | "value": 1.0
375 | }
376 | }
377 | }
378 | },
379 | "LamaSurface": {
380 | "mapping": {
381 | "BaseColor": {
382 | "param": "graph",
383 | "type": "color",
384 | "ocio": "srgb_texture"
385 | },
386 | "Specular": {
387 | "param": "graph",
388 | "type": "color",
389 | "ocio": "srgb_texture"
390 | },
391 | "Roughness": {
392 | "param": "graph",
393 | "type": "float",
394 | "ocio": "data"
395 | },
396 | "Metallic": {
397 | "param": "graph",
398 | "type": "float",
399 | "ocio": "data"
400 | },
401 | "Opacity": {
402 | "param": "presence",
403 | "type": "float",
404 | "ocio": "data"
405 | },
406 | "Emissive": {
407 | "param": "graph",
408 | "type": "color",
409 | "ocio": "srgb_texture"
410 | },
411 | "Normal": {
412 | "param": "graph",
413 | "type": "normal",
414 | "ocio": "data"
415 | },
416 | "Height": {
417 | "param": null,
418 | "type": null,
419 | "ocio": "data"
420 | }
421 | },
422 | "graph": {
423 | "nodes": {
424 | "_clearcoatLayer": {
425 | "nodetype": "LamaLayer",
426 | "category": "bxdf",
427 | "params": {
428 | "topMix": {
429 | "type": "float",
430 | "value": 0.0
431 | }
432 | }
433 | },
434 | "_clearcoat": {
435 | "nodetype": "LamaDielectric",
436 | "category": "bxdf",
437 | "params": {}
438 | },
439 | "_diffSpec": {
440 | "nodetype": "LamaAdd",
441 | "category": "bxdf",
442 | "params": {
443 | "weight1": {
444 | "type": "float",
445 | "value": 1.0
446 | },
447 | "weight2": {
448 | "type": "float",
449 | "value": 1.0
450 | }
451 | }
452 | },
453 | "_diffuse": {
454 | "nodetype": "LamaDiffuse",
455 | "category": "bxdf",
456 | "params": {}
457 | },
458 | "_specular": {
459 | "nodetype": "LamaConductor",
460 | "category": "bxdf",
461 | "params": {
462 | "fresnelMode": {
463 | "type": "int",
464 | "value": 0
465 | }
466 | }
467 | },
468 | "_converter": {
469 | "nodetype": "PxrMetallicWorkflow",
470 | "category": "pattern",
471 | "params": {}
472 | }
473 | },
474 | "connections": [
475 | {
476 | "src": {
477 | "node": "_clearcoatLayer",
478 | "param": "outColor"
479 | },
480 | "dst": {
481 | "node": "LamaSurface",
482 | "param": "materialFront",
483 | "type": "bxdf"
484 | }
485 | },
486 | {
487 | "src": {
488 | "node": "_clearcoat",
489 | "param": "outColor"
490 | },
491 | "dst": {
492 | "node": "_clearcoatLayer",
493 | "param": "materialTop",
494 | "type": "bxdf"
495 | }
496 | },
497 | {
498 | "src": {
499 | "node": "_diffSpec",
500 | "param": "outColor"
501 | },
502 | "dst": {
503 | "node": "_clearcoatLayer",
504 | "param": "materialBase",
505 | "type": "bxdf"
506 | }
507 | },
508 | {
509 | "src": {
510 | "node": "_diffuse",
511 | "param": "outColor"
512 | },
513 | "dst": {
514 | "node": "_diffSpec",
515 | "param": "material1",
516 | "type": "bxdf"
517 | }
518 | },
519 | {
520 | "src": {
521 | "node": "_specular",
522 | "param": "outColor"
523 | },
524 | "dst": {
525 | "node": "_diffSpec",
526 | "param": "material2",
527 | "type": "bxdf"
528 | }
529 | },
530 | {
531 | "src": {
532 | "node": "ch:Normal",
533 | "param": "resultN"
534 | },
535 | "dst": {
536 | "node": "_diffuse",
537 | "param": "normal",
538 | "type": "normal"
539 | }
540 | },
541 | {
542 | "src": {
543 | "node": "ch:Normal",
544 | "param": "resultN"
545 | },
546 | "dst": {
547 | "node": "_specular",
548 | "param": "normal",
549 | "type": "normal"
550 | }
551 | },
552 | {
553 | "src": {
554 | "node": "_converter",
555 | "param": "resultDiffuseRGB"
556 | },
557 | "dst": {
558 | "node": "_diffuse",
559 | "param": "color",
560 | "type": "color"
561 | }
562 | },
563 | {
564 | "src": {
565 | "node": "_converter",
566 | "param": "resultSpecularEdgeRGB"
567 | },
568 | "dst": {
569 | "node": "_specular",
570 | "param": "edgeColor",
571 | "type": "color"
572 | }
573 | },
574 | {
575 | "src": {
576 | "node": "_converter",
577 | "param": "resultSpecularFaceRGB"
578 | },
579 | "dst": {
580 | "node": "_specular",
581 | "param": "reflectivity",
582 | "type": "color"
583 | }
584 | },
585 | {
586 | "src": {
587 | "node": "ch:Roughness",
588 | "param": "resultR"
589 | },
590 | "dst": {
591 | "node": "_specular",
592 | "param": "roughness",
593 | "type": "float"
594 | }
595 | },
596 | {
597 | "src": {
598 | "node": "ch:BaseColor",
599 | "param": "resultRGB"
600 | },
601 | "dst": {
602 | "node": "_converter",
603 | "param": "baseColor",
604 | "type": "color"
605 | }
606 | },
607 | {
608 | "src": {
609 | "node": "ch:Metallic",
610 | "param": "resultR"
611 | },
612 | "dst": {
613 | "node": "_converter",
614 | "param": "metallic",
615 | "type": "float"
616 | }
617 | }
618 | ]
619 | },
620 | "settings": {
621 | "Normal": {
622 | "orientation": {
623 | "type": "int",
624 | "value": 1
625 | },
626 | "adjustAmount": {
627 | "type": "float",
628 | "value": 1.0
629 | }
630 | }
631 | }
632 | }
633 | },
634 | "export_config": {
635 | "exportShaderParams": false,
636 | "exportPath": null,
637 | "defaultExportPreset": "rman_pbr",
638 | "exportList": [
639 | null
640 | ],
641 | "exportPresets": [
642 | {
643 | "name": "rman_pbr",
644 | "maps": [
645 | {
646 | "fileName": "$textureSet_BaseColor(.$udim)",
647 | "channels": [
648 | {
649 | "srcChannel": "R",
650 | "destChannel": "R",
651 | "srcMapType": "documentMap",
652 | "srcMapName": "baseColor"
653 | },
654 | {
655 | "srcChannel": "G",
656 | "destChannel": "G",
657 | "srcMapType": "documentMap",
658 | "srcMapName": "baseColor"
659 | },
660 | {
661 | "srcChannel": "B",
662 | "destChannel": "B",
663 | "srcMapType": "documentMap",
664 | "srcMapName": "baseColor"
665 | }
666 | ]
667 | },
668 | {
669 | "fileName": "$textureSet_Metallic(.$udim)",
670 | "channels": [
671 | {
672 | "srcChannel": "R",
673 | "destChannel": "R",
674 | "srcMapType": "documentMap",
675 | "srcMapName": "metallic"
676 | }
677 | ]
678 | },
679 | {
680 | "fileName": "$textureSet_Roughness(.$udim)",
681 | "channels": [
682 | {
683 | "srcChannel": "R",
684 | "destChannel": "R",
685 | "srcMapType": "documentMap",
686 | "srcMapName": "roughness"
687 | }
688 | ]
689 | },
690 | {
691 | "fileName": "$textureSet_Normal(.$udim)",
692 | "channels": [
693 | {
694 | "srcChannel": "R",
695 | "destChannel": "R",
696 | "srcMapType": "virtualMap",
697 | "srcMapName": "Normal_DirectX"
698 | },
699 | {
700 | "srcChannel": "G",
701 | "destChannel": "G",
702 | "srcMapType": "virtualMap",
703 | "srcMapName": "Normal_DirectX"
704 | },
705 | {
706 | "srcChannel": "B",
707 | "destChannel": "B",
708 | "srcMapType": "virtualMap",
709 | "srcMapName": "Normal_DirectX"
710 | }
711 | ],
712 | "parameters": {
713 | "fileFormat": "png",
714 | "bitDepth": "16",
715 | "dithering": false,
716 | "paddingAlgorithm": "infinite"
717 | }
718 | },
719 | {
720 | "fileName": "$textureSet_Height(.$udim)",
721 | "channels": [
722 | {
723 | "srcChannel": "R",
724 | "destChannel": "R",
725 | "srcMapType": "documentMap",
726 | "srcMapName": "height"
727 | }
728 | ],
729 | "parameters": {
730 | "fileFormat": "png",
731 | "bitDepth": "16",
732 | "dithering": false,
733 | "paddingAlgorithm": "infinite"
734 | }
735 | },
736 | {
737 | "fileName": "$textureSet_Emission(.$udim)",
738 | "channels": [
739 | {
740 | "srcChannel": "R",
741 | "destChannel": "R",
742 | "srcMapType": "documentMap",
743 | "srcMapName": "emissive"
744 | },
745 | {
746 | "srcChannel": "G",
747 | "destChannel": "G",
748 | "srcMapType": "documentMap",
749 | "srcMapName": "emissive"
750 | },
751 | {
752 | "srcChannel": "B",
753 | "destChannel": "B",
754 | "srcMapType": "documentMap",
755 | "srcMapName": "emissive"
756 | }
757 | ]
758 | },
759 | {
760 | "fileName": "$textureSet_Opacity(.$udim)",
761 | "channels": [
762 | {
763 | "srcChannel": "R",
764 | "destChannel": "R",
765 | "srcMapType": "documentMap",
766 | "srcMapName": "opacity"
767 | }
768 | ]
769 | }
770 | ]
771 | }
772 | ],
773 | "exportParameters": [
774 | {
775 | "parameters": {
776 | "fileFormat": "png",
777 | "bitDepth": "8",
778 | "dithering": true,
779 | "paddingAlgorithm": "infinite",
780 | "filter": {
781 | "outputMaps": [
782 | "$textureSet_baseColor",
783 | "$textureSet_metallic",
784 | "$textureSet_roughness",
785 | "$textureSet_normal",
786 | "$textureSet_emission",
787 | "$textureSet_height"
788 | ]
789 | }
790 | }
791 | }
792 | ]
793 | }
794 | }
--------------------------------------------------------------------------------
/resources/PxrDisney.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
100 |
--------------------------------------------------------------------------------
/resources/PxrSurface.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
100 |
--------------------------------------------------------------------------------
/shaders/.gitignore:
--------------------------------------------------------------------------------
1 | ref
2 |
--------------------------------------------------------------------------------