Skip to content
This repository was archived by the owner on Oct 10, 2024. It is now read-only.

Commit d38371f

Browse files
authored
Merge pull request #99 from pypeclub/feature/blender_implementation
Feature/blender implementation
2 parents 76edfbc + 90706e1 commit d38371f

File tree

10 files changed

+1022
-10
lines changed

10 files changed

+1022
-10
lines changed

avalon/blender/__init__.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Public API
2+
3+
Anything that isn't defined here is INTERNAL and unreliable for external use.
4+
5+
"""
6+
7+
from .pipeline import (
8+
install,
9+
uninstall,
10+
Creator,
11+
Loader,
12+
ls,
13+
publish,
14+
containerise,
15+
)
16+
17+
from .workio import (
18+
open_file,
19+
save_file,
20+
current_file,
21+
has_unsaved_changes,
22+
file_extensions,
23+
work_root,
24+
)
25+
26+
from .lib import (
27+
lsattr,
28+
lsattrs,
29+
read,
30+
maintained_selection,
31+
# unique_name,
32+
)
33+
34+
35+
__all__ = [
36+
"install",
37+
"uninstall",
38+
"Creator",
39+
"Loader",
40+
"ls",
41+
"publish",
42+
"containerise",
43+
44+
# Workfiles API
45+
"open_file",
46+
"save_file",
47+
"current_file",
48+
"has_unsaved_changes",
49+
"file_extensions",
50+
"work_root",
51+
52+
# Utility functions
53+
"maintained_selection",
54+
"lsattr",
55+
"lsattrs",
56+
"read",
57+
# "unique_name",
58+
]
632 Bytes
Loading

avalon/blender/lib.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""Standalone helper functions."""
2+
3+
import contextlib
4+
from typing import Dict, List, Union
5+
6+
import bpy
7+
from ..lib import logger
8+
from . import pipeline
9+
10+
11+
def get_selection():
12+
return [obj for obj in bpy.context.scene.objects if obj.select_get()]
13+
14+
15+
def imprint(node: bpy.types.bpy_struct_meta_idprop, data: Dict):
16+
r"""Write `data` to `node` as userDefined attributes
17+
18+
Arguments:
19+
node: Long name of node
20+
data: Dictionary of key/value pairs
21+
22+
Example:
23+
>>> import bpy
24+
>>> def compute():
25+
... return 6
26+
...
27+
>>> bpy.ops.mesh.primitive_cube_add()
28+
>>> cube = bpy.context.view_layer.objects.active
29+
>>> imprint(cube, {
30+
... "regularString": "myFamily",
31+
... "computedValue": lambda: compute()
32+
... })
33+
...
34+
>>> cube['avalon']['computedValue']
35+
6
36+
"""
37+
38+
imprint_data = dict()
39+
40+
for key, value in data.items():
41+
if value is None:
42+
continue
43+
44+
if callable(value):
45+
# Support values evaluated at imprint
46+
value = value()
47+
48+
if not isinstance(value, (int, float, bool, str, list)):
49+
raise TypeError(f"Unsupported type: {type(value)}")
50+
51+
imprint_data[key] = value
52+
53+
pipeline.metadata_update(node, imprint_data)
54+
55+
56+
def lsattr(
57+
attr: str,
58+
value: Union[str, int, bool, List, Dict, None] = None
59+
) -> List:
60+
r"""Return nodes matching `attr` and `value`
61+
62+
Arguments:
63+
attr: Name of Blender property
64+
value: Value of attribute. If none
65+
is provided, return all nodes with this attribute.
66+
67+
Example:
68+
>>> lsattr("id", "myId")
69+
... [bpy.data.objects["myNode"]
70+
>>> lsattr("id")
71+
... [bpy.data.objects["myNode"], bpy.data.objects["myOtherNode"]]
72+
73+
Returns:
74+
list
75+
"""
76+
77+
return lsattrs({attr: value})
78+
79+
80+
def lsattrs(attrs: Dict) -> List:
81+
r"""Return nodes with the given attribute(s).
82+
83+
Arguments:
84+
attrs: Name and value pairs of expected matches
85+
86+
Example:
87+
>>> lsattrs({"age": 5}) # Return nodes with an `age` of 5
88+
# Return nodes with both `age` and `color` of 5 and blue
89+
>>> lsattrs({"age": 5, "color": "blue"})
90+
91+
Returns a list.
92+
93+
"""
94+
95+
# For now return all objects, not filtered by scene/collection/view_layer.
96+
matches = set()
97+
for coll in dir(bpy.data):
98+
nodes = getattr(bpy.data, coll, None)
99+
if not isinstance(
100+
nodes, bpy.types.bpy_prop_collection,
101+
):
102+
continue
103+
for node in nodes:
104+
for attr, value in attrs.items():
105+
avalon_prop = node.get(pipeline.AVALON_PROPERTY)
106+
if not avalon_prop:
107+
continue
108+
109+
avalon_prop_val = avalon_prop.get(attr)
110+
if not avalon_prop_val:
111+
continue
112+
113+
if value is None or avalon_prop_val == value:
114+
matches.add(node)
115+
116+
return list(matches)
117+
118+
119+
def read(node: bpy.types.bpy_struct_meta_idprop):
120+
"""Return user-defined attributes from `node`"""
121+
122+
data = dict(node.get(pipeline.AVALON_PROPERTY))
123+
124+
# Ignore hidden/internal data
125+
data = {
126+
key: value
127+
for key, value in data.items() if not key.startswith("_")
128+
}
129+
130+
return data
131+
132+
133+
@contextlib.contextmanager
134+
def maintained_selection():
135+
r"""Maintain selection during context
136+
137+
Example:
138+
>>> with maintained_selection():
139+
... # Modify selection
140+
... bpy.ops.object.select_all(action='DESELECT')
141+
>>> # Selection restored
142+
"""
143+
previous_selection = get_selection()
144+
previous_active = bpy.context.view_layer.objects.active
145+
try:
146+
yield
147+
finally:
148+
# Clear the selection
149+
for node in get_selection():
150+
node.select_set(state=False)
151+
if previous_selection:
152+
for node in previous_selection:
153+
try:
154+
node.select_set(state=True)
155+
except ReferenceError:
156+
# This could happen if a selected node was deleted during
157+
# the context.
158+
logger.exception("Failed to reselect")
159+
continue
160+
try:
161+
bpy.context.view_layer.objects.active = previous_active
162+
except ReferenceError:
163+
# This could happen if the active node was deleted during the
164+
# context.
165+
logger.exception("Failed to set active object.")

0 commit comments

Comments
 (0)