@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.
- package/CHANGELOG.md +14 -0
- package/dist/asrs-crane.d.ts +10 -1
- package/dist/asrs-crane.js.map +1 -1
- package/dist/asrs-rack.d.ts +9 -0
- package/dist/asrs-rack.js +2 -3
- package/dist/asrs-rack.js.map +1 -1
- package/dist/box.d.ts +7 -0
- package/dist/box.js.map +1 -1
- package/dist/generic-container-3d.js.map +1 -1
- package/dist/generic-container.d.ts +10 -0
- package/dist/generic-container.js.map +1 -1
- package/dist/pallet.d.ts +7 -0
- package/dist/pallet.js.map +1 -1
- package/dist/parcel.d.ts +7 -0
- package/dist/parcel.js.map +1 -1
- package/dist/rack-cell-3d.js +1 -1
- package/dist/rack-cell-3d.js.map +1 -1
- package/dist/rack-cell.d.ts +8 -0
- package/dist/rack-cell.js +7 -10
- package/dist/rack-cell.js.map +1 -1
- package/dist/spot-3d.js.map +1 -1
- package/dist/spot.d.ts +8 -0
- package/dist/spot.js.map +1 -1
- package/package.json +3 -3
- package/src/asrs-crane.ts +21 -6
- package/src/asrs-rack.ts +23 -7
- package/src/box.ts +13 -1
- package/src/generic-container-3d.ts +1 -1
- package/src/generic-container.ts +21 -4
- package/src/pallet.ts +15 -3
- package/src/parcel.ts +13 -1
- package/src/rack-cell-3d.ts +1 -1
- package/src/rack-cell.ts +23 -10
- package/src/spot-3d.ts +1 -1
- package/src/spot.ts +13 -2
- package/tsconfig.tsbuildinfo +1 -1
package/dist/rack-cell.js.map
CHANGED
|
@@ -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"]}
|
package/dist/spot-3d.js.map
CHANGED
|
@@ -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":"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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\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.
|
|
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.
|
|
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": "
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 = (
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
107
|
+
return new Box3D(this)
|
|
96
108
|
}
|
|
97
109
|
}
|
package/src/generic-container.ts
CHANGED
|
@@ -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
|
|
127
|
+
return this.state.actuators ?? {}
|
|
111
128
|
}
|
|
112
129
|
|
|
113
130
|
get actuatorValues(): Record<string, number> {
|
|
114
|
-
return
|
|
131
|
+
return this.state.actuatorValues ?? {}
|
|
115
132
|
}
|
|
116
133
|
|
|
117
134
|
containable(component: Component): boolean {
|
|
118
|
-
return component.isDescendible(this
|
|
135
|
+
return component.isDescendible(this)
|
|
119
136
|
}
|
|
120
137
|
|
|
121
138
|
buildRealObject() {
|
|
122
|
-
return new GenericContainer3D(this
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
84
|
+
return new Parcel3D(this)
|
|
73
85
|
}
|
|
74
86
|
}
|
package/src/rack-cell-3d.ts
CHANGED
|
@@ -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 =
|
|
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)
|