@deepblend/dsh-blender-provider-local 0.2.12 → 0.2.14
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/lib/index.js +41 -6
- 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 +16 -0
- package/python/deepblend_scene.py +59 -0
package/lib/index.js
CHANGED
|
@@ -591,6 +591,14 @@ export default class LocalBlenderRuntime extends Service {
|
|
|
591
591
|
/** The kept Blender for `sessionActions`, and the timer that closes it when it goes quiet. */
|
|
592
592
|
_session = null
|
|
593
593
|
_sessionTimer = null
|
|
594
|
+
/**
|
|
595
|
+
* Which transport the kept session came from: `configured`, `discovered` or `spawned`.
|
|
596
|
+
*
|
|
597
|
+
* Recorded rather than inferred, because "is this deployment driving the user's Blender or one of
|
|
598
|
+
* its own" is the first question anybody asks of a Live Bridge that behaves unexpectedly — and the
|
|
599
|
+
* only place that knows is the branch that chose.
|
|
600
|
+
*/
|
|
601
|
+
_sessionOrigin = null
|
|
594
602
|
|
|
595
603
|
/**
|
|
596
604
|
* @param {import('@deepseek-ai/cordis').Context} ctx
|
|
@@ -1021,12 +1029,39 @@ export default class LocalBlenderRuntime extends Service {
|
|
|
1021
1029
|
*/
|
|
1022
1030
|
async _keptSession() {
|
|
1023
1031
|
if (this._session !== null && !this._session.closed) return this._session
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1032
|
+
|
|
1033
|
+
const configured = this.config.sessionSocket
|
|
1034
|
+
if (typeof configured === 'string' && configured.length > 0) {
|
|
1035
|
+
// NAMED BY THE OPERATOR, so it is used and a failure is theirs to see. `attachSession` owns the
|
|
1036
|
+
// refusal and its message; this only decides which transport the configured actions travel over.
|
|
1037
|
+
this._session = await this.attachSession(configured)
|
|
1038
|
+
this._sessionOrigin = 'configured'
|
|
1039
|
+
return this._session
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// DISCOVERY, WHICH IS NOT A GUESS. The convention says a bridge listens at
|
|
1043
|
+
// `<workspace>/.deepblend/bridge.sock`, and a deployment that opted into sessions should not have
|
|
1044
|
+
// to write that path down as well. A GUESS asserts and fails when it is wrong; this CHECKS and
|
|
1045
|
+
// falls back — something answering at the conventional path is used, and nothing answering means
|
|
1046
|
+
// exactly what happened before this existed. The wrong-blender check still applies to whatever is
|
|
1047
|
+
// found, so discovery cannot attach to another workspace's Blender.
|
|
1048
|
+
const conventional = join(this.workspaceRoot, '.deepblend', 'bridge.sock')
|
|
1049
|
+
if (existsSync(conventional)) {
|
|
1050
|
+
const found = await this.attachSession(conventional).then(
|
|
1051
|
+
session => session,
|
|
1052
|
+
() => null,
|
|
1053
|
+
)
|
|
1054
|
+
if (found !== null) {
|
|
1055
|
+
this._session = found
|
|
1056
|
+
this._sessionOrigin = 'discovered'
|
|
1057
|
+
return this._session
|
|
1058
|
+
}
|
|
1059
|
+
// A file that exists and does not answer is the state a crash leaves; the bridge replaces it
|
|
1060
|
+
// when it starts again, and until then this deployment spawns its own Blender as before.
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
this._session = await this.openSession()
|
|
1064
|
+
this._sessionOrigin = 'spawned'
|
|
1030
1065
|
return this._session
|
|
1031
1066
|
}
|
|
1032
1067
|
|
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.14",
|
|
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.14"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/python/bootstrap.py
CHANGED
|
@@ -227,6 +227,22 @@ def action_compile_scene(request, options):
|
|
|
227
227
|
"engine": report["engine"],
|
|
228
228
|
"requestedEngine": report["requestedEngine"],
|
|
229
229
|
"profileName": report["profileName"],
|
|
230
|
+
# THE RIGS THIS SCENE DECLARES, with their bones. A count alone would say an armature exists;
|
|
231
|
+
# a rig is only useful if the bones are the ones the spec named, so the names travel with it and
|
|
232
|
+
# a test can read them back rather than trust the count.
|
|
233
|
+
"armatures": [
|
|
234
|
+
{
|
|
235
|
+
"id": armature_id,
|
|
236
|
+
"object": report["armatureNames"].get(armature_id),
|
|
237
|
+
"bones": [
|
|
238
|
+
{"name": bone["name"], "parent": bone.get("parent")}
|
|
239
|
+
for bone in armature_entry["bones"]
|
|
240
|
+
],
|
|
241
|
+
}
|
|
242
|
+
for armature_id, armature_entry in (
|
|
243
|
+
(entry["id"], entry) for entry in (spec.get("armatures") or [])
|
|
244
|
+
)
|
|
245
|
+
],
|
|
230
246
|
"validation": validation,
|
|
231
247
|
"sceneFingerprint": scene_fingerprint(spec, describe_objects()),
|
|
232
248
|
}
|
|
@@ -1324,6 +1324,57 @@ def assign_view_transform(scene, name):
|
|
|
1324
1324
|
# ---------------------------------------------------------------------------
|
|
1325
1325
|
|
|
1326
1326
|
|
|
1327
|
+
ARMATURE_PREFIX = "db_arm__"
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
def build_armature(entry, guard):
|
|
1331
|
+
"""Create one armature and its bones, and return the object.
|
|
1332
|
+
|
|
1333
|
+
WHY BONES ARE RESOLVED IN A SECOND PASS: the schema lets a bone name its parent without requiring
|
|
1334
|
+
the parent to be declared first, because a rig's declaration should read like the skeleton rather
|
|
1335
|
+
than like its storage order. So every bone is created as a root, and the parents are applied
|
|
1336
|
+
afterwards — with an unknown parent reported rather than silently ignored, since a bone that
|
|
1337
|
+
quietly has no parent is a rig that deforms differently from what the file says.
|
|
1338
|
+
"""
|
|
1339
|
+
armature_id = entry["id"]
|
|
1340
|
+
data = bpy.data.armatures.new("%s%s" % (ARMATURE_PREFIX, armature_id))
|
|
1341
|
+
obj = bpy.data.objects.new("%s%s" % (ARMATURE_PREFIX, armature_id), data)
|
|
1342
|
+
bpy.context.scene.collection.objects.link(obj)
|
|
1343
|
+
obj["deepblend_id"] = armature_id
|
|
1344
|
+
obj["deepblend_kind"] = "armature"
|
|
1345
|
+
|
|
1346
|
+
transform = entry.get("transform") or {}
|
|
1347
|
+
apply_transform(obj, transform)
|
|
1348
|
+
|
|
1349
|
+
# Edit mode is the only place `edit_bones` exists, and Blender's own context management is what
|
|
1350
|
+
# makes it available — a manual mode switch without this leaves the object in a state the rest of
|
|
1351
|
+
# the compile does not expect.
|
|
1352
|
+
bpy.context.view_layer.objects.active = obj
|
|
1353
|
+
bpy.ops.object.mode_set(mode="EDIT")
|
|
1354
|
+
try:
|
|
1355
|
+
created = {}
|
|
1356
|
+
for bone in entry["bones"]:
|
|
1357
|
+
edit_bone = data.edit_bones.new(bone["name"])
|
|
1358
|
+
edit_bone.head = tuple(float(value) for value in bone["head"])
|
|
1359
|
+
edit_bone.tail = tuple(float(value) for value in bone["tail"])
|
|
1360
|
+
created[bone["name"]] = edit_bone
|
|
1361
|
+
for bone in entry["bones"]:
|
|
1362
|
+
parent = bone.get("parent")
|
|
1363
|
+
if parent is None:
|
|
1364
|
+
continue
|
|
1365
|
+
if parent not in created:
|
|
1366
|
+
raise ActionError(
|
|
1367
|
+
"SCENE_SPEC_INVALID",
|
|
1368
|
+
'bone "%s" of armature "%s" names parent "%s", which the same armature does not declare'
|
|
1369
|
+
% (bone["name"], armature_id, parent),
|
|
1370
|
+
)
|
|
1371
|
+
created[bone["name"]].parent = created[parent]
|
|
1372
|
+
finally:
|
|
1373
|
+
bpy.ops.object.mode_set(mode="OBJECT")
|
|
1374
|
+
|
|
1375
|
+
return obj
|
|
1376
|
+
|
|
1377
|
+
|
|
1327
1378
|
def build_scene(spec, options, guard):
|
|
1328
1379
|
"""Compile a SceneSpec into a live Blender scene.
|
|
1329
1380
|
|
|
@@ -1372,6 +1423,13 @@ def build_scene(spec, options, guard):
|
|
|
1372
1423
|
|
|
1373
1424
|
assets = {asset["id"]: asset for asset in spec.get("assets") or []}
|
|
1374
1425
|
|
|
1426
|
+
# ARMATURES BEFORE ENTITIES: an entity that is skinned to one has to find it already there. Nothing
|
|
1427
|
+
# binds yet — that is the next slice — but the order is the one that will not have to change.
|
|
1428
|
+
report_progress("build_armatures", 30)
|
|
1429
|
+
armature_objects = {}
|
|
1430
|
+
for entry in spec.get("armatures") or []:
|
|
1431
|
+
armature_objects[entry["id"]] = build_armature(entry, guard)
|
|
1432
|
+
|
|
1375
1433
|
report_progress("build_entities", 35)
|
|
1376
1434
|
entity_objects = {}
|
|
1377
1435
|
entity_positions = {}
|
|
@@ -1501,6 +1559,7 @@ def build_scene(spec, options, guard):
|
|
|
1501
1559
|
"requestedEngine": requested_engine,
|
|
1502
1560
|
"profileName": profile_name,
|
|
1503
1561
|
"entityObjectNames": {key: value.name for key, value in entity_objects.items()},
|
|
1562
|
+
"armatureNames": {key: value.name for key, value in armature_objects.items()},
|
|
1504
1563
|
"cameraNames": {key: value.name for key, value in cameras.items()},
|
|
1505
1564
|
}
|
|
1506
1565
|
|