@deepblend/dsh-blender-provider-local 0.11.0 → 0.13.0
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 +10 -1
- package/package.json +2 -2
- package/python/bootstrap.py +8 -0
- package/python/deepblend_camera_framing.py +64 -0
package/lib/index.js
CHANGED
|
@@ -1512,12 +1512,21 @@ export default class LocalBlenderRuntime extends Service {
|
|
|
1512
1512
|
)
|
|
1513
1513
|
}
|
|
1514
1514
|
|
|
1515
|
+
if (request.cameraFraming !== undefined) {
|
|
1516
|
+
const selection = request.cameraFraming
|
|
1517
|
+
if (!selection || typeof selection !== 'object' || Array.isArray(selection)
|
|
1518
|
+
|| Object.keys(selection).length !== 3 || selection.entityId !== 'cup'
|
|
1519
|
+
|| selection.cameraId !== 'hero' || selection.margin !== 0.03) {
|
|
1520
|
+
throw new BlenderError(BlenderErrorCode.SCENE_SPEC_INVALID, 'Expected the bounded cup/hero framing selection')
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1515
1523
|
const args = ['--scene-spec', request.sceneSpecPath, '--output-blend', outputBlend]
|
|
1516
1524
|
if (request.profile !== undefined) args.push('--profile', String(request.profile))
|
|
1517
1525
|
if (request.projectRoot !== undefined) args.push('--project-root', request.projectRoot)
|
|
1518
1526
|
|
|
1519
1527
|
const run = await this.runBootstrap(
|
|
1520
|
-
{ action: 'compile_scene', jobId: request.jobId
|
|
1528
|
+
{ action: 'compile_scene', jobId: request.jobId,
|
|
1529
|
+
...(request.cameraFraming === undefined ? {} : { payload: { cameraFraming: request.cameraFraming } }) },
|
|
1521
1530
|
{
|
|
1522
1531
|
signal: request.signal,
|
|
1523
1532
|
session: request.session,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepblend/dsh-blender-provider-local",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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.
|
|
14
|
+
"@deepblend/dsh-blender-contracts": "0.13.0"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
package/python/bootstrap.py
CHANGED
|
@@ -216,6 +216,11 @@ def action_compile_scene(request, options):
|
|
|
216
216
|
"project_root": options.get("project_root"),
|
|
217
217
|
}
|
|
218
218
|
report = build_scene(spec, build_options, guard)
|
|
219
|
+
framing_selection = (request.get('payload') or {}).get('cameraFraming')
|
|
220
|
+
framing = None
|
|
221
|
+
if framing_selection is not None:
|
|
222
|
+
from deepblend_camera_framing import measure_cup_hero_framing
|
|
223
|
+
framing = measure_cup_hero_framing(spec, framing_selection)
|
|
219
224
|
|
|
220
225
|
report_progress("validate", 92)
|
|
221
226
|
validation = validate_scene(bpy.context.scene, spec, guard)
|
|
@@ -268,6 +273,9 @@ def action_compile_scene(request, options):
|
|
|
268
273
|
"sceneFingerprint": scene_fingerprint(spec, objects),
|
|
269
274
|
}
|
|
270
275
|
|
|
276
|
+
if framing is not None:
|
|
277
|
+
payload['cameraFraming'] = framing
|
|
278
|
+
|
|
271
279
|
# The checkpoint is written even when validation reported errors: the caller
|
|
272
280
|
# decides whether to publish, and a diagnostic blend is far more useful than
|
|
273
281
|
# no blend at all when a compile went wrong. Nothing is published by THIS
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Bounded, read-only framing measurements of one evaluated cup and hero camera."""
|
|
2
|
+
import math
|
|
3
|
+
import bpy
|
|
4
|
+
from deepblend_util import ActionError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def measure_cup_hero_framing(spec, selection):
|
|
8
|
+
if not isinstance(selection, dict) or set(selection) != {'entityId', 'cameraId', 'margin'} or selection != {'entityId': 'cup', 'cameraId': 'hero', 'margin': 0.03}:
|
|
9
|
+
raise ActionError('SCENE_VALIDATION_FAILED', 'Expected one bounded cup/hero framing selection')
|
|
10
|
+
entity = next((e for e in spec.get('entities', []) if e.get('id') == 'cup'), None)
|
|
11
|
+
if not entity or entity.get('generator', {}).get('shape') != 'handled_cup':
|
|
12
|
+
raise ActionError('SCENE_VALIDATION_FAILED', 'Cup framing needs the declared handled-cup entity')
|
|
13
|
+
camera = bpy.data.objects.get('db_camera__hero')
|
|
14
|
+
obj = bpy.data.objects.get('db_entity__cup')
|
|
15
|
+
if camera is None or obj is None or camera.type != 'CAMERA' or obj.type != 'MESH' or camera.data.type != 'PERSP':
|
|
16
|
+
raise ActionError('SCENE_VALIDATION_FAILED', 'Native cup and perspective hero are required for framing')
|
|
17
|
+
scene = bpy.context.scene
|
|
18
|
+
if not obj.visible_get(view_layer=bpy.context.view_layer) or obj.hide_render:
|
|
19
|
+
raise ActionError('SCENE_VALIDATION_FAILED', 'Cannot frame an unavailable cup surface')
|
|
20
|
+
if any(t.get('targetKind') == 'camera' and t.get('targetEntityId') == 'hero' for t in spec.get('animationTracks', [])):
|
|
21
|
+
raise ActionError('SCENE_VALIDATION_FAILED', 'An authored camera animation cannot be reframed')
|
|
22
|
+
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
23
|
+
measured_camera = camera.evaluated_get(depsgraph)
|
|
24
|
+
inverse = measured_camera.matrix_world.inverted()
|
|
25
|
+
frame = measured_camera.data.view_frame(scene=scene)
|
|
26
|
+
slopes = [(p.x / -p.z, p.y / -p.z) for p in frame]
|
|
27
|
+
left, right = min(p[0] for p in slopes), max(p[0] for p in slopes)
|
|
28
|
+
bottom, top = min(p[1] for p in slopes), max(p[1] for p in slopes)
|
|
29
|
+
margin = selection['margin']
|
|
30
|
+
left, right = left + (right-left)*margin, right - (right-left)*margin
|
|
31
|
+
bottom, top = bottom + (top-bottom)*margin, top - (top-bottom)*margin
|
|
32
|
+
if not left < 0 < right or not bottom < 0 < top:
|
|
33
|
+
raise ActionError('SCENE_VALIDATION_FAILED', 'The hero optical axis is outside the usable frame')
|
|
34
|
+
evaluated = obj.evaluated_get(depsgraph)
|
|
35
|
+
mesh = evaluated.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph)
|
|
36
|
+
try:
|
|
37
|
+
if mesh is None or not 1 <= len(mesh.vertices) <= 2000000:
|
|
38
|
+
raise ActionError('SCENE_TOO_HEAVY', 'Cup framing requires 1 to 2000000 evaluated vertices')
|
|
39
|
+
backoff, nearest, farthest = 0.0, math.inf, -math.inf
|
|
40
|
+
for vertex in mesh.vertices:
|
|
41
|
+
point = inverse @ (evaluated.matrix_world @ vertex.co)
|
|
42
|
+
depth = -point.z
|
|
43
|
+
if not all(math.isfinite(v) for v in [point.x, point.y, depth]):
|
|
44
|
+
raise ActionError('SCENE_VALIDATION_FAILED', 'Non-finite evaluated framing vertex')
|
|
45
|
+
nearest, farthest = min(nearest, depth), max(farthest, depth)
|
|
46
|
+
backoff = max(backoff, point.x/right-depth, point.x/left-depth,
|
|
47
|
+
point.y/top-depth, point.y/bottom-depth,
|
|
48
|
+
camera.data.clip_start+1e-5-depth)
|
|
49
|
+
# Avoid a second corrective compile due to single-precision matrix noise.
|
|
50
|
+
backoff = backoff + 1e-5 if backoff > 1e-6 else 0.0
|
|
51
|
+
if not math.isfinite(backoff) or farthest+backoff >= camera.data.clip_end-1e-5:
|
|
52
|
+
raise ActionError('SCENE_VALIDATION_FAILED', 'Cup framing exceeds the hero far clip')
|
|
53
|
+
return {'schemaVersion': 'deepblend.camera-framing/v1', **selection,
|
|
54
|
+
'frame': int(scene.frame_current), 'resolution': [scene.render.resolution_x, scene.render.resolution_y],
|
|
55
|
+
'resolutionPercentage': scene.render.resolution_percentage,
|
|
56
|
+
'pixelAspect': [scene.render.pixel_aspect_x, scene.render.pixel_aspect_y],
|
|
57
|
+
'cameraLocation': list(measured_camera.matrix_world.translation),
|
|
58
|
+
'cameraType': camera.data.type, 'lens': camera.data.lens, 'sensorWidth': camera.data.sensor_width,
|
|
59
|
+
'backoffM': backoff, 'vertices': len(mesh.vertices),
|
|
60
|
+
'nearestDepthM': nearest+backoff, 'farthestDepthM': farthest+backoff,
|
|
61
|
+
'scope': 'evaluated-cup-vertices-at-reported-frame-and-render-aspect'}
|
|
62
|
+
finally:
|
|
63
|
+
if mesh is not None:
|
|
64
|
+
evaluated.to_mesh_clear()
|