@operato/scene-storage 10.0.0-beta.30 → 10.0.0-beta.31

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.
@@ -1 +1 @@
1
- {"version":3,"file":"rack-cell.js","sourceRoot":"","sources":["../src/rack-cell.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * RackCell — a single storage slot within an AsrsRack.\n *\n * A RackCell is a virtual component: it occupies a specific (bay, row, level)\n * coordinate within the parent rack and acts as a CarrierHolder for one carrier\n * (or several, depending on `cellType`).\n *\n * The crane (AsrsCrane) navigates toward a RackCell as its `place()` destination —\n * because the rack cell is a discrete component, Mover.moveTo() can target it\n * directly and arrive at exactly the right bay × level position.\n *\n * Visual: invisible in 2D (no visible 2D footprint — rack cells don't make\n * sense as 2D top-down boxes). In 3D, each cell is an invisible Group\n * positioned within the rack's coordinate space (RackCell3D handles\n * this via updateTransform override).\n *\n * Lifecycle: AsrsRack._buildCells() instantiates RackCell children.\n * Do not create RackCell components manually — they are managed by the rack.\n *\n * Domain aliases:\n * cell.store(carrier) ← cell.receive(carrier)\n * cell.retrieve(carrier, target) ← cell.dispatch(carrier, target)\n */\n\nimport {\n Component,\n ComponentNature,\n ContainerAbstract,\n RealObject,\n TRANSFER_SLOT_KEY,\n sceneComponent\n} from '@hatiolab/things-scene'\nimport { CarrierHolder, type AttachFrame } from '@operato/scene-base'\n\nimport { RackCell3D } from './rack-cell-3d.js'\n\n/**\n * How many carriers a cell can hold simultaneously.\n * - single: exactly 1 (typical pallet bay)\n * - multi: small stack (up to 4, e.g. a multi-deep tray)\n * - bulk: unlimited (e.g. a floor area measured in slots)\n */\nexport type RackCellType = 'single' | 'multi' | 'bulk'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: false,\n rotatable: false,\n properties: [\n {\n type: 'string',\n label: 'cell-id',\n name: 'cellId',\n placeholder: 'e.g. 0-0-0'\n },\n {\n type: 'select',\n label: 'cell-type',\n name: 'cellType',\n property: {\n options: [\n { display: 'Single', value: 'single' },\n { display: 'Multi', value: 'multi' },\n { display: 'Bulk', value: 'bulk' }\n ]\n }\n }\n ],\n help: 'scene/component/rack-cell'\n}\n\n/**\n * RackCell — single-slot storage cell inside an AsrsRack.\n *\n * Mixin chain: CarrierHolder(ContainerAbstract)\n * - CarrierHolder: publishes attachPointFor(), gates containable() to Carriables\n * - ContainerAbstract: manages child carrier components\n *\n * No Placeable mixin — RackCell3D self-positions from the parent rack's\n * CellMap (via updateTransform override), bypassing things-scene's standard\n * 2D→3D coordinate mapping which cannot express 3D levels.\n */\n@sceneComponent('rack-cell')\nexport default class RackCell extends CarrierHolder(ContainerAbstract) {\n // ── Identification ────────────────────────────────────────────────────────\n\n get cellId(): string {\n return (this.state.cellId as string) || ''\n }\n\n get cellType(): RackCellType {\n return ((this.state.cellType as RackCellType) || 'single')\n }\n\n /** Maximum carrier count for this cell based on cellType. */\n get capacity(): number {\n switch (this.cellType) {\n case 'single': return 1\n case 'multi': return 4\n case 'bulk': return Infinity\n }\n }\n\n // ── Interface ─────────────────────────────────────────────────────────────\n\n get nature(): ComponentNature {\n return NATURE\n }\n\n get anchors(): [] {\n return []\n }\n\n // ── Transfer protocol ─────────────────────────────────────────────────────\n\n /** True when fewer carriers are currently held than capacity. */\n canReceive(_component?: any): boolean {\n const occupied = ((this as any).components as Component[] | undefined)?.length ?? 0\n return occupied < this.capacity\n }\n\n /**\n * Accept a carrier into this cell.\n * Sets TRANSFER_SLOT_KEY = cellId on the carrier, then reparents.\n * Fires 'transfer-received' so monitors can react.\n */\n async receive(carrier: any, options: any = {}): Promise<void> {\n if (!this.canReceive(carrier)) {\n ;(this as any).trigger?.('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'no-slot'\n })\n return\n }\n carrier[TRANSFER_SLOT_KEY] = this.cellId\n ;(this as any).reparent?.(carrier, options)\n ;(this as any).trigger?.('transfer-received', {\n type: 'transfer-received',\n component: carrier,\n container: this,\n slotId: this.cellId\n })\n }\n\n /**\n * Release a carrier from this cell to `target`.\n * Delegates to `target.receive()` if available, otherwise `target.reparent()`.\n */\n async dispatch(carrier: any, target: any, options: any = {}): Promise<void> {\n if (target?.canReceive && !target.canReceive(carrier)) {\n ;(this as any).trigger?.('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'target-full'\n })\n return\n }\n delete carrier[TRANSFER_SLOT_KEY]\n if (typeof target?.receive === 'function') {\n await target.receive(carrier, options)\n } else {\n ;(target as any).reparent?.(carrier, options)\n }\n ;(this as any).trigger?.('transfer-dispatched', {\n type: 'transfer-dispatched',\n component: carrier,\n container: this,\n target\n })\n }\n\n // ── Domain aliases ────────────────────────────────────────────────────────\n\n /** Alias for receive() — semantic sugar for the storage domain. */\n store(carrier: any, options?: any): Promise<void> {\n return this.receive(carrier, options)\n }\n\n /** Alias for dispatch() — semantic sugar for the storage domain. */\n retrieve(carrier: any, target: any, options?: any): Promise<void> {\n return this.dispatch(carrier, target, options)\n }\n\n // ── 3D attach frame ───────────────────────────────────────────────────────\n\n /**\n * Return the 3D attach frame for carriers placed in this cell.\n * Carriers are lifted by their own halfDepth so the bottom face\n * rests at the cell's Y-center (which is levelHeight/2 above the beam).\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const root = (this as any)._realObject?.object3d\n if (!root) return null\n const carrierDepth = resolveCarrierDepth(carrier)\n return {\n attach: root,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n\n // ── 2D rendering ──────────────────────────────────────────────────────────\n\n /** RackCell has no 2D visual — the rack draws its own structure. */\n render(_ctx: CanvasRenderingContext2D) {\n // intentional no-op\n }\n\n // ── 3D ───────────────────────────────────────────────────────────────────\n\n buildRealObject(): RealObject | undefined {\n return new RackCell3D(this as any)\n }\n}\n\nfunction resolveCarrierDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
1
+ {"version":3,"file":"rack-cell.js","sourceRoot":"","sources":["../src/rack-cell.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * RackCell — a single storage slot within an AsrsRack.\n *\n * A RackCell is a virtual component: it occupies a specific (bay, row, level)\n * coordinate within the parent rack and acts as a CarrierHolder for one carrier\n * (or several, depending on `cellType`).\n *\n * The crane (AsrsCrane) navigates toward a RackCell as its `place()` destination —\n * because the rack cell is a discrete component, Mover.moveTo() can target it\n * directly and arrive at exactly the right bay × level position.\n *\n * Visual: invisible in 2D (no visible 2D footprint — rack cells don't make\n * sense as 2D top-down boxes). In 3D, each cell is an invisible Group\n * positioned within the rack's coordinate space (RackCell3D handles\n * this via updateTransform override).\n *\n * Lifecycle: AsrsRack._buildCells() instantiates RackCell children.\n * Do not create RackCell components manually — they are managed by the rack.\n *\n * Domain aliases:\n * cell.store(carrier) ← cell.receive(carrier)\n * cell.retrieve(carrier, target) ← cell.dispatch(carrier, target)\n */\n\nimport {\n Component,\n ComponentNature,\n ContainerAbstract,\n RealObject,\n TRANSFER_SLOT_KEY,\n sceneComponent\n} from '@hatiolab/things-scene'\nimport type { State, Material3D } from '@hatiolab/things-scene'\nimport { CarrierHolder, type AttachFrame } from '@operato/scene-base'\n\nimport { RackCell3D } from './rack-cell-3d.js'\n\n/**\n * How many carriers a cell can hold simultaneously.\n * - single: exactly 1 (typical pallet bay)\n * - multi: small stack (up to 4, e.g. a multi-deep tray)\n * - bulk: unlimited (e.g. a floor area measured in slots)\n */\nexport type RackCellType = 'single' | 'multi' | 'bulk'\n\n/** RackCell 컴포넌트 state */\nexport interface RackCellState extends State {\n // ── 식별 ──\n cellId?: string\n cellType?: RackCellType\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: false,\n rotatable: false,\n properties: [\n {\n type: 'string',\n label: 'cell-id',\n name: 'cellId',\n placeholder: 'e.g. 0-0-0'\n },\n {\n type: 'select',\n label: 'cell-type',\n name: 'cellType',\n property: {\n options: [\n { display: 'Single', value: 'single' },\n { display: 'Multi', value: 'multi' },\n { display: 'Bulk', value: 'bulk' }\n ]\n }\n }\n ],\n help: 'scene/component/rack-cell'\n}\n\n/**\n * RackCell — single-slot storage cell inside an AsrsRack.\n *\n * Mixin chain: CarrierHolder(ContainerAbstract)\n * - CarrierHolder: publishes attachPointFor(), gates containable() to Carriables\n * - ContainerAbstract: manages child carrier components\n *\n * No Placeable mixin — RackCell3D self-positions from the parent rack's\n * CellMap (via updateTransform override), bypassing things-scene's standard\n * 2D→3D coordinate mapping which cannot express 3D levels.\n */\n@sceneComponent('rack-cell')\nexport default class RackCell extends CarrierHolder(ContainerAbstract) {\n declare state: RackCellState\n\n // ── Identification ────────────────────────────────────────────────────────\n\n get cellId(): string {\n return this.state.cellId ?? ''\n }\n\n get cellType(): RackCellType {\n return this.state.cellType ?? 'single'\n }\n\n /** Maximum carrier count for this cell based on cellType. */\n get capacity(): number {\n switch (this.cellType) {\n case 'single': return 1\n case 'multi': return 4\n case 'bulk': return Infinity\n }\n }\n\n // ── Interface ─────────────────────────────────────────────────────────────\n\n get nature(): ComponentNature {\n return NATURE\n }\n\n get anchors(): [] {\n return []\n }\n\n // ── Transfer protocol ─────────────────────────────────────────────────────\n\n /** True when fewer carriers are currently held than capacity. */\n canReceive(_component?: any): boolean {\n const occupied = (this.components as Component[] | undefined)?.length ?? 0\n return occupied < this.capacity\n }\n\n /**\n * Accept a carrier into this cell.\n * Sets TRANSFER_SLOT_KEY = cellId on the carrier, then reparents.\n * Fires 'transfer-received' so monitors can react.\n */\n async receive(carrier: any, options: any = {}): Promise<void> {\n if (!this.canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'no-slot'\n })\n return\n }\n carrier[TRANSFER_SLOT_KEY] = this.cellId\n this.reparent(carrier, options)\n this.trigger('transfer-received', {\n type: 'transfer-received',\n component: carrier,\n container: this,\n slotId: this.cellId\n })\n }\n\n /**\n * Release a carrier from this cell to `target`.\n * Delegates to `target.receive()` if available, otherwise `target.reparent()`.\n */\n async dispatch(carrier: any, target: any, options: any = {}): Promise<void> {\n if (target?.canReceive && !target.canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'target-full'\n })\n return\n }\n delete carrier[TRANSFER_SLOT_KEY]\n if (typeof target?.receive === 'function') {\n await target.receive(carrier, options)\n } else {\n ;(target as any).reparent?.(carrier, options)\n }\n this.trigger('transfer-dispatched', {\n type: 'transfer-dispatched',\n component: carrier,\n container: this,\n target\n })\n }\n\n // ── Domain aliases ────────────────────────────────────────────────────────\n\n /** Alias for receive() — semantic sugar for the storage domain. */\n store(carrier: any, options?: any): Promise<void> {\n return this.receive(carrier, options)\n }\n\n /** Alias for dispatch() — semantic sugar for the storage domain. */\n retrieve(carrier: any, target: any, options?: any): Promise<void> {\n return this.dispatch(carrier, target, options)\n }\n\n // ── 3D attach frame ───────────────────────────────────────────────────────\n\n /**\n * Return the 3D attach frame for carriers placed in this cell.\n * Carriers are lifted by their own halfDepth so the bottom face\n * rests at the cell's Y-center (which is levelHeight/2 above the beam).\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const root = this._realObject?.object3d\n if (!root) return null\n const carrierDepth = resolveCarrierDepth(carrier)\n return {\n attach: root,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n\n // ── 2D rendering ──────────────────────────────────────────────────────────\n\n /** RackCell has no 2D visual — the rack draws its own structure. */\n render(_ctx: CanvasRenderingContext2D) {\n // intentional no-op\n }\n\n // ── 3D ───────────────────────────────────────────────────────────────────\n\n buildRealObject(): RealObject | undefined {\n return new RackCell3D(this)\n }\n}\n\nfunction resolveCarrierDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"spot-3d.js","sourceRoot":"","sources":["../src/spot-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Spot 3D — translucent floor pad.\n *\n * Renders only the FLOOR face of the conceptual zone box; side walls are\n * absent. Children (carriers) sit on the top face via an explicit attach\n * frame; the pad doesn't occlude them (`depthWrite: false`).\n *\n * Standard things-scene properties read directly:\n * - state.fillStyle → pad color (sole color source)\n * - state.strokeStyle → outline color (defaults to fillStyle)\n * - state.alpha → pad transparency, multiplied with the base 0.35 tint\n * - state.text → label, rendered as a CanvasTexture quad on the pad\n * - state.fontColor → label fill (defaults to fillStyle)\n * - state.fontSize / fontFamily / bold / italic → label typography\n * (composed via the things-scene fontStyle helper)\n * - state.material3d → metalness / roughness / castShadow / receiveShadow\n * (resolved + applied via the things-scene helpers,\n * no hard-coded numbers)\n */\n\nimport * as THREE from 'three'\nimport {\n RealObjectGroup,\n resolveMaterial3d,\n applyMaterial3dProps,\n fontStyle,\n opaqueColor,\n type Material3D\n} from '@hatiolab/things-scene'\n\nconst DEFAULT_PAD_COLOR = '#3a8fbd'\nconst BASE_PAD_OPACITY = 0.35 // multiplied by state.alpha\n\nexport class Spot3D extends RealObjectGroup {\n build() {\n super.build()\n\n const state = this.component.state as any\n const w = Math.max(Math.abs(numOr(state.width, 100)), 1)\n const h = Math.max(Math.abs(numOr(state.height, 100)), 1)\n const d = this.effectiveDepth // 2 by default (thin pad)\n // opaqueColor strips alpha from rgba/hsla strings — THREE.Color doesn't\n // honor alpha, would emit a console warning, and ignore the alpha bit.\n // The actual transparency comes through the material.opacity below.\n const padColor = opaqueColor((state.fillStyle as string) || DEFAULT_PAD_COLOR)\n const alpha = clamp(numOr(state.alpha, 1), 0, 1)\n const padOpacity = clamp(BASE_PAD_OPACITY * alpha, 0.05, 1)\n\n // material3d: pulls user-set metalness / roughness / shadow / side.\n // Local defaults (transparent + DoubleSide + depthWrite:false) come\n // from the constructor below; user values override via applyMaterial3dProps.\n const resolved = resolveMaterial3d(state.material3d as Material3D | undefined)\n\n // ── Floor pad (the only visible surface of the conceptual zone box) ──\n const padThickness = Math.max(d * 0.4, 0.5)\n const padMat = new THREE.MeshStandardMaterial({\n color: padColor,\n transparent: true,\n opacity: padOpacity,\n side: THREE.DoubleSide,\n depthWrite: false\n })\n applyMaterial3dProps(padMat, resolved)\n const pad = new THREE.Mesh(new THREE.BoxGeometry(w, padThickness, h), padMat)\n pad.position.set(0, -d / 2 + padThickness / 2, 0)\n pad.castShadow = resolved.castShadow\n pad.receiveShadow = resolved.receiveShadow\n this.object3d.add(pad)\n\n // ── Outline of the zone footprint (line on the floor) ──\n const outlineGeo = new THREE.BufferGeometry().setFromPoints([\n new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, -h / 2),\n new THREE.Vector3(w / 2, -d / 2 + padThickness + 0.05, -h / 2),\n new THREE.Vector3(w / 2, -d / 2 + padThickness + 0.05, h / 2),\n new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, h / 2),\n new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, -h / 2)\n ])\n const outlineMat = new THREE.LineBasicMaterial({\n color: opaqueColor((state.strokeStyle as string) || padColor),\n transparent: true,\n opacity: 0.7 * alpha\n })\n const outline = new THREE.Line(outlineGeo, outlineMat)\n this.object3d.add(outline)\n\n // ── Label (uses standard text + font fields) ──\n const text = state.text\n if (typeof text === 'string' && text.length > 0) {\n const label = this._buildLabel(text, state, padColor, w, h)\n if (label) {\n label.position.set(0, -d / 2 + padThickness + Math.max(w, h) * 0.05, 0)\n this.object3d.add(label)\n }\n }\n }\n\n /**\n * Build the label as a canvas-textured quad. Uses the same `fontStyle`\n * helper things-scene uses for its 2D text rendering, so the label\n * here matches what the property panel previews.\n */\n private _buildLabel(text: string, state: any, defaultColor: string, w: number, h: number): THREE.Mesh | null {\n const fontSize = clamp(numOr(state.fontSize, 36), 8, 200)\n const fontFamily = String(state.fontFamily ?? 'sans-serif')\n const bold = !!state.bold\n const italic = !!state.italic\n const color = (state.fontColor as string) || defaultColor\n\n const canvas = document.createElement('canvas')\n canvas.width = 512\n canvas.height = 128\n const ctx = canvas.getContext('2d')\n if (!ctx) return null\n ctx.clearRect(0, 0, canvas.width, canvas.height)\n ctx.font = fontStyle(bold, italic, fontSize, fontFamily)\n ctx.fillStyle = color\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.fillText(text, canvas.width / 2, canvas.height / 2)\n const tex = new THREE.CanvasTexture(canvas)\n tex.needsUpdate = true\n const labelMat = new THREE.MeshBasicMaterial({\n map: tex,\n transparent: true,\n depthWrite: false,\n side: THREE.DoubleSide\n })\n const labelW = Math.min(w, h) * 0.6\n const labelH = labelW * (canvas.height / canvas.width)\n const mesh = new THREE.Mesh(new THREE.PlaneGeometry(labelW, labelH), labelMat)\n mesh.rotation.x = -Math.PI / 2 // lay flat, readable from above\n return mesh\n }\n\n /**\n * The sub-frame that carrier components should mount onto.\n *\n * Semantically Spot is a virtual cuboid SPACE — it marks \"stuff goes\n * here\" — and carriers are placed INSIDE that space, resting on the\n * cuboid's BOTTOM face. So the attach frame sits at the cuboid's\n * bottom (`y = -d/2` in spot-local), NOT at the top of the rendered\n * pad. The pad is just a translucent visual marker for the zone; the\n * floor of the conceptual volume is what carriers stand on.\n *\n * The Spot.attachPointFor mixin lifts the carrier by its own halfDepth\n * (in the +Y direction within this frame), placing the carrier's\n * BOTTOM face exactly at the cuboid floor.\n */\n getAttachFrame(): THREE.Object3D {\n if (!this._attachFrame) {\n const d = this.effectiveDepth\n this._attachFrame = new THREE.Object3D()\n this._attachFrame.position.set(0, -d / 2, 0)\n this.object3d.add(this._attachFrame)\n }\n return this._attachFrame\n }\n\n private _attachFrame?: THREE.Object3D\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'width' in after ||\n 'height' in after ||\n 'depth' in after ||\n 'fillStyle' in after ||\n 'strokeStyle' in after ||\n 'alpha' in after ||\n 'text' in after ||\n 'fontColor' in after ||\n 'fontSize' in after ||\n 'fontFamily' in after ||\n 'bold' in after ||\n 'italic' in after ||\n 'material3d' in after\n ) {\n this._attachFrame = undefined\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n // alpha is rebuilt into the materials on every build; opt out of the\n // base RealObject's \"multiply existing material opacity\" pass to avoid\n // double-application.\n updateAlpha() {}\n}\n\nfunction clamp(v: number, lo: number, hi: number) {\n return Math.max(lo, Math.min(hi, v))\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
1
+ {"version":3,"file":"spot-3d.js","sourceRoot":"","sources":["../src/spot-3d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAE/B,MAAM,iBAAiB,GAAG,SAAS,CAAA;AACnC,MAAM,gBAAgB,GAAG,IAAI,CAAA,CAAC,4BAA4B;AAE1D,MAAM,OAAO,MAAO,SAAQ,eAAe;IACzC,KAAK;QACH,KAAK,CAAC,KAAK,EAAE,CAAA;QAEb,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAA;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QACxD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,CAAA,CAAC,0BAA0B;QACxD,wEAAwE;QACxE,uEAAuE;QACvE,oEAAoE;QACpE,MAAM,QAAQ,GAAG,WAAW,CAAE,KAAK,CAAC,SAAoB,IAAI,iBAAiB,CAAC,CAAA;QAC9E,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QAChD,MAAM,UAAU,GAAG,KAAK,CAAC,gBAAgB,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAE3D,oEAAoE;QACpE,oEAAoE;QACpE,6EAA6E;QAC7E,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,CAAC,UAAoC,CAAC,CAAA;QAE9E,wEAAwE;QACxE,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,CAAA;QAC3C,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC;YAC5C,KAAK,EAAE,QAAQ;YACf,WAAW,EAAE,IAAI;YACjB,OAAO,EAAE,UAAU;YACnB,IAAI,EAAE,KAAK,CAAC,UAAU;YACtB,UAAU,EAAE,KAAK;SAClB,CAAC,CAAA;QACF,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QACtC,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;QAC7E,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;QACjD,GAAG,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAA;QACpC,GAAG,CAAC,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAA;QAC1C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAEtB,0DAA0D;QAC1D,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC,aAAa,CAAC;YAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YAC/D,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YAC9D,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;YAC7D,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;YAC9D,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;SAChE,CAAC,CAAA;QACF,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,iBAAiB,CAAC;YAC7C,KAAK,EAAE,WAAW,CAAE,KAAK,CAAC,WAAsB,IAAI,QAAQ,CAAC;YAC7D,WAAW,EAAE,IAAI;YACjB,OAAO,EAAE,GAAG,GAAG,KAAK;SACrB,CAAC,CAAA;QACF,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;QACtD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAE1B,iDAAiD;QACjD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;QACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;YAC3D,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAA;gBACvE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,WAAW,CAAC,IAAY,EAAE,KAAU,EAAE,YAAoB,EAAE,CAAS,EAAE,CAAS;QACtF,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAA;QACzD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,IAAI,YAAY,CAAC,CAAA;QAC3D,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAA;QACzB,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAA;QAC7B,MAAM,KAAK,GAAI,KAAK,CAAC,SAAoB,IAAI,YAAY,CAAA;QAEzD,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;QAC/C,MAAM,CAAC,KAAK,GAAG,GAAG,CAAA;QAClB,MAAM,CAAC,MAAM,GAAG,GAAG,CAAA;QACnB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAA;QACrB,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;QAChD,GAAG,CAAC,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAA;QACxD,GAAG,CAAC,SAAS,GAAG,KAAK,CAAA;QACrB,GAAG,CAAC,SAAS,GAAG,QAAQ,CAAA;QACxB,GAAG,CAAC,YAAY,GAAG,QAAQ,CAAA;QAC3B,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACvD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;QAC3C,GAAG,CAAC,WAAW,GAAG,IAAI,CAAA;QACtB,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,iBAAiB,CAAC;YAC3C,GAAG,EAAE,GAAG;YACR,WAAW,EAAE,IAAI;YACjB,UAAU,EAAE,KAAK;YACjB,IAAI,EAAE,KAAK,CAAC,UAAU;SACvB,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAA;QACnC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;QACtD,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAA;QAC9E,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAA,CAAC,gCAAgC;QAC/D,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,cAAc;QACZ,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,CAAA;YAC7B,IAAI,CAAC,YAAY,GAAG,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAA;YACxC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;YAC5C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QACtC,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAA;IAC1B,CAAC;IAEO,YAAY,CAAiB;IAErC,eAAe,KAAI,CAAC;IAEpB,QAAQ,CAAC,KAA8B,EAAE,MAA+B;QACtE,IACE,OAAO,IAAI,KAAK;YAChB,QAAQ,IAAI,KAAK;YACjB,OAAO,IAAI,KAAK;YAChB,WAAW,IAAI,KAAK;YACpB,aAAa,IAAI,KAAK;YACtB,OAAO,IAAI,KAAK;YAChB,MAAM,IAAI,KAAK;YACf,WAAW,IAAI,KAAK;YACpB,UAAU,IAAI,KAAK;YACnB,YAAY,IAAI,KAAK;YACrB,MAAM,IAAI,KAAK;YACf,QAAQ,IAAI,KAAK;YACjB,YAAY,IAAI,KAAK,EACrB,CAAC;YACD,IAAI,CAAC,YAAY,GAAG,SAAS,CAAA;YAC7B,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,OAAM;QACR,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC/B,CAAC;IAED,qEAAqE;IACrE,uEAAuE;IACvE,sBAAsB;IACtB,WAAW,KAAI,CAAC;CACjB;AAED,SAAS,KAAK,CAAC,CAAS,EAAE,EAAU,EAAE,EAAU;IAC9C,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;AACtC,CAAC;AAED,SAAS,KAAK,CAAC,CAAU,EAAE,IAAY;IACrC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAC/D,CAAC","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Spot 3D — translucent floor pad.\n *\n * Renders only the FLOOR face of the conceptual zone box; side walls are\n * absent. Children (carriers) sit on the top face via an explicit attach\n * frame; the pad doesn't occlude them (`depthWrite: false`).\n *\n * Standard things-scene properties read directly:\n * - state.fillStyle → pad color (sole color source)\n * - state.strokeStyle → outline color (defaults to fillStyle)\n * - state.alpha → pad transparency, multiplied with the base 0.35 tint\n * - state.text → label, rendered as a CanvasTexture quad on the pad\n * - state.fontColor → label fill (defaults to fillStyle)\n * - state.fontSize / fontFamily / bold / italic → label typography\n * (composed via the things-scene fontStyle helper)\n * - state.material3d → metalness / roughness / castShadow / receiveShadow\n * (resolved + applied via the things-scene helpers,\n * no hard-coded numbers)\n */\n\nimport * as THREE from 'three'\nimport {\n RealObjectGroup,\n resolveMaterial3d,\n applyMaterial3dProps,\n fontStyle,\n opaqueColor,\n type Material3D\n} from '@hatiolab/things-scene'\n\nconst DEFAULT_PAD_COLOR = '#3a8fbd'\nconst BASE_PAD_OPACITY = 0.35 // multiplied by state.alpha\n\nexport class Spot3D extends RealObjectGroup {\n build() {\n super.build()\n\n const state = this.component.state\n const w = Math.max(Math.abs(numOr(state.width, 100)), 1)\n const h = Math.max(Math.abs(numOr(state.height, 100)), 1)\n const d = this.effectiveDepth // 2 by default (thin pad)\n // opaqueColor strips alpha from rgba/hsla strings — THREE.Color doesn't\n // honor alpha, would emit a console warning, and ignore the alpha bit.\n // The actual transparency comes through the material.opacity below.\n const padColor = opaqueColor((state.fillStyle as string) || DEFAULT_PAD_COLOR)\n const alpha = clamp(numOr(state.alpha, 1), 0, 1)\n const padOpacity = clamp(BASE_PAD_OPACITY * alpha, 0.05, 1)\n\n // material3d: pulls user-set metalness / roughness / shadow / side.\n // Local defaults (transparent + DoubleSide + depthWrite:false) come\n // from the constructor below; user values override via applyMaterial3dProps.\n const resolved = resolveMaterial3d(state.material3d as Material3D | undefined)\n\n // ── Floor pad (the only visible surface of the conceptual zone box) ──\n const padThickness = Math.max(d * 0.4, 0.5)\n const padMat = new THREE.MeshStandardMaterial({\n color: padColor,\n transparent: true,\n opacity: padOpacity,\n side: THREE.DoubleSide,\n depthWrite: false\n })\n applyMaterial3dProps(padMat, resolved)\n const pad = new THREE.Mesh(new THREE.BoxGeometry(w, padThickness, h), padMat)\n pad.position.set(0, -d / 2 + padThickness / 2, 0)\n pad.castShadow = resolved.castShadow\n pad.receiveShadow = resolved.receiveShadow\n this.object3d.add(pad)\n\n // ── Outline of the zone footprint (line on the floor) ──\n const outlineGeo = new THREE.BufferGeometry().setFromPoints([\n new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, -h / 2),\n new THREE.Vector3(w / 2, -d / 2 + padThickness + 0.05, -h / 2),\n new THREE.Vector3(w / 2, -d / 2 + padThickness + 0.05, h / 2),\n new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, h / 2),\n new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, -h / 2)\n ])\n const outlineMat = new THREE.LineBasicMaterial({\n color: opaqueColor((state.strokeStyle as string) || padColor),\n transparent: true,\n opacity: 0.7 * alpha\n })\n const outline = new THREE.Line(outlineGeo, outlineMat)\n this.object3d.add(outline)\n\n // ── Label (uses standard text + font fields) ──\n const text = state.text\n if (typeof text === 'string' && text.length > 0) {\n const label = this._buildLabel(text, state, padColor, w, h)\n if (label) {\n label.position.set(0, -d / 2 + padThickness + Math.max(w, h) * 0.05, 0)\n this.object3d.add(label)\n }\n }\n }\n\n /**\n * Build the label as a canvas-textured quad. Uses the same `fontStyle`\n * helper things-scene uses for its 2D text rendering, so the label\n * here matches what the property panel previews.\n */\n private _buildLabel(text: string, state: any, defaultColor: string, w: number, h: number): THREE.Mesh | null {\n const fontSize = clamp(numOr(state.fontSize, 36), 8, 200)\n const fontFamily = String(state.fontFamily ?? 'sans-serif')\n const bold = !!state.bold\n const italic = !!state.italic\n const color = (state.fontColor as string) || defaultColor\n\n const canvas = document.createElement('canvas')\n canvas.width = 512\n canvas.height = 128\n const ctx = canvas.getContext('2d')\n if (!ctx) return null\n ctx.clearRect(0, 0, canvas.width, canvas.height)\n ctx.font = fontStyle(bold, italic, fontSize, fontFamily)\n ctx.fillStyle = color\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.fillText(text, canvas.width / 2, canvas.height / 2)\n const tex = new THREE.CanvasTexture(canvas)\n tex.needsUpdate = true\n const labelMat = new THREE.MeshBasicMaterial({\n map: tex,\n transparent: true,\n depthWrite: false,\n side: THREE.DoubleSide\n })\n const labelW = Math.min(w, h) * 0.6\n const labelH = labelW * (canvas.height / canvas.width)\n const mesh = new THREE.Mesh(new THREE.PlaneGeometry(labelW, labelH), labelMat)\n mesh.rotation.x = -Math.PI / 2 // lay flat, readable from above\n return mesh\n }\n\n /**\n * The sub-frame that carrier components should mount onto.\n *\n * Semantically Spot is a virtual cuboid SPACE — it marks \"stuff goes\n * here\" — and carriers are placed INSIDE that space, resting on the\n * cuboid's BOTTOM face. So the attach frame sits at the cuboid's\n * bottom (`y = -d/2` in spot-local), NOT at the top of the rendered\n * pad. The pad is just a translucent visual marker for the zone; the\n * floor of the conceptual volume is what carriers stand on.\n *\n * The Spot.attachPointFor mixin lifts the carrier by its own halfDepth\n * (in the +Y direction within this frame), placing the carrier's\n * BOTTOM face exactly at the cuboid floor.\n */\n getAttachFrame(): THREE.Object3D {\n if (!this._attachFrame) {\n const d = this.effectiveDepth\n this._attachFrame = new THREE.Object3D()\n this._attachFrame.position.set(0, -d / 2, 0)\n this.object3d.add(this._attachFrame)\n }\n return this._attachFrame\n }\n\n private _attachFrame?: THREE.Object3D\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'width' in after ||\n 'height' in after ||\n 'depth' in after ||\n 'fillStyle' in after ||\n 'strokeStyle' in after ||\n 'alpha' in after ||\n 'text' in after ||\n 'fontColor' in after ||\n 'fontSize' in after ||\n 'fontFamily' in after ||\n 'bold' in after ||\n 'italic' in after ||\n 'material3d' in after\n ) {\n this._attachFrame = undefined\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n // alpha is rebuilt into the materials on every build; opt out of the\n // base RealObject's \"multiply existing material opacity\" pass to avoid\n // double-application.\n updateAlpha() {}\n}\n\nfunction clamp(v: number, lo: number, hi: number) {\n return Math.max(lo, Math.min(hi, v))\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
package/dist/spot.d.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import type { State, Material3D } from '@hatiolab/things-scene';
2
3
  import { type AttachFrame, type Alignment, type Heights, type PlacementArchetype } from '@operato/scene-base';
4
+ import { Spot3D } from './spot-3d.js';
5
+ /** Spot 컴포넌트 state */
6
+ export interface SpotState extends State {
7
+ material3d?: Material3D;
8
+ }
3
9
  declare const Spot_base: any;
4
10
  export default class Spot extends Spot_base {
11
+ state: SpotState;
12
+ _realObject?: Spot3D;
5
13
  static placement: PlacementArchetype;
6
14
  static align: Alignment;
7
15
  static defaultDepth: (_h: Heights) => number;
package/dist/spot.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"spot.js","sourceRoot":"","sources":["../src/spot.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Spot — virtual pickup / drop zone.\n *\n * A modeling-time anchor for \"this is where things land\" — the destination\n * of a robot arm pick-and-place, the slot of an AGV stop, the staging\n * zone next to a conveyor. Spot itself does not move and does\n * not perform any logistics action; it only marks a location and accepts\n * carrier components as children.\n *\n * Visual identity:\n * - 2D: outlined rectangle with corner \"L\" marks (so it reads as a\n * virtual zone, not a solid object).\n * - 3D: a thin translucent floor pad — only the floor of the conceptual\n * box is rendered, the side walls are absent.\n *\n * Standard things-scene properties used (no component-specific extras —\n * keep the property-panel UX uniform with other components):\n * - `fillStyle` — pad / outline color (sole color source)\n * - `strokeStyle` — outline color override (defaults to fillStyle)\n * - `lineWidth` / `lineDash` — outline stroke style\n * - `alpha` — overall transparency, framework-applied\n * - `text` / `fontColor` / `fontSize` / `fontFamily` / `bold` / `italic`\n * — label rendered by the standard text pipeline\n * - `material3d` (3D) — metalness / roughness / castShadow / receiveShadow\n *\n * Role: `CarrierHolder` — accepts any Carrier as a child and lays it on\n * the top face of the pad (overrides default attachPointFor).\n */\n\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n Placeable,\n type AttachFrame,\n type Alignment,\n type Heights,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Spot3D } from './spot-3d.js'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n // No component-specific properties — fillStyle / strokeStyle / lineWidth /\n // alpha / text / font* are framework-standard, surfaced by the property\n // panel automatically.\n properties: [],\n help: 'scene/component/spot'\n}\n\n// ContainerAbstract base — Spot accepts carrier children (parcel/box/pallet/...).\n// CarrierHolder mixin only publishes the attach-point hook; the actual\n// child-list management comes from things-scene's container abstract.\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Spot is purely 3D.\n@sceneComponent('spot')\nexport default class Spot extends CarrierHolder(Placeable(ContainerAbstract)) {\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (_h: Heights) => 2 // a thin pad\n\n get nature(): ComponentNature {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — outlined rectangle + corner L marks. The pad body is drawn with a\n * fixed low alpha (0.15) on top of the user's `fillStyle` so the zone reads\n * as virtual even when fillStyle is fully opaque. Outline + corner marks\n * use `strokeStyle` (or fall back to `fillStyle`) at the user's `lineWidth`\n * and `lineDash`. The text label is drawn by the framework's standard\n * postrender pipeline using `text` / `fontColor` / `fontSize` / etc.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { left = 0, top = 0, width = 100, height = 100 } = this.state\n const fillStyle = (this.state.fillStyle as string) || '#3a8fbd'\n const strokeStyle = (this.state.strokeStyle as string) || fillStyle\n const lineWidth = numOr(this.state.lineWidth, 1)\n const lineDashStyle = String(this.state.lineDash ?? 'dash')\n\n // ── Pad body (fixed-low-alpha tint of fillStyle) ───────────────────\n ctx.save()\n ctx.fillStyle = fillStyle\n ctx.globalAlpha = 0.15\n ctx.fillRect(left, top, width, height)\n ctx.restore()\n\n // ── Outline ────────────────────────────────────────────────────────\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = lineWidth\n applyLineDash(ctx, lineDashStyle, lineWidth)\n ctx.strokeRect(\n left + lineWidth / 2,\n top + lineWidth / 2,\n width - lineWidth,\n height - lineWidth\n )\n ctx.setLineDash([])\n ctx.restore()\n\n // ── Corner L marks (solid, slightly heavier than outline) ──────────\n const ml = Math.min(width, height) * 0.18\n const cornerW = Math.max(lineWidth * 1.5, 1.5)\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = cornerW\n for (const [cx, cy, sx, sy] of [\n [left, top, 1, 1],\n [left + width, top, -1, 1],\n [left + width, top + height, -1, -1],\n [left, top + height, 1, -1]\n ] as [number, number, number, number][]) {\n ctx.beginPath()\n ctx.moveTo(cx + sx * ml, cy)\n ctx.lineTo(cx, cy)\n ctx.lineTo(cx, cy + sy * ml)\n ctx.stroke()\n }\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Spot3D(this as any)\n }\n\n /**\n * Mount carriers on the TOP of the pad (Spot3D's `getAttachFrame` is\n * already at pad-top in spot-local). Then lift the carrier by its\n * own halfDepth so the carrier's BOTTOM rests ON the pad surface, not\n * its volumetric center — without this lift, half the carrier would\n * sink below the pad / floor.\n *\n * Reads `_realObject.effectiveDepth` first (the framework-resolved\n * value, accounting for `static defaultDepth` and parent context),\n * falling back to raw `state.depth` for components built before\n * RealObject creation.\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const ro = (this as any)._realObject as Spot3D | undefined\n const frame = ro?.getAttachFrame?.()\n if (!frame) return null\n const carrierDepth = resolveDepth(carrier)\n return {\n attach: frame,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n}\n\nfunction resolveDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n\n/**\n * Map a things-scene `lineDash` string to a Canvas dash pattern. Mirrors\n * the keys understood by things-scene's `drawer/stroke.ts` so users see\n * consistent options across components. Unknown strings fall through to\n * a plain dashed pattern instead of throwing on setLineDash.\n */\nfunction applyLineDash(ctx: CanvasRenderingContext2D, style: string, lw: number) {\n switch (style) {\n case 'solid':\n ctx.setLineDash([])\n return\n case 'round-dot':\n ctx.setLineDash([0.1, lw * 2])\n ctx.lineCap = 'round'\n return\n case 'square-dot':\n ctx.setLineDash([lw, lw])\n return\n case 'long-dash':\n ctx.setLineDash([lw * 6, lw * 3])\n return\n case 'dash-dot':\n ctx.setLineDash([lw * 4, lw * 2, lw, lw * 2])\n return\n case 'dash':\n default:\n ctx.setLineDash([lw * 4, lw * 1.5])\n return\n }\n}\n"]}
1
+ {"version":3,"file":"spot.js","sourceRoot":"","sources":["../src/spot.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Spot — virtual pickup / drop zone.\n *\n * A modeling-time anchor for \"this is where things land\" — the destination\n * of a robot arm pick-and-place, the slot of an AGV stop, the staging\n * zone next to a conveyor. Spot itself does not move and does\n * not perform any logistics action; it only marks a location and accepts\n * carrier components as children.\n *\n * Visual identity:\n * - 2D: outlined rectangle with corner \"L\" marks (so it reads as a\n * virtual zone, not a solid object).\n * - 3D: a thin translucent floor pad — only the floor of the conceptual\n * box is rendered, the side walls are absent.\n *\n * Standard things-scene properties used (no component-specific extras —\n * keep the property-panel UX uniform with other components):\n * - `fillStyle` — pad / outline color (sole color source)\n * - `strokeStyle` — outline color override (defaults to fillStyle)\n * - `lineWidth` / `lineDash` — outline stroke style\n * - `alpha` — overall transparency, framework-applied\n * - `text` / `fontColor` / `fontSize` / `fontFamily` / `bold` / `italic`\n * — label rendered by the standard text pipeline\n * - `material3d` (3D) — metalness / roughness / castShadow / receiveShadow\n *\n * Role: `CarrierHolder` — accepts any Carrier as a child and lays it on\n * the top face of the pad (overrides default attachPointFor).\n */\n\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n Placeable,\n type AttachFrame,\n type Alignment,\n type Heights,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Spot3D } from './spot-3d.js'\n\n/** Spot 컴포넌트 state */\nexport interface SpotState extends State {\n // Spot has no component-specific state — it uses standard fillStyle /\n // strokeStyle / lineWidth / alpha / text / font* / material3d.\n material3d?: Material3D\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n // No component-specific properties — fillStyle / strokeStyle / lineWidth /\n // alpha / text / font* are framework-standard, surfaced by the property\n // panel automatically.\n properties: [],\n help: 'scene/component/spot'\n}\n\n// ContainerAbstract base — Spot accepts carrier children (parcel/box/pallet/...).\n// CarrierHolder mixin only publishes the attach-point hook; the actual\n// child-list management comes from things-scene's container abstract.\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Spot is purely 3D.\n@sceneComponent('spot')\nexport default class Spot extends CarrierHolder(Placeable(ContainerAbstract)) {\n declare state: SpotState\n declare _realObject?: Spot3D\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (_h: Heights) => 2 // a thin pad\n\n get nature(): ComponentNature {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — outlined rectangle + corner L marks. The pad body is drawn with a\n * fixed low alpha (0.15) on top of the user's `fillStyle` so the zone reads\n * as virtual even when fillStyle is fully opaque. Outline + corner marks\n * use `strokeStyle` (or fall back to `fillStyle`) at the user's `lineWidth`\n * and `lineDash`. The text label is drawn by the framework's standard\n * postrender pipeline using `text` / `fontColor` / `fontSize` / etc.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { left = 0, top = 0, width = 100, height = 100 } = this.state\n const fillStyle = (this.state.fillStyle as string) || '#3a8fbd'\n const strokeStyle = (this.state.strokeStyle as string) || fillStyle\n const lineWidth = numOr(this.state.lineWidth, 1)\n const lineDashStyle = String(this.state.lineDash ?? 'dash')\n\n // ── Pad body (fixed-low-alpha tint of fillStyle) ───────────────────\n ctx.save()\n ctx.fillStyle = fillStyle\n ctx.globalAlpha = 0.15\n ctx.fillRect(left, top, width, height)\n ctx.restore()\n\n // ── Outline ────────────────────────────────────────────────────────\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = lineWidth\n applyLineDash(ctx, lineDashStyle, lineWidth)\n ctx.strokeRect(\n left + lineWidth / 2,\n top + lineWidth / 2,\n width - lineWidth,\n height - lineWidth\n )\n ctx.setLineDash([])\n ctx.restore()\n\n // ── Corner L marks (solid, slightly heavier than outline) ──────────\n const ml = Math.min(width, height) * 0.18\n const cornerW = Math.max(lineWidth * 1.5, 1.5)\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = cornerW\n for (const [cx, cy, sx, sy] of [\n [left, top, 1, 1],\n [left + width, top, -1, 1],\n [left + width, top + height, -1, -1],\n [left, top + height, 1, -1]\n ] as [number, number, number, number][]) {\n ctx.beginPath()\n ctx.moveTo(cx + sx * ml, cy)\n ctx.lineTo(cx, cy)\n ctx.lineTo(cx, cy + sy * ml)\n ctx.stroke()\n }\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Spot3D(this)\n }\n\n /**\n * Mount carriers on the TOP of the pad (Spot3D's `getAttachFrame` is\n * already at pad-top in spot-local). Then lift the carrier by its\n * own halfDepth so the carrier's BOTTOM rests ON the pad surface, not\n * its volumetric center — without this lift, half the carrier would\n * sink below the pad / floor.\n *\n * Reads `_realObject.effectiveDepth` first (the framework-resolved\n * value, accounting for `static defaultDepth` and parent context),\n * falling back to raw `state.depth` for components built before\n * RealObject creation.\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const ro = this._realObject\n const frame = ro?.getAttachFrame?.()\n if (!frame) return null\n const carrierDepth = resolveDepth(carrier)\n return {\n attach: frame,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n}\n\nfunction resolveDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n\n/**\n * Map a things-scene `lineDash` string to a Canvas dash pattern. Mirrors\n * the keys understood by things-scene's `drawer/stroke.ts` so users see\n * consistent options across components. Unknown strings fall through to\n * a plain dashed pattern instead of throwing on setLineDash.\n */\nfunction applyLineDash(ctx: CanvasRenderingContext2D, style: string, lw: number) {\n switch (style) {\n case 'solid':\n ctx.setLineDash([])\n return\n case 'round-dot':\n ctx.setLineDash([0.1, lw * 2])\n ctx.lineCap = 'round'\n return\n case 'square-dot':\n ctx.setLineDash([lw, lw])\n return\n case 'long-dash':\n ctx.setLineDash([lw * 6, lw * 3])\n return\n case 'dash-dot':\n ctx.setLineDash([lw * 4, lw * 2, lw, lw * 2])\n return\n case 'dash':\n default:\n ctx.setLineDash([lw * 4, lw * 1.5])\n return\n }\n}\n"]}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@operato/scene-storage",
3
3
  "description": "Storage-domain components for things-scene (smart factory / logistics) — pallet, box, parcel; AS/RS and shelves planned.",
4
4
  "author": "heartyoh",
5
- "version": "10.0.0-beta.30",
5
+ "version": "10.0.0-beta.31",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "module": "dist/index.js",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@hatiolab/things-scene": "^10.0.0-beta.1",
29
- "@operato/scene-base": "^10.0.0-beta.30",
29
+ "@operato/scene-base": "^10.0.0-beta.31",
30
30
  "three": "^0.183.0"
31
31
  },
32
32
  "devDependencies": {
@@ -45,5 +45,5 @@
45
45
  "typescript": "^5.0.4"
46
46
  },
47
47
  "prettier": "@hatiolab/prettier-config",
48
- "gitHead": "06b35b1726ec4f27ee76657ce341c6c6f3ba1b3a"
48
+ "gitHead": "fdafbd04fd083a43690be937230c7d96a3ee5da3"
49
49
  }
package/src/asrs-crane.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Copyright © HatioLab Inc. All rights reserved.
3
3
  */
4
4
  import { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, sceneComponent } from '@hatiolab/things-scene'
5
- import type { SlotDef } from '@hatiolab/things-scene'
5
+ import type { SlotDef, State, Material3D } from '@hatiolab/things-scene'
6
6
  import {
7
7
  CarrierHolder,
8
8
  Legendable,
@@ -30,6 +30,18 @@ import { AsrsCrane3D } from './asrs-crane-3d.js'
30
30
  */
31
31
  export type AsrsCraneStatus = 'idle' | 'moving' | 'loading' | 'unloading' | 'error'
32
32
 
33
+ /** AsrsCrane 컴포넌트 state */
34
+ export interface AsrsCraneState extends State {
35
+ // ── 운영 상태 ──
36
+ status?: AsrsCraneStatus
37
+
38
+ // ── 액추에이터 ──
39
+ carriageHeight?: number
40
+
41
+ // ── 3D 재질 ──
42
+ material3d?: Material3D
43
+ }
44
+
33
45
  const BODY_LEGEND = {
34
46
  idle: '#888',
35
47
  moving: '#aabbcc',
@@ -108,6 +120,9 @@ const NATURE: ComponentNature = {
108
120
  */
109
121
  @sceneComponent('asrs-crane')
110
122
  export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract))))) {
123
+ declare state: AsrsCraneState
124
+ declare _realObject?: AsrsCrane3D
125
+
111
126
  static legends: Record<string, LegendBinding> = {
112
127
  bodyColor: { from: 'status', legend: BODY_LEGEND },
113
128
  lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
@@ -147,13 +162,13 @@ export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Leg
147
162
  * crane's own object3d centre.
148
163
  */
149
164
  attachPointFor(carrier: Component): AttachFrame | null {
150
- const ro = (this as any)._realObject as AsrsCrane3D | undefined
165
+ const ro = this._realObject
151
166
  const frame = ro?.getCarriageFrame?.()
152
167
  if (frame) {
153
168
  const carrierDepth = resolveCarrierDepth(carrier)
154
169
  return { attach: frame, localPosition: { x: 0, y: carrierDepth / 2, z: 0 } }
155
170
  }
156
- const root = (this as any)._realObject?.object3d
171
+ const root = this._realObject?.object3d
157
172
  if (!root) return null
158
173
  return { attach: root }
159
174
  }
@@ -199,12 +214,12 @@ export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Leg
199
214
 
200
215
  /** Fetch a carrier from a rack cell (semantically = pick). */
201
216
  fetch(carrier: Component, options?: MoveOptions): Promise<void> {
202
- return (this as any).pick(carrier, options)
217
+ return this.pick(carrier, options)
203
218
  }
204
219
 
205
220
  /** Deposit a carrier into a rack cell (semantically = place). */
206
221
  deposit(carrier: Component, cell: Component, options?: MoveOptions): Promise<void> {
207
- return (this as any).place(carrier, cell, options)
222
+ return this.place(carrier, cell, options)
208
223
  }
209
224
 
210
225
  // ── 2D rendering ─────────────────────────────────────────────────────────
@@ -227,7 +242,7 @@ export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Leg
227
242
  // ── 3D ───────────────────────────────────────────────────────────────────
228
243
 
229
244
  buildRealObject(): RealObject | undefined {
230
- return new AsrsCrane3D(this as any)
245
+ return new AsrsCrane3D(this)
231
246
  }
232
247
  }
233
248
 
package/src/asrs-rack.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Copyright © HatioLab Inc. All rights reserved.
3
3
  */
4
4
  import { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'
5
+ import type { State, Material3D } from '@hatiolab/things-scene'
5
6
  import {
6
7
  CellContainer,
7
8
  CellMap,
@@ -15,6 +16,19 @@ import {
15
16
 
16
17
  import { AsrsRack3D } from './asrs-rack-3d.js'
17
18
 
19
+ /** AsrsRack 컴포넌트 state */
20
+ export interface AsrsRackState extends State {
21
+ // ── 토폴로지 ──
22
+ bays?: number
23
+ levels?: number
24
+
25
+ // ── 디버그 ──
26
+ debugCells?: boolean
27
+
28
+ // ── 3D 재질 ──
29
+ material3d?: Material3D
30
+ }
31
+
18
32
  const NATURE: ComponentNature = {
19
33
  mutable: false,
20
34
  resizable: true,
@@ -64,6 +78,8 @@ const NATURE: ComponentNature = {
64
78
  */
65
79
  @sceneComponent('asrs-rack')
66
80
  export default class AsrsRack extends CellContainer(CarrierHolder(Placeable(ContainerAbstract))) {
81
+ declare state: AsrsRackState
82
+
67
83
  static placement: PlacementArchetype = 'floor'
68
84
  static align: Alignment = 'bottom'
69
85
  static defaultDepth = (h: Heights) => h.ceiling - h.floor
@@ -115,10 +131,10 @@ export default class AsrsRack extends CellContainer(CarrierHolder(Placeable(Cont
115
131
  */
116
132
  _buildCells(): void {
117
133
  // Remove existing rack-cell children
118
- const existing = ((this as any).components as Component[] | undefined) ?? []
134
+ const existing = (this.components as Component[] | undefined) ?? []
119
135
  for (const child of [...existing]) {
120
136
  if ((child as any).state?.type === 'rack-cell') {
121
- ;(this as any).removeComponent?.(child)
137
+ this.removeComponent(child)
122
138
  }
123
139
  }
124
140
 
@@ -129,7 +145,7 @@ export default class AsrsRack extends CellContainer(CarrierHolder(Placeable(Cont
129
145
  return
130
146
  }
131
147
 
132
- const context = (this as any)._app
148
+ const context = this._app
133
149
  for (const cell of this.cellMap.cells) {
134
150
  const model = {
135
151
  type: 'rack-cell',
@@ -139,7 +155,7 @@ export default class AsrsRack extends CellContainer(CarrierHolder(Placeable(Cont
139
155
  depth: cell.size.height // 3D Y = level height
140
156
  }
141
157
  const rackCell = new RackCellClass(model, context)
142
- ;(this as any).addComponent?.(rackCell)
158
+ this.addComponent(rackCell)
143
159
  }
144
160
  }
145
161
 
@@ -157,7 +173,7 @@ export default class AsrsRack extends CellContainer(CarrierHolder(Placeable(Cont
157
173
  if ((component as any).state?.type === 'rack-cell') return true
158
174
  const archetype = (component.constructor as any).placement
159
175
  if (archetype === 'operation') return true
160
- return component.isDescendible(this as any)
176
+ return component.isDescendible(this)
161
177
  }
162
178
 
163
179
  // ── CarrierHolder — attach frame for direct carrier children ─────────────
@@ -173,7 +189,7 @@ export default class AsrsRack extends CellContainer(CarrierHolder(Placeable(Cont
173
189
  * returns the rack's own object3d as the attach frame (default behavior).
174
190
  */
175
191
  attachPointFor(_carrier: Component): AttachFrame | null {
176
- const root = (this as any)._realObject?.object3d
192
+ const root = this._realObject?.object3d
177
193
  if (!root) return null
178
194
  return { attach: root }
179
195
  }
@@ -206,6 +222,6 @@ export default class AsrsRack extends CellContainer(CarrierHolder(Placeable(Cont
206
222
  // ── 3D ───────────────────────────────────────────────────────────────────
207
223
 
208
224
  buildRealObject(): RealObject | undefined {
209
- return new AsrsRack3D(this as any)
225
+ return new AsrsRack3D(this)
210
226
  }
211
227
  }
package/src/box.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Copyright © HatioLab Inc. All rights reserved.
3
3
  */
4
4
  import { ComponentNature, RealObject, RectPath, Shape, sceneComponent } from '@hatiolab/things-scene'
5
+ import type { State, Material3D } from '@hatiolab/things-scene'
5
6
  import {
6
7
  Carriable,
7
8
  Legendable,
@@ -26,6 +27,15 @@ import { Box3D } from './box-3d.js'
26
27
  */
27
28
  export type BoxMaterial = 'wood' | 'plastic'
28
29
 
30
+ /** Box 컴포넌트 state */
31
+ export interface BoxState extends State {
32
+ // ── 외관 ──
33
+ material?: BoxMaterial
34
+
35
+ // ── 3D 재질 ──
36
+ material3d?: Material3D
37
+ }
38
+
29
39
  const BODY_LEGEND = {
30
40
  wood: '#a87644',
31
41
  plastic: '#3a5078',
@@ -64,6 +74,8 @@ const NATURE: ComponentNature = {
64
74
  */
65
75
  @sceneComponent('box')
66
76
  export default class Box extends Carriable(Legendable(Placeable(RectPath(Shape)))) {
77
+ declare state: BoxState
78
+
67
79
  static legends: Record<string, LegendBinding> = {
68
80
  bodyColor: { from: 'material', legend: BODY_LEGEND }
69
81
  }
@@ -92,6 +104,6 @@ export default class Box extends Carriable(Legendable(Placeable(RectPath(Shape))
92
104
  }
93
105
 
94
106
  buildRealObject(): RealObject | undefined {
95
- return new Box3D(this as any)
107
+ return new Box3D(this)
96
108
  }
97
109
  }
@@ -31,7 +31,7 @@ export class GenericContainer3D extends RealObjectGLTF {
31
31
  }
32
32
 
33
33
  private _applyActuators() {
34
- const state = this.component.state as any
34
+ const state = this.component.state
35
35
  applyActuators(this, state.actuators, state.actuatorValues)
36
36
  }
37
37
 
@@ -32,6 +32,7 @@ import {
32
32
  gltfNatureProperties,
33
33
  sceneComponent
34
34
  } from '@hatiolab/things-scene'
35
+ import type { State, Material3D } from '@hatiolab/things-scene'
35
36
  import {
36
37
  Legendable,
37
38
  Placeable,
@@ -45,6 +46,20 @@ import { GenericContainer3D } from './generic-container-3d.js'
45
46
  import type { ActuatorDef } from '@operato/scene-base'
46
47
 
47
48
  export type ContainerStatus = 'empty' | 'partial' | 'full' | 'error'
49
+ export type ContainerFill = ContainerStatus
50
+
51
+ /** GenericContainer 컴포넌트 state */
52
+ export interface GenericContainerState extends State {
53
+ // ── 운영 상태 ──
54
+ fill?: ContainerFill
55
+
56
+ // ── GLB 동적 노드 ──
57
+ actuators?: Record<string, ActuatorDef>
58
+ actuatorValues?: Record<string, number>
59
+
60
+ // ── 3D 재질 ──
61
+ material3d?: Material3D
62
+ }
48
63
 
49
64
  const BODY_LEGEND = {
50
65
  empty: '#a8b8c4',
@@ -89,6 +104,8 @@ const NATURE: ComponentNature = {
89
104
  // (GenericFacility 와 동일 패턴)
90
105
  @sceneComponent('container')
91
106
  export default class GenericContainer extends GltfComponent(Legendable(Placeable(ContainerAbstract))) {
107
+ declare state: GenericContainerState
108
+
92
109
  static legends: Record<string, LegendBinding> = {
93
110
  bodyColor: { from: 'fill', legend: BODY_LEGEND },
94
111
  lampEmissive: { from: 'fill', legend: LAMP_EMISSIVE_LEGEND }
@@ -107,18 +124,18 @@ export default class GenericContainer extends GltfComponent(Legendable(Placeable
107
124
  }
108
125
 
109
126
  get actuators(): Record<string, ActuatorDef> {
110
- return ((this.state as any).actuators as Record<string, ActuatorDef> | undefined) ?? {}
127
+ return this.state.actuators ?? {}
111
128
  }
112
129
 
113
130
  get actuatorValues(): Record<string, number> {
114
- return ((this.state as any).actuatorValues as Record<string, number> | undefined) ?? {}
131
+ return this.state.actuatorValues ?? {}
115
132
  }
116
133
 
117
134
  containable(component: Component): boolean {
118
- return component.isDescendible(this as any)
135
+ return component.isDescendible(this)
119
136
  }
120
137
 
121
138
  buildRealObject() {
122
- return new GenericContainer3D(this as any)
139
+ return new GenericContainer3D(this)
123
140
  }
124
141
  }
package/src/pallet.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Copyright © HatioLab Inc. All rights reserved.
3
3
  */
4
4
  import { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'
5
+ import type { State, Material3D } from '@hatiolab/things-scene'
5
6
  import {
6
7
  Carriable,
7
8
  Legendable,
@@ -26,6 +27,15 @@ import { Pallet3D } from './pallet-3d.js'
26
27
  */
27
28
  export type PalletMaterial = 'wood' | 'plastic'
28
29
 
30
+ /** Pallet 컴포넌트 state */
31
+ export interface PalletState extends State {
32
+ // ── 외관 ──
33
+ material?: PalletMaterial
34
+
35
+ // ── 3D 재질 ──
36
+ material3d?: Material3D
37
+ }
38
+
29
39
  const BODY_LEGEND = {
30
40
  wood: '#a87644',
31
41
  plastic: '#5a6a78',
@@ -88,6 +98,8 @@ const NATURE: ComponentNature = {
88
98
  */
89
99
  @sceneComponent('pallet')
90
100
  export default class Pallet extends Carriable(Legendable(Placeable(ContainerAbstract))) {
101
+ declare state: PalletState
102
+
91
103
  static legends: Record<string, LegendBinding> = {
92
104
  bodyColor: { from: 'material', legend: BODY_LEGEND }
93
105
  }
@@ -108,7 +120,7 @@ export default class Pallet extends Carriable(Legendable(Placeable(ContainerAbst
108
120
  containable(component: Component) {
109
121
  const archetype = (component.constructor as any).placement
110
122
  if (archetype === 'operation') return true
111
- return component.isDescendible(this as any)
123
+ return component.isDescendible(this)
112
124
  }
113
125
 
114
126
  /**
@@ -135,7 +147,7 @@ export default class Pallet extends Carriable(Legendable(Placeable(ContainerAbst
135
147
  super.postrender?.(ctx)
136
148
 
137
149
  const { width, height, left, top } = this.state
138
- const isPlastic = ((this.state as any).material as PalletMaterial) === 'plastic'
150
+ const isPlastic = this.state.material === 'plastic'
139
151
 
140
152
  ctx.save()
141
153
 
@@ -187,6 +199,6 @@ export default class Pallet extends Carriable(Legendable(Placeable(ContainerAbst
187
199
  }
188
200
 
189
201
  buildRealObject(): RealObject | undefined {
190
- return new Pallet3D(this as any)
202
+ return new Pallet3D(this)
191
203
  }
192
204
  }
package/src/parcel.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Copyright © HatioLab Inc. All rights reserved.
3
3
  */
4
4
  import { ComponentNature, RealObject, RectPath, Shape, sceneComponent } from '@hatiolab/things-scene'
5
+ import type { State, Material3D } from '@hatiolab/things-scene'
5
6
  import {
6
7
  Carriable,
7
8
  Placeable,
@@ -11,6 +12,15 @@ import {
11
12
 
12
13
  import { Parcel3D } from './parcel-3d.js'
13
14
 
15
+ /** Parcel 컴포넌트 state */
16
+ export interface ParcelState extends State {
17
+ // ── 정체 ──
18
+ trackingId?: string
19
+
20
+ // ── 3D 재질 ──
21
+ material3d?: Material3D
22
+ }
23
+
14
24
  const NATURE: ComponentNature = {
15
25
  mutable: false,
16
26
  resizable: true,
@@ -45,6 +55,8 @@ const NATURE: ComponentNature = {
45
55
  */
46
56
  @sceneComponent('parcel')
47
57
  export default class Parcel extends Carriable(Placeable(RectPath(Shape))) {
58
+ declare state: ParcelState
59
+
48
60
  static placement: PlacementArchetype = 'operation'
49
61
  static align: Alignment = 'bottom'
50
62
  static defaultDepth = 150
@@ -69,6 +81,6 @@ export default class Parcel extends Carriable(Placeable(RectPath(Shape))) {
69
81
  }
70
82
 
71
83
  buildRealObject(): RealObject | undefined {
72
- return new Parcel3D(this as any)
84
+ return new Parcel3D(this)
73
85
  }
74
86
  }
@@ -56,7 +56,7 @@ export class RackCell3D extends RealObjectGroup {
56
56
  const rack = (this.component as any).parent
57
57
  if (!rack?.cellMap) return
58
58
 
59
- const cellId = (this.component as any).state?.cellId as string | undefined
59
+ const cellId = this.component.state.cellId as string | undefined
60
60
  if (!cellId) return
61
61
 
62
62
  const cell = rack.cellMap.findById(cellId)