@deepblend/dsh-blender-host 0.10.0 → 0.12.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.
@@ -0,0 +1,21 @@
1
+ /** Describe saved object-origin moves and changed generators, not surface clearances. */
2
+ import { canonicalStringify } from '@deepblend/dsh-blender-contracts'
3
+ export function assemblyPlacementChanges(before, after, fromRevision, toRevision) {
4
+ const previous = new Map(before.entities.map(entity => [entity.id, entity]))
5
+ const groups = new Map()
6
+ const generatorChangedEntityIds = []
7
+ for (const entity of after.entities) {
8
+ const original = previous.get(entity.id)
9
+ if (!original) continue
10
+ if (canonicalStringify(entity.generator) !== canonicalStringify(original.generator)) generatorChangedEntityIds.push(entity.id)
11
+ const from = original.transform?.location ?? [0, 0, 0]
12
+ const to = entity.transform?.location ?? [0, 0, 0]
13
+ const deltaMm = to.map((value, axis) => Number(((value - from[axis]) * 1000).toFixed(9)))
14
+ if (deltaMm.every(value => value === 0)) continue
15
+ const key = JSON.stringify(deltaMm)
16
+ if (!groups.has(key)) groups.set(key, { deltaMm, entityIds: [] })
17
+ groups.get(key).entityIds.push(entity.id)
18
+ }
19
+ return { source: 'saved-scene-spec', coordinateField: 'entity.transform.location',
20
+ fromRevision, toRevision, groups: [...groups.values()], generatorChangedEntityIds }
21
+ }
package/lib/index.js CHANGED
@@ -27,6 +27,7 @@
27
27
 
28
28
  import { assertRuntimeSceneFeatures } from './runtime-scene-features.js'
29
29
  import { readRecipeContext } from './recipe-state.js'
30
+ import { assemblyPlacementChanges } from './assembly-changes.js'
30
31
  import { instantiateRecipeLock, planRecipeGeometryEdit, recipeGeometryParameters } from '@deepblend/dsh-blender-contracts'
31
32
  import { Service } from '@deepseek-ai/cordis'
32
33
  import z from '@deepseek-ai/schemastery'
@@ -671,7 +672,7 @@ export default class BlenderStudio extends Service {
671
672
  const digest = sceneSpecDigest(spec)
672
673
  return {
673
674
  ...toCanonicalProjectSummary({
674
- record: { ...record, goal: spec.project.goal ?? null },
675
+ record: { ...record, projectsRoot: this.projectsRoot, goal: spec.project.goal ?? null },
675
676
  revisionCount: record.revisionCount ?? this.store.listRevisions(projectId).length,
676
677
  sceneSummary: summarizeSceneSpec(spec, {
677
678
  revision,
@@ -679,6 +680,7 @@ export default class BlenderStudio extends Service {
679
680
  digest,
680
681
  }),
681
682
  }),
683
+ projectDirectory: this.store.projectDirectory(projectId),
682
684
  revisions: this.store.listRevisions(projectId).map(id => {
683
685
  const manifest = this.store.readRevisionManifest(projectId, id)
684
686
  return {
@@ -792,8 +794,14 @@ export default class BlenderStudio extends Service {
792
794
  const locked = context.state ? { ...context.lock, values: context.state.values,
793
795
  valuesSha256: context.state.valuesSha256 } : context.lock
794
796
  const resolved = instantiateRecipeLock(locked)
797
+ const currentSpec = this.store.readRevisionSpec(projectId, revision)
798
+ const animatedBindings = definitions.flatMap(parameter => parameter.bindings).filter(binding => binding.offsetAnimation === true)
799
+ const linkedAnimationTracks = (currentSpec.animationTracks ?? []).filter(track => (track.targetKind ?? 'entity') === 'entity'
800
+ && animatedBindings.some(binding => binding.entityId === track.targetEntityId && track.property === `location.${binding.axis}`))
801
+ .map(track => ({ id: track.id, targetEntityId: track.targetEntityId, property: track.property,
802
+ keyframes: structuredClone(track.keyframes) }))
795
803
  let conflicts = []
796
- try { planRecipeGeometryEdit(context.lock, this.store.readRevisionSpec(projectId, revision), context.state ?? undefined, {}) }
804
+ try { planRecipeGeometryEdit(context.lock, currentSpec, context.state ?? undefined, {}) }
797
805
  catch (error) {
798
806
  if (error.code !== 'RECIPE_PARAMETER_CONFLICT') throw error
799
807
  conflicts = error.details.conflicts
@@ -802,7 +810,7 @@ export default class BlenderStudio extends Service {
802
810
  recipe: { id: context.lock.id, version: context.lock.version }, valuesSha256: resolved.recipe.valuesSha256,
803
811
  parameters: definitions.map(parameter => ({ id: parameter.id, title: parameter.title, type: parameter.type,
804
812
  minimum: parameter.minimum, maximum: parameter.maximum, default: parameter.default, value: resolved.values[parameter.id] })),
805
- conflicts }
813
+ conflicts, linkedAnimationTracks, animationSource: 'saved-scene-spec' }
806
814
  }
807
815
 
808
816
  async updateRecipeParameters(request) {
@@ -818,9 +826,13 @@ export default class BlenderStudio extends Service {
818
826
  throw new BlenderError('REVISION_CONFLICT', 'The active project store changed; read the selected work again')
819
827
  }
820
828
  const outcome = await this.transactions.applyRecipeParameters(input, { signal })
829
+ const placementChanges = assemblyPlacementChanges(
830
+ this.store.readRevisionSpec(input.projectId, input.baseRevision),
831
+ this.store.readRevisionSpec(input.projectId, outcome.revision.revision),
832
+ input.baseRevision, outcome.revision.revision)
821
833
  return { ...toCanonicalRevisionSummary({ manifest: outcome.revision, checkpointPath: outcome.revision.checkpoint }),
822
834
  unchanged: outcome.unchanged === true, idempotentReplay: outcome.idempotentReplay === true,
823
- parameterState: outcome.parameterState, preservedCameraIds: outcome.preservedCameraIds,
835
+ parameterState: outcome.parameterState, preservedCameraIds: outcome.preservedCameraIds, placementChanges,
824
836
  parameters: await this.getRecipeParameters(input.projectId, { revision: outcome.revision.revision }),
825
837
  ...(outcome.job ? { job: toCanonicalJobRecord(outcome.job) } : {}) }
826
838
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepblend/dsh-blender-host",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "DeepBlend Host service: the blenderStudio business facade over the BlenderRuntime seam.",
6
6
  "main": "lib/index.js",
@@ -20,7 +20,7 @@
20
20
  "./package.json": "./package.json"
21
21
  },
22
22
  "dependencies": {
23
- "@deepblend/dsh-blender-contracts": "0.10.0",
23
+ "@deepblend/dsh-blender-contracts": "0.12.0",
24
24
  "sharp": "0.35.5"
25
25
  },
26
26
  "license": "MIT",