@deepblend/dsh-blender-provider-local 0.2.17 → 0.2.18
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepblend/dsh-blender-provider-local",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
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.18"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
Binary file
|
|
Binary file
|
|
@@ -1486,7 +1486,7 @@ 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):
|
|
1489
|
+
def build_rigid_bodies(spec, entity_objects, guard, cloth_entities=None):
|
|
1490
1490
|
"""Give the entities their physics, and BAKE the result into the checkpoint.
|
|
1491
1491
|
|
|
1492
1492
|
WHY THE COMPILE BAKES: a rigid body that has not been baked is a SETTING, not a motion — Blender
|
|
@@ -1499,6 +1499,7 @@ def build_rigid_bodies(spec, entity_objects, guard):
|
|
|
1499
1499
|
why this is not just "the setup exists": a body that is configured correctly and never enabled looks
|
|
1500
1500
|
exactly like one that fell, unless something reads where it ended up.
|
|
1501
1501
|
"""
|
|
1502
|
+
cloth_entities = cloth_entities or {}
|
|
1502
1503
|
settings = spec.get("simulation") or {}
|
|
1503
1504
|
scene = bpy.context.scene
|
|
1504
1505
|
if "gravity" in settings:
|
|
@@ -1543,13 +1544,25 @@ def build_rigid_bodies(spec, entity_objects, guard):
|
|
|
1543
1544
|
obj.rigid_body.friction = float(entry["friction"])
|
|
1544
1545
|
bodies[entity["id"]] = entry
|
|
1545
1546
|
|
|
1546
|
-
if not bodies:
|
|
1547
|
+
if not bodies and not cloth_entities:
|
|
1547
1548
|
return {}
|
|
1548
1549
|
|
|
1550
|
+
# A SCENE WITH CLOTH AND NO RIGID BODIES STILL HAS CACHES TO BAKE. MEASURED: the first version
|
|
1551
|
+
# returned early when there were no bodies, and a cloth-only scene reported an empty reading — the
|
|
1552
|
+
# bake and the whole reading sat behind a condition that had nothing to do with cloth.
|
|
1553
|
+
_ = bodies
|
|
1554
|
+
|
|
1549
1555
|
# THE GROUND BODIES DO NOT MOVE, so they need the physics world to have something to collide against
|
|
1550
1556
|
# and Blender needs to be told the world exists at all.
|
|
1551
1557
|
scene.use_gravity = True
|
|
1552
1558
|
|
|
1559
|
+
# THE CLOTH MODIFIERS ARE CREATED BEFORE THE BAKE, which is the whole reason they are built here
|
|
1560
|
+
# rather than in `build_scene`. MEASURED, and the comment above this loop said so before the code
|
|
1561
|
+
# did: created after it, the bake had nothing to walk and the reading saw an object with no
|
|
1562
|
+
# modifiers — a fabric that never moved, reported as a simulation that did nothing.
|
|
1563
|
+
for entity_id, entry in cloth_entities.items():
|
|
1564
|
+
build_cloth(entry, entity_objects[entity_id], guard)
|
|
1565
|
+
|
|
1553
1566
|
reading = {}
|
|
1554
1567
|
first_frame = int((spec.get("project") or {}).get("frameStart", 1))
|
|
1555
1568
|
last_frame = int((spec.get("project") or {}).get("frameEnd", first_frame))
|
|
@@ -1565,6 +1578,12 @@ def build_rigid_bodies(spec, entity_objects, guard):
|
|
|
1565
1578
|
"message": "the simulation could not be baked: %s" % (error_text(exc),),
|
|
1566
1579
|
})
|
|
1567
1580
|
|
|
1581
|
+
for entity_id in cloth_entities:
|
|
1582
|
+
# INTO THE SAME READING as the rigid bodies: one report of what the physics did, whatever the
|
|
1583
|
+
# mechanism, because a caller asking "what happened in this scene" should not have to know which
|
|
1584
|
+
# kind of simulation each entity used.
|
|
1585
|
+
reading[entity_id] = {"kind": "cloth", **cloth_reading(entity_objects[entity_id], first_frame, last_frame)}
|
|
1586
|
+
|
|
1568
1587
|
for entity_id in bodies:
|
|
1569
1588
|
obj = entity_objects[entity_id]
|
|
1570
1589
|
scene.frame_set(first_frame)
|
|
@@ -1584,6 +1603,83 @@ def build_rigid_bodies(spec, entity_objects, guard):
|
|
|
1584
1603
|
return reading
|
|
1585
1604
|
|
|
1586
1605
|
|
|
1606
|
+
def build_cloth(entry, obj, guard):
|
|
1607
|
+
"""Hang a mesh as fabric, and report where its lowest point ended up.
|
|
1608
|
+
|
|
1609
|
+
WHY THE PIN GROUP IS BUILT FROM GEOMETRY: Blender pins cloth through a vertex group, and a
|
|
1610
|
+
SceneSpec that named one would be referring to something no generator creates. What the spec says
|
|
1611
|
+
is the RELATIONSHIP ("hang this from its top edge"); the group is the compiler's answer to it.
|
|
1612
|
+
|
|
1613
|
+
MEASURED, AND IT DIFFERS FROM RIGID BODIES: cloth is evaluated LIVE, so it drapes with or without a
|
|
1614
|
+
bake — but the BAKED result differs from the unbaked one, because cloth integrates frame by frame
|
|
1615
|
+
and a bake walks every frame while a jump to the last one does not. That is why nothing here claims
|
|
1616
|
+
"no bake means no motion": for cloth that claim would be false, and a false assertion is worse than
|
|
1617
|
+
none.
|
|
1618
|
+
"""
|
|
1619
|
+
settings = entry or {}
|
|
1620
|
+
pin_top = settings.get("pinTop", True)
|
|
1621
|
+
group_name = None
|
|
1622
|
+
if pin_top:
|
|
1623
|
+
ys = [vertex.co.y for vertex in obj.data.vertices]
|
|
1624
|
+
if ys:
|
|
1625
|
+
top = max(ys)
|
|
1626
|
+
pinned = [vertex.index for vertex in obj.data.vertices if abs(vertex.co.y - top) < 1e-4]
|
|
1627
|
+
group = obj.vertex_groups.new(name="DeepBlendClothPin")
|
|
1628
|
+
group.add(pinned, 1.0, "REPLACE")
|
|
1629
|
+
group_name = group.name
|
|
1630
|
+
|
|
1631
|
+
# CLOTH NEEDS GEOMETRY TO BEND, AND THE PRODUCT'S PRIMITIVES ARE LOW-POLY. MEASURED: a cube has eight
|
|
1632
|
+
# vertices, so a cloth modifier on one has nothing to drape with — the reading came back with the
|
|
1633
|
+
# fabric exactly where it started, and it looked like a broken simulation rather than a mesh with no
|
|
1634
|
+
# interior. A SIMPLE subdivision keeps the shape and gives the solver something to work with; the
|
|
1635
|
+
# reading reports the vertex count so the subdivision is visible rather than assumed.
|
|
1636
|
+
subdivision = obj.modifiers.new(name="DeepBlendClothSubdivision", type="SUBSURF")
|
|
1637
|
+
subdivision.subdivision_type = "SIMPLE"
|
|
1638
|
+
subdivision.levels = int(settings.get("subdivisions", 3))
|
|
1639
|
+
subdivision.render_levels = subdivision.levels
|
|
1640
|
+
|
|
1641
|
+
modifier = obj.modifiers.new(name="DeepBlendCloth", type="CLOTH")
|
|
1642
|
+
cloth_settings = modifier.settings
|
|
1643
|
+
if group_name is not None:
|
|
1644
|
+
cloth_settings.vertex_group_mass = group_name
|
|
1645
|
+
if settings.get("mass") is not None:
|
|
1646
|
+
cloth_settings.mass = float(settings["mass"])
|
|
1647
|
+
if settings.get("stiffness") is not None:
|
|
1648
|
+
cloth_settings.tension_stiffness = float(settings["stiffness"]) * 15.0
|
|
1649
|
+
return obj
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
def cloth_reading(obj, first_frame, last_frame):
|
|
1653
|
+
"""The lowest and the highest simulated vertex, at each end of the range.
|
|
1654
|
+
|
|
1655
|
+
Read off the EVALUATED mesh rather than the modifier: what the fabric did is the geometry, and the
|
|
1656
|
+
modifier only says what was asked for. The highest point is the pinned edge, which is how the
|
|
1657
|
+
reading says "it draped FROM somewhere" rather than merely "it moved".
|
|
1658
|
+
"""
|
|
1659
|
+
scene = bpy.context.scene
|
|
1660
|
+
reading = {}
|
|
1661
|
+
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
1662
|
+
for label, frame in (("firstFrame", first_frame), ("lastFrame", last_frame)):
|
|
1663
|
+
scene.frame_set(frame)
|
|
1664
|
+
bpy.context.view_layer.update()
|
|
1665
|
+
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
1666
|
+
evaluated = obj.evaluated_get(depsgraph)
|
|
1667
|
+
mesh = evaluated.to_mesh()
|
|
1668
|
+
heights = [(obj.matrix_world @ vertex.co).z for vertex in mesh.vertices]
|
|
1669
|
+
evaluated.to_mesh_clear()
|
|
1670
|
+
if heights:
|
|
1671
|
+
reading[label + "LowestZ"] = round(min(heights), 6)
|
|
1672
|
+
reading[label + "HighestZ"] = round(max(heights), 6)
|
|
1673
|
+
# THE VERTEX COUNT IS PART OF THE READING, not a diagnostic: cloth needs geometry to bend and
|
|
1674
|
+
# the product's primitives are low-poly, so the compiler subdivides — and a reading that says
|
|
1675
|
+
# how many vertices the fabric had is how "it draped" can be told apart from "it had nothing
|
|
1676
|
+
# to drape with".
|
|
1677
|
+
reading[label + "Vertices"] = len(heights)
|
|
1678
|
+
if "firstFrameLowestZ" in reading and "lastFrameLowestZ" in reading:
|
|
1679
|
+
reading["droppedZ"] = round(reading["firstFrameLowestZ"] - reading["lastFrameLowestZ"], 6)
|
|
1680
|
+
return reading
|
|
1681
|
+
|
|
1682
|
+
|
|
1587
1683
|
def build_scene(spec, options, guard):
|
|
1588
1684
|
"""Compile a SceneSpec into a live Blender scene.
|
|
1589
1685
|
|
|
@@ -1762,8 +1858,21 @@ def build_scene(spec, options, guard):
|
|
|
1762
1858
|
"dataPaths": sorted({curve.data_path for curve in action_fcurves(action)}),
|
|
1763
1859
|
})
|
|
1764
1860
|
|
|
1861
|
+
# CLOTH ENTRIES ARE RESOLVED HERE, and the modifiers are created inside the simulation step so that
|
|
1862
|
+
# everything the bake has to walk is in place before it runs.
|
|
1765
1863
|
report_progress("build_simulation", 70)
|
|
1766
|
-
|
|
1864
|
+
cloth_entities = {}
|
|
1865
|
+
for entity in spec.get("entities") or []:
|
|
1866
|
+
entry = entity.get("cloth")
|
|
1867
|
+
if entry is None:
|
|
1868
|
+
continue
|
|
1869
|
+
if entity_objects.get(entity["id"]) is None:
|
|
1870
|
+
raise ActionError(
|
|
1871
|
+
"SCENE_SPEC_INVALID",
|
|
1872
|
+
'entity "%s" declares cloth but has no object to hang it on' % (entity["id"],),
|
|
1873
|
+
)
|
|
1874
|
+
cloth_entities[entity["id"]] = entry
|
|
1875
|
+
simulation_reading = build_rigid_bodies(spec, entity_objects, guard, cloth_entities)
|
|
1767
1876
|
|
|
1768
1877
|
report_progress("configure_render", 85)
|
|
1769
1878
|
render_config = configure_scene(scene, spec, profile, guard)
|