Skip to content
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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0e80eff
maya gltf texture convertor and validator
moonyuet Dec 22, 2022
92fd765
maya gltf texture convertor and validator
moonyuet Dec 22, 2022
96c8029
maya gltf texture convertor and validator
moonyuet Dec 22, 2022
7f02e8c
maya gltf texture convertor and validator
moonyuet Dec 22, 2022
76a1df7
Merge branch 'ynput:develop' into feature/OP-4668-maya-gltf-textures-…
moonyuet Jan 4, 2023
338660d
Merge branch 'ynput:develop' into feature/OP-4668-maya-gltf-textures-…
moonyuet Jan 17, 2023
24d7de4
improve the validator for gltf texture name
moonyuet Jan 17, 2023
3dd02ce
update the validator
moonyuet Jan 17, 2023
eb8c40a
update the validator for ORM
moonyuet Jan 17, 2023
ba45473
update the texture conversion of ORM
moonyuet Jan 17, 2023
0af4f7d
Update convert_gltf_shader.py
moonyuet Jan 17, 2023
2578597
Update convert_gltf_shader.py
moonyuet Jan 17, 2023
bdc4a09
Update convert_gltf_shader.py
moonyuet Jan 17, 2023
62ebd77
clean up the code for validator and add the config options for glsl s…
moonyuet Jan 18, 2023
e885192
Merge pull request #5 from ynput/feature/OP-4668-maya-gltf-textures-a…
moonyuet Jan 18, 2023
6b3b849
Merge branch 'develop' of https://github.com/moonyuet/OpenPype_fork i…
moonyuet Jan 19, 2023
7d74425
add extractors and validators for cameras
moonyuet Jan 26, 2023
c4fe43a
Delete convert_gltf_shader.py
moonyuet Jan 26, 2023
d370f8a
Delete validate_gltf_textures_names.py
moonyuet Jan 26, 2023
32b557e
Delete maya.json
moonyuet Jan 26, 2023
f0fb628
Delete schema_maya_publish.json
moonyuet Jan 26, 2023
4ecb955
hound fix
moonyuet Jan 26, 2023
ab7737d
hound fix
moonyuet Jan 26, 2023
b29f382
resolve conflict
moonyuet Jan 26, 2023
53186ff
resolve conflict
moonyuet Jan 26, 2023
20e32d0
clean up the extractors and validator
moonyuet Jan 26, 2023
5cf9ce7
fix typo
moonyuet Jan 26, 2023
4dcd147
fix typo
moonyuet Jan 26, 2023
6144f98
Merge branch 'develop' into feature/OP-4244-Data-Exchange-Cameras
moonyuet Jan 26, 2023
8334323
fix typo
moonyuet Jan 26, 2023
01a70c0
add loaders for fbx import and max scene import
moonyuet Jan 30, 2023
92986bc
hound fix
moonyuet Jan 30, 2023
ef29a14
Merge branch 'develop' into feature/OP-4244-Data-Exchange-Cameras
moonyuet Jan 30, 2023
d22d51d
Merge branch 'ynput:develop' into feature/OP-4244-Data-Exchange-Cameras
moonyuet Jan 30, 2023
4a6eb02
Merge branch 'feature/OP-4244-Data-Exchange-Cameras' of https://githu…
moonyuet Jan 30, 2023
bca05d5
Merge branch 'develop' into feature/OP-4244-Data-Exchange-Cameras
moonyuet Jan 30, 2023
46996bb
add camera family in abc loader
moonyuet Jan 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions openpype/hosts/max/plugins/create/create_camera.py
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"))
49 changes: 49 additions & 0 deletions openpype/hosts/max/plugins/load/load_camera_fbx.py
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)
50 changes: 50 additions & 0 deletions openpype/hosts/max/plugins/load/load_max_scene.py
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)
5 changes: 4 additions & 1 deletion openpype/hosts/max/plugins/load/load_pointcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
class AbcLoader(load.LoaderPlugin):
"""Alembic loader."""

families = ["model", "animation", "pointcache"]
families = ["model",
"camera",
"animation",
"pointcache"]
label = "Load Alembic"
representations = ["abc"]
order = -10
Expand Down
75 changes: 75 additions & 0 deletions openpype/hosts/max/plugins/publish/extract_camera_abc.py
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
Comment on lines +38 to +52
Copy link
Collaborator

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?

Copy link
Member Author

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.

Copy link
Member Author

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.


""")

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))
75 changes: 75 additions & 0 deletions openpype/hosts/max/plugins/publish/extract_camera_fbx.py
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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it weird maybe that this is ExtractCameraFbx but the other is ExtractAlembicCamera? :)

Should they either both be:

ExtractCameraAlembic
ExtractCameraFbx

or:

ExtractFbxCamera
ExtractAlembicCamera

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))
60 changes: 60 additions & 0 deletions openpype/hosts/max/plugins/publish/extract_max_scene_raw.py
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
self.log.info("Writing max file '%s' to '%s'" % (filename,
max_path))
self.log.info(f"Writing max file: {max_path}/{filename}")


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))
2 changes: 1 addition & 1 deletion openpype/hosts/max/plugins/publish/extract_pointcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ExtractAlembic(publish.Extractor):
order = pyblish.api.ExtractorOrder
label = "Extract Pointcache"
hosts = ["max"]
families = ["pointcache", "camera"]
families = ["pointcache"]

def process(self, instance):
start = float(instance.data.get("frameStartHandle", 1))
Expand Down
Loading