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

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":"pallet.js","sourceRoot":"","sources":["../src/pallet.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Legendable,\n Placeable,\n type Alignment,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Pallet3D } from './pallet-3d.js'\n\n/**\n * Pallet material — drives both 2D fill color and 3D structure.\n *\n * - `wood` — traditional EUR / EPAL pallet: parallel slats on top and\n * bottom, three perpendicular stringers between them.\n * - `plastic` — molded one-piece pallet: solid top deck with cutouts,\n * hollow underside with feet. Distinct ribbed underside.\n *\n * Adding a third material (e.g. metal, composite) is a one-line change to the\n * legend + a 3D variant in pallet-3d.ts.\n */\nexport type PalletMaterial = 'wood' | 'plastic'\n\nconst BODY_LEGEND = {\n wood: '#a87644',\n plastic: '#5a6a78',\n default: '#a87644'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'material',\n name: 'material',\n property: {\n options: [\n { display: 'Wood', value: 'wood' },\n { display: 'Plastic', value: 'plastic' }\n ]\n }\n }\n ],\n help: 'scene/component/pallet'\n}\n\n// Carriable: a pallet can sit on AGV / Forklift / robot-arm gripper / Spot\n// and also accept boxes / parcels as children (ContainerAbstract base\n// provides the child-container behavior; Carriable only adds the\n// holder-mount hook).\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Pallet renders only as a 3D mesh.\n/**\n * Pallet — a flat transport structure that goods are stacked and stored on.\n *\n * Standard EUR pallet is 1200 × 800mm × 144mm; we don't enforce these\n * dimensions but they're a good starting point for the catalog templates.\n *\n * **Container-based.** Boxes / parcels stacked on the pallet are added as\n * children — same `containable()` archetype-filter pattern as Forklift / Agv.\n * Visual stacking (children rendering on top of the pallet rather than at\n * absolute operation level) is a v2 concern; see ARCHITECTURE NOTES below.\n *\n * **Placement = `operation`.** A pallet's *normal* state is loaded and in\n * transit on a conveyor / AGV / forklift fork — at operation level. Empty\n * pallets in a floor-storage area are an exceptional state where the user\n * sets `state.zPos = 0` explicitly. Default to the common case.\n *\n * ## ARCHITECTURE NOTES — visual stacking\n *\n * When a Box (also `placement: 'operation'`) is added as a child of a\n * Pallet, both default to z = operation_height. They overlap visually\n * rather than the box sitting on top of the pallet. Solving this cleanly\n * (parent-relative z derivation when the parent is a structural carrier)\n * is a follow-up — fmsim's pattern is to detect parent type at render time\n * (machine-3d.ts:113-122). v1 accepts the visual overlap; v2 will add the\n * detection.\n */\n@sceneComponent('pallet')\nexport default class Pallet extends Carriable(Legendable(Placeable(ContainerAbstract))) {\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'material', legend: BODY_LEGEND }\n }\n\n static placement: PlacementArchetype = 'operation'\n static align: Alignment = 'bottom'\n static defaultDepth = 150 // EUR pallet is 144mm; 150 is the round number convention\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** Accept other operation-archetype cargo (boxes, parcels, smaller pallets) as stacked children. */\n containable(component: Component) {\n const archetype = (component.constructor as any).placement\n if (archetype === 'operation') return true\n return component.isDescendible(this as any)\n }\n\n /**\n * 2D — top-down silhouette. Body is a flat rectangle (wood/plastic deck);\n * `postrender()` adds the deck pattern + edge stroke so the pallet reads\n * as a pallet instead of a featureless rectangle.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n /**\n * Deck pattern + edge stroke. Wood: parallel slats with darker grooves\n * between (typical EUR pallet deck). Plastic: cross-cutout pattern\n * suggesting the molded reinforcement ribs.\n *\n * Slats run along the *short* axis of the rectangle (= along the longer\n * stringer direction in real life), so a 1200×800 pallet shows multiple\n * narrow slats across the 1200mm dimension — matching the EUR layout.\n */\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const isPlastic = ((this.state as any).material as PalletMaterial) === 'plastic'\n\n ctx.save()\n\n if (!isPlastic) {\n // Wood — slats. Run them along the longer axis so the grooves are\n // perpendicular to the longer side (typical pallet appearance).\n const longAxisHorizontal = width >= height\n const slatCount = 5\n const grooveColor = '#7a4f25'\n ctx.fillStyle = grooveColor\n if (longAxisHorizontal) {\n // grooves vertical (X direction across the width)\n const grooveW = Math.max(1, width * 0.012)\n const slatW = (width - grooveW * (slatCount - 1)) / slatCount\n for (let i = 1; i < slatCount; i++) {\n const x = left + i * slatW + (i - 1) * grooveW\n ctx.fillRect(x, top + height * 0.05, grooveW, height * 0.9)\n }\n } else {\n const grooveH = Math.max(1, height * 0.012)\n const slatH = (height - grooveH * (slatCount - 1)) / slatCount\n for (let i = 1; i < slatCount; i++) {\n const y = top + i * slatH + (i - 1) * grooveH\n ctx.fillRect(left + width * 0.05, y, width * 0.9, grooveH)\n }\n }\n } else {\n // Plastic — cross + corner cutouts hint\n ctx.strokeStyle = '#3a4956'\n ctx.lineWidth = Math.max(1, Math.min(width, height) * 0.012)\n ctx.beginPath()\n ctx.moveTo(left + width * 0.5, top + height * 0.1)\n ctx.lineTo(left + width * 0.5, top + height * 0.9)\n ctx.moveTo(left + width * 0.1, top + height * 0.5)\n ctx.lineTo(left + width * 0.9, top + height * 0.5)\n ctx.stroke()\n }\n\n // Edge stroke\n ctx.strokeStyle = isPlastic ? '#2a3946' : '#5e3818'\n ctx.lineWidth = 1\n ctx.strokeRect(left, top, width, height)\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#a87644'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Pallet3D(this as any)\n }\n}\n"]}
1
+ {"version":3,"file":"pallet.js","sourceRoot":"","sources":["../src/pallet.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport {\n Component,\n ComponentNature,\n ContainerAbstract,\n RealObject,\n Pose6DOF,\n rectangularFootprintFrames,\n getWorldPose,\n sceneComponent\n} from '@hatiolab/things-scene'\nimport type { State, Material3D, PickupFrame, PoseSerialized } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Legendable,\n Placeable,\n type Alignment,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Pallet3D } from './pallet-3d.js'\n\n/**\n * Pallet material — drives both 2D fill color and 3D structure.\n *\n * - `wood` — traditional EUR / EPAL pallet: parallel slats on top and\n * bottom, three perpendicular stringers between them.\n * - `plastic` — molded one-piece pallet: solid top deck with cutouts,\n * hollow underside with feet. Distinct ribbed underside.\n *\n * Adding a third material (e.g. metal, composite) is a one-line change to the\n * legend + a 3D variant in pallet-3d.ts.\n */\nexport type PalletMaterial = 'wood' | 'plastic'\n\n/** Pallet 컴포넌트 state */\nexport interface PalletState extends State {\n // ── 외관 ──\n material?: PalletMaterial\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst BODY_LEGEND = {\n wood: '#a87644',\n plastic: '#5a6a78',\n default: '#a87644'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'material',\n name: 'material',\n property: {\n options: [\n { display: 'Wood', value: 'wood' },\n { display: 'Plastic', value: 'plastic' }\n ]\n }\n }\n ],\n help: 'scene/component/pallet'\n}\n\n// Carriable: a pallet can sit on AGV / Forklift / robot-arm gripper / Spot\n// and also accept boxes / parcels as children (ContainerAbstract base\n// provides the child-container behavior; Carriable only adds the\n// holder-mount hook).\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Pallet renders only as a 3D mesh.\n/**\n * Pallet — a flat transport structure that goods are stacked and stored on.\n *\n * Standard EUR pallet is 1200 × 800mm × 144mm; we don't enforce these\n * dimensions but they're a good starting point for the catalog templates.\n *\n * **Container-based.** Boxes / parcels stacked on the pallet are added as\n * children — same `containable()` archetype-filter pattern as Forklift / Agv.\n * Visual stacking (children rendering on top of the pallet rather than at\n * absolute operation level) is a v2 concern; see ARCHITECTURE NOTES below.\n *\n * **Placement = `operation`.** A pallet's *normal* state is loaded and in\n * transit on a conveyor / AGV / forklift fork — at operation level. Empty\n * pallets in a floor-storage area are an exceptional state where the user\n * sets `state.zPos = 0` explicitly. Default to the common case.\n *\n * ## ARCHITECTURE NOTES — visual stacking\n *\n * When a Box (also `placement: 'operation'`) is added as a child of a\n * Pallet, both default to z = operation_height. They overlap visually\n * rather than the box sitting on top of the pallet. Solving this cleanly\n * (parent-relative z derivation when the parent is a structural carrier)\n * is a follow-up — fmsim's pattern is to detect parent type at render time\n * (machine-3d.ts:113-122). v1 accepts the visual overlap; v2 will add the\n * detection.\n */\n@sceneComponent('pallet')\nexport default class Pallet extends Carriable(Legendable(Placeable(ContainerAbstract))) {\n declare state: PalletState\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'material', legend: BODY_LEGEND }\n }\n\n static placement: PlacementArchetype = 'operation'\n static align: Alignment = 'bottom'\n static defaultDepth = 150 // EUR pallet is 144mm; 150 is the round number convention\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** Accept other operation-archetype cargo (boxes, parcels, smaller pallets) as stacked children. */\n containable(component: Component) {\n const archetype = (component.constructor as any).placement\n if (archetype === 'operation') return true\n return component.isDescendible(this)\n }\n\n /**\n * 2D — top-down silhouette. Body is a flat rectangle (wood/plastic deck);\n * `postrender()` adds the deck pattern + edge stroke so the pallet reads\n * as a pallet instead of a featureless rectangle.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n /**\n * Deck pattern + edge stroke. Wood: parallel slats with darker grooves\n * between (typical EUR pallet deck). Plastic: cross-cutout pattern\n * suggesting the molded reinforcement ribs.\n *\n * Slats run along the *short* axis of the rectangle (= along the longer\n * stringer direction in real life), so a 1200×800 pallet shows multiple\n * narrow slats across the 1200mm dimension — matching the EUR layout.\n */\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const isPlastic = this.state.material === 'plastic'\n\n ctx.save()\n\n if (!isPlastic) {\n // Wood — slats. Run them along the longer axis so the grooves are\n // perpendicular to the longer side (typical pallet appearance).\n const longAxisHorizontal = width >= height\n const slatCount = 5\n const grooveColor = '#7a4f25'\n ctx.fillStyle = grooveColor\n if (longAxisHorizontal) {\n // grooves vertical (X direction across the width)\n const grooveW = Math.max(1, width * 0.012)\n const slatW = (width - grooveW * (slatCount - 1)) / slatCount\n for (let i = 1; i < slatCount; i++) {\n const x = left + i * slatW + (i - 1) * grooveW\n ctx.fillRect(x, top + height * 0.05, grooveW, height * 0.9)\n }\n } else {\n const grooveH = Math.max(1, height * 0.012)\n const slatH = (height - grooveH * (slatCount - 1)) / slatCount\n for (let i = 1; i < slatCount; i++) {\n const y = top + i * slatH + (i - 1) * grooveH\n ctx.fillRect(left + width * 0.05, y, width * 0.9, grooveH)\n }\n }\n } else {\n // Plastic — cross + corner cutouts hint\n ctx.strokeStyle = '#3a4956'\n ctx.lineWidth = Math.max(1, Math.min(width, height) * 0.012)\n ctx.beginPath()\n ctx.moveTo(left + width * 0.5, top + height * 0.1)\n ctx.lineTo(left + width * 0.5, top + height * 0.9)\n ctx.moveTo(left + width * 0.1, top + height * 0.5)\n ctx.lineTo(left + width * 0.9, top + height * 0.5)\n ctx.stroke()\n }\n\n // Edge stroke\n ctx.strokeStyle = isPlastic ? '#2a3946' : '#5e3818'\n ctx.lineWidth = 1\n ctx.strokeRect(left, top, width, height)\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#a87644'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Pallet3D(this)\n }\n\n /**\n * Phase H — pickup contract. EUR/EPAL pallet 의 fork 진입은 양 단면 (긴 변\n * 두 군데). 'east' / 'west' = pallet 의 짧은 축 방향에서 fork 가 들어감.\n *\n * fork 진입 높이 (entryHeight) 는 pallet 의 fork pocket 위치 — pallet 의\n * 바닥에서 약 50mm (top deck 와 bottom deck 사이의 stringer 영역). 표준\n * EUR pallet 의 fork pocket 은 144mm 두께의 약 50% 지점.\n *\n * approachDistance 는 forklift 가 fork 끝을 pallet 에 닿기 직전 hover 자세 —\n * pallet 길이만큼 떨어져서 fork 길이 (보통 1100mm) 가 다 들어가도록 한다.\n * 여기선 conservative 하게 pallet 너비 + 200mm.\n */\n pickupFrames(): PickupFrame[] {\n const { width = 1200, height = 800 } = this.state\n const palletDepth = (this.constructor as any).defaultDepth ?? 150\n\n // 4-way pallet: 모든 면에서 fork 진입 가능. 2-way 는 sides 를 ['east','west']\n // 로 한정 — pallet 의 stringer 방향에 따라 다르나 default 는 4-way 가정.\n // (state.palletType 같은 속성 추가 시 분기 가능 — 현재는 4-way 가정.)\n const me: PoseSerialized = (() => {\n const wp = getWorldPose(this)\n return {\n position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },\n rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }\n }\n })()\n\n const longerAxis = Math.max(width, height)\n\n return rectangularFootprintFrames({\n carrierWorld: me,\n width,\n depth: height,\n entryHeight: palletDepth * 0.4, // fork pocket 중심\n approachDistance: longerAxis + 200,\n sides: ['east', 'west', 'north', 'south'],\n toolType: 'forklift-fork',\n tolerance: { positionMm: 50, angleDeg: 5 },\n priority: 0\n })\n }\n}\n"]}
package/dist/parcel.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import { ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import type { State, Material3D, PickupFrame } from '@hatiolab/things-scene';
2
3
  import { type Alignment, type PlacementArchetype } from '@operato/scene-base';
4
+ /** Parcel 컴포넌트 state */
5
+ export interface ParcelState extends State {
6
+ trackingId?: string;
7
+ material3d?: Material3D;
8
+ }
3
9
  declare const Parcel_base: any;
4
10
  /**
5
11
  * Parcel — a cardboard package, the typical e-commerce / parcel-sortation unit.
@@ -17,6 +23,7 @@ declare const Parcel_base: any;
17
23
  * inspected indicators would add a status legend then.
18
24
  */
19
25
  export default class Parcel extends Parcel_base {
26
+ state: ParcelState;
20
27
  static placement: PlacementArchetype;
21
28
  static align: Alignment;
22
29
  static defaultDepth: number;
@@ -26,5 +33,11 @@ export default class Parcel extends Parcel_base {
26
33
  render(ctx: CanvasRenderingContext2D): void;
27
34
  get fillStyle(): string;
28
35
  buildRealObject(): RealObject | undefined;
36
+ /**
37
+ * Phase H — pickup contract. Parcel 은 위에서 vacuum gripper / suction cup 으로
38
+ * 집기 — Box 와 동일한 패턴이지만 cardboard 표면이라 더 큰 흡착 면 필요.
39
+ * tolerance 도 약간 완화 (cardboard 변형 가능성).
40
+ */
41
+ pickupFrames(): PickupFrame[];
29
42
  }
30
43
  export {};
package/dist/parcel.js CHANGED
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
2
2
  /*
3
3
  * Copyright © HatioLab Inc. All rights reserved.
4
4
  */
5
- import { RectPath, Shape, sceneComponent } from '@hatiolab/things-scene';
5
+ import { RectPath, Shape, topApproachFrame, getWorldPose, sceneComponent } from '@hatiolab/things-scene';
6
6
  import { Carriable, Placeable } from '@operato/scene-base';
7
7
  import { Parcel3D } from './parcel-3d.js';
8
8
  const NATURE = {
@@ -58,6 +58,30 @@ let Parcel = class Parcel extends Carriable(Placeable(RectPath(Shape))) {
58
58
  buildRealObject() {
59
59
  return new Parcel3D(this);
60
60
  }
61
+ /**
62
+ * Phase H — pickup contract. Parcel 은 위에서 vacuum gripper / suction cup 으로
63
+ * 집기 — Box 와 동일한 패턴이지만 cardboard 표면이라 더 큰 흡착 면 필요.
64
+ * tolerance 도 약간 완화 (cardboard 변형 가능성).
65
+ */
66
+ pickupFrames() {
67
+ const wp = getWorldPose(this);
68
+ const me = {
69
+ position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },
70
+ rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }
71
+ };
72
+ const parcelDepth = this.constructor.defaultDepth ?? 150;
73
+ return [
74
+ topApproachFrame({
75
+ carrierWorld: me,
76
+ topY: parcelDepth,
77
+ approachDistance: 80, // gripper hover 거리 (Box 보다 더 — vacuum 펼침)
78
+ toolType: 'gripper',
79
+ tolerance: { positionMm: 10, angleDeg: 2 }, // cardboard 변형 감안
80
+ priority: 0,
81
+ id: 'top-suction'
82
+ })
83
+ ];
84
+ }
61
85
  };
62
86
  Parcel = __decorate([
63
87
  sceneComponent('parcel')
@@ -1 +1 @@
1
- {"version":3,"file":"parcel.js","sourceRoot":"","sources":["../src/parcel.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA+B,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACrG,OAAO,EACL,SAAS,EACT,SAAS,EAGV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,UAAU,EAAE;QACV;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,aAAa;YACpB,IAAI,EAAE,YAAY;SACnB;KACF;IACD,IAAI,EAAE,wBAAwB;CAC/B,CAAA;AAED,yEAAyE;AACzE,uEAAuE;AACvE,oDAAoD;AACpD;;;;;;;;;;;;;;GAcG;AAEY,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACvE,MAAM,CAAC,SAAS,GAAuB,WAAW,CAAA;IAClD,MAAM,CAAC,KAAK,GAAc,QAAQ,CAAA;IAClC,MAAM,CAAC,YAAY,GAAG,GAAG,CAAA;IAEzB,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED,gDAAgD;IAChD,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;IACpC,CAAC;IAED,IAAI,SAAS;QACX,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,eAAe;QACb,OAAO,IAAI,QAAQ,CAAC,IAAW,CAAC,CAAA;IAClC,CAAC;;AA1BkB,MAAM;IAD1B,cAAc,CAAC,QAAQ,CAAC;GACJ,MAAM,CA2B1B;eA3BoB,MAAM","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { ComponentNature, RealObject, RectPath, Shape, sceneComponent } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Placeable,\n type Alignment,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Parcel3D } from './parcel-3d.js'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'string',\n label: 'tracking-id',\n name: 'trackingId'\n }\n ],\n help: 'scene/component/parcel'\n}\n\n// Carriable: parcel can be a child of any CarrierHolder (Spot, robot-arm\n// gripper, AGV deck, …). Mixin wraps add() so the parcel's 3D object3d\n// is reattached to the holder's chosen mount frame.\n/**\n * Parcel — a cardboard package, the typical e-commerce / parcel-sortation unit.\n *\n * Distinct from `Box` because parcels have:\n * - cardboard appearance (tan/brown corrugate, not wood / plastic)\n * - tape line down the center (the visual signature that says \"package\")\n * - typically a label on top (where shipping info goes)\n * - flatter / more elongated proportions in real-world parcel networks\n *\n * No `material` prop — parcels are always cardboard. If a future shipping\n * domain needs metal cases or polybags, those become separate components.\n *\n * No Legendable for v1 — parcel color is fixed cardboard. Future damaged /\n * inspected indicators would add a status legend then.\n */\n@sceneComponent('parcel')\nexport default class Parcel extends Carriable(Placeable(RectPath(Shape))) {\n static placement: PlacementArchetype = 'operation'\n static align: Alignment = 'bottom'\n static defaultDepth = 150\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** 2D — top-down rectangle in cardboard tan. */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n get fillStyle() {\n return '#c8a878'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Parcel3D(this as any)\n }\n}\n"]}
1
+ {"version":3,"file":"parcel.js","sourceRoot":"","sources":["../src/parcel.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAGL,QAAQ,EACR,KAAK,EACL,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACf,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EACL,SAAS,EACT,SAAS,EAGV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAWzC,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,UAAU,EAAE;QACV;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,aAAa;YACpB,IAAI,EAAE,YAAY;SACnB;KACF;IACD,IAAI,EAAE,wBAAwB;CAC/B,CAAA;AAED,yEAAyE;AACzE,uEAAuE;AACvE,oDAAoD;AACpD;;;;;;;;;;;;;;GAcG;AAEY,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAGvE,MAAM,CAAC,SAAS,GAAuB,WAAW,CAAA;IAClD,MAAM,CAAC,KAAK,GAAc,QAAQ,CAAA;IAClC,MAAM,CAAC,YAAY,GAAG,GAAG,CAAA;IAEzB,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED,gDAAgD;IAChD,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;IACpC,CAAC;IAED,IAAI,SAAS;QACX,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,eAAe;QACb,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IAED;;;;OAIG;IACH,YAAY;QACV,MAAM,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;QAC7B,MAAM,EAAE,GAAmB;YACzB,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE;YAClE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE;SACrF,CAAA;QACD,MAAM,WAAW,GAAI,IAAI,CAAC,WAAmB,CAAC,YAAY,IAAI,GAAG,CAAA;QAEjE,OAAO;YACL,gBAAgB,CAAC;gBACf,YAAY,EAAE,EAAE;gBAChB,IAAI,EAAE,WAAW;gBACjB,gBAAgB,EAAE,EAAE,EAAa,0CAA0C;gBAC3E,QAAQ,EAAE,SAAS;gBACnB,SAAS,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAG,kBAAkB;gBAC/D,QAAQ,EAAE,CAAC;gBACX,EAAE,EAAE,aAAa;aAClB,CAAC;SACH,CAAA;IACH,CAAC;;AAtDkB,MAAM;IAD1B,cAAc,CAAC,QAAQ,CAAC;GACJ,MAAM,CAuD1B;eAvDoB,MAAM","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport {\n ComponentNature,\n RealObject,\n RectPath,\n Shape,\n topApproachFrame,\n getWorldPose,\n sceneComponent\n} from '@hatiolab/things-scene'\nimport type { State, Material3D, PickupFrame, PoseSerialized } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Placeable,\n type Alignment,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Parcel3D } from './parcel-3d.js'\n\n/** Parcel 컴포넌트 state */\nexport interface ParcelState extends State {\n // ── 정체 ──\n trackingId?: string\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'string',\n label: 'tracking-id',\n name: 'trackingId'\n }\n ],\n help: 'scene/component/parcel'\n}\n\n// Carriable: parcel can be a child of any CarrierHolder (Spot, robot-arm\n// gripper, AGV deck, …). Mixin wraps add() so the parcel's 3D object3d\n// is reattached to the holder's chosen mount frame.\n/**\n * Parcel — a cardboard package, the typical e-commerce / parcel-sortation unit.\n *\n * Distinct from `Box` because parcels have:\n * - cardboard appearance (tan/brown corrugate, not wood / plastic)\n * - tape line down the center (the visual signature that says \"package\")\n * - typically a label on top (where shipping info goes)\n * - flatter / more elongated proportions in real-world parcel networks\n *\n * No `material` prop — parcels are always cardboard. If a future shipping\n * domain needs metal cases or polybags, those become separate components.\n *\n * No Legendable for v1 — parcel color is fixed cardboard. Future damaged /\n * inspected indicators would add a status legend then.\n */\n@sceneComponent('parcel')\nexport default class Parcel extends Carriable(Placeable(RectPath(Shape))) {\n declare state: ParcelState\n\n static placement: PlacementArchetype = 'operation'\n static align: Alignment = 'bottom'\n static defaultDepth = 150\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** 2D — top-down rectangle in cardboard tan. */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n get fillStyle() {\n return '#c8a878'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Parcel3D(this)\n }\n\n /**\n * Phase H — pickup contract. Parcel 은 위에서 vacuum gripper / suction cup 으로\n * 집기 — Box 와 동일한 패턴이지만 cardboard 표면이라 더 큰 흡착 면 필요.\n * tolerance 도 약간 완화 (cardboard 변형 가능성).\n */\n pickupFrames(): PickupFrame[] {\n const wp = getWorldPose(this)\n const me: PoseSerialized = {\n position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },\n rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }\n }\n const parcelDepth = (this.constructor as any).defaultDepth ?? 150\n\n return [\n topApproachFrame({\n carrierWorld: me,\n topY: parcelDepth,\n approachDistance: 80, // gripper hover 거리 (Box 보다 더 — vacuum 펼침)\n toolType: 'gripper',\n tolerance: { positionMm: 10, angleDeg: 2 }, // cardboard 변형 감안\n priority: 0,\n id: 'top-suction'\n })\n ]\n }\n}\n"]}
@@ -50,7 +50,7 @@ export class RackCell3D extends RealObjectGroup {
50
50
  const rack = this.component.parent;
51
51
  if (!rack?.cellMap)
52
52
  return;
53
- const cellId = this.component.state?.cellId;
53
+ const cellId = this.component.state.cellId;
54
54
  if (!cellId)
55
55
  return;
56
56
  const cell = rack.cellMap.findById(cellId);
@@ -1 +1 @@
1
- {"version":3,"file":"rack-cell-3d.js","sourceRoot":"","sources":["../src/rack-cell-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * RackCell 3D — invisible anchor group positioned at the cell's location\n * within the parent rack's 3D coordinate space.\n *\n * RackCell has no geometry of its own. Its sole 3D purpose is to provide\n * an Object3D that carriers can be attached to (via Three.js `.attach()`),\n * placed at the exact cell position within the rack. The position is derived\n * from the parent AsrsRack's CellMap (by cellId), not from 2D state fields —\n * rack cells occupy 3D levels that have no 2D analogue.\n *\n * updateTransform() override: things-scene's standard updateTransform\n * reads `component.center` (2D) and flattens it to 3D. For rack cells this\n * is wrong — we need the 3D cell position (bay x, level y, row z) from the\n * parent rack. So we override and read it directly from the CellMap.\n */\n\nimport * as THREE from 'three'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nexport class RackCell3D extends RealObjectGroup {\n build() {\n super.build()\n this._repositionFromCellMap()\n }\n\n updateDimension() {\n // intentional no-op — size comes from the cell definition, not state\n }\n\n updateTransform() {\n this._repositionFromCellMap()\n }\n\n updateAlpha() {\n // invisible — no materials to update\n }\n\n /**\n * Position this group at the cell's localPosition within the rack's 3D space.\n *\n * CellMap.grid() places cell origins at:\n * x = b * bayWidth, y = l * levelHeight, z = r * rowDepth\n * (starting from 0,0,0 at the rack's bottom-left-front corner).\n *\n * The rack's object3d is centered: X spans [-width/2, +width/2],\n * Y spans [-depth/2, +depth/2], Z spans [-height/2, +height/2].\n *\n * So the cell centre in rack-local 3D space:\n * x3d = cellPos.x + bayWidth/2 - width/2\n * y3d = cellPos.y + levelHeight/2 - rackDepth/2\n * z3d = cellPos.z + rowDepth/2 - rackHeight/2\n */\n _repositionFromCellMap() {\n const rack = (this.component as any).parent\n if (!rack?.cellMap) return\n\n const cellId = (this.component as any).state?.cellId as string | undefined\n if (!cellId) return\n\n const cell = rack.cellMap.findById(cellId)\n if (!cell) return\n\n const rs = rack.state as any\n const rackWidth = (rs?.width as number) || 1000\n const rackDepth = (rs?.depth as number) || 3000 // Y dimension (floor→ceiling)\n const rackHeight = (rs?.height as number) || 600 // Z dimension (front→back, 2D height)\n const bays = Math.max(1, Math.floor(rs?.bays || 5))\n const levels = Math.max(1, Math.floor(rs?.levels || 4))\n const rows = 1\n\n const bayWidth = rackWidth / bays\n const levelHeight = rackDepth / levels\n const rowDepth = rackHeight / rows\n\n const x3d = cell.localPosition.x + bayWidth / 2 - rackWidth / 2\n const y3d = cell.localPosition.y + levelHeight / 2 - rackDepth / 2\n const z3d = cell.localPosition.z + rowDepth / 2 - rackHeight / 2\n\n this.object3d.position.set(x3d, y3d, z3d)\n\n // Optionally visualise cells in debug mode (outline box, very faint)\n if (process.env.NODE_ENV !== 'production' && (rack.state as any)?.debugCells) {\n this._addDebugBox(bayWidth, levelHeight, rowDepth)\n }\n }\n\n private _debugBox?: THREE.LineSegments\n\n private _addDebugBox(w: number, h: number, d: number) {\n if (this._debugBox) return\n const geo = new THREE.BoxGeometry(w * 0.98, h * 0.98, d * 0.98)\n const edges = new THREE.EdgesGeometry(geo)\n this._debugBox = new THREE.LineSegments(\n edges,\n new THREE.LineBasicMaterial({ color: 0x00ff88, transparent: true, opacity: 0.2 })\n )\n this.object3d.add(this._debugBox)\n }\n}\n"]}
1
+ {"version":3,"file":"rack-cell-3d.js","sourceRoot":"","sources":["../src/rack-cell-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * RackCell 3D — invisible anchor group positioned at the cell's location\n * within the parent rack's 3D coordinate space.\n *\n * RackCell has no geometry of its own. Its sole 3D purpose is to provide\n * an Object3D that carriers can be attached to (via Three.js `.attach()`),\n * placed at the exact cell position within the rack. The position is derived\n * from the parent AsrsRack's CellMap (by cellId), not from 2D state fields —\n * rack cells occupy 3D levels that have no 2D analogue.\n *\n * updateTransform() override: things-scene's standard updateTransform\n * reads `component.center` (2D) and flattens it to 3D. For rack cells this\n * is wrong — we need the 3D cell position (bay x, level y, row z) from the\n * parent rack. So we override and read it directly from the CellMap.\n */\n\nimport * as THREE from 'three'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nexport class RackCell3D extends RealObjectGroup {\n build() {\n super.build()\n this._repositionFromCellMap()\n }\n\n updateDimension() {\n // intentional no-op — size comes from the cell definition, not state\n }\n\n updateTransform() {\n this._repositionFromCellMap()\n }\n\n updateAlpha() {\n // invisible — no materials to update\n }\n\n /**\n * Position this group at the cell's localPosition within the rack's 3D space.\n *\n * CellMap.grid() places cell origins at:\n * x = b * bayWidth, y = l * levelHeight, z = r * rowDepth\n * (starting from 0,0,0 at the rack's bottom-left-front corner).\n *\n * The rack's object3d is centered: X spans [-width/2, +width/2],\n * Y spans [-depth/2, +depth/2], Z spans [-height/2, +height/2].\n *\n * So the cell centre in rack-local 3D space:\n * x3d = cellPos.x + bayWidth/2 - width/2\n * y3d = cellPos.y + levelHeight/2 - rackDepth/2\n * z3d = cellPos.z + rowDepth/2 - rackHeight/2\n */\n _repositionFromCellMap() {\n const rack = (this.component as any).parent\n if (!rack?.cellMap) return\n\n const cellId = this.component.state.cellId as string | undefined\n if (!cellId) return\n\n const cell = rack.cellMap.findById(cellId)\n if (!cell) return\n\n const rs = rack.state as any\n const rackWidth = (rs?.width as number) || 1000\n const rackDepth = (rs?.depth as number) || 3000 // Y dimension (floor→ceiling)\n const rackHeight = (rs?.height as number) || 600 // Z dimension (front→back, 2D height)\n const bays = Math.max(1, Math.floor(rs?.bays || 5))\n const levels = Math.max(1, Math.floor(rs?.levels || 4))\n const rows = 1\n\n const bayWidth = rackWidth / bays\n const levelHeight = rackDepth / levels\n const rowDepth = rackHeight / rows\n\n const x3d = cell.localPosition.x + bayWidth / 2 - rackWidth / 2\n const y3d = cell.localPosition.y + levelHeight / 2 - rackDepth / 2\n const z3d = cell.localPosition.z + rowDepth / 2 - rackHeight / 2\n\n this.object3d.position.set(x3d, y3d, z3d)\n\n // Optionally visualise cells in debug mode (outline box, very faint)\n if (process.env.NODE_ENV !== 'production' && (rack.state as any)?.debugCells) {\n this._addDebugBox(bayWidth, levelHeight, rowDepth)\n }\n }\n\n private _debugBox?: THREE.LineSegments\n\n private _addDebugBox(w: number, h: number, d: number) {\n if (this._debugBox) return\n const geo = new THREE.BoxGeometry(w * 0.98, h * 0.98, d * 0.98)\n const edges = new THREE.EdgesGeometry(geo)\n this._debugBox = new THREE.LineSegments(\n edges,\n new THREE.LineBasicMaterial({ color: 0x00ff88, transparent: true, opacity: 0.2 })\n )\n this.object3d.add(this._debugBox)\n }\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import type { State, Material3D } from '@hatiolab/things-scene';
2
3
  import { type AttachFrame } from '@operato/scene-base';
3
4
  /**
4
5
  * How many carriers a cell can hold simultaneously.
@@ -7,6 +8,12 @@ import { type AttachFrame } from '@operato/scene-base';
7
8
  * - bulk: unlimited (e.g. a floor area measured in slots)
8
9
  */
9
10
  export type RackCellType = 'single' | 'multi' | 'bulk';
11
+ /** RackCell 컴포넌트 state */
12
+ export interface RackCellState extends State {
13
+ cellId?: string;
14
+ cellType?: RackCellType;
15
+ material3d?: Material3D;
16
+ }
10
17
  declare const RackCell_base: any;
11
18
  /**
12
19
  * RackCell — single-slot storage cell inside an AsrsRack.
@@ -20,6 +27,7 @@ declare const RackCell_base: any;
20
27
  * 2D→3D coordinate mapping which cannot express 3D levels.
21
28
  */
22
29
  export default class RackCell extends RackCell_base {
30
+ state: RackCellState;
23
31
  get cellId(): string;
24
32
  get cellType(): RackCellType;
25
33
  /** Maximum carrier count for this cell based on cellType. */
package/dist/rack-cell.js CHANGED
@@ -67,10 +67,10 @@ const NATURE = {
67
67
  let RackCell = class RackCell extends CarrierHolder(ContainerAbstract) {
68
68
  // ── Identification ────────────────────────────────────────────────────────
69
69
  get cellId() {
70
- return this.state.cellId || '';
70
+ return this.state.cellId ?? '';
71
71
  }
72
72
  get cellType() {
73
- return (this.state.cellType || 'single');
73
+ return this.state.cellType ?? 'single';
74
74
  }
75
75
  /** Maximum carrier count for this cell based on cellType. */
76
76
  get capacity() {
@@ -100,8 +100,7 @@ let RackCell = class RackCell extends CarrierHolder(ContainerAbstract) {
100
100
  */
101
101
  async receive(carrier, options = {}) {
102
102
  if (!this.canReceive(carrier)) {
103
- ;
104
- this.trigger?.('transfer-rejected', {
103
+ this.trigger('transfer-rejected', {
105
104
  type: 'transfer-rejected',
106
105
  component: carrier,
107
106
  container: this,
@@ -110,8 +109,8 @@ let RackCell = class RackCell extends CarrierHolder(ContainerAbstract) {
110
109
  return;
111
110
  }
112
111
  carrier[TRANSFER_SLOT_KEY] = this.cellId;
113
- this.reparent?.(carrier, options);
114
- this.trigger?.('transfer-received', {
112
+ this.reparent(carrier, options);
113
+ this.trigger('transfer-received', {
115
114
  type: 'transfer-received',
116
115
  component: carrier,
117
116
  container: this,
@@ -124,8 +123,7 @@ let RackCell = class RackCell extends CarrierHolder(ContainerAbstract) {
124
123
  */
125
124
  async dispatch(carrier, target, options = {}) {
126
125
  if (target?.canReceive && !target.canReceive(carrier)) {
127
- ;
128
- this.trigger?.('transfer-rejected', {
126
+ this.trigger('transfer-rejected', {
129
127
  type: 'transfer-rejected',
130
128
  component: carrier,
131
129
  container: this,
@@ -141,8 +139,7 @@ let RackCell = class RackCell extends CarrierHolder(ContainerAbstract) {
141
139
  ;
142
140
  target.reparent?.(carrier, options);
143
141
  }
144
- ;
145
- this.trigger?.('transfer-dispatched', {
142
+ this.trigger('transfer-dispatched', {
146
143
  type: 'transfer-dispatched',
147
144
  component: carrier,
148
145
  container: this,
@@ -1 +1 @@
1
- {"version":3,"file":"rack-cell.js","sourceRoot":"","sources":["../src/rack-cell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;;AAEH,OAAO,EAGL,iBAAiB,EAEjB,iBAAiB,EACjB,cAAc,EACf,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAoB,MAAM,qBAAqB,CAAA;AAErE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAU9C,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,KAAK;IAChB,SAAS,EAAE,KAAK;IAChB,UAAU,EAAE;QACV;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,SAAS;YAChB,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,YAAY;SAC1B;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,UAAU;YAChB,QAAQ,EAAE;gBACR,OAAO,EAAE;oBACP,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;oBACtC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;oBACpC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;iBACnC;aACF;SACF;KACF;IACD,IAAI,EAAE,2BAA2B;CAClC,CAAA;AAED;;;;;;;;;;GAUG;AAEY,IAAM,QAAQ,GAAd,MAAM,QAAS,SAAQ,aAAa,CAAC,iBAAiB,CAAC;IACpE,6EAA6E;IAE7E,IAAI,MAAM;QACR,OAAQ,IAAI,CAAC,KAAK,CAAC,MAAiB,IAAI,EAAE,CAAA;IAC5C,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,CAAE,IAAI,CAAC,KAAK,CAAC,QAAyB,IAAI,QAAQ,CAAC,CAAA;IAC5D,CAAC;IAED,6DAA6D;IAC7D,IAAI,QAAQ;QACV,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtB,KAAK,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAA;YACvB,KAAK,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;YACtB,KAAK,MAAM,CAAC,CAAC,OAAO,QAAQ,CAAA;QAC9B,CAAC;IACH,CAAC;IAED,6EAA6E;IAE7E,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED,6EAA6E;IAE7E,iEAAiE;IACjE,UAAU,CAAC,UAAgB;QACzB,MAAM,QAAQ,GAAK,IAAY,CAAC,UAAsC,EAAE,MAAM,IAAI,CAAC,CAAA;QACnF,OAAO,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;IACjC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,OAAO,CAAC,OAAY,EAAE,UAAe,EAAE;QAC3C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,CAAC;YAAC,IAAY,CAAC,OAAO,EAAE,CAAC,mBAAmB,EAAE;gBAC5C,IAAI,EAAE,mBAAmB;gBACzB,SAAS,EAAE,OAAO;gBAClB,SAAS,EAAE,IAAI;gBACf,MAAM,EAAE,SAAS;aAClB,CAAC,CAAA;YACF,OAAM;QACR,CAAC;QACD,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,MAAM,CACvC;QAAC,IAAY,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAC1C;QAAC,IAAY,CAAC,OAAO,EAAE,CAAC,mBAAmB,EAAE;YAC5C,IAAI,EAAE,mBAAmB;YACzB,SAAS,EAAE,OAAO;YAClB,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC,CAAA;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,QAAQ,CAAC,OAAY,EAAE,MAAW,EAAE,UAAe,EAAE;QACzD,IAAI,MAAM,EAAE,UAAU,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,CAAC;YAAC,IAAY,CAAC,OAAO,EAAE,CAAC,mBAAmB,EAAE;gBAC5C,IAAI,EAAE,mBAAmB;gBACzB,SAAS,EAAE,OAAO;gBAClB,SAAS,EAAE,IAAI;gBACf,MAAM,EAAE,aAAa;aACtB,CAAC,CAAA;YACF,OAAM;QACR,CAAC;QACD,OAAO,OAAO,CAAC,iBAAiB,CAAC,CAAA;QACjC,IAAI,OAAO,MAAM,EAAE,OAAO,KAAK,UAAU,EAAE,CAAC;YAC1C,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACxC,CAAC;aAAM,CAAC;YACN,CAAC;YAAC,MAAc,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC/C,CAAC;QACD,CAAC;QAAC,IAAY,CAAC,OAAO,EAAE,CAAC,qBAAqB,EAAE;YAC9C,IAAI,EAAE,qBAAqB;YAC3B,SAAS,EAAE,OAAO;YAClB,SAAS,EAAE,IAAI;YACf,MAAM;SACP,CAAC,CAAA;IACJ,CAAC;IAED,6EAA6E;IAE7E,mEAAmE;IACnE,KAAK,CAAC,OAAY,EAAE,OAAa;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IACvC,CAAC;IAED,oEAAoE;IACpE,QAAQ,CAAC,OAAY,EAAE,MAAW,EAAE,OAAa;QAC/C,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;IAChD,CAAC;IAED,6EAA6E;IAE7E;;;;OAIG;IACH,cAAc,CAAC,OAAkB;QAC/B,MAAM,IAAI,GAAI,IAAY,CAAC,WAAW,EAAE,QAAQ,CAAA;QAChD,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QACtB,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;QACjD,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,aAAa,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,YAAY,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;SACnD,CAAA;IACH,CAAC;IAED,6EAA6E;IAE7E,oEAAoE;IACpE,MAAM,CAAC,IAA8B;QACnC,oBAAoB;IACtB,CAAC;IAED,4EAA4E;IAE5E,eAAe;QACb,OAAO,IAAI,UAAU,CAAC,IAAW,CAAC,CAAA;IACpC,CAAC;CACF,CAAA;AApIoB,QAAQ;IAD5B,cAAc,CAAC,WAAW,CAAC;GACP,QAAQ,CAoI5B;eApIoB,QAAQ;AAsI7B,SAAS,mBAAmB,CAAC,CAAY;IACvC,MAAM,GAAG,GAAI,CAAS,CAAC,WAAW,EAAE,cAAc,CAAA;IAClD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAA;IAC/D,OAAO,KAAK,CAAE,CAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;AAC3C,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 * 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":"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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.30",
5
+ "version": "10.0.0-beta.32",
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.32",
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": "9c9fec2b993ccda805cdd53fe11e15fb4b71254e"
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 }
@@ -120,6 +135,15 @@ export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Leg
120
135
  /** Yaw offset: crane model is drawn with the aisle axis along X (right = forward). */
121
136
  static yawOffset = 0
122
137
 
138
+ /**
139
+ * Phase H — ASRS crane 은 telescoping forks 로 rack cell 안의 carrier 픽업.
140
+ * forklift-fork 와 동일 mechanism — pallet 의 fork pocket 진입.
141
+ * (Mover mixin chain 이 :any 라 override 키워드 불가, getter 만 정의.)
142
+ */
143
+ get toolType(): string {
144
+ return 'forklift-fork'
145
+ }
146
+
123
147
  get nature() {
124
148
  return NATURE
125
149
  }
@@ -147,13 +171,13 @@ export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Leg
147
171
  * crane's own object3d centre.
148
172
  */
149
173
  attachPointFor(carrier: Component): AttachFrame | null {
150
- const ro = (this as any)._realObject as AsrsCrane3D | undefined
174
+ const ro = this._realObject
151
175
  const frame = ro?.getCarriageFrame?.()
152
176
  if (frame) {
153
177
  const carrierDepth = resolveCarrierDepth(carrier)
154
178
  return { attach: frame, localPosition: { x: 0, y: carrierDepth / 2, z: 0 } }
155
179
  }
156
- const root = (this as any)._realObject?.object3d
180
+ const root = this._realObject?.object3d
157
181
  if (!root) return null
158
182
  return { attach: root }
159
183
  }
@@ -199,12 +223,12 @@ export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Leg
199
223
 
200
224
  /** Fetch a carrier from a rack cell (semantically = pick). */
201
225
  fetch(carrier: Component, options?: MoveOptions): Promise<void> {
202
- return (this as any).pick(carrier, options)
226
+ return this.pick(carrier, options)
203
227
  }
204
228
 
205
229
  /** Deposit a carrier into a rack cell (semantically = place). */
206
230
  deposit(carrier: Component, cell: Component, options?: MoveOptions): Promise<void> {
207
- return (this as any).place(carrier, cell, options)
231
+ return this.place(carrier, cell, options)
208
232
  }
209
233
 
210
234
  // ── 2D rendering ─────────────────────────────────────────────────────────
@@ -227,7 +251,7 @@ export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Leg
227
251
  // ── 3D ───────────────────────────────────────────────────────────────────
228
252
 
229
253
  buildRealObject(): RealObject | undefined {
230
- return new AsrsCrane3D(this as any)
254
+ return new AsrsCrane3D(this)
231
255
  }
232
256
  }
233
257