-
Notifications
You must be signed in to change notification settings - Fork 128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Data exchange cameras for 3d Studio Max #4376
Changes from all commits
0e80eff
92fd765
96c8029
7f02e8c
76a1df7
338660d
24d7de4
3dd02ce
eb8c40a
ba45473
0af4f7d
2578597
bdc4a09
62ebd77
e885192
6b3b849
7d74425
c4fe43a
d370f8a
32b557e
f0fb628
4ecb955
ab7737d
b29f382
53186ff
20e32d0
5cf9ce7
4dcd147
6144f98
8334323
01a70c0
92986bc
ef29a14
d22d51d
4a6eb02
bca05d5
46996bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# -*- coding: utf-8 -*- | ||
"""Creator plugin for creating camera.""" | ||
from openpype.hosts.max.api import plugin | ||
from openpype.pipeline import CreatedInstance | ||
|
||
|
||
class CreateCamera(plugin.MaxCreator): | ||
identifier = "io.openpype.creators.max.camera" | ||
label = "Camera" | ||
family = "camera" | ||
icon = "gear" | ||
|
||
def create(self, subset_name, instance_data, pre_create_data): | ||
from pymxs import runtime as rt | ||
sel_obj = list(rt.selection) | ||
instance = super(CreateCamera, self).create( | ||
subset_name, | ||
instance_data, | ||
pre_create_data) # type: CreatedInstance | ||
container = rt.getNodeByName(instance.data.get("instance_node")) | ||
# TODO: Disable "Add to Containers?" Panel | ||
# parent the selected cameras into the container | ||
for obj in sel_obj: | ||
obj.parent = container | ||
# for additional work on the node: | ||
# instance_node = rt.getNodeByName(instance.get("instance_node")) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import os | ||
from openpype.pipeline import ( | ||
load | ||
) | ||
|
||
|
||
class FbxLoader(load.LoaderPlugin): | ||
"""Fbx Loader""" | ||
|
||
families = ["camera"] | ||
representations = ["fbx"] | ||
order = -9 | ||
icon = "code-fork" | ||
color = "white" | ||
|
||
def load(self, context, name=None, namespace=None, data=None): | ||
from pymxs import runtime as rt | ||
|
||
filepath = os.path.normpath(self.fname) | ||
|
||
fbx_import_cmd = ( | ||
f""" | ||
|
||
FBXImporterSetParam "Animation" true | ||
FBXImporterSetParam "Cameras" true | ||
FBXImporterSetParam "AxisConversionMethod" true | ||
FbxExporterSetParam "UpAxis" "Y" | ||
FbxExporterSetParam "Preserveinstances" true | ||
|
||
importFile @"{filepath}" #noPrompt using:FBXIMP | ||
""") | ||
|
||
self.log.debug(f"Executing command: {fbx_import_cmd}") | ||
rt.execute(fbx_import_cmd) | ||
|
||
container_name = f"{name}_CON" | ||
|
||
asset = rt.getNodeByName(f"{name}") | ||
# rename the container with "_CON" | ||
container = rt.container(name=container_name) | ||
asset.Parent = container | ||
|
||
return container | ||
|
||
def remove(self, container): | ||
from pymxs import runtime as rt | ||
|
||
node = container["node"] | ||
rt.delete(node) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import os | ||
from openpype.pipeline import ( | ||
load | ||
) | ||
|
||
|
||
class MaxSceneLoader(load.LoaderPlugin): | ||
"""Max Scene Loader""" | ||
|
||
families = ["camera"] | ||
representations = ["max"] | ||
order = -8 | ||
icon = "code-fork" | ||
color = "green" | ||
|
||
def load(self, context, name=None, namespace=None, data=None): | ||
from pymxs import runtime as rt | ||
path = os.path.normpath(self.fname) | ||
# import the max scene by using "merge file" | ||
path = path.replace('\\', '/') | ||
|
||
merge_before = { | ||
c for c in rt.rootNode.Children | ||
if rt.classOf(c) == rt.Container | ||
} | ||
rt.mergeMaxFile(path) | ||
|
||
merge_after = { | ||
c for c in rt.rootNode.Children | ||
if rt.classOf(c) == rt.Container | ||
} | ||
max_containers = merge_after.difference(merge_before) | ||
|
||
if len(max_containers) != 1: | ||
self.log.error("Something failed when loading.") | ||
|
||
max_container = max_containers.pop() | ||
container_name = f"{name}_CON" | ||
# rename the container with "_CON" | ||
# get the original container | ||
container = rt.container(name=container_name) | ||
max_container.Parent = container | ||
|
||
return container | ||
|
||
def remove(self, container): | ||
from pymxs import runtime as rt | ||
|
||
node = container["node"] | ||
rt.delete(node) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import os | ||
import pyblish.api | ||
from openpype.pipeline import ( | ||
publish, | ||
OptionalPyblishPluginMixin | ||
) | ||
from pymxs import runtime as rt | ||
from openpype.hosts.max.api import ( | ||
maintained_selection, | ||
get_all_children | ||
) | ||
|
||
|
||
class ExtractCameraAlembic(publish.Extractor, | ||
OptionalPyblishPluginMixin): | ||
""" | ||
Extract Camera with AlembicExport | ||
""" | ||
|
||
order = pyblish.api.ExtractorOrder - 0.1 | ||
label = "Extract Alembic Camera" | ||
hosts = ["max"] | ||
families = ["camera"] | ||
optional = True | ||
|
||
def process(self, instance): | ||
if not self.is_active(instance.data): | ||
return | ||
start = float(instance.data.get("frameStartHandle", 1)) | ||
end = float(instance.data.get("frameEndHandle", 1)) | ||
|
||
container = instance.data["instance_node"] | ||
|
||
self.log.info("Extracting Camera ...") | ||
|
||
stagingdir = self.staging_dir(instance) | ||
filename = "{name}.abc".format(**instance.data) | ||
path = os.path.join(stagingdir, filename) | ||
|
||
# We run the render | ||
self.log.info("Writing alembic '%s' to '%s'" % (filename, | ||
stagingdir)) | ||
|
||
export_cmd = ( | ||
f""" | ||
AlembicExport.ArchiveType = #ogawa | ||
AlembicExport.CoordinateSystem = #maya | ||
AlembicExport.StartFrame = {start} | ||
AlembicExport.EndFrame = {end} | ||
AlembicExport.CustomAttributes = true | ||
|
||
exportFile @"{path}" #noPrompt selectedOnly:on using:AlembicExport | ||
|
||
""") | ||
|
||
self.log.debug(f"Executing command: {export_cmd}") | ||
|
||
with maintained_selection(): | ||
# select and export | ||
rt.select(get_all_children(rt.getNodeByName(container))) | ||
rt.execute(export_cmd) | ||
|
||
self.log.info("Performing Extraction ...") | ||
if "representations" not in instance.data: | ||
instance.data["representations"] = [] | ||
|
||
representation = { | ||
'name': 'abc', | ||
'ext': 'abc', | ||
'files': filename, | ||
"stagingDir": stagingdir, | ||
} | ||
instance.data["representations"].append(representation) | ||
self.log.info("Extracted instance '%s' to: %s" % (instance.name, | ||
path)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import os | ||
import pyblish.api | ||
from openpype.pipeline import ( | ||
publish, | ||
OptionalPyblishPluginMixin | ||
) | ||
from pymxs import runtime as rt | ||
from openpype.hosts.max.api import ( | ||
maintained_selection, | ||
get_all_children | ||
) | ||
|
||
|
||
class ExtractCameraFbx(publish.Extractor, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it weird maybe that this is Should they either both be:
or:
|
||
OptionalPyblishPluginMixin): | ||
""" | ||
Extract Camera with FbxExporter | ||
""" | ||
|
||
order = pyblish.api.ExtractorOrder - 0.2 | ||
label = "Extract Fbx Camera" | ||
hosts = ["max"] | ||
families = ["camera"] | ||
optional = True | ||
|
||
def process(self, instance): | ||
if not self.is_active(instance.data): | ||
return | ||
container = instance.data["instance_node"] | ||
|
||
self.log.info("Extracting Camera ...") | ||
stagingdir = self.staging_dir(instance) | ||
filename = "{name}.fbx".format(**instance.data) | ||
|
||
filepath = os.path.join(stagingdir, filename) | ||
self.log.info("Writing fbx file '%s' to '%s'" % (filename, | ||
filepath)) | ||
|
||
# Need to export: | ||
# Animation = True | ||
# Cameras = True | ||
# AxisConversionMethod | ||
fbx_export_cmd = ( | ||
f""" | ||
|
||
FBXExporterSetParam "Animation" true | ||
FBXExporterSetParam "Cameras" true | ||
FBXExporterSetParam "AxisConversionMethod" "Animation" | ||
FbxExporterSetParam "UpAxis" "Y" | ||
FbxExporterSetParam "Preserveinstances" true | ||
|
||
exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP | ||
|
||
""") | ||
|
||
self.log.debug(f"Executing command: {fbx_export_cmd}") | ||
|
||
with maintained_selection(): | ||
# select and export | ||
rt.select(get_all_children(rt.getNodeByName(container))) | ||
rt.execute(fbx_export_cmd) | ||
|
||
self.log.info("Performing Extraction ...") | ||
if "representations" not in instance.data: | ||
instance.data["representations"] = [] | ||
|
||
representation = { | ||
'name': 'fbx', | ||
'ext': 'fbx', | ||
'files': filename, | ||
"stagingDir": stagingdir, | ||
} | ||
instance.data["representations"].append(representation) | ||
self.log.info("Extracted instance '%s' to: %s" % (instance.name, | ||
filepath)) |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,60 @@ | ||||||||
import os | ||||||||
import pyblish.api | ||||||||
from openpype.pipeline import ( | ||||||||
publish, | ||||||||
OptionalPyblishPluginMixin | ||||||||
) | ||||||||
from pymxs import runtime as rt | ||||||||
from openpype.hosts.max.api import ( | ||||||||
maintained_selection, | ||||||||
get_all_children | ||||||||
) | ||||||||
|
||||||||
|
||||||||
class ExtractMaxSceneRaw(publish.Extractor, | ||||||||
OptionalPyblishPluginMixin): | ||||||||
""" | ||||||||
Extract Raw Max Scene with SaveSelected | ||||||||
""" | ||||||||
|
||||||||
order = pyblish.api.ExtractorOrder - 0.2 | ||||||||
label = "Extract Max Scene (Raw)" | ||||||||
hosts = ["max"] | ||||||||
families = ["camera"] | ||||||||
optional = True | ||||||||
|
||||||||
def process(self, instance): | ||||||||
if not self.is_active(instance.data): | ||||||||
return | ||||||||
container = instance.data["instance_node"] | ||||||||
|
||||||||
# publish the raw scene for camera | ||||||||
self.log.info("Extracting Raw Max Scene ...") | ||||||||
|
||||||||
stagingdir = self.staging_dir(instance) | ||||||||
filename = "{name}.max".format(**instance.data) | ||||||||
|
||||||||
max_path = os.path.join(stagingdir, filename) | ||||||||
self.log.info("Writing max file '%s' to '%s'" % (filename, | ||||||||
max_path)) | ||||||||
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're using f string formatting down below we could so here too.
Suggested change
|
||||||||
|
||||||||
if "representations" not in instance.data: | ||||||||
instance.data["representations"] = [] | ||||||||
|
||||||||
# saving max scene | ||||||||
with maintained_selection(): | ||||||||
# need to figure out how to select the camera | ||||||||
rt.select(get_all_children(rt.getNodeByName(container))) | ||||||||
rt.execute(f'saveNodes selection "{max_path}" quiet:true') | ||||||||
|
||||||||
self.log.info("Performing Extraction ...") | ||||||||
|
||||||||
representation = { | ||||||||
'name': 'max', | ||||||||
'ext': 'max', | ||||||||
'files': filename, | ||||||||
"stagingDir": stagingdir, | ||||||||
} | ||||||||
instance.data["representations"].append(representation) | ||||||||
self.log.info("Extracted instance '%s' to: %s" % (instance.name, | ||||||||
max_path)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it that the path does not need escaping for backslashes here but it did need it for the extract max scene raw?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's only for max scene export. If you are using abc/fbx export, you dont need the path escaping for backslashes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe you are right. let me double check.