-
Notifications
You must be signed in to change notification settings - Fork 0
/
commands.py
401 lines (295 loc) · 11.2 KB
/
commands.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
"""Scripting interface for Ragdoll operators
Operators use this module to perform scene manipulation and anything that
does not involve user interactivity or selection.
This module shall not access any operators, and is intended for either
operators or scripting of Ragdoll via Python.
"""
import bpy
import ragdollc
from ragdollc import registry
from .vendor import bpx
from . import scene, util, constants, types
def reassign(transform, marker):
assert isinstance(transform, bpx.BpxType), (
"%s was not a BpxObject or BpxBone" % transform
)
assert marker and marker.type() in ("rdMarker", "rdEnvironment"), (
"%s was not a rdMarker" % marker
)
entry = {
"object": transform.handle(),
}
if isinstance(transform, bpx.BpxBone):
entry.update({
"boneid": transform.boneid(),
"boneidx": transform.boneidx(),
})
marker["sourceTransform"] = entry
def retarget(transform, marker, append=False):
"""Retarget `marker` to `transform`
When recording, write simulation from `marker` onto `transform`,
regardless of where it is assigned.
Arguments:
marker (bpx.BpxType): Marker object.
transform (bpx.BpxBone | bpx.BpxObject): An object or pose-bone to
bake simulation to.
append: Append one more transform into recording list if `True`.
Previous targets will be replaced by `transform` if `False`.
"""
assert marker and marker.type() == "rdMarker", (
"%s was not a rdMarker" % marker
)
if not append:
untarget(marker)
entry = {
"object": transform.handle(),
}
if isinstance(transform, bpx.BpxBone):
entry.update({
"boneid": transform.boneid(),
"boneidx": transform.boneidx(),
})
marker["destinationTransforms"].append(entry)
# Update recordability
marker["recordTranslation"] = any(transform.unlocked_location())
marker["recordRotation"] = any(transform.unlocked_rotation())
marker.property_group().on_property_changed(
marker.data["entity"], "destinationTransforms"
)
def untarget(marker):
"""Remove all recording targets from `marker`
Arguments:
marker (bpx.BpxType): Marker object.
"""
assert marker and marker.type() == "rdMarker", (
"%s was not a rdMarker" % marker
)
count = 0
# Remove `entity` from old target
for dest in marker["destinationTransforms"]:
xobj = scene.source_to_object(dest)
xobj.data.pop("entity", None)
count += 1
marker["destinationTransforms"].clear()
marker.property_group().on_property_changed(
marker.data["entity"], "destinationTransforms"
)
return count
def replace_mesh(marker, mesh, maintain_offset=True, maintain_history=False):
assert isinstance(marker, bpx.BpxObject) and marker.type() == "rdMarker", (
"%s wasa not a marker" % marker
)
assert isinstance(mesh, bpx.BpxObject), (
"%s was not a bpy.types.Mesh" % mesh
)
assert isinstance(mesh.handle().data, bpy.types.Mesh), (
"%s was not a bpy.types.Mesh" % mesh
)
if not maintain_history:
name = mesh.name() + "_copy"
new_mesh = bpx.create_object(bpx.e_cube, name=name)
new_mesh.handle().matrix_world = mesh.handle().matrix_world
# Get the final evaluated mesh, rather than the undeformed one
handle = mesh.handle()
dg = bpy.context.evaluated_depsgraph_get()
try:
msh = handle.evaluated_get(dg).to_mesh()
except RuntimeError:
# No mesh here, this would mean the connected
# object is not a mesh
return
new_mesh.handle().data = msh.copy()
# Don't bother the user with this
bpx.hide(new_mesh)
mesh = new_mesh
if maintain_offset:
source = marker["sourceTransform"].read()
source_mtx = source.matrix()
mesh_mtx = mesh.matrix()
mesh_mtx = source_mtx.inverted_safe() @ mesh_mtx
marker["inputGeometryMatrix"] = mesh_mtx
marker["shapeType"] = constants.MeshShape
marker["inputGeometry"] = {"object": mesh.handle()}
def create_group(solver, name="rGroup"):
group = scene.create("rdGroup", name=name)
bpx.link(group, util.find_assembly())
solver["members"].append({"object": group.handle()})
return group
def move_to_group(markers, group):
assert all(isinstance(m, bpx.BpxType) for m in markers), (
"%s was not a series of markers"
)
assert isinstance(group, bpx.BpxObject) and group.type() == "rdGroup", (
"%s was not a rdGroup"
)
for marker in markers:
group["members"].append({"object": marker.handle()})
# Dirty "members" property
for xobj in bpx.ls(type="rdSolver"):
entity = xobj.data.get("entity")
ragdollc.scene.propertyChanged(entity, "members")
def remove_from_group(markers, group=None):
"""Remove `markers` from `group` or all groups"""
if group is None:
groups = bpx.ls(type="rdGroup")
else:
groups = (group,)
for group in groups:
for marker in markers:
try:
index = group["members"].index({"object": marker.handle()})
except IndexError:
continue
# Ungroup
group["members"].remove(index)
# Dirty "members" property
for xobj in bpx.ls(type="rdSolver"):
entity = xobj.data.get("entity")
ragdollc.scene.propertyChanged(entity, "members")
def uncache(solver):
solver["cache"] = False
ragdollc.scene.evaluate(solver.data["entity"])
def cache(solver):
for _ in cache_iter([solver]):
pass # Iter from start frame to end
def cache_iter(solvers):
"""Persistently store the simulated result of the `solvers`
Use this to scrub the timeline both backwards and forwards without
re-simulating anything.
"""
context_scene = bpy.context.scene
# Remember where we came from
initial_frame = context_scene.frame_current
entities = {s.data["entity"] for s in solvers}
start_frame = min({
registry.get("TimeComponent", solver_entity).startFrame
for solver_entity in entities
})
start_frame = int(start_frame)
end_frame = context_scene.frame_end
total = end_frame - start_frame
context_scene.frame_set(start_frame)
for solver in solvers:
# Clear existing cache
solver["cache"] = False
ragdollc.scene.evaluate(solver.data["entity"])
# Updated cache
solver["cache"] = True
for frame in range(start_frame, end_frame + 1):
context_scene.frame_set(frame)
for solver_entity in entities:
ragdollc.scene.evaluate(solver_entity)
percentage = 100 * float(frame - start_frame) / total
yield percentage
# Restore where we came from
context_scene.frame_set(initial_frame)
def snap_to_simulation(solver, opts=None):
"""Transfer current simulated pose into keyframes
Wherever Markers are within a solver, this function transfers them
out into the "real world" as actual keyframes on anything that isn't
locked or kinematic.
"""
opts = dict({
"iterations": 2
}, **(opts or {}))
def transfer(dst):
entity = dst.data["entity"]
mtx = ragdollc.scene.outputMatrix(entity)
# Maintain whatever offset exists between the pure FK
# marker and the current controller.
# TODO: Need to store the destination rest matrix
src_rest_mtx = registry.get("RestComponent", entity).value
dst_rest_mtx = src_rest_mtx
offset_mtx = dst_rest_mtx @ src_rest_mtx.inverted()
mtx = offset_mtx @ mtx
if isinstance(dst, bpx.BpxBone):
armature = dst.handle()
handle = dst.pose_bone()
handle.matrix_basis = armature.convert_space(
pose_bone=handle,
matrix=types.to_bltype(mtx),
from_space="WORLD",
to_space="LOCAL",
)
else:
handle = dst.handle()
handle.matrix_world = types.to_bltype(mtx)
current_frame = bpy.context.scene.frame_current
# Find roots
roots = set()
for el in solver["members"]:
marker = bpx.BpxType(el.object)
if marker.type() != "rdMarker":
continue
entity = marker.data["entity"]
rigid = registry.get("RigidComponent", entity)
if rigid.kinematic:
continue
dst = marker["destinationTransforms"][0]
if dst.object:
roots.add(dst.object)
# Find destinations
dsts = []
for root in roots:
if isinstance(root.data, bpy.types.Armature):
for bone in root.pose.bones:
xbone = bpx.BpxBone(bone)
entity = xbone.data.get("entity")
if not entity:
continue
rigid = registry.get("RigidComponent", entity)
if rigid and rigid.kinematic:
continue
level = len(bone.parent_recursive)
# Sort both by armature and level, such that
# each armature gets transferred in full before
# the next one.
dsts.append((xbone, id(root) + level))
else:
xobj = bpx.BpxObject(root)
level = 0
parent = root.parent
while parent:
level += 1
parent = parent.parent
dsts.append((xobj, level))
for it in range(opts["iterations"]):
last_iteration = it = opts["iterations"] - 1
for dst, _ in sorted(dsts, key=lambda i: i[1]):
transfer(dst)
# Keyframe ahead of updating the dependency graph,
# otherwise existing keyframe animation or constraints
# would override what we've just transferred.
if last_iteration:
if isinstance(dst, bpx.BpxBone):
handle = dst.pose_bone()
else:
handle = dst.handle()
for axis in dst.unlocked_rotation():
handle.keyframe_insert("rotation_euler",
frame=current_frame,
index=axis)
for axis in dst.unlocked_location():
handle.keyframe_insert("location",
frame=current_frame,
index=axis)
# We've moved what is possibly a parent and need
# children to update to their new position before
# attempting to transfer more matrices.
dg = bpy.context.evaluated_depsgraph_get()
dg.update()
def return_to_start(solver=None):
start = 1e9
if solver is None:
solvers = bpx.ls(type="rdSolver")
else:
solvers = [solver]
# Find the first out of potentially many start frames
for solver in solvers:
entity = solver.data["entity"]
Time = registry.get("TimeComponent", entity)
if Time.startFrame < start:
start = Time.startFrame
if start != 1e9:
bpy.context.scene.frame_set(start)
bpx.info("Returned to %d" % start)