-
Notifications
You must be signed in to change notification settings - Fork 49
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
Clarisse Integration #502
Comments
@ddesmond has been playing with Avalon and a prototype Clarisse integration and got the basis working - however the Clarisse Qt helper workaround is still the tricky one as it means we'll need to customize how the QApplication instance is created, since you'd need to run It says Qt needs to be imported before the helper is imported, so: # Allow Clarisse to install the Qt Helper for QApplication
# See: https://www.clarissewiki.com/4.0/sdk/using_pyqt.html
# First we need to import Qt, then import pyqt_clarisse
from avalon.vendor.Qt import QtWidgets
import pyqt_clarisse This could be in Any ideas on how to get this operational without hardcoding it in I mentioned this potential hack to @ddesmond: from avalon.vendor.Qt import QtWidgets
import pyqt_clarisse
# Initialize Qt application
app = QtWidgets.QApplication([])
pyqt_clarisse.exec_(app)
# Now because a Qt Application instance exists
# this means the Workfiles tool should be using that one
import avalon.tools.workfiles
avalon.tools.workfiles.show() Which showed the tool fine since it hits this line in
However, I still feel it's quite the hack and not how the |
@ddesmond You asked me some questions regarding how to potentially implement the import ix
from collections import OrderedDict
# TODO Import this from avalon.pipeline
# from ..pipeline import AVALON_CONTAINER_ID
AVALON_CONTAINER_ID = "pyblish.avalon.container"
def imprint(node, data):
"""Store attributes with value on a node
Args:
node (framework.PyOfObject): The node to imprint data on.
data (dict): Key value pairs of attributes to create.
Returns:
None
"""
for attr, value in data.items():
# Create the attribute
node.add_attribute(attr,
ix.api.OfAttr.TYPE_STRING,
ix.api.OfAttr.CONTAINER_SINGLE,
ix.api.OfAttr.VISUAL_HINT_DEFAULT,
"avalon")
# Set the attribute's value
setattr(node.attrs, attr, value)
def containerise(node, name, namespace, context, loader):
"""Imprint `node` with container metadata.
Arguments:
node (framework.PyOfObject: The node to containerise.
name (str): Name of resulting assembly
namespace (str): Namespace under which to host container
context (dict): Asset information
loader (str): Name of loader used to produce this container.
Returns:
None
"""
data = [
("schema", "avalon-core:container-2.0"),
("id", AVALON_CONTAINER_ID),
("name", name),
("namespace", namespace),
("loader", str(loader)),
("representation", context["representation"]["_id"])
]
# We use an OrderedDict to make sure the attributes
# are always created in the same order. This is solely
# to make debugging easier when reading the values in
# the attribute editor.
imprint(node, OrderedDict(data))
def parse_container(node):
"""Return the container node's full container data.
Args:
node (framework.PyOfObject: A node to parse as container.
Returns:
dict: The container schema data for this container node.
"""
# If not all required data return None
required = ['id', 'schema', 'name',
'namespace', 'loader', 'representation']
if not all(node.attribute_exists(attr) for attr in required):
return
data = {attr: getattr(node.attrs, attr)[0] for attr in required}
# Store the node's name
data["objectName"] = node.get_full_name()
# Store reference to the node object
data["node"] = node
return data
def ls():
"""Yields containers from active Clarisse project
This is the host-equivalent of api.ls(), but instead of listing
assets on disk, it lists assets already loaded in Clarisse; once
loaded they are called 'containers'
Yields:
dict: container
"""
# Iterate all objects in the scene
# and parse them to see if they are containerised
# TODO: Allow this to iterate *ALL* objects
# NOTE: These types are somewhat randomly chosen.
types = ["GeometryBundleAlembic",
"GeometryBundleUsd",
"TextureMapFile",
"TextureStreamedMapFile",
"TextureOslFile",
"LayerFile"]
nodes = ix.api.OfObjectArray()
for t in types:
ix.application.get_factory().get_all_objects(t, nodes)
for i in range(nodes.get_count()):
container = parse_container(nodes[i])
if container:
yield container See the TODO in nodes = ix.api.OfObjectArray()
ix.application.get_factory().get_all_objects(nodes)
for i in range(nodes.get_count()):
print nodes[i]
# project://__system_vars
# project://__builtin_vars
# project://__custom_vars
# project://__app_prefs_vars
# project://__project_prefs_vars
# project://clone_stamp_3d
# project://particle_paint
# project://property_paint
# project://pick_fit
# project://picker Hope it helps! Be aware that I've never used Clarisse before. It's the first time I opened it, so I have no clue whether "containerising" per node makes sense or whether you'd prefer to have it grouped like an actual containered reference or alike. Totally depends on how you end up using loaded content in the app and what you're expecting to load. The containerise name is somewhat confusing when it actually operates on a single node. That behavior however is similar to Fusion |
Seems fine to me. Might want to check whether there's already a QApplication running first (they are singletons), but other than that it's just initialisation, which we already do in the host integration anyway (e.g. to install menus). |
@ddesmond showed me an example of how he usually works with clarisse. Based on that I did some research on how to continue with a first Loader and some additional notes. Example reference loader (like File > Reference File)It seems you cannot create custom attributes to "Reference" nodes through the User Interface nor do they have Note that
Nevertheless the actual API does work. So we'll need to make sure to use that workflow. # Select a reference node and run this
node = ix.selection[0]
node.add_attribute("id",
ix.api.OfAttr.TYPE_STRING,
ix.api.OfAttr.CONTAINER_SINGLE,
ix.api.OfAttr.VISUAL_HINT_DEFAULT,
"Avalon")
# Set the value (attributes in Clarisse API are always arrays
# even when it's a single value container) so we use [0]
attr = node.get_attribute("id")
attr[0] = "my custom value"
# Get the value
attr = node.get_attribute("id")
print(attr[0]) Psuedocode Loader examplefrom avalon import api
import ix
class ReferenceLoader(api.Loader):
"""Reference content into Clarisse"""
label = "Reference"
families = ["*"]
representations = ["obj", "abc", "usd", "usda"]
order = 0
icon = "code-fork"
color = "orange"
def load(self, context, name=None, namespace=None, data=None):
filepath = self.fname
# Create the file reference
scene_context = "project://scene"
paths = [filepath]
reference = ix.cmds.CreateFileReference(scene_context, paths)
# todo: actually imprint it with data
# Imprint it with some data so ls() can find this
# particular loaded content and can return it as a
# valid container
# imprint_container(reference,)
def update(self, container, representation):
node = container["node"]
filepath = api.get_representation_path(representation)
ix.cmds.SetReferenceFilename([node.get_full_name()],
filepath)
# todo: do we need to explicitly trigger reload?
def remove(self, container):
node = container["node"]
ix.cmds.DeleteItems([node.get_full_name()]) Clarisse Command BatchingThis is extra important when running code from the Qt interface:
The code to run in Clarisse might need to be forced into a command batch to make sure undo (if allowed) works logically. Otherwise you might end up with every command being a single undo, potentially leaving scene in a broken state. ix.begin_command_batch("Load")
# Put your code here
ix.end_command_batch() Or with a context manager: import contextlib
@contextlib.contextmanager
def command_batch(name):
try:
ix.begin_command_batch(name)
yield
finally:
ix.end_command_batch()
with command_batch("load"):
# Put your code here Clarisse Survival Kit importing@ddesmond pointed me to Clarisse Survival Kit (CSK) as to batch loading assets since it provides a lot of nice functionality to prepare the content. That's totally fine. You'd just use whatever functions they expose to load the data. Since it seems the loaded content is packaged up after load into a folder (its own mini-context) I'd pick that folder as the "container node" and imprint custom data there when possible. It would basically mean adapting the example Loader plugin provided above so that it loads the content exactly the way you'd like. However, the Loader usually loads a specific "publish" and not multiple content that also works separately. Technically it is possible for a Loader to find and load additional published content, e.g. textures if it can find it. It has just never been done in any integration/config I have seen so far. |
|
Issue
This is about an Isotropix Clarisse Integration for Avalon.
Implementation details
init_menus.py
and some examples here and hereexec_()
. We will need to make sure the Avalon tools run correctly and trigger through this QApplication execution.The text was updated successfully, but these errors were encountered: