@deepblend/dsh-blender-provider-local 0.2.18 → 0.2.19
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.19",
|
|
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.19"
|
|
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, cloth_entities=None):
|
|
1489
|
+
def build_rigid_bodies(spec, entity_objects, guard, cloth_entities=None, soft_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
|
|
@@ -1500,6 +1500,7 @@ def build_rigid_bodies(spec, entity_objects, guard, cloth_entities=None):
|
|
|
1500
1500
|
exactly like one that fell, unless something reads where it ended up.
|
|
1501
1501
|
"""
|
|
1502
1502
|
cloth_entities = cloth_entities or {}
|
|
1503
|
+
soft_entities = soft_entities or {}
|
|
1503
1504
|
settings = spec.get("simulation") or {}
|
|
1504
1505
|
scene = bpy.context.scene
|
|
1505
1506
|
if "gravity" in settings:
|
|
@@ -1544,7 +1545,7 @@ def build_rigid_bodies(spec, entity_objects, guard, cloth_entities=None):
|
|
|
1544
1545
|
obj.rigid_body.friction = float(entry["friction"])
|
|
1545
1546
|
bodies[entity["id"]] = entry
|
|
1546
1547
|
|
|
1547
|
-
if not bodies and not cloth_entities:
|
|
1548
|
+
if not bodies and not cloth_entities and not soft_entities:
|
|
1548
1549
|
return {}
|
|
1549
1550
|
|
|
1550
1551
|
# A SCENE WITH CLOTH AND NO RIGID BODIES STILL HAS CACHES TO BAKE. MEASURED: the first version
|
|
@@ -1563,6 +1564,13 @@ def build_rigid_bodies(spec, entity_objects, guard, cloth_entities=None):
|
|
|
1563
1564
|
for entity_id, entry in cloth_entities.items():
|
|
1564
1565
|
build_cloth(entry, entity_objects[entity_id], guard)
|
|
1565
1566
|
|
|
1567
|
+
# SOFT BODIES ARE BUILT IN THE SAME PLACE, AND FOR THE SAME MEASURED REASON: MEASURED, and unlike
|
|
1568
|
+
# cloth, a soft body does NOT move without a bake (1.75 -> 1.75 before, 1.75 -> 1.6252 after), so its
|
|
1569
|
+
# modifier has to exist before `bake_all` — the three mechanisms behave three different ways, and each
|
|
1570
|
+
# one was measured rather than assumed from the last.
|
|
1571
|
+
for entity_id, entry in soft_entities.items():
|
|
1572
|
+
build_soft_body(entry, entity_objects[entity_id], guard)
|
|
1573
|
+
|
|
1566
1574
|
reading = {}
|
|
1567
1575
|
first_frame = int((spec.get("project") or {}).get("frameStart", 1))
|
|
1568
1576
|
last_frame = int((spec.get("project") or {}).get("frameEnd", first_frame))
|
|
@@ -1578,11 +1586,14 @@ def build_rigid_bodies(spec, entity_objects, guard, cloth_entities=None):
|
|
|
1578
1586
|
"message": "the simulation could not be baked: %s" % (error_text(exc),),
|
|
1579
1587
|
})
|
|
1580
1588
|
|
|
1589
|
+
for entity_id in soft_entities:
|
|
1590
|
+
reading[entity_id] = {"kind": "soft-body", **deform_reading(entity_objects[entity_id], first_frame, last_frame)}
|
|
1591
|
+
|
|
1581
1592
|
for entity_id in cloth_entities:
|
|
1582
1593
|
# INTO THE SAME READING as the rigid bodies: one report of what the physics did, whatever the
|
|
1583
1594
|
# mechanism, because a caller asking "what happened in this scene" should not have to know which
|
|
1584
1595
|
# kind of simulation each entity used.
|
|
1585
|
-
reading[entity_id] = {"kind": "cloth", **
|
|
1596
|
+
reading[entity_id] = {"kind": "cloth", **deform_reading(entity_objects[entity_id], first_frame, last_frame)}
|
|
1586
1597
|
|
|
1587
1598
|
for entity_id in bodies:
|
|
1588
1599
|
obj = entity_objects[entity_id]
|
|
@@ -1649,9 +1660,14 @@ def build_cloth(entry, obj, guard):
|
|
|
1649
1660
|
return obj
|
|
1650
1661
|
|
|
1651
1662
|
|
|
1652
|
-
def
|
|
1663
|
+
def deform_reading(obj, first_frame, last_frame):
|
|
1653
1664
|
"""The lowest and the highest simulated vertex, at each end of the range.
|
|
1654
1665
|
|
|
1666
|
+
SHARED BY EVERYTHING THAT DEFORMS A MESH rather than copied per mechanism: cloth and soft bodies ask
|
|
1667
|
+
the same question of the same evaluated geometry, and a second copy is the one that would miss the
|
|
1668
|
+
next fix — this reading already needed one when it turned out to be taken from the base mesh.
|
|
1669
|
+
|
|
1670
|
+
|
|
1655
1671
|
Read off the EVALUATED mesh rather than the modifier: what the fabric did is the geometry, and the
|
|
1656
1672
|
modifier only says what was asked for. The highest point is the pinned edge, which is how the
|
|
1657
1673
|
reading says "it draped FROM somewhere" rather than merely "it moved".
|
|
@@ -1670,16 +1686,58 @@ def cloth_reading(obj, first_frame, last_frame):
|
|
|
1670
1686
|
if heights:
|
|
1671
1687
|
reading[label + "LowestZ"] = round(min(heights), 6)
|
|
1672
1688
|
reading[label + "HighestZ"] = round(max(heights), 6)
|
|
1673
|
-
# THE VERTEX
|
|
1674
|
-
#
|
|
1675
|
-
#
|
|
1676
|
-
#
|
|
1689
|
+
# THE VERTEX COUNTS ARE PART OF THE READING, not diagnostics. MEASURED, and a surviving
|
|
1690
|
+
# mutation is why there are TWO of them: "the fabric had at least 64 vertices" passed on a
|
|
1691
|
+
# generator that already had that many, so it proved nothing about the subdivision. What
|
|
1692
|
+
# proves it is the RATIO — how many vertices the evaluated mesh has against how many the
|
|
1693
|
+
# object was built with — and that needs both numbers to be read.
|
|
1677
1694
|
reading[label + "Vertices"] = len(heights)
|
|
1695
|
+
reading[label + "SourceVertices"] = len(obj.data.vertices)
|
|
1678
1696
|
if "firstFrameLowestZ" in reading and "lastFrameLowestZ" in reading:
|
|
1679
1697
|
reading["droppedZ"] = round(reading["firstFrameLowestZ"] - reading["lastFrameLowestZ"], 6)
|
|
1680
1698
|
return reading
|
|
1681
1699
|
|
|
1682
1700
|
|
|
1701
|
+
def build_soft_body(entry, obj, guard):
|
|
1702
|
+
"""Give a mesh soft-body physics: it deforms, and it remembers its shape.
|
|
1703
|
+
|
|
1704
|
+
THE GOAL IS WHAT MAKES IT A SOFT BODY rather than cloth — `goal` is how strongly each vertex wants to
|
|
1705
|
+
return to where it started, and Blender's default is high (0.7). MEASURED: a 0.5m cube dropped 25
|
|
1706
|
+
frames falls 0.125m and keeps its 0.5m spread, which is exactly what a body with a goal should do,
|
|
1707
|
+
and both halves of that are asserted so a future default change cannot quietly turn it into cloth.
|
|
1708
|
+
"""
|
|
1709
|
+
settings = entry or {}
|
|
1710
|
+
|
|
1711
|
+
# THE SUBDIVISION IS NOT OPTIONAL, for the same measured reason as cloth: a primitive has no interior
|
|
1712
|
+
# to deform, so a soft body on an eight-vertex cube is a cube that moves.
|
|
1713
|
+
subdivision = obj.modifiers.new(name="DeepBlendSoftBodySubdivision", type="SUBSURF")
|
|
1714
|
+
subdivision.subdivision_type = "SIMPLE"
|
|
1715
|
+
subdivision.levels = int(settings.get("subdivisions", 3))
|
|
1716
|
+
subdivision.render_levels = subdivision.levels
|
|
1717
|
+
|
|
1718
|
+
modifier = obj.modifiers.new(name="DeepBlendSoftBody", type="SOFT_BODY")
|
|
1719
|
+
body_settings = modifier.settings
|
|
1720
|
+
if settings.get("mass") is not None:
|
|
1721
|
+
body_settings.mass = float(settings["mass"])
|
|
1722
|
+
if settings.get("goal") is not None:
|
|
1723
|
+
body_settings.goal_default = float(settings["goal"])
|
|
1724
|
+
|
|
1725
|
+
# PINNING IS EXPRESSED AS A GOAL, which is this modifier's own word for it: a vertex group at goal
|
|
1726
|
+
# 1.0 holds its vertices exactly, and one at 0.0 lets them go.
|
|
1727
|
+
if settings.get("pinTop"):
|
|
1728
|
+
ys = [vertex.co.y for vertex in obj.data.vertices]
|
|
1729
|
+
if ys:
|
|
1730
|
+
top = max(ys)
|
|
1731
|
+
pinned = [vertex.index for vertex in obj.data.vertices if abs(vertex.co.y - top) < 1e-4]
|
|
1732
|
+
group = obj.vertex_groups.new(name="DeepBlendSoftBodyPin")
|
|
1733
|
+
group.add(pinned, 1.0, "REPLACE")
|
|
1734
|
+
body_settings.use_goal = True
|
|
1735
|
+
body_settings.vertex_group_goal = group.name
|
|
1736
|
+
body_settings.goal_min = 1.0
|
|
1737
|
+
body_settings.goal_max = 1.0
|
|
1738
|
+
return obj
|
|
1739
|
+
|
|
1740
|
+
|
|
1683
1741
|
def build_scene(spec, options, guard):
|
|
1684
1742
|
"""Compile a SceneSpec into a live Blender scene.
|
|
1685
1743
|
|
|
@@ -1872,7 +1930,18 @@ def build_scene(spec, options, guard):
|
|
|
1872
1930
|
'entity "%s" declares cloth but has no object to hang it on' % (entity["id"],),
|
|
1873
1931
|
)
|
|
1874
1932
|
cloth_entities[entity["id"]] = entry
|
|
1875
|
-
|
|
1933
|
+
soft_entities = {}
|
|
1934
|
+
for entity in spec.get("entities") or []:
|
|
1935
|
+
entry = entity.get("softBody")
|
|
1936
|
+
if entry is None:
|
|
1937
|
+
continue
|
|
1938
|
+
if entity_objects.get(entity["id"]) is None:
|
|
1939
|
+
raise ActionError(
|
|
1940
|
+
"SCENE_SPEC_INVALID",
|
|
1941
|
+
'entity "%s" declares a soft body but has no object to simulate' % (entity["id"],),
|
|
1942
|
+
)
|
|
1943
|
+
soft_entities[entity["id"]] = entry
|
|
1944
|
+
simulation_reading = build_rigid_bodies(spec, entity_objects, guard, cloth_entities, soft_entities)
|
|
1876
1945
|
|
|
1877
1946
|
report_progress("configure_render", 85)
|
|
1878
1947
|
render_config = configure_scene(scene, spec, profile, guard)
|