@hraness/slopcamera 3.3.2 → 3.3.4

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.
Files changed (37) hide show
  1. package/README.md +49 -25
  2. package/apps/desktop/application/directing-contract.ts +15 -3
  3. package/apps/desktop/application/directing-plan.ts +2 -1
  4. package/apps/desktop/application/operation.ts +1 -1
  5. package/apps/desktop/cli/args.ts +1 -1
  6. package/apps/desktop/cli/cinema-renderer.ts +9 -6
  7. package/apps/desktop/cli/commands.ts +8 -1
  8. package/apps/desktop/cli/directing-blob.ts +17 -12
  9. package/apps/desktop/cli/directing-media.ts +14 -4
  10. package/apps/desktop/cli/directing-quote.ts +29 -11
  11. package/apps/desktop/cli/directing-service.ts +6 -2
  12. package/apps/desktop/cli/gateway-media-catalog.ts +57 -0
  13. package/apps/desktop/cli/gateway-media-service.ts +12 -2
  14. package/apps/desktop/cli/help.ts +4 -4
  15. package/apps/desktop/cli/main.ts +1 -1
  16. package/apps/desktop/cli/project-renderer.ts +23 -9
  17. package/apps/desktop/cli/spatial-scene-service.ts +1 -1
  18. package/apps/desktop/dist/cli/main.js +254 -248
  19. package/apps/desktop/html-overlay/spatial-splat-runtime.ts +6 -0
  20. package/dist/cli.js +1 -1
  21. package/dist/code/index.js +4 -4
  22. package/dist/{index-gs48afpt.js → index-c8jpst4g.js} +2 -2
  23. package/dist/index-z1kd1x8j.js +3 -0
  24. package/dist/index.js +1 -1
  25. package/docs/README.md +6 -3
  26. package/docs/studio.md +63 -3
  27. package/package.json +13 -7
  28. package/skills/slopcamera/references/directing-video.md +3 -3
  29. package/skills/slopcamera/references/educational-video.md +1 -1
  30. package/skills/slopcamera/references/install.md +1 -1
  31. package/skills/slopcamera/references/parametric-design.md +5 -1
  32. package/skills/slopcamera/references/scene-building.md +2 -0
  33. package/skills/slopcamera/references/workflows-sdk.md +1 -1
  34. package/src/spatial-scene/geometry.ts +88 -35
  35. package/src/spatial-scene/parametric.ts +42 -15
  36. package/src/version.ts +1 -1
  37. package/dist/index-zhg36tf9.js +0 -3
@@ -18,7 +18,7 @@ import {
18
18
  import { SpatialAssetFactsV1Schema, SpatialCollisionProxySchema, type SpatialAssetFactsV1, type SpatialCollisionProxy, type SpatialRetainedArtifact } from "./asset-admission.js"
19
19
  import { mulberry32 } from "./build.js"
20
20
  import {
21
- SPATIAL_GEOMETRY_PROFILE, SpatialGeometryGraphSchema, emitSpatialGeometryGlb, evaluateSpatialGeometry, estimateSpatialGeometryGraph, parseSpatialGeometryGraph,
21
+ SPATIAL_GEOMETRY_PROFILE, SPATIAL_GEOMETRY_BOOLEAN_COMPILER, SpatialGeometryGraphSchema, emitSpatialGeometryGlb, evaluateSpatialGeometry, estimateSpatialGeometryGraph, parseSpatialGeometryGraph,
22
22
  type SpatialGeometryMesh,
23
23
  } from "./geometry.js"
24
24
  import {
@@ -68,6 +68,8 @@ const specVec3 = z.tuple([coordinate, coordinate, coordinate])
68
68
  const editable = z.array(z.enum(["color", "opacity", "transform"])).max(3).optional()
69
69
  const partPlacement = { transform: SpatialTransformSchema.optional(), editable, castShadow: z.boolean().optional(), receiveShadow: z.boolean().optional() }
70
70
 
71
+ /** center is [horizontal center, sill height from the wall base]. Arch height
72
+ * includes the semicircular crown, whose radius is half its width. */
71
73
  const opening = z.discriminatedUnion("kind", [
72
74
  z.strictObject({ kind: z.literal("rect"), center: specVec2, width: meter, height: meter }),
73
75
  z.strictObject({ kind: z.literal("arch"), center: specVec2, width: meter, height: meter }),
@@ -155,6 +157,9 @@ function boxBounds(bounds: Bounds): SpatialCollisionProxy {
155
157
  }
156
158
 
157
159
  const LOD1_DISTANCE_M = 25
160
+ // Wall lowering changed while the retained GLB/facts format remains v1. Keep
161
+ // the revision local to this planner so unrelated generators retain identity.
162
+ export const SPATIAL_PARAMETRIC_WALL_COMPILER = "slopcamera.parametric-wall-v2" as const
158
163
 
159
164
  function planWall(spec: Extract<SpatialParametricSpec, { kind: "wall" }>): PartPlan[] {
160
165
  const { length, height, thickness } = spec
@@ -165,19 +170,33 @@ function planWall(spec: Extract<SpatialParametricSpec, { kind: "wall" }>): PartP
165
170
  if (cx - item.width / 2 < -length / 2 || cx + item.width / 2 > length / 2 || baseY < 0 || baseY + item.height > height) {
166
171
  pfail(`Wall opening ${index} must stay inside the wall rectangle.`, "spec.openings")
167
172
  }
168
- const rectId = `cut${index}`
169
- cutterNodes.push({ id: rectId, kind: "box", size: [item.width, item.height, thickness * 4] })
170
- cutterNodes.push({ id: `${rectId}p`, kind: "transform", input: rectId, transform: { position: [cx, baseY + item.height / 2, 0], rotation: [0, 0, 0, 1], scale: [1, 1, 1] } })
171
- cutterIds.push(`${rectId}p`)
173
+ const cutterId = `cut${index}`
174
+ // The body is still centered until the final lift. Authored opening Y is
175
+ // measured from its base, so cutters must share the body's local frame.
176
+ let cutterY = baseY + item.height / 2 - height / 2
172
177
  if (item.kind === "arch") {
173
178
  const radius = item.width / 2
174
179
  if (radius > item.height) pfail(`Arch opening ${index} rise exceeds its height.`, "spec.openings")
175
- const cylId = `cyl${index}`
176
- // Half-cylinder lintel: cylinder rotated so its axis runs through the wall thickness.
177
- cutterNodes.push({ id: cylId, kind: "cylinder", radius, height: thickness * 4, segments: 24 })
178
- cutterNodes.push({ id: `${cylId}r`, kind: "transform", input: cylId, transform: { position: [cx, baseY + item.height - radius, 0], rotation: [Math.SQRT1_2, 0, 0, Math.SQRT1_2], scale: [1, 1, 1] } })
179
- cutterIds.push(`${cylId}r`)
180
+ const springline = item.height - radius
181
+ // One extruded cutter has a flat sill and a semicircular crown. A full
182
+ // circle would cut below a shallow opening, while a full-height box
183
+ // would remove the masonry that should remain at the arch shoulders.
184
+ const points: number[][] = [[-radius, 0], [radius, 0]]
185
+ if (springline > 0) points.push([radius, springline])
186
+ for (let segment = 1; segment <= 12; segment++) {
187
+ const angle = Math.PI * segment / 12
188
+ points.push(segment === 12 ? [-radius, springline] : [radius * Math.cos(angle), springline + radius * Math.sin(angle)])
189
+ }
190
+ // At height == radius the arc ends at the first profile point.
191
+ if (springline === 0) points.pop()
192
+ cutterNodes.push({ id: `${cutterId}profile`, kind: "profile", points })
193
+ cutterNodes.push({ id: cutterId, kind: "extrude", profile: `${cutterId}profile`, depth: thickness * 4 })
194
+ cutterY = baseY - height / 2
195
+ } else {
196
+ cutterNodes.push({ id: cutterId, kind: "box", size: [item.width, item.height, thickness * 4] })
180
197
  }
198
+ cutterNodes.push({ id: `${cutterId}p`, kind: "transform", input: cutterId, transform: { position: [cx, cutterY, 0], rotation: [0, 0, 0, 1], scale: [1, 1, 1] } })
199
+ cutterIds.push(`${cutterId}p`)
181
200
  }
182
201
  const nodes: unknown[] = [{ id: "body", kind: "box", size: [length, height, thickness] }]
183
202
  let output = "body"
@@ -534,6 +553,8 @@ export interface SpatialParametricArtifact { readonly assetId: string; readonly
534
553
  export interface SpatialParametricFacts { readonly manifest: SpatialAssetManifest; readonly facts: SpatialAssetFactsV1 }
535
554
  export interface SpatialParametricReceipt {
536
555
  readonly name?: string
556
+ readonly compiler?: typeof SPATIAL_PARAMETRIC_WALL_COMPILER
557
+ readonly booleanCompiler?: typeof SPATIAL_GEOMETRY_BOOLEAN_COMPILER
537
558
  readonly kind: "slopcamera.spatial-parametric-receipt"
538
559
  readonly schemaVersion: 1
539
560
  readonly generatorId: string
@@ -580,14 +601,19 @@ export function emitSpatialParametric(input: unknown): SpatialParametricOutput {
580
601
  outputs: [...outputs],
581
602
  })
582
603
  }
583
- const sourceSha256 = spatialValueSha256({ domain: "slopcamera.parametric-source.v1", profile: SPATIAL_GEOMETRY_PROFILE, kind: spec.kind })
584
- const parametersSha256 = spatialGeneratorParametersSha256(spec)
585
- const specSha256 = spatialValueSha256({ domain: "slopcamera.parametric-spec.v1", spec })
586
- const seed = request.seed ?? deriveSpatialGeneratorSeed(sourceSha256)
587
- const runtimeSha256 = spatialValueSha256({ domain: "slopcamera.parametric-runtime.v1", profile: SPATIAL_GEOMETRY_PROFILE })
588
604
  const plans = PLANNERS[spec.kind](spec as never)
589
605
  if (plans.length > SPATIAL_PARAMETRIC_LIMITS.parts) pfail("Spec expands beyond the part budget.", "spec")
590
606
  validatePartMaterials(plans)
607
+ const usesBoolean = plans.some(part => part.lods.some(lod => parseSpatialGeometryGraph(lod.graph).nodes.some(node => node.kind === "boolean")))
608
+ const compiler: Pick<SpatialParametricReceipt, "compiler" | "booleanCompiler"> = {
609
+ ...(spec.kind === "wall" ? { compiler: SPATIAL_PARAMETRIC_WALL_COMPILER } : {}),
610
+ ...(usesBoolean ? { booleanCompiler: SPATIAL_GEOMETRY_BOOLEAN_COMPILER } : {}),
611
+ }
612
+ const sourceSha256 = spatialValueSha256({ domain: "slopcamera.parametric-source.v1", profile: SPATIAL_GEOMETRY_PROFILE, kind: spec.kind, ...compiler })
613
+ const parametersSha256 = spatialGeneratorParametersSha256(spec)
614
+ const specSha256 = spatialValueSha256({ domain: "slopcamera.parametric-spec.v1", spec })
615
+ const seed = request.seed ?? deriveSpatialGeneratorSeed(sourceSha256)
616
+ const runtimeSha256 = spatialValueSha256({ domain: "slopcamera.parametric-runtime.v1", profile: SPATIAL_GEOMETRY_PROFILE, ...compiler })
591
617
  const manifests: SpatialAssetManifest[] = []
592
618
  const artifacts: SpatialParametricArtifact[] = []
593
619
  const entities: SpatialEntity[] = []
@@ -682,6 +708,7 @@ export function emitSpatialParametric(input: unknown): SpatialParametricOutput {
682
708
  generatorId, specSha256, parametersSha256, seed,
683
709
  ...(request.name === undefined ? {} : { name: request.name }),
684
710
  profile: SPATIAL_GEOMETRY_PROFILE,
711
+ ...compiler,
685
712
  assets: receiptAssets,
686
713
  entities: entities.map(entity => entity.entityId).sort(),
687
714
  native: retainedArtifacts.map(artifact => ({ requestSha256: artifact.requestSha256, receiptSha256: artifact.receiptSha256 })),
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SLOPCAMERA_VERSION = "3.3.2" as const
1
+ export const SLOPCAMERA_VERSION = "3.3.4" as const