Skip to content

Commit

Permalink
Merge pull request #32 from BigRoy/enhancement/variants
Browse files Browse the repository at this point in the history
Enhancement: Add Variant Set management
  • Loading branch information
BigRoy authored Dec 4, 2023
2 parents 4fa5ed9 + 8ca2ae2 commit 4bb4fbe
Show file tree
Hide file tree
Showing 4 changed files with 516 additions and 36 deletions.
27 changes: 25 additions & 2 deletions usd_qtpy/lib/usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,25 @@ def rename_prim(prim: Usd.Prim, new_name: str) -> bool:
return True

prim_path = prim.GetPath()
new_prim_path = prim_path.GetParentPath().AppendChild(new_name)
new_prim_path = prim_path.ReplaceName(new_name)

# We want to map the path to the current edit target of its stage so that
# if the user is renaming a prim in an edit target currently editing
# within a variant set, that we rename that particular opinion. However,
# we only do that if the source prim path existed in the edit target
# otherwise we will edit it on the layer regularly
# WARNING This will crash on calls to `prim.GetPrimStack()` afterwards
# See: https://forum.aousd.org/t/perform-namespace-edit-inside-a-variant-set-edit-target/1006
# stage = prim.GetStage()
# edit_target = stage.GetEditTarget()
# remapped_prim_path = edit_target.MapToSpecPath(prim_path)
# if (
# prim_path != remapped_prim_path
# ):
# logging.debug("Remapping prim path to within edit target: %s",
# remapped_prim_path)
# prim_path = remapped_prim_path
# new_prim_path = edit_target.MapToSpecPath(new_prim_path)

stage = prim.GetStage()
with Sdf.ChangeBlock():
Expand Down Expand Up @@ -293,7 +311,12 @@ def remove_spec(spec):
del spec.owner.properties[spec.name]

elif isinstance(spec, Sdf.VariantSetSpec):
# Owner is Sdf.PrimSpec (or can also be Sdf.VariantSpec)
del spec.owner.variantSets[spec.name]

elif isinstance(spec, Sdf.VariantSpec):
# Owner is Sdf.VariantSetSpec
spec.owner.RemoveVariant(spec)

else:
raise TypeError(f"Unsupported spec type: {spec}")
raise TypeError(f"Unsupported spec type: {spec}")
7 changes: 5 additions & 2 deletions usd_qtpy/prim_hierarchy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from .prim_delegate import DrawRectsDelegate
from .prim_hierarchy_model import HierarchyModel
from .references import ReferenceListWidget
from .variants import CreateVariantSetDialog
from .variants import CreateVariantSetDialog, VariantSetsWidget

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -168,7 +168,10 @@ def on_prim_tag_clicked(self, event, index, block):
self.on_manage_prim_reference_payload(prim)

elif text == "VAR":
raise NotImplementedError("To be implemented")
prim = index.data(HierarchyModel.PrimRole)
widget = VariantSetsWidget(prim=prim, parent=self)
widget.resize(250, 100)
widget.show()

def select_paths(self, paths: list[Sdf.Path]):
"""Select prims in the hierarchy view that match the Sdf.Path
Expand Down
120 changes: 88 additions & 32 deletions usd_qtpy/prim_spec_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,14 @@ class StageSdfModel(TreeModel):
"Layer": QtGui.QColor("#008EC5"),
"PseudoRootSpec": QtGui.QColor("#A2D2EF"),
"PrimSpec": QtGui.QColor("#A2D2EF"),
"VariantSetSpec": QtGui.QColor("#A2D2EF"),
"RelationshipSpec": QtGui.QColor("#FCD057"),
"AttributeSpec": QtGui.QColor("#FFC8DD"),
"reference": QtGui.QColor("#C8DDDD"),
"payload": QtGui.QColor("#DDC8DD"),
"variantSetName": QtGui.QColor("#DDDDC8"),
"VariantSetSpec": QtGui.QColor("#AAE48B"),
"VariantSpec": QtGui.QColor("#D6E8CC"),
"variantSetName": QtGui.QColor("#D6E8CC"),
"variantSelections": QtGui.QColor("#D6E8CC"),
}

def __init__(self, stage=None, parent=None):
Expand Down Expand Up @@ -145,6 +147,10 @@ def _traverse(path):
"type": spec.__class__.__name__
})

element_string = spec.path.elementString
if element_string and spec.name != element_string:
spec_item["name"] = element_string

if hasattr(spec, "GetTypeName"):
spec_type_name = spec.GetTypeName()
icon = self._icon_provider.get_icon_from_type_name(
Expand All @@ -169,23 +175,16 @@ def _traverse(path):
type_name = spec.typeName
spec_item["typeName"] = type_name

for attr in [
"variantSelections",
"relocates",
# Variant sets is redundant because these basically
# refer to VariantSetSpecs which will actually be
# traversed path in the layer anyway
# TODO: Remove this commented key?
# "variantSets"
]:
def _add_map_item(attr):
"""Add MapProxyItem for list attribute on Spec"""
proxy = getattr(spec, attr)

# `prim_spec.variantSelections.keys()` can fail
# todo: figure out why this workaround is needed
try:
keys = list(proxy.keys())
except RuntimeError:
continue
return

for key in keys:
proxy_item = MapProxyItem(
Expand All @@ -194,17 +193,15 @@ def _traverse(path):
data={
"name": key,
"default": proxy.get(key), # value
"type": attr
"type": attr,
"typeName": attr,
}
)
spec_item.add_child(proxy_item)

for key in [
"variantSetName",
"reference",
"payload"
]:
list_changes = getattr(spec, key + "List")
def _add_list_item(attr):
"""Add ListProxyItem for list attribute on Spec"""
list_changes = getattr(spec, attr + "List")
for change_type in LIST_ATTRS:
changes_for_type = getattr(list_changes,
change_type)
Expand All @@ -224,14 +221,26 @@ def _traverse(path):
"name": name,
# Strip off "Items"
"default": change_type[:-5],
"type": key,
"typeName": key,
"type": attr,
"typeName": attr,
"parent": changes_for_type
}
)
spec_item.add_child(list_change_item)
if list_changes:
spec_item[key] = str(list_changes)
spec_item[attr] = str(list_changes)

# Add these types intermixed just so we order attributes
# together nicely that are somewhat related, e.g. variant
# information together
for attr, add_fn in [
("reference", _add_list_item),
("payload", _add_list_item),
("relocates", _add_map_item),
("variantSelections", _add_map_item),
("variantSetName", _add_list_item),
]:
add_fn(attr)

elif isinstance(spec, Sdf.AttributeSpec):
value = spec.default
Expand Down Expand Up @@ -267,7 +276,7 @@ def flags(self, index):

return super(StageSdfModel, self).flags(index)

def setData(self, index, value, role):
def setData(self, index, value, role) -> bool:

if index.column() == 1: # specifier
item = index.internalPointer()
Expand All @@ -278,6 +287,9 @@ def setData(self, index, value, role):
}
value = lookup[value]
spec.specifier = value
return True

return super(StageSdfModel, self).setData(index, value, role)

def data(self, index, role):

Expand All @@ -291,6 +303,13 @@ def data(self, index, role):
item = index.data(TreeModel.ItemRole)
return item.get("icon")

if role == QtCore.Qt.ToolTipRole:
item = index.data(TreeModel.ItemRole)
path = item.get("path")
if path and isinstance(path, Sdf.Path):
path = path.pathString
return path

return super(StageSdfModel, self).data(index, role)


Expand Down Expand Up @@ -326,23 +345,59 @@ def filterAcceptsRow(self, source_row, source_parent):
class FilterListWidget(QtWidgets.QListWidget):
def __init__(self):
super(FilterListWidget, self).__init__()
self.addItems([

# Some labels are indented just to easily visually group some related
# options together
labels = [
"Layer",
# This is hidden since it's usually not filtered to
# "PseudoRootSpec",
"PrimSpec",
" reference",
" payload",
" variantSetName",
" relocates",
"AttributeSpec",
"RelationshipSpec",
"VariantSetSpec",
" VariantSpec",
" variantSetName",
" variantSelections",
]
tooltips = {
"Layer": "A single <b>Layer</b> in the stage.",
"PrimSpec": "A <b>Prim</b> description",
"reference": "Represents a reference.",
"payload": (
"Represents a payload.<br>"
"Payloads are similar to prim references with the major "
"difference that payloads are explicitly loaded by the user."
),
"relocates": "Namespace relocations specified on a prim",
"AttributeSpec": "A property that holds typed data.",
"RelationshipSpec": (
"A property that contains a reference to "
"one or more prim specs."
),
"VariantSetSpec": "A variant set.",
"VariantSpec": "A single variant opinion for a variant set.",
"variantSetName": "Defines available variant set name on a prim.",
"variantSelections": (
"Defines the selected variant for a variant set."
)
}

for label in labels:
type_name = label.lstrip(" ")
item = QtWidgets.QListWidgetItem(label, self)

# Set color
item.setData(QtCore.Qt.ForegroundRole,
StageSdfModel.Colors.get(type_name))

tooltip = tooltips.get(type_name)
if tooltip:
item.setData(QtCore.Qt.ToolTipRole, tooltip)

# TODO: Still to be implemented in the StageSdfModel
# "variantSelections",
# "variantSets",
# "relocates"
])
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)


Expand Down Expand Up @@ -526,8 +581,9 @@ def delete_indexes(self, indexes):

with Sdf.ChangeBlock():
for spec in specs:
log.debug(f"Removing spec: %s", spec.path)
remove_spec(spec)
if spec and not spec.expired:
log.debug(f"Removing spec: %s", spec.path)
remove_spec(spec)
for deletable in deletables:
deletable.delete()
return True
Expand Down
Loading

0 comments on commit 4bb4fbe

Please sign in to comment.