@deepblend/dsh-blender-provider-local 0.2.16 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/python/__pycache__/bootstrap.cpython-313.pyc +0 -0
- package/python/__pycache__/bootstrap.cpython-314.pyc +0 -0
- package/python/__pycache__/deepblend_scene.cpython-313.pyc +0 -0
- package/python/__pycache__/deepblend_scene.cpython-314.pyc +0 -0
- package/python/bootstrap.py +7 -0
- package/python/deepblend_scene.py +102 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepblend/dsh-blender-provider-local",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local Blender Provider: the BlenderRuntime Service over ctx.subprocess (batch execution).",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"./package.json": "./package.json"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@deepblend/dsh-blender-contracts": "0.2.
|
|
14
|
+
"@deepblend/dsh-blender-contracts": "0.2.17"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/python/bootstrap.py
CHANGED
|
@@ -233,6 +233,13 @@ def action_compile_scene(request, options):
|
|
|
233
233
|
# WHICH ENTITIES ARE SKINNED, AND TO WHAT. The vertex groups are the evidence that weights
|
|
234
234
|
# exist at all: an entity bound to an armature with no groups renders as a static object while
|
|
235
235
|
# the file says it is rigged, and the reading is where that difference becomes visible.
|
|
236
|
+
# WHAT THE PHYSICS DID, per entity. Not the settings: a body that is configured and never
|
|
237
|
+
# enabled looks exactly like one that fell, and only a reading of where it ended up tells them
|
|
238
|
+
# apart.
|
|
239
|
+
"simulation": [
|
|
240
|
+
{"entityId": entity_id, **entry}
|
|
241
|
+
for entity_id, entry in sorted((report.get("simulation") or {}).items())
|
|
242
|
+
],
|
|
236
243
|
"skinned": [
|
|
237
244
|
{
|
|
238
245
|
"entityId": entity_id,
|
|
@@ -1486,6 +1486,104 @@ def skin_entity(obj, armature_obj, armature_id, guard):
|
|
|
1486
1486
|
return True
|
|
1487
1487
|
|
|
1488
1488
|
|
|
1489
|
+
def build_rigid_bodies(spec, entity_objects, guard):
|
|
1490
|
+
"""Give the entities their physics, and BAKE the result into the checkpoint.
|
|
1491
|
+
|
|
1492
|
+
WHY THE COMPILE BAKES: a rigid body that has not been baked is a SETTING, not a motion — Blender
|
|
1493
|
+
evaluates it on the fly in the viewport and a batch render of an unbaked simulation renders the
|
|
1494
|
+
initial pose. Baking here is what makes the checkpoint mean "this is what the simulation did", and
|
|
1495
|
+
it is why `simulation.bake: false` exists for a build that only wants the setup.
|
|
1496
|
+
|
|
1497
|
+
Returns a reading of what the physics DID, not of what it was told: each active body's height at the
|
|
1498
|
+
first and the last frame of the scene's range. MEASURED, and the lesson from the animation slice is
|
|
1499
|
+
why this is not just "the setup exists": a body that is configured correctly and never enabled looks
|
|
1500
|
+
exactly like one that fell, unless something reads where it ended up.
|
|
1501
|
+
"""
|
|
1502
|
+
settings = spec.get("simulation") or {}
|
|
1503
|
+
scene = bpy.context.scene
|
|
1504
|
+
if "gravity" in settings:
|
|
1505
|
+
scene.gravity[2] = -abs(float(settings["gravity"]))
|
|
1506
|
+
|
|
1507
|
+
# THE PHYSICS WORLD HAS TO EXIST BEFORE A BODY CAN BE ATTACHED TO IT. MEASURED: without this line
|
|
1508
|
+
# `obj.rigid_body` is None and the assignment fails with an AttributeError — Blender's rigid-body
|
|
1509
|
+
# settings live on a scene-level world, and a scene that has never had one has nowhere to put them.
|
|
1510
|
+
if scene.rigidbody_world is None:
|
|
1511
|
+
bpy.ops.rigidbody.world_add()
|
|
1512
|
+
# AND THE WORLD NEEDS A COLLECTION, which a fresh one does not have. MEASURED, in three steps that
|
|
1513
|
+
# each looked like it should work: without the world there is nowhere to put a body; with the world
|
|
1514
|
+
# but no collection, `world.collection` is None; and only an object LINKED INTO that collection
|
|
1515
|
+
# gets its `rigid_body` materialised. The Blender UI does all three when a user clicks "Rigid Body".
|
|
1516
|
+
if scene.rigidbody_world.collection is None:
|
|
1517
|
+
collection = bpy.data.collections.new("DeepBlendRigidBodies")
|
|
1518
|
+
scene.collection.children.link(collection)
|
|
1519
|
+
scene.rigidbody_world.collection = collection
|
|
1520
|
+
|
|
1521
|
+
bodies = {}
|
|
1522
|
+
for entity in spec.get("entities") or []:
|
|
1523
|
+
entry = entity.get("rigidBody")
|
|
1524
|
+
if entry is None:
|
|
1525
|
+
continue
|
|
1526
|
+
obj = entity_objects.get(entity["id"])
|
|
1527
|
+
if obj is None:
|
|
1528
|
+
raise ActionError(
|
|
1529
|
+
"SCENE_SPEC_INVALID",
|
|
1530
|
+
'entity "%s" declares a rigid body but has no object to attach it to' % (entity["id"],),
|
|
1531
|
+
)
|
|
1532
|
+
# JOINING THE PHYSICS COLLECTION IS WHAT CREATES `obj.rigid_body`. MEASURED, twice: adding the
|
|
1533
|
+
# world is not enough — Blender materialises the rigid-body settings when the object is linked
|
|
1534
|
+
# into the world's own collection, so an object that has "a rigid body" the obvious way is an
|
|
1535
|
+
# object with `None`.
|
|
1536
|
+
if obj.name not in scene.rigidbody_world.collection.objects:
|
|
1537
|
+
scene.rigidbody_world.collection.objects.link(obj)
|
|
1538
|
+
|
|
1539
|
+
obj.rigid_body.type = "ACTIVE" if entry["kind"] == "active" else "PASSIVE"
|
|
1540
|
+
if entry.get("mass") is not None:
|
|
1541
|
+
obj.rigid_body.mass = float(entry["mass"])
|
|
1542
|
+
if entry.get("friction") is not None:
|
|
1543
|
+
obj.rigid_body.friction = float(entry["friction"])
|
|
1544
|
+
bodies[entity["id"]] = entry
|
|
1545
|
+
|
|
1546
|
+
if not bodies:
|
|
1547
|
+
return {}
|
|
1548
|
+
|
|
1549
|
+
# THE GROUND BODIES DO NOT MOVE, so they need the physics world to have something to collide against
|
|
1550
|
+
# and Blender needs to be told the world exists at all.
|
|
1551
|
+
scene.use_gravity = True
|
|
1552
|
+
|
|
1553
|
+
reading = {}
|
|
1554
|
+
first_frame = int((spec.get("project") or {}).get("frameStart", 1))
|
|
1555
|
+
last_frame = int((spec.get("project") or {}).get("frameEnd", first_frame))
|
|
1556
|
+
if settings.get("bake", True):
|
|
1557
|
+
scene.frame_set(first_frame)
|
|
1558
|
+
# `bake_all` walks every cache in the scene, which is what a compile wants: the caller asked for
|
|
1559
|
+
# a checkpoint that shows the simulation, not for one cache to be fresh.
|
|
1560
|
+
try:
|
|
1561
|
+
bpy.ops.ptcache.bake_all(bake=True)
|
|
1562
|
+
except Exception as exc:
|
|
1563
|
+
guard.warnings.append({
|
|
1564
|
+
"code": "SIMULATION_BAKE_FAILED",
|
|
1565
|
+
"message": "the simulation could not be baked: %s" % (error_text(exc),),
|
|
1566
|
+
})
|
|
1567
|
+
|
|
1568
|
+
for entity_id in bodies:
|
|
1569
|
+
obj = entity_objects[entity_id]
|
|
1570
|
+
scene.frame_set(first_frame)
|
|
1571
|
+
bpy.context.view_layer.update()
|
|
1572
|
+
first = float((obj.matrix_world.translation)[2])
|
|
1573
|
+
scene.frame_set(last_frame)
|
|
1574
|
+
bpy.context.view_layer.update()
|
|
1575
|
+
last = float((obj.matrix_world.translation)[2])
|
|
1576
|
+
reading[entity_id] = {
|
|
1577
|
+
"kind": bodies[entity_id]["kind"],
|
|
1578
|
+
"firstFrameZ": round(first, 6),
|
|
1579
|
+
"lastFrameZ": round(last, 6),
|
|
1580
|
+
"droppedZ": round(first - last, 6),
|
|
1581
|
+
}
|
|
1582
|
+
scene.frame_set(first_frame)
|
|
1583
|
+
bpy.context.view_layer.update()
|
|
1584
|
+
return reading
|
|
1585
|
+
|
|
1586
|
+
|
|
1489
1587
|
def build_scene(spec, options, guard):
|
|
1490
1588
|
"""Compile a SceneSpec into a live Blender scene.
|
|
1491
1589
|
|
|
@@ -1664,6 +1762,9 @@ def build_scene(spec, options, guard):
|
|
|
1664
1762
|
"dataPaths": sorted({curve.data_path for curve in action_fcurves(action)}),
|
|
1665
1763
|
})
|
|
1666
1764
|
|
|
1765
|
+
report_progress("build_simulation", 70)
|
|
1766
|
+
simulation_reading = build_rigid_bodies(spec, entity_objects, guard)
|
|
1767
|
+
|
|
1667
1768
|
report_progress("configure_render", 85)
|
|
1668
1769
|
render_config = configure_scene(scene, spec, profile, guard)
|
|
1669
1770
|
|
|
@@ -1708,6 +1809,7 @@ def build_scene(spec, options, guard):
|
|
|
1708
1809
|
"profileName": profile_name,
|
|
1709
1810
|
"entityObjectNames": {key: value.name for key, value in entity_objects.items()},
|
|
1710
1811
|
"armatureNames": {key: value.name for key, value in armature_objects.items()},
|
|
1812
|
+
"simulation": simulation_reading,
|
|
1711
1813
|
"skinned": {
|
|
1712
1814
|
key: {
|
|
1713
1815
|
"armatureId": entry["armatureId"],
|