@operato/scene-transport 10.0.0-beta.31 → 10.0.0-beta.33

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 CHANGED
@@ -3,6 +3,29 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [10.0.0-beta.33](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.32...v10.0.0-beta.33) (2026-05-11)
7
+
8
+ **Note:** Version bump only for package @operato/scene-transport
9
+
10
+
11
+
12
+
13
+
14
+ ## [10.0.0-beta.32](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.31...v10.0.0-beta.32) (2026-05-09)
15
+
16
+
17
+ ### :rocket: New Features
18
+
19
+ * **mover/transport/manufacturing:** Phase H — Mover.pick 의 pickup compatibility 검사 ([d269366](https://github.com/things-scene/operato-scene/commit/d2693662f40f0fb6de1879b86c79f360348caaf9))
20
+ * **transport:** Phase H — Vehicle 별 toolType 추가 (Agv/Tugger/Worker) ([989596c](https://github.com/things-scene/operato-scene/commit/989596c04e29c43ec2406472ca5b3dd1d25a8bb4))
21
+
22
+
23
+ ### :bug: Bug Fix
24
+
25
+ * **manufacturing/transport:** Phase G6 — sim-time aware sleep + forklift settle ([864888f](https://github.com/things-scene/operato-scene/commit/864888fdcfdff628d19de99728e4748af6fcad08))
26
+
27
+
28
+
6
29
  ## [10.0.0-beta.31](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.30...v10.0.0-beta.31) (2026-05-08)
7
30
 
8
31
 
package/dist/agv.d.ts CHANGED
@@ -46,6 +46,15 @@ declare const Base: typeof Component & {
46
46
  */
47
47
  export default class Agv extends Base {
48
48
  get state(): AgvState;
49
+ /**
50
+ * Phase H — pickup contract 호환성 type. AGV 는 carrier 를 deck 에 올리는
51
+ * 식이라 carrier 가 'agv-deck' 진입을 노출하면 매칭. (예: pallet 위 AGV 가
52
+ * 자체 deck 으로 pallet 들어올리는 시나리오)
53
+ *
54
+ * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아
55
+ * TS4113 회피.)
56
+ */
57
+ get toolType(): string;
49
58
  static legends: Record<string, LegendBinding>;
50
59
  /**
51
60
  * AGV sits on its wheels — `floor` archetype. Default depth = operation,
package/dist/agv.js CHANGED
@@ -96,6 +96,17 @@ let Agv = class Agv extends Base {
96
96
  get state() {
97
97
  return super.state;
98
98
  }
99
+ /**
100
+ * Phase H — pickup contract 호환성 type. AGV 는 carrier 를 deck 에 올리는
101
+ * 식이라 carrier 가 'agv-deck' 진입을 노출하면 매칭. (예: pallet 위 AGV 가
102
+ * 자체 deck 으로 pallet 들어올리는 시나리오)
103
+ *
104
+ * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아
105
+ * TS4113 회피.)
106
+ */
107
+ get toolType() {
108
+ return 'agv-deck';
109
+ }
99
110
  static legends = {
100
111
  bodyColor: { from: 'status', legend: BODY_LEGEND },
101
112
  lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
package/dist/agv.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"agv.js","sourceRoot":"","sources":["../src/agv.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { SlotDef, State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Agv3D } from './agv-3d.js'\n\n/**\n * Agv status — common to both payload and towing AGVs (kept narrow on purpose).\n *\n * - `idle` — parked, awaiting task\n * - `moving` — driving along a path / executing transport\n * - `charging` — at a charging dock / battery station\n * - `error` — fault / blocked / e-stop\n *\n * Loaded-vs-empty distinction is *not* in the status enum because for a\n * Kiva-style payload AGV it's already obvious from the children (cargo\n * components) parented to it — duplicating that as a status flag would\n * invite drift.\n */\nexport type AgvStatus = 'idle' | 'moving' | 'charging' | 'error'\n\n/** Agv 컴포넌트 state */\nexport interface AgvState extends State {\n // ── 운영 상태 ──\n status?: AgvStatus\n\n // ── 시뮬레이션/모니터링 ──\n battery?: number\n speed?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\n/**\n * Body color — neutral industrial gray base, slightly modulated by status.\n * AGVs typically have status-color LED strips rather than full body color\n * change; the body legend stays subtle so the LED strip + lamp do the\n * communicating.\n */\nconst BODY_LEGEND = {\n idle: '#999',\n moving: '#aaa',\n charging: '#aaa',\n error: '#c66',\n default: '#999'\n}\n\n/**\n * LED strip emissive — the dominant status indicator for AGVs (typically a\n * color-band running around the chassis perimeter).\n */\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44', // green (operating)\n charging: '#ffaa00', // amber (charging)\n error: '#ff3333', // red\n default: '#222222'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Moving', value: 'moving' },\n { display: 'Charging', value: 'charging' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'battery',\n name: 'battery',\n placeholder: '0..1'\n },\n {\n // AGV travel speed in scene units / second. Used by Mover.moveTo\n // to derive motion duration. Tune per board scale; forklift convention.\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/agv'\n}\n\n// Composition:\n// FloorBound → Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract\n//\n// ContainerCapacity: receive() / dispatch() / canReceive() / slots — Mover.pick uses\n// receive() for slot-tracking and event emission on every cargo transfer.\n// CarrierHolder: attachPointFor() — deck-top 3D mount frame; Carriable.added()\n// calls applyHolderAttachPoint() which uses our override for 3D positioning.\n// Mover: moveTo / pick / place / pickAndPlace / executeMission.\n// FloorBound: outermost rotation guard.\n//\n// `ContainerAbstract` (not `Container`) — avoids isHTMLElement()=true that would\n// trip the 3D pipeline's addObject DOM-skip gate.\nconst Base = FloorBound(Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract)))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n }\n}\n\n/**\n * Agv — payload (unit-load) automated guided vehicle. The Kiva-style flat-deck\n * AGV that drives under or carries cargo to/from operation surfaces.\n *\n * **Container-based for cargo containment.** A payload Agv has a flat top\n * deck whose surface is at the scene's operation height. Boxes, parcels,\n * loaded pallets (anything with `placement: 'operation'`) can be added as\n * children — when they are, their natural archetype-derived zPos puts them\n * exactly on the AGV's deck (since AGV depth = operation - floor).\n *\n * For the towing variant (no cargo deck, pulls trailers behind), see Tugger.\n */\n@sceneComponent('agv')\nexport default class Agv extends Base {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): AgvState {\n return super.state as AgvState\n }\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n /**\n * AGV sits on its wheels — `floor` archetype. Default depth = operation,\n * so the top deck lands at the scene's operation height. This is the\n * design point of payload AGVs: the deck height matches conveyor belt\n * height, equipment ports, and forklift fork height — cargo transfers\n * across all of them at the same level.\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.operation - h.floor\n\n /**\n * Heading yaw offset (rad). things-scene's vehicle convention is\n * `vehicle forward = component-local -Z` (= \"rotation=0 → toward canvas\n * top edge\"); the framework default `Math.PI / 2` aligns with that.\n * Stated explicitly so the model's forward axis is documented at the\n * class level rather than relying silently on the framework default.\n */\n static yawOffset = Math.PI / 2\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n // ── ContainerCapacity ─────────────────────────────────────────────────────\n\n /**\n * Single-cargo policy: AGV 는 한 번에 1개의 carrier 만 운반.\n * 이미 1개를 보유한 상태에서는 `canReceive()` → false → `pick()` 거부.\n */\n get slots(): SlotDef[] {\n return [{ id: 'deck', maxCount: 1 }]\n }\n\n /** Accept logistics packages (placement='operation') as deck cargo. */\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 * AGV attach point — flat top deck. Cargo lands on the deck surface\n * (top of the AGV envelope, which is `+depth/2` in component-local Y).\n * Multiple cargo items stack vertically by their own depth.\n *\n * `attach` is the AGV's `_realObject.object3d` directly — there is no\n * inner sub-frame the way Forklift has a fork-tip frame, because the\n * deck moves rigidly with the chassis. Cargo Y reflects the cargoDepth\n * × slot index stack offset.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.object3d) return undefined\n\n const depth = (this.state.depth as number) ?? 0\n const slotIdx = this._slotIndexOf(carrier)\n const cargoDepth = (carrier.state as any).depth ?? 100\n // 3D origin convention (beta.66+): carrier 의 origin = center.\n // 데크 표면 (= AGV 상단) y = depth/2. carrier 의 bottom 을 데크 표면에 두려면\n // carrier center y = deckY + cargoDepth/2. 이후 slot 별로 cargoDepth 씩 stack.\n const deckY = depth / 2\n\n return {\n attach: ro.object3d,\n localPosition: {\n x: 0,\n y: deckY + cargoDepth / 2 + slotIdx * cargoDepth,\n z: 0\n },\n // Phase F: AGV 가 회전하면 cargo 도 같이 회전 (deck 위 고정 lock).\n carryPolicy: 'follow-holder'\n }\n }\n\n /**\n * Stack slot = the carrier's index among siblings. Stable because\n * children order is preserved; no persisted state needed.\n */\n private _slotIndexOf(carrier: Component): number {\n const children = this.components ?? []\n const idx = children.indexOf(carrier)\n return idx < 0 ? children.length : idx\n }\n\n /**\n * pick 보완: super.pick 후 carrier 의 자세 정렬 + attachPointFor 강제 재적용.\n *\n * 흐름의 마지막에 ContainerCapacity.receive 의 setState({left, top}) 가 표준\n * pipeline 을 트리거해 obj3d.position 이 attachPointFor 의 localPosition 을 덮어씀.\n * (= 첫번째 carrier 가 데크 위가 아니라 잘못된 위치/공중에 잡히는 원인)\n * super.pick 종료 후 attachPointFor 를 다시 적용하고 suppressTransform=true 로\n * pipeline 의 추가 override 차단. AGV 가 움직이면 carrier 는 scene-graph 통해 자동 추종.\n */\n override async pick(carrier: Component, options: any = {}): Promise<void> {\n // 1-capacity 정책: 이미 1개 보유 시 pick 거부 (moveTo / engage 낭비 방지).\n if (!(this as any).canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'agv-at-capacity'\n })\n return\n }\n await super.pick(carrier, options)\n carrier.setState?.({ rotation: 0, rotationX: 0, rotationY: 0 })\n this._snapToAttachPoint(carrier)\n }\n\n private _snapToAttachPoint(carrier: Component): void {\n const point = this.attachPointFor(carrier)\n const ro = (carrier as any)._realObject\n const obj3d = ro?.object3d\n if (!obj3d || !point?.localPosition) return\n const lp = point.localPosition\n obj3d.position.set(lp.x, lp.y, lp.z)\n if (point.localRotation) {\n obj3d.rotation.set(point.localRotation.x, point.localRotation.y, point.localRotation.z)\n } else {\n obj3d.quaternion.identity()\n }\n if (ro) ro.suppressTransform = true\n }\n\n /**\n * 2D — render() sets up the rounded chassis path; the framework fills it\n * with `fillStyle` (= bodyColor from Legendable) and strokes with\n * `strokeStyle`. Additional top-down details (bumpers, LiDAR, lift pad,\n * direction triangle) are drawn in `postrender()`.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const radius = Math.min(width, height) * 0.12\n ctx.beginPath()\n ctx.roundRect(left, top, width, height, radius)\n }\n\n /** Top-view accent details — bumpers, lift pad, LiDAR, direction triangle. */\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const cx = left + width / 2\n const cy = top + height / 2\n const accentColor = (this.state.lampEmissive as string) || '#44ff44'\n\n ctx.save()\n\n // Hi-vis bumper strips (front + rear)\n const bumperT = Math.min(width, height) * 0.06\n ctx.fillStyle = '#eeaa00'\n ctx.fillRect(left + width * 0.15, top, width * 0.7, bumperT)\n ctx.fillRect(left + width * 0.15, top + height - bumperT, width * 0.7, bumperT)\n ctx.fillStyle = '#111'\n ctx.fillRect(left + width * 0.02, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.02, top + height - bumperT, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top + height - bumperT, width * 0.13, bumperT)\n\n // Lift pad (center circle — Kiva-style under-shelf lift)\n const padR = Math.min(width, height) * 0.22\n ctx.fillStyle = '#4a4a55'\n ctx.strokeStyle = '#222'\n ctx.lineWidth = 1\n ctx.beginPath()\n ctx.ellipse(cx, cy, padR, padR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // LiDAR sensor (small filled circle near front)\n const lidarR = Math.min(width, height) * 0.07\n ctx.fillStyle = '#222233'\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR, lidarR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n // Status hint on LiDAR\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n // Direction-of-travel triangle\n ctx.fillStyle = accentColor\n ctx.strokeStyle = '#111'\n ctx.beginPath()\n ctx.moveTo(cx, top + height * 0.06)\n ctx.lineTo(cx - width * 0.06, top + height * 0.13)\n ctx.lineTo(cx + width * 0.06, top + height * 0.13)\n ctx.closePath()\n ctx.fill()\n ctx.stroke()\n\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Agv3D(this)\n }\n}\n"]}
1
+ {"version":3,"file":"agv.js","sourceRoot":"","sources":["../src/agv.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { SlotDef, State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Agv3D } from './agv-3d.js'\n\n/**\n * Agv status — common to both payload and towing AGVs (kept narrow on purpose).\n *\n * - `idle` — parked, awaiting task\n * - `moving` — driving along a path / executing transport\n * - `charging` — at a charging dock / battery station\n * - `error` — fault / blocked / e-stop\n *\n * Loaded-vs-empty distinction is *not* in the status enum because for a\n * Kiva-style payload AGV it's already obvious from the children (cargo\n * components) parented to it — duplicating that as a status flag would\n * invite drift.\n */\nexport type AgvStatus = 'idle' | 'moving' | 'charging' | 'error'\n\n/** Agv 컴포넌트 state */\nexport interface AgvState extends State {\n // ── 운영 상태 ──\n status?: AgvStatus\n\n // ── 시뮬레이션/모니터링 ──\n battery?: number\n speed?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\n/**\n * Body color — neutral industrial gray base, slightly modulated by status.\n * AGVs typically have status-color LED strips rather than full body color\n * change; the body legend stays subtle so the LED strip + lamp do the\n * communicating.\n */\nconst BODY_LEGEND = {\n idle: '#999',\n moving: '#aaa',\n charging: '#aaa',\n error: '#c66',\n default: '#999'\n}\n\n/**\n * LED strip emissive — the dominant status indicator for AGVs (typically a\n * color-band running around the chassis perimeter).\n */\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44', // green (operating)\n charging: '#ffaa00', // amber (charging)\n error: '#ff3333', // red\n default: '#222222'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Moving', value: 'moving' },\n { display: 'Charging', value: 'charging' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'battery',\n name: 'battery',\n placeholder: '0..1'\n },\n {\n // AGV travel speed in scene units / second. Used by Mover.moveTo\n // to derive motion duration. Tune per board scale; forklift convention.\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/agv'\n}\n\n// Composition:\n// FloorBound → Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract\n//\n// ContainerCapacity: receive() / dispatch() / canReceive() / slots — Mover.pick uses\n// receive() for slot-tracking and event emission on every cargo transfer.\n// CarrierHolder: attachPointFor() — deck-top 3D mount frame; Carriable.added()\n// calls applyHolderAttachPoint() which uses our override for 3D positioning.\n// Mover: moveTo / pick / place / pickAndPlace / executeMission.\n// FloorBound: outermost rotation guard.\n//\n// `ContainerAbstract` (not `Container`) — avoids isHTMLElement()=true that would\n// trip the 3D pipeline's addObject DOM-skip gate.\nconst Base = FloorBound(Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract)))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n }\n}\n\n/**\n * Agv — payload (unit-load) automated guided vehicle. The Kiva-style flat-deck\n * AGV that drives under or carries cargo to/from operation surfaces.\n *\n * **Container-based for cargo containment.** A payload Agv has a flat top\n * deck whose surface is at the scene's operation height. Boxes, parcels,\n * loaded pallets (anything with `placement: 'operation'`) can be added as\n * children — when they are, their natural archetype-derived zPos puts them\n * exactly on the AGV's deck (since AGV depth = operation - floor).\n *\n * For the towing variant (no cargo deck, pulls trailers behind), see Tugger.\n */\n@sceneComponent('agv')\nexport default class Agv extends Base {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): AgvState {\n return super.state as AgvState\n }\n\n /**\n * Phase H — pickup contract 호환성 type. AGV 는 carrier 를 deck 에 올리는\n * 식이라 carrier 가 'agv-deck' 진입을 노출하면 매칭. (예: pallet 위 AGV 가\n * 자체 deck 으로 pallet 들어올리는 시나리오)\n *\n * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아\n * TS4113 회피.)\n */\n get toolType(): string {\n return 'agv-deck'\n }\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n /**\n * AGV sits on its wheels — `floor` archetype. Default depth = operation,\n * so the top deck lands at the scene's operation height. This is the\n * design point of payload AGVs: the deck height matches conveyor belt\n * height, equipment ports, and forklift fork height — cargo transfers\n * across all of them at the same level.\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.operation - h.floor\n\n /**\n * Heading yaw offset (rad). things-scene's vehicle convention is\n * `vehicle forward = component-local -Z` (= \"rotation=0 → toward canvas\n * top edge\"); the framework default `Math.PI / 2` aligns with that.\n * Stated explicitly so the model's forward axis is documented at the\n * class level rather than relying silently on the framework default.\n */\n static yawOffset = Math.PI / 2\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n // ── ContainerCapacity ─────────────────────────────────────────────────────\n\n /**\n * Single-cargo policy: AGV 는 한 번에 1개의 carrier 만 운반.\n * 이미 1개를 보유한 상태에서는 `canReceive()` → false → `pick()` 거부.\n */\n get slots(): SlotDef[] {\n return [{ id: 'deck', maxCount: 1 }]\n }\n\n /** Accept logistics packages (placement='operation') as deck cargo. */\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 * AGV attach point — flat top deck. Cargo lands on the deck surface\n * (top of the AGV envelope, which is `+depth/2` in component-local Y).\n * Multiple cargo items stack vertically by their own depth.\n *\n * `attach` is the AGV's `_realObject.object3d` directly — there is no\n * inner sub-frame the way Forklift has a fork-tip frame, because the\n * deck moves rigidly with the chassis. Cargo Y reflects the cargoDepth\n * × slot index stack offset.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.object3d) return undefined\n\n const depth = (this.state.depth as number) ?? 0\n const slotIdx = this._slotIndexOf(carrier)\n const cargoDepth = (carrier.state as any).depth ?? 100\n // 3D origin convention (beta.66+): carrier 의 origin = center.\n // 데크 표면 (= AGV 상단) y = depth/2. carrier 의 bottom 을 데크 표면에 두려면\n // carrier center y = deckY + cargoDepth/2. 이후 slot 별로 cargoDepth 씩 stack.\n const deckY = depth / 2\n\n return {\n attach: ro.object3d,\n localPosition: {\n x: 0,\n y: deckY + cargoDepth / 2 + slotIdx * cargoDepth,\n z: 0\n },\n // Phase F: AGV 가 회전하면 cargo 도 같이 회전 (deck 위 고정 lock).\n carryPolicy: 'follow-holder'\n }\n }\n\n /**\n * Stack slot = the carrier's index among siblings. Stable because\n * children order is preserved; no persisted state needed.\n */\n private _slotIndexOf(carrier: Component): number {\n const children = this.components ?? []\n const idx = children.indexOf(carrier)\n return idx < 0 ? children.length : idx\n }\n\n /**\n * pick 보완: super.pick 후 carrier 의 자세 정렬 + attachPointFor 강제 재적용.\n *\n * 흐름의 마지막에 ContainerCapacity.receive 의 setState({left, top}) 가 표준\n * pipeline 을 트리거해 obj3d.position 이 attachPointFor 의 localPosition 을 덮어씀.\n * (= 첫번째 carrier 가 데크 위가 아니라 잘못된 위치/공중에 잡히는 원인)\n * super.pick 종료 후 attachPointFor 를 다시 적용하고 suppressTransform=true 로\n * pipeline 의 추가 override 차단. AGV 가 움직이면 carrier 는 scene-graph 통해 자동 추종.\n */\n override async pick(carrier: Component, options: any = {}): Promise<void> {\n // 1-capacity 정책: 이미 1개 보유 시 pick 거부 (moveTo / engage 낭비 방지).\n if (!(this as any).canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'agv-at-capacity'\n })\n return\n }\n await super.pick(carrier, options)\n carrier.setState?.({ rotation: 0, rotationX: 0, rotationY: 0 })\n this._snapToAttachPoint(carrier)\n }\n\n private _snapToAttachPoint(carrier: Component): void {\n const point = this.attachPointFor(carrier)\n const ro = (carrier as any)._realObject\n const obj3d = ro?.object3d\n if (!obj3d || !point?.localPosition) return\n const lp = point.localPosition\n obj3d.position.set(lp.x, lp.y, lp.z)\n if (point.localRotation) {\n obj3d.rotation.set(point.localRotation.x, point.localRotation.y, point.localRotation.z)\n } else {\n obj3d.quaternion.identity()\n }\n if (ro) ro.suppressTransform = true\n }\n\n /**\n * 2D — render() sets up the rounded chassis path; the framework fills it\n * with `fillStyle` (= bodyColor from Legendable) and strokes with\n * `strokeStyle`. Additional top-down details (bumpers, LiDAR, lift pad,\n * direction triangle) are drawn in `postrender()`.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const radius = Math.min(width, height) * 0.12\n ctx.beginPath()\n ctx.roundRect(left, top, width, height, radius)\n }\n\n /** Top-view accent details — bumpers, lift pad, LiDAR, direction triangle. */\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const cx = left + width / 2\n const cy = top + height / 2\n const accentColor = (this.state.lampEmissive as string) || '#44ff44'\n\n ctx.save()\n\n // Hi-vis bumper strips (front + rear)\n const bumperT = Math.min(width, height) * 0.06\n ctx.fillStyle = '#eeaa00'\n ctx.fillRect(left + width * 0.15, top, width * 0.7, bumperT)\n ctx.fillRect(left + width * 0.15, top + height - bumperT, width * 0.7, bumperT)\n ctx.fillStyle = '#111'\n ctx.fillRect(left + width * 0.02, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.02, top + height - bumperT, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top + height - bumperT, width * 0.13, bumperT)\n\n // Lift pad (center circle — Kiva-style under-shelf lift)\n const padR = Math.min(width, height) * 0.22\n ctx.fillStyle = '#4a4a55'\n ctx.strokeStyle = '#222'\n ctx.lineWidth = 1\n ctx.beginPath()\n ctx.ellipse(cx, cy, padR, padR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // LiDAR sensor (small filled circle near front)\n const lidarR = Math.min(width, height) * 0.07\n ctx.fillStyle = '#222233'\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR, lidarR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n // Status hint on LiDAR\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n // Direction-of-travel triangle\n ctx.fillStyle = accentColor\n ctx.strokeStyle = '#111'\n ctx.beginPath()\n ctx.moveTo(cx, top + height * 0.06)\n ctx.lineTo(cx - width * 0.06, top + height * 0.13)\n ctx.lineTo(cx + width * 0.06, top + height * 0.13)\n ctx.closePath()\n ctx.fill()\n ctx.stroke()\n\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Agv3D(this)\n }\n}\n"]}
@@ -48,6 +48,16 @@ declare const Base: typeof Component & {
48
48
  export default class Forklift extends Base {
49
49
  get state(): ForkliftState;
50
50
  _realObject: Forklift3D | undefined;
51
+ /**
52
+ * Phase H — pickup contract 호환성 검사용. carrier 의 pickupFramesFor 가
53
+ * 'forklift-fork' toolType 으로 query 받았을 때 매칭되는 entry 만 통과한다.
54
+ * Pallet 처럼 fork 진입 가능한 carrier 는 이 type 에 호환되는 entry 를 선언하고,
55
+ * 호환 안 되는 carrier (예: drum 의 magnet-only) 는 자동으로 reject 된다.
56
+ *
57
+ * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아
58
+ * TS4113 회피.)
59
+ */
60
+ get toolType(): string;
51
61
  static legends: Record<string, LegendBinding>;
52
62
  /**
53
63
  * Forklift sits on its wheels — `floor` archetype. Default depth is the
package/dist/forklift.js CHANGED
@@ -3,7 +3,7 @@ import { __decorate } from "tslib";
3
3
  * Copyright © HatioLab Inc. All rights reserved.
4
4
  */
5
5
  import * as THREE from 'three';
6
- import { ContainerAbstract, ContainerCapacity, sceneComponent } from '@hatiolab/things-scene';
6
+ import { ContainerAbstract, ContainerCapacity, frameClock, sceneComponent } from '@hatiolab/things-scene';
7
7
  import { CarrierHolder, FloorBound, Legendable, Mover, Placeable } from '@operato/scene-base';
8
8
  import { Forklift3D } from './forklift-3d.js';
9
9
  /** Body color — yellow base hue, modulated slightly by status for at-a-glance state. */
@@ -93,6 +93,18 @@ let Forklift = class Forklift extends Base {
93
93
  get state() {
94
94
  return super.state;
95
95
  }
96
+ /**
97
+ * Phase H — pickup contract 호환성 검사용. carrier 의 pickupFramesFor 가
98
+ * 'forklift-fork' toolType 으로 query 받았을 때 매칭되는 entry 만 통과한다.
99
+ * Pallet 처럼 fork 진입 가능한 carrier 는 이 type 에 호환되는 entry 를 선언하고,
100
+ * 호환 안 되는 carrier (예: drum 의 magnet-only) 는 자동으로 reject 된다.
101
+ *
102
+ * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아
103
+ * TS4113 회피.)
104
+ */
105
+ get toolType() {
106
+ return 'forklift-fork';
107
+ }
96
108
  static legends = {
97
109
  bodyColor: { from: 'status', legend: BODY_LEGEND },
98
110
  lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
@@ -241,8 +253,20 @@ let Forklift = class Forklift extends Base {
241
253
  const desired = targetPos.y - myPos.y + forkliftDepth / 2 - wheelR - forkH;
242
254
  const targetForkHeight = Math.max(0, Math.min(desired, forkliftDepth * 0.85));
243
255
  this.setState?.({ forkHeight: targetForkHeight });
244
- // mast 재구성 + attach point 재계산 안정화 시간.
245
- await new Promise(resolve => setTimeout(resolve, 200));
256
+ // mast 재구성 + attach point 재계산 안정화 시간 — sim-time 기준 (frameClock).
257
+ // speed 변경 시에도 sim animation 과 같은 시간축 유지.
258
+ await new Promise(resolve => {
259
+ let elapsed = 0;
260
+ let unsub = null;
261
+ unsub = frameClock.subscribe(simDt => {
262
+ elapsed += simDt;
263
+ if (elapsed >= 200) {
264
+ unsub?.();
265
+ unsub = null;
266
+ resolve();
267
+ }
268
+ }, { description: 'forklift mast settle' });
269
+ });
246
270
  }
247
271
  /**
248
272
  * pick 보완: super.pick 후 자세 정렬 + attachPointFor 강제 재적용.
@@ -1 +1 @@
1
- {"version":3,"file":"forklift.js","sourceRoot":"","sources":["../src/forklift.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport * as THREE from 'three'\nimport { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { SlotDef, State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Forklift3D } from './forklift-3d.js'\n\n/**\n * Forklift status — the operating state of a forklift truck.\n *\n * - `idle` — parked, engine off\n * - `running` — driving (no load)\n * - `lifting` — actively lifting / lowering forks\n * - `loaded` — driving with cargo on forks\n * - `error` — fault / emergency stop\n *\n * The 5-state set differs from Conveyor's because forklifts have a distinct\n * loaded-vs-empty distinction relevant for fleet visualization.\n */\nexport type ForkliftStatus = 'idle' | 'running' | 'lifting' | 'loaded' | 'error'\n\n/** Forklift 컴포넌트 state */\nexport interface ForkliftState extends State {\n // ── 운영 상태 ──\n status?: ForkliftStatus\n\n // ── 액추에이터/시뮬레이션 ──\n forkHeight?: number\n speed?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\n/** Body color — yellow base hue, modulated slightly by status for at-a-glance state. */\nconst BODY_LEGEND = {\n idle: '#d4a017', // muted yellow (parked)\n running: '#FFD700', // bright yellow (active)\n lifting: '#FFD700',\n loaded: '#FFA500', // orange tint (carrying)\n error: '#e9746b', // red\n default: '#d4a017'\n}\n\n/** Status lamp emissive — saturated for at-a-glance status from a distance. */\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#333333',\n running: '#44ff44', // green (operating)\n lifting: '#44aaff', // blue (lifting)\n loaded: '#ffaa00', // amber (loaded)\n error: '#ff3333', // red\n default: '#333333'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Running', value: 'running' },\n { display: 'Lifting', value: 'lifting' },\n { display: 'Loaded', value: 'loaded' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'fork-height',\n name: 'forkHeight',\n placeholder: '0..2000mm'\n },\n {\n // Forklift travel speed in scene units / second. Used by `Mover.moveTo`\n // to derive motion duration as `distance / speed × 1000`. Set to a value\n // that gives the look you want on this board's scale — defaults are\n // intentionally unguessed (board scale varies).\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/forklift'\n}\n\n// Composition order:\n// FloorBound → Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract\n//\n// ContainerCapacity: receive() / dispatch() / canReceive() / slots — Mover.pick uses\n// receive() (slot-tracking path) instead of the reparent() fallback. Event emission\n// ('transfer-received', 'transfer-dispatched') fires on every cargo transfer.\n// CarrierHolder: attachPointFor() — fork-tip 3D mount frame; Carriable.added()\n// calls applyHolderAttachPoint() which uses our override for 3D positioning.\n// Mover: moveTo / pick / place / pickAndPlace / executeMission.\n// FloorBound: rotation guard — outermost, sees state changes last.\nconst Base = FloorBound(Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract)))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n }\n}\n\n/**\n * Forklift — a powered industrial truck used to lift and transport material\n * over short distances.\n *\n * **Container + CarrierHolder for cargo carrying.** Children (boxes, parcels,\n * pallets) attach to the fork-tip frame published by `attachPointFor`, so\n * their 3D pose follows `state.forkHeight` automatically and they read as\n * \"sitting on the forks\". Multiple cargo items stack vertically on the forks.\n *\n * Procedural (non-GLB) build — keeps the hand-tuned silhouette in\n * `forklift-3d.ts`. The transportable layer is purely additive: it does not\n * change the rendering path, only how children's 3D objects are reparented.\n */\n@sceneComponent('forklift')\nexport default class Forklift extends Base {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): ForkliftState {\n return super.state as ForkliftState\n }\n\n declare _realObject: Forklift3D | undefined\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n /**\n * Forklift sits on its wheels — `floor` archetype. Default depth is the\n * forklift's overall envelope with mast collapsed (~= operation level).\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.operation - h.floor\n\n /**\n * Heading yaw offset (in radians) applied by `Mover.moveTo` when feeding\n * direction-of-travel to the Waypoint animation. Forklift uses\n * things-scene's standard vehicle convention (forward = -Z, Three.js\n * camera convention) — same as the framework default. Stated explicitly\n * here so the model's forward axis is documented at the class level\n * rather than being a silent dependency on the framework default.\n */\n static yawOffset = Math.PI / 2\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n // ── ContainerCapacity ─────────────────────────────────────────────────────\n\n /**\n * Forks hold up to 3 stacked load units (pallet + boxes).\n * 3D stacking is handled by `attachPointFor()` (Y offset per slotIdx) —\n * the slot maxCount here is the hard cap for `canReceive()`.\n */\n get slots(): SlotDef[] {\n // Single-cargo 정책: 이미 1개 보유 시 `canReceive()` → false → `pick()` 거부.\n return [{ id: 'forks', maxCount: 1 }]\n }\n\n /**\n * Forklift 정책: **파레트만** 자식으로 받음. 박스, 카트론, 파셀 등은 받지 않음.\n * 실제 forklift 가 fork pocket 을 통해 들기 때문에 pocket 구조가 있는 파레트만\n * 들 수 있음. 박스 등은 AGV 같은 평탄 데크 차량이 운반.\n */\n containable(component: Component) {\n return (component as any)?.model?.type === 'pallet'\n }\n\n /**\n * Forklift has a single mount frame: the **forks**. Multiple cargo items\n * stack vertically on the forks (slotIdx → +Y by cargo depth).\n *\n * Returns:\n * - `attach`: the Forklift's RealObjectGroup root (`object3d`). The\n * fork-tip *position* is supplied via `localPosition` rather than a\n * dedicated 3D mount frame, because Forklift3D rebuilds on\n * forkHeight changes and any nested mount node would be re-created\n * on each rebuild (losing scene-graph identity for cargo).\n * - `localPosition`: `Forklift3D.cargoMount.{x,y,z}` (already includes\n * forkHeight) plus a per-slot Y offset for stacking.\n *\n * On forkHeight changes, `onchange` below re-runs this for each child so\n * cargo tracks the lift.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.cargoMount) return undefined\n\n // Attach onto modelGroup (rotated to match things-scene -Z forward\n // convention) so the carrier follows the visible mesh's rotation. The\n // cargoMount coordinates are authored in the model's natural +Z-forward\n // frame, which IS the modelGroup's local frame — so localPosition\n // applies directly without an extra rotation.\n const attach = ro.modelGroup ?? ro.object3d\n if (!attach) return undefined\n\n // mount: { x, y (fork blade top), z (mid-fork), width, depth (= forkLen) }\n const mount = ro.cargoMount as { x: number; y: number; z: number; depth: number }\n const slotIdx = this._slotIndexOf(carrier)\n const cargoDepth = (carrier.state as any).depth ?? 100\n // carrier 의 carrier-local 의 fork 방향 길이 (height = -Z forward 방향).\n const carrierLenAlongFork = (carrier.state as any).height ?? cargoDepth\n\n // Fork-in-pocket 정렬: forklift 의 fork blade 가 파레트의 fork pocket 공간 안으로\n // 들어가도록 carrier center y = mount.y (= fork blade top). carrier 의 하반부가 fork\n // 아래로 내려와 fork 를 감쌈 (pocket 공간). carrier 의 상반부는 fork 위쪽 = 적재 영역.\n // 이게 실제 forklift-pallet 결합 시각과 일치.\n // (single-cargo 정책이라 slotIdx 는 항상 0; 멀티 stack 시 cargoDepth 로 stack)\n const y = mount.y + slotIdx * cargoDepth\n\n // Z (fork 방향): carrier 의 forklift-쪽 면(= +Z, 본체에 가까운 쪽)을 **fork heel\n // (= 포크의 시작부, 본체와 만나는 지점)** 에 정렬. carrier 본체는 fork tip 쪽으로\n // 뻗어나가며 forklift 본체 안으로는 절대 파고들지 않음. 큰 파레트는 fork tip 너머로\n // overhang.\n // mount.z 는 fork mid, mount.depth 는 fork length. fork heel = mount.z + forkLen/2\n // (heel 이 본체 쪽 = +Z, less negative).\n const forkHeelZ = mount.z + mount.depth / 2\n const z = forkHeelZ - carrierLenAlongFork / 2\n\n return {\n attach,\n localPosition: { x: mount.x, y, z },\n // Phase F: forklift 가 회전하면 fork 의 cargo 도 함께 회전 (fork 위 lock).\n carryPolicy: 'follow-holder'\n }\n }\n\n /**\n * Stack slot = the carrier's index among its sibling children. Stable\n * because things-scene preserves children order, so re-calling\n * `attachPointFor` after a forkHeight change keeps each cargo at its\n * original stack position. No persisted state needed.\n *\n * If the carrier isn't a child yet (mid-reparent — `attachPointFor` is\n * invoked from CarrierHolder.reparent before/around the add), default\n * to \"next free slot\" = current child count, which becomes its index\n * once the add settles.\n */\n private _slotIndexOf(carrier: Component): number {\n const children = this.components ?? []\n const idx = children.indexOf(carrier)\n return idx < 0 ? children.length : idx\n }\n\n /**\n * engage 보완: pick 시 fork blade top 의 world Y 가 carrier (파레트) 의\n * world center Y 와 일치하도록 forkHeight 를 들어올림. → fork 가 pallet 의\n * 높이 중간 (= pocket 공간) 에 정확히 위치.\n *\n * forklift-3d 의 cargoMountLocal 공식:\n * mount.y = -forklift.depth/2 + wheelR + forkHeight + forkH\n * fork blade top 의 world Y = forklift.world.y + mount.y (origin=center 가정).\n *\n * 목표: fork blade top world Y = pallet world center Y\n * ⟹ forkHeight = pallet_worldY - forklift_worldY + forklift.depth/2 - wheelR - forkH\n *\n * forkHeight clamp: [0, forklift.depth * 0.85] (cargoMountLocal 의 hard cap).\n */\n async engage(target: Component, kind: 'pick' | 'place', _options: any = {}): Promise<void> {\n if (kind !== 'pick') return\n\n const myObj3d = (this as any)._realObject?.object3d\n const targetObj3d = (target as any)._realObject?.object3d\n if (!myObj3d || !targetObj3d) return\n\n myObj3d.updateWorldMatrix(true, false)\n targetObj3d.updateWorldMatrix(true, false)\n const myPos = new THREE.Vector3()\n const targetPos = new THREE.Vector3()\n myObj3d.getWorldPosition(myPos)\n targetObj3d.getWorldPosition(targetPos)\n\n const forkliftDepth = (this.state as any).depth ?? 200\n const wheelR = forkliftDepth * 0.07\n const forkH = forkliftDepth * 0.018\n const desired = targetPos.y - myPos.y + forkliftDepth / 2 - wheelR - forkH\n const targetForkHeight = Math.max(0, Math.min(desired, forkliftDepth * 0.85))\n this.setState?.({ forkHeight: targetForkHeight })\n // mast 재구성 + attach point 재계산 안정화 시간.\n await new Promise(resolve => setTimeout(resolve, 200))\n }\n\n /**\n * pick 보완: super.pick 후 자세 정렬 + attachPointFor 강제 재적용.\n * 흐름의 마지막에 ContainerCapacity.receive 의 setState({left, top}) pipeline 이\n * obj3d.position 을 덮는 것을 다시 잡음. suppressTransform=true 로 향후 차단.\n * forklift 가 움직이면 carrier 는 scene-graph 통해 자동 추종.\n */\n override async pick(carrier: Component, options: any = {}): Promise<void> {\n if (!(this as any).canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'forklift-at-capacity'\n })\n return\n }\n await super.pick(carrier, options)\n carrier.setState?.({ rotation: 0, rotationX: 0, rotationY: 0 })\n this._snapToAttachPoint(carrier)\n }\n\n private _snapToAttachPoint(carrier: Component): void {\n const point = this.attachPointFor(carrier)\n const ro = (carrier as any)._realObject\n const obj3d = ro?.object3d\n if (!obj3d || !point?.localPosition) return\n const lp = point.localPosition\n obj3d.position.set(lp.x, lp.y, lp.z)\n if (point.localRotation) {\n obj3d.rotation.set(point.localRotation.x, point.localRotation.y, point.localRotation.z)\n } else {\n obj3d.quaternion.identity()\n }\n if (ro) ro.suppressTransform = true\n }\n\n /**\n * 2D — top-down silhouette of a forklift. Layout (top-down view):\n *\n * ┌──────────────────────┐ ← canvas top edge (forks point here, -Z forward)\n * │ ▒ forks ▒ │\n * │ ▒ ▒ │\n * │═════ mast bar ═══════│\n * │ ┌─────cab──────┐ │\n * │ │ ▢ seat │ │ ← driver in cab (faces forks/top edge)\n * │ └───────────────┘ │\n * │ ● ● rear-w │\n * ╰──────────────────────╯ ← counterweight bulge at bottom edge\n * ● ●\n * tail lights\n *\n * `forks` extend toward `top` (canvas y minus). Counterweight bulge\n * extends past `top + height` (canvas y plus). Matches things-scene's\n * vehicle convention (rotation=0 → vehicle points to canvas top edge).\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const forkLen = height * 0.28\n const cwBulge = height * 0.08\n const radius = Math.min(width, height) * 0.08\n\n // Body silhouette: from (top + forkLen) past (top + height) by cwBulge.\n // `top..top+forkLen` is reserved for the fork prongs (drawn in postrender).\n ctx.beginPath()\n ctx.roundRect(left, top + forkLen, width, height - forkLen + cwBulge, radius)\n }\n\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const forkLen = height * 0.28\n const cwBulge = height * 0.08\n const accentColor = (this.state.lampEmissive as string) || '#44ff44'\n const mastY = top + forkLen\n const bodyBottom = top + height + cwBulge\n\n ctx.save()\n\n // Mast bar — horizontal across the front of the body, where forks attach\n ctx.fillStyle = '#444455'\n ctx.fillRect(left + width * 0.10, mastY - height * 0.022, width * 0.80, height * 0.025)\n\n // Forks — two prongs extending toward canvas top (forward = -Z)\n const forkW = width * 0.10\n ctx.fillStyle = '#222233'\n ctx.strokeStyle = '#111'\n ctx.lineWidth = 1\n for (const xFrac of [0.20, 0.70]) {\n ctx.beginPath()\n ctx.rect(left + width * xFrac, top, forkW, forkLen)\n ctx.fill()\n ctx.stroke()\n }\n\n // Cab outline (centered, just behind the mast)\n const cabL = left + width * 0.18\n const cabT = mastY + height * 0.05\n const cabW = width * 0.64\n const cabH = height * 0.38\n ctx.fillStyle = 'rgba(0,0,0,0.06)'\n ctx.strokeStyle = '#333'\n ctx.lineWidth = 1\n ctx.fillRect(cabL, cabT, cabW, cabH)\n ctx.strokeRect(cabL, cabT, cabW, cabH)\n\n // Driver seat hint (rear half of cab — driver faces forks)\n ctx.fillStyle = '#1a1a22'\n ctx.fillRect(\n left + width * 0.40,\n cabT + cabH * 0.50,\n width * 0.20,\n cabH * 0.35\n )\n\n // Steering wheel hint (front of cab)\n ctx.fillStyle = '#222'\n ctx.beginPath()\n ctx.arc(left + width / 2, cabT + cabH * 0.30, Math.min(width, height) * 0.05, 0, Math.PI * 2)\n ctx.stroke()\n\n // Wheels — 4 corners. Front (drive) wheels behind mast; rear (caster) at bottom.\n ctx.fillStyle = '#1a1a1a'\n const wheelW = width * 0.07\n const wheelH = height * 0.10\n // Front wheels — just below mast, sides of body\n ctx.fillRect(left + width * 0.04, mastY + height * 0.02, wheelW, wheelH)\n ctx.fillRect(left + width - width * 0.04 - wheelW, mastY + height * 0.02, wheelW, wheelH)\n // Rear wheels — near body bottom (excluding cwBulge)\n const rearWheelY = top + height - wheelH * 1.1\n ctx.fillRect(left + width * 0.10, rearWheelY, wheelW * 0.85, wheelH * 0.85)\n ctx.fillRect(left + width - width * 0.10 - wheelW * 0.85, rearWheelY, wheelW * 0.85, wheelH * 0.85)\n\n // Status lamp on overhead guard top (over cab, near front)\n const lampR = Math.min(width, height) * 0.045\n ctx.fillStyle = accentColor\n ctx.strokeStyle = '#111'\n ctx.beginPath()\n ctx.ellipse(left + width / 2, cabT + cabH * 0.15, lampR, lampR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // Tail lights — bottom of counterweight bulge\n ctx.fillStyle = '#ff2222'\n const tailLightW = width * 0.08\n const tailLightH = height * 0.022\n const tailY = bodyBottom - tailLightH * 1.5\n ctx.fillRect(left + width * 0.15, tailY, tailLightW, tailLightH)\n ctx.fillRect(left + width - width * 0.15 - tailLightW, tailY, tailLightW, tailLightH)\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#d4a017'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Forklift3D(this)\n }\n\n /**\n * forkHeight change → mast & forks rebuild AND cargo Y must follow.\n *\n * Forklift3D.onchange already handles the mast/fork rebuild via update().\n * What's missing is moving each carrier's `object3d` to the new fork-tip\n * position. We do that here at the component level (not in Forklift3D)\n * because the attach-point policy lives on the holder component, and\n * Carriable's `applyHolderAttachPoint` is the established protocol.\n *\n * Carriable children expose `applyHolderAttachPoint` — calling it makes\n * them re-fetch our `attachPointFor` and snap to the new localPosition.\n * For non-Carriable children we fall through to direct object3d update\n * so legacy cargo (operation-archetype components without the Carriable\n * mixin) still tracks the lift.\n */\n onchange(after: any, before: any) {\n super.onchange?.(after, before)\n\n if (!('forkHeight' in (after ?? {}))) return\n\n const children = this.components ?? []\n for (const child of children) {\n if ((child as any).applyHolderAttachPoint) {\n ;(child as any).applyHolderAttachPoint()\n continue\n }\n // Non-Carriable cargo: snap object3d directly using our attach policy.\n const point = this.attachPointFor(child)\n const obj3d = (child as any)._realObject?.object3d\n if (point?.localPosition && obj3d?.position) {\n obj3d.position.set(point.localPosition.x, point.localPosition.y, point.localPosition.z)\n }\n }\n }\n\n}\n"]}
1
+ {"version":3,"file":"forklift.js","sourceRoot":"","sources":["../src/forklift.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport * as THREE from 'three'\nimport { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, frameClock, sceneComponent } from '@hatiolab/things-scene'\nimport type { SlotDef, State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Forklift3D } from './forklift-3d.js'\n\n/**\n * Forklift status — the operating state of a forklift truck.\n *\n * - `idle` — parked, engine off\n * - `running` — driving (no load)\n * - `lifting` — actively lifting / lowering forks\n * - `loaded` — driving with cargo on forks\n * - `error` — fault / emergency stop\n *\n * The 5-state set differs from Conveyor's because forklifts have a distinct\n * loaded-vs-empty distinction relevant for fleet visualization.\n */\nexport type ForkliftStatus = 'idle' | 'running' | 'lifting' | 'loaded' | 'error'\n\n/** Forklift 컴포넌트 state */\nexport interface ForkliftState extends State {\n // ── 운영 상태 ──\n status?: ForkliftStatus\n\n // ── 액추에이터/시뮬레이션 ──\n forkHeight?: number\n speed?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\n/** Body color — yellow base hue, modulated slightly by status for at-a-glance state. */\nconst BODY_LEGEND = {\n idle: '#d4a017', // muted yellow (parked)\n running: '#FFD700', // bright yellow (active)\n lifting: '#FFD700',\n loaded: '#FFA500', // orange tint (carrying)\n error: '#e9746b', // red\n default: '#d4a017'\n}\n\n/** Status lamp emissive — saturated for at-a-glance status from a distance. */\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#333333',\n running: '#44ff44', // green (operating)\n lifting: '#44aaff', // blue (lifting)\n loaded: '#ffaa00', // amber (loaded)\n error: '#ff3333', // red\n default: '#333333'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Running', value: 'running' },\n { display: 'Lifting', value: 'lifting' },\n { display: 'Loaded', value: 'loaded' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'fork-height',\n name: 'forkHeight',\n placeholder: '0..2000mm'\n },\n {\n // Forklift travel speed in scene units / second. Used by `Mover.moveTo`\n // to derive motion duration as `distance / speed × 1000`. Set to a value\n // that gives the look you want on this board's scale — defaults are\n // intentionally unguessed (board scale varies).\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/forklift'\n}\n\n// Composition order:\n// FloorBound → Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract\n//\n// ContainerCapacity: receive() / dispatch() / canReceive() / slots — Mover.pick uses\n// receive() (slot-tracking path) instead of the reparent() fallback. Event emission\n// ('transfer-received', 'transfer-dispatched') fires on every cargo transfer.\n// CarrierHolder: attachPointFor() — fork-tip 3D mount frame; Carriable.added()\n// calls applyHolderAttachPoint() which uses our override for 3D positioning.\n// Mover: moveTo / pick / place / pickAndPlace / executeMission.\n// FloorBound: rotation guard — outermost, sees state changes last.\nconst Base = FloorBound(Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract)))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n }\n}\n\n/**\n * Forklift — a powered industrial truck used to lift and transport material\n * over short distances.\n *\n * **Container + CarrierHolder for cargo carrying.** Children (boxes, parcels,\n * pallets) attach to the fork-tip frame published by `attachPointFor`, so\n * their 3D pose follows `state.forkHeight` automatically and they read as\n * \"sitting on the forks\". Multiple cargo items stack vertically on the forks.\n *\n * Procedural (non-GLB) build — keeps the hand-tuned silhouette in\n * `forklift-3d.ts`. The transportable layer is purely additive: it does not\n * change the rendering path, only how children's 3D objects are reparented.\n */\n@sceneComponent('forklift')\nexport default class Forklift extends Base {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): ForkliftState {\n return super.state as ForkliftState\n }\n\n declare _realObject: Forklift3D | undefined\n\n /**\n * Phase H — pickup contract 호환성 검사용. carrier 의 pickupFramesFor 가\n * 'forklift-fork' toolType 으로 query 받았을 때 매칭되는 entry 만 통과한다.\n * Pallet 처럼 fork 진입 가능한 carrier 는 이 type 에 호환되는 entry 를 선언하고,\n * 호환 안 되는 carrier (예: drum 의 magnet-only) 는 자동으로 reject 된다.\n *\n * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아\n * TS4113 회피.)\n */\n get toolType(): string {\n return 'forklift-fork'\n }\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n /**\n * Forklift sits on its wheels — `floor` archetype. Default depth is the\n * forklift's overall envelope with mast collapsed (~= operation level).\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.operation - h.floor\n\n /**\n * Heading yaw offset (in radians) applied by `Mover.moveTo` when feeding\n * direction-of-travel to the Waypoint animation. Forklift uses\n * things-scene's standard vehicle convention (forward = -Z, Three.js\n * camera convention) — same as the framework default. Stated explicitly\n * here so the model's forward axis is documented at the class level\n * rather than being a silent dependency on the framework default.\n */\n static yawOffset = Math.PI / 2\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n // ── ContainerCapacity ─────────────────────────────────────────────────────\n\n /**\n * Forks hold up to 3 stacked load units (pallet + boxes).\n * 3D stacking is handled by `attachPointFor()` (Y offset per slotIdx) —\n * the slot maxCount here is the hard cap for `canReceive()`.\n */\n get slots(): SlotDef[] {\n // Single-cargo 정책: 이미 1개 보유 시 `canReceive()` → false → `pick()` 거부.\n return [{ id: 'forks', maxCount: 1 }]\n }\n\n /**\n * Forklift 정책: **파레트만** 자식으로 받음. 박스, 카트론, 파셀 등은 받지 않음.\n * 실제 forklift 가 fork pocket 을 통해 들기 때문에 pocket 구조가 있는 파레트만\n * 들 수 있음. 박스 등은 AGV 같은 평탄 데크 차량이 운반.\n */\n containable(component: Component) {\n return (component as any)?.model?.type === 'pallet'\n }\n\n /**\n * Forklift has a single mount frame: the **forks**. Multiple cargo items\n * stack vertically on the forks (slotIdx → +Y by cargo depth).\n *\n * Returns:\n * - `attach`: the Forklift's RealObjectGroup root (`object3d`). The\n * fork-tip *position* is supplied via `localPosition` rather than a\n * dedicated 3D mount frame, because Forklift3D rebuilds on\n * forkHeight changes and any nested mount node would be re-created\n * on each rebuild (losing scene-graph identity for cargo).\n * - `localPosition`: `Forklift3D.cargoMount.{x,y,z}` (already includes\n * forkHeight) plus a per-slot Y offset for stacking.\n *\n * On forkHeight changes, `onchange` below re-runs this for each child so\n * cargo tracks the lift.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.cargoMount) return undefined\n\n // Attach onto modelGroup (rotated to match things-scene -Z forward\n // convention) so the carrier follows the visible mesh's rotation. The\n // cargoMount coordinates are authored in the model's natural +Z-forward\n // frame, which IS the modelGroup's local frame — so localPosition\n // applies directly without an extra rotation.\n const attach = ro.modelGroup ?? ro.object3d\n if (!attach) return undefined\n\n // mount: { x, y (fork blade top), z (mid-fork), width, depth (= forkLen) }\n const mount = ro.cargoMount as { x: number; y: number; z: number; depth: number }\n const slotIdx = this._slotIndexOf(carrier)\n const cargoDepth = (carrier.state as any).depth ?? 100\n // carrier 의 carrier-local 의 fork 방향 길이 (height = -Z forward 방향).\n const carrierLenAlongFork = (carrier.state as any).height ?? cargoDepth\n\n // Fork-in-pocket 정렬: forklift 의 fork blade 가 파레트의 fork pocket 공간 안으로\n // 들어가도록 carrier center y = mount.y (= fork blade top). carrier 의 하반부가 fork\n // 아래로 내려와 fork 를 감쌈 (pocket 공간). carrier 의 상반부는 fork 위쪽 = 적재 영역.\n // 이게 실제 forklift-pallet 결합 시각과 일치.\n // (single-cargo 정책이라 slotIdx 는 항상 0; 멀티 stack 시 cargoDepth 로 stack)\n const y = mount.y + slotIdx * cargoDepth\n\n // Z (fork 방향): carrier 의 forklift-쪽 면(= +Z, 본체에 가까운 쪽)을 **fork heel\n // (= 포크의 시작부, 본체와 만나는 지점)** 에 정렬. carrier 본체는 fork tip 쪽으로\n // 뻗어나가며 forklift 본체 안으로는 절대 파고들지 않음. 큰 파레트는 fork tip 너머로\n // overhang.\n // mount.z 는 fork mid, mount.depth 는 fork length. fork heel = mount.z + forkLen/2\n // (heel 이 본체 쪽 = +Z, less negative).\n const forkHeelZ = mount.z + mount.depth / 2\n const z = forkHeelZ - carrierLenAlongFork / 2\n\n return {\n attach,\n localPosition: { x: mount.x, y, z },\n // Phase F: forklift 가 회전하면 fork 의 cargo 도 함께 회전 (fork 위 lock).\n carryPolicy: 'follow-holder'\n }\n }\n\n /**\n * Stack slot = the carrier's index among its sibling children. Stable\n * because things-scene preserves children order, so re-calling\n * `attachPointFor` after a forkHeight change keeps each cargo at its\n * original stack position. No persisted state needed.\n *\n * If the carrier isn't a child yet (mid-reparent — `attachPointFor` is\n * invoked from CarrierHolder.reparent before/around the add), default\n * to \"next free slot\" = current child count, which becomes its index\n * once the add settles.\n */\n private _slotIndexOf(carrier: Component): number {\n const children = this.components ?? []\n const idx = children.indexOf(carrier)\n return idx < 0 ? children.length : idx\n }\n\n /**\n * engage 보완: pick 시 fork blade top 의 world Y 가 carrier (파레트) 의\n * world center Y 와 일치하도록 forkHeight 를 들어올림. → fork 가 pallet 의\n * 높이 중간 (= pocket 공간) 에 정확히 위치.\n *\n * forklift-3d 의 cargoMountLocal 공식:\n * mount.y = -forklift.depth/2 + wheelR + forkHeight + forkH\n * fork blade top 의 world Y = forklift.world.y + mount.y (origin=center 가정).\n *\n * 목표: fork blade top world Y = pallet world center Y\n * ⟹ forkHeight = pallet_worldY - forklift_worldY + forklift.depth/2 - wheelR - forkH\n *\n * forkHeight clamp: [0, forklift.depth * 0.85] (cargoMountLocal 의 hard cap).\n */\n async engage(target: Component, kind: 'pick' | 'place', _options: any = {}): Promise<void> {\n if (kind !== 'pick') return\n\n const myObj3d = (this as any)._realObject?.object3d\n const targetObj3d = (target as any)._realObject?.object3d\n if (!myObj3d || !targetObj3d) return\n\n myObj3d.updateWorldMatrix(true, false)\n targetObj3d.updateWorldMatrix(true, false)\n const myPos = new THREE.Vector3()\n const targetPos = new THREE.Vector3()\n myObj3d.getWorldPosition(myPos)\n targetObj3d.getWorldPosition(targetPos)\n\n const forkliftDepth = (this.state as any).depth ?? 200\n const wheelR = forkliftDepth * 0.07\n const forkH = forkliftDepth * 0.018\n const desired = targetPos.y - myPos.y + forkliftDepth / 2 - wheelR - forkH\n const targetForkHeight = Math.max(0, Math.min(desired, forkliftDepth * 0.85))\n this.setState?.({ forkHeight: targetForkHeight })\n // mast 재구성 + attach point 재계산 안정화 시간 — sim-time 기준 (frameClock).\n // speed 변경 시에도 sim animation 과 같은 시간축 유지.\n await new Promise<void>(resolve => {\n let elapsed = 0\n let unsub: (() => void) | null = null\n unsub = frameClock.subscribe(\n simDt => {\n elapsed += simDt\n if (elapsed >= 200) {\n unsub?.()\n unsub = null\n resolve()\n }\n },\n { description: 'forklift mast settle' }\n )\n })\n }\n\n /**\n * pick 보완: super.pick 후 자세 정렬 + attachPointFor 강제 재적용.\n * 흐름의 마지막에 ContainerCapacity.receive 의 setState({left, top}) pipeline 이\n * obj3d.position 을 덮는 것을 다시 잡음. suppressTransform=true 로 향후 차단.\n * forklift 가 움직이면 carrier 는 scene-graph 통해 자동 추종.\n */\n override async pick(carrier: Component, options: any = {}): Promise<void> {\n if (!(this as any).canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'forklift-at-capacity'\n })\n return\n }\n await super.pick(carrier, options)\n carrier.setState?.({ rotation: 0, rotationX: 0, rotationY: 0 })\n this._snapToAttachPoint(carrier)\n }\n\n private _snapToAttachPoint(carrier: Component): void {\n const point = this.attachPointFor(carrier)\n const ro = (carrier as any)._realObject\n const obj3d = ro?.object3d\n if (!obj3d || !point?.localPosition) return\n const lp = point.localPosition\n obj3d.position.set(lp.x, lp.y, lp.z)\n if (point.localRotation) {\n obj3d.rotation.set(point.localRotation.x, point.localRotation.y, point.localRotation.z)\n } else {\n obj3d.quaternion.identity()\n }\n if (ro) ro.suppressTransform = true\n }\n\n /**\n * 2D — top-down silhouette of a forklift. Layout (top-down view):\n *\n * ┌──────────────────────┐ ← canvas top edge (forks point here, -Z forward)\n * │ ▒ forks ▒ │\n * │ ▒ ▒ │\n * │═════ mast bar ═══════│\n * │ ┌─────cab──────┐ │\n * │ │ ▢ seat │ │ ← driver in cab (faces forks/top edge)\n * │ └───────────────┘ │\n * │ ● ● rear-w │\n * ╰──────────────────────╯ ← counterweight bulge at bottom edge\n * ● ●\n * tail lights\n *\n * `forks` extend toward `top` (canvas y minus). Counterweight bulge\n * extends past `top + height` (canvas y plus). Matches things-scene's\n * vehicle convention (rotation=0 → vehicle points to canvas top edge).\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const forkLen = height * 0.28\n const cwBulge = height * 0.08\n const radius = Math.min(width, height) * 0.08\n\n // Body silhouette: from (top + forkLen) past (top + height) by cwBulge.\n // `top..top+forkLen` is reserved for the fork prongs (drawn in postrender).\n ctx.beginPath()\n ctx.roundRect(left, top + forkLen, width, height - forkLen + cwBulge, radius)\n }\n\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const forkLen = height * 0.28\n const cwBulge = height * 0.08\n const accentColor = (this.state.lampEmissive as string) || '#44ff44'\n const mastY = top + forkLen\n const bodyBottom = top + height + cwBulge\n\n ctx.save()\n\n // Mast bar — horizontal across the front of the body, where forks attach\n ctx.fillStyle = '#444455'\n ctx.fillRect(left + width * 0.10, mastY - height * 0.022, width * 0.80, height * 0.025)\n\n // Forks — two prongs extending toward canvas top (forward = -Z)\n const forkW = width * 0.10\n ctx.fillStyle = '#222233'\n ctx.strokeStyle = '#111'\n ctx.lineWidth = 1\n for (const xFrac of [0.20, 0.70]) {\n ctx.beginPath()\n ctx.rect(left + width * xFrac, top, forkW, forkLen)\n ctx.fill()\n ctx.stroke()\n }\n\n // Cab outline (centered, just behind the mast)\n const cabL = left + width * 0.18\n const cabT = mastY + height * 0.05\n const cabW = width * 0.64\n const cabH = height * 0.38\n ctx.fillStyle = 'rgba(0,0,0,0.06)'\n ctx.strokeStyle = '#333'\n ctx.lineWidth = 1\n ctx.fillRect(cabL, cabT, cabW, cabH)\n ctx.strokeRect(cabL, cabT, cabW, cabH)\n\n // Driver seat hint (rear half of cab — driver faces forks)\n ctx.fillStyle = '#1a1a22'\n ctx.fillRect(\n left + width * 0.40,\n cabT + cabH * 0.50,\n width * 0.20,\n cabH * 0.35\n )\n\n // Steering wheel hint (front of cab)\n ctx.fillStyle = '#222'\n ctx.beginPath()\n ctx.arc(left + width / 2, cabT + cabH * 0.30, Math.min(width, height) * 0.05, 0, Math.PI * 2)\n ctx.stroke()\n\n // Wheels — 4 corners. Front (drive) wheels behind mast; rear (caster) at bottom.\n ctx.fillStyle = '#1a1a1a'\n const wheelW = width * 0.07\n const wheelH = height * 0.10\n // Front wheels — just below mast, sides of body\n ctx.fillRect(left + width * 0.04, mastY + height * 0.02, wheelW, wheelH)\n ctx.fillRect(left + width - width * 0.04 - wheelW, mastY + height * 0.02, wheelW, wheelH)\n // Rear wheels — near body bottom (excluding cwBulge)\n const rearWheelY = top + height - wheelH * 1.1\n ctx.fillRect(left + width * 0.10, rearWheelY, wheelW * 0.85, wheelH * 0.85)\n ctx.fillRect(left + width - width * 0.10 - wheelW * 0.85, rearWheelY, wheelW * 0.85, wheelH * 0.85)\n\n // Status lamp on overhead guard top (over cab, near front)\n const lampR = Math.min(width, height) * 0.045\n ctx.fillStyle = accentColor\n ctx.strokeStyle = '#111'\n ctx.beginPath()\n ctx.ellipse(left + width / 2, cabT + cabH * 0.15, lampR, lampR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // Tail lights — bottom of counterweight bulge\n ctx.fillStyle = '#ff2222'\n const tailLightW = width * 0.08\n const tailLightH = height * 0.022\n const tailY = bodyBottom - tailLightH * 1.5\n ctx.fillRect(left + width * 0.15, tailY, tailLightW, tailLightH)\n ctx.fillRect(left + width - width * 0.15 - tailLightW, tailY, tailLightW, tailLightH)\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#d4a017'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Forklift3D(this)\n }\n\n /**\n * forkHeight change → mast & forks rebuild AND cargo Y must follow.\n *\n * Forklift3D.onchange already handles the mast/fork rebuild via update().\n * What's missing is moving each carrier's `object3d` to the new fork-tip\n * position. We do that here at the component level (not in Forklift3D)\n * because the attach-point policy lives on the holder component, and\n * Carriable's `applyHolderAttachPoint` is the established protocol.\n *\n * Carriable children expose `applyHolderAttachPoint` — calling it makes\n * them re-fetch our `attachPointFor` and snap to the new localPosition.\n * For non-Carriable children we fall through to direct object3d update\n * so legacy cargo (operation-archetype components without the Carriable\n * mixin) still tracks the lift.\n */\n onchange(after: any, before: any) {\n super.onchange?.(after, before)\n\n if (!('forkHeight' in (after ?? {}))) return\n\n const children = this.components ?? []\n for (const child of children) {\n if ((child as any).applyHolderAttachPoint) {\n ;(child as any).applyHolderAttachPoint()\n continue\n }\n // Non-Carriable cargo: snap object3d directly using our attach policy.\n const point = this.attachPointFor(child)\n const obj3d = (child as any)._realObject?.object3d\n if (point?.localPosition && obj3d?.position) {\n obj3d.position.set(point.localPosition.x, point.localPosition.y, point.localPosition.z)\n }\n }\n }\n\n}\n"]}
package/dist/tugger.d.ts CHANGED
@@ -40,6 +40,13 @@ declare const Base: typeof Component & {
40
40
  */
41
41
  export default class Tugger extends Base {
42
42
  get state(): TuggerState;
43
+ /**
44
+ * Phase H — Tugger 는 trailer 를 끌고 다님. carrier (trailer) 의
45
+ * 'tugger-hitch' entry 가 있으면 매칭.
46
+ * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아
47
+ * TS4113 회피.)
48
+ */
49
+ get toolType(): string;
43
50
  static legends: Record<string, LegendBinding>;
44
51
  static placement: PlacementArchetype;
45
52
  static align: Alignment;
package/dist/tugger.js CHANGED
@@ -80,6 +80,15 @@ let Tugger = class Tugger extends Base {
80
80
  get state() {
81
81
  return super.state;
82
82
  }
83
+ /**
84
+ * Phase H — Tugger 는 trailer 를 끌고 다님. carrier (trailer) 의
85
+ * 'tugger-hitch' entry 가 있으면 매칭.
86
+ * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아
87
+ * TS4113 회피.)
88
+ */
89
+ get toolType() {
90
+ return 'tugger-hitch';
91
+ }
83
92
  static legends = {
84
93
  bodyColor: { from: 'status', legend: BODY_LEGEND },
85
94
  lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
@@ -1 +1 @@
1
- {"version":3,"file":"tugger.js","sourceRoot":"","sources":["../src/tugger.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA8B,iBAAiB,EAAc,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAElH,OAAO,EACL,aAAa,EACb,UAAU,EACV,UAAU,EACV,KAAK,EACL,SAAS,EAOV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AA4BzC,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,MAAM;IAChB,KAAK,EAAE,MAAM;IACb,OAAO,EAAE,MAAM;CAChB,CAAA;AAED,MAAM,oBAAoB,GAAG;IAC3B,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,QAAQ,EAAE,SAAS;IACnB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,SAAS;CACnB,CAAA;AAED,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,QAAQ;YACf,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE;gBACR,OAAO,EAAE;oBACP,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;oBAClC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;oBACtC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;oBAC1C,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;iBACrC;aACF;SACF;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,SAAS;YAChB,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,MAAM;SACpB;QACD;YACE,oEAAoE;YACpE,mDAAmD;YACnD,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,OAAO;YACd,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,WAAW;SACzB;KACF;IACD,IAAI,EAAE,wBAAwB;CAC/B,CAAA;AAED,gGAAgG;AAChG,wEAAwE;AACxE,0EAA0E;AAC1E,mEAAmE;AACnE,oBAAoB;AACpB,EAAE;AACF,2FAA2F;AAC3F,mEAAmE;AACnE,oEAAoE;AACpE,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CASrF,CAAA;AAED;;;;;;;;GAQG;AAEY,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,IAAI;IACtC,qEAAqE;IACrE,wEAAwE;IACxE,4EAA4E;IAC5E,IAAa,KAAK;QAChB,OAAO,KAAK,CAAC,KAAoB,CAAA;IACnC,CAAC;IAED,MAAM,CAAC,OAAO,GAAkC;QAC9C,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE;QAClD,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,oBAAoB,EAAE;KAC/D,CAAA;IAED,MAAM,CAAC,SAAS,GAAuB,OAAO,CAAA;IAC9C,MAAM,CAAC,KAAK,GAAc,QAAQ,CAAA;IAClC,MAAM,CAAC,YAAY,GAAG,GAAG,CAAA;IAEzB;;;;OAIG;IACH,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAA;IAE9B,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED;;;;;;;;OAQG;IACH,cAAc,CAAC,OAAkB;QAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAA;QAC3B,IAAI,CAAC,EAAE,EAAE,QAAQ;YAAE,OAAO,SAAS,CAAA;QAEnC,MAAM,KAAK,GAAI,IAAI,CAAC,KAAK,CAAC,KAAgB,IAAI,GAAG,CAAA;QACjD,MAAM,MAAM,GAAI,IAAI,CAAC,KAAK,CAAC,MAAiB,IAAI,GAAG,CAAA;QACnD,OAAO;YACL,MAAM,EAAE,EAAE,CAAC,QAAQ;YACnB,aAAa,EAAE;gBACb,CAAC,EAAE,CAAC;gBACJ,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,EAAE,mBAAmB;gBACtC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,aAAa;aAC/B;SACF,CAAA;IACH,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC7C,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;IACxE,CAAC;IAED,UAAU,CAAC,GAA6B;QACtC,KAAK,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAA;QAEvB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,MAAM,EAAE,GAAG,IAAI,GAAG,KAAK,GAAG,CAAC,CAAA;QAC3B,MAAM,WAAW,GAAI,IAAI,CAAC,KAAK,CAAC,YAAuB,IAAI,SAAS,CAAA;QAEpE,GAAG,CAAC,IAAI,EAAE,CAAA;QAEV,2DAA2D;QAC3D,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,WAAW,GAAG,MAAM,CAAA;QACxB,GAAG,CAAC,SAAS,GAAG,CAAC,CAAA;QACjB,MAAM,MAAM,GAAG,KAAK,GAAG,IAAI,CAAA;QAC3B,MAAM,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;QAC5B,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QAClD,GAAG,CAAC,UAAU,CAAC,EAAE,GAAG,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QACpD,eAAe;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC5C,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QAC7D,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,MAAM,EAAE,CAAA;QAEZ,iCAAiC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC7C,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACvE,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,MAAM,EAAE,CAAA;QACZ,GAAG,CAAC,SAAS,GAAG,WAAW,CAAA;QAC3B,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACnF,GAAG,CAAC,IAAI,EAAE,CAAA;QAEV,+CAA+C;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC5C,GAAG,CAAC,SAAS,GAAG,WAAW,CAAA;QAC3B,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACrE,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,MAAM,EAAE,CAAA;QAEZ,GAAG,CAAC,OAAO,EAAE,CAAA;IACf,CAAC;IAED,IAAI,SAAS;QACX,OAAQ,IAAI,CAAC,KAAK,CAAC,SAAoB,IAAI,MAAM,CAAA;IACnD,CAAC;IAED,eAAe;QACb,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;;AA1HkB,MAAM;IAD1B,cAAc,CAAC,QAAQ,CAAC;GACJ,MAAM,CA2H1B;eA3HoB,MAAM","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Tugger3D } from './tugger-3d.js'\n\nimport type { AgvStatus } from './agv.js'\n\n/**\n * Tugger — a towing AGV. Pulls trailers / carts behind it; has no cargo deck\n * of its own. Common in mail sorting, automotive line-side delivery, hospital\n * supply transport.\n *\n * Status enum is reused from {@link AgvStatus} — the tugger and the unit-load\n * AGV share the same operating-state semantics. The structural difference\n * (cargo deck vs hitch) is in geometry, not state.\n */\nexport type TuggerStatus = AgvStatus\n\n/** Tugger 컴포넌트 state */\nexport interface TuggerState extends State {\n // ── 운영 상태 ──\n status?: TuggerStatus\n\n // ── 시뮬레이션/모니터링 ──\n battery?: number\n speed?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst BODY_LEGEND = {\n idle: '#666',\n moving: '#777',\n charging: '#777',\n error: '#c66',\n default: '#666'\n}\n\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44',\n charging: '#ffaa00',\n error: '#ff3333',\n default: '#222222'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Moving', value: 'moving' },\n { display: 'Charging', value: 'charging' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'battery',\n name: 'battery',\n placeholder: '0..1'\n },\n {\n // Tugger travel speed in scene units / second. Used by Mover.moveTo\n // to derive motion duration. Tune per board scale.\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/tugger'\n}\n\n// Composition: ContainerAbstract → Placeable → Legendable → CarrierHolder → Mover → FloorBound.\n// Same chain as Forklift / AGV — Tugger is a wheeled tractor that pulls\n// trailers (CarrierHolder), drives via Mover.moveTo, and stays upright on\n// its wheels (FloorBound). Trailer attach is via hitch, encoded in\n// `attachPointFor`.\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Tugger lives only in the 3D scene graph.\nconst Base = FloorBound(Mover(CarrierHolder(Legendable(Placeable(ContainerAbstract))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n }\n}\n\n/**\n * Tugger has no cargo deck — just a powered tractor with a hitch. Trailers\n * are separate scene components and are linked by data binding (a\n * `currentTrailer` data field) rather than scene-tree containment, since\n * trailers swap dynamically and don't share the tractor's transform.\n *\n * Default depth is shorter than a payload AGV — there's no operation surface\n * to align to. ~600mm is enough for the motor housing + control panel.\n */\n@sceneComponent('tugger')\nexport default class Tugger extends Base {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): TuggerState {\n return super.state as TuggerState\n }\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = 600\n\n /**\n * Heading yaw offset (rad). things-scene's vehicle convention is\n * `vehicle forward = component-local -Z` (= \"rotation=0 → toward canvas\n * top edge\"); the framework default `Math.PI / 2` aligns with that.\n */\n static yawOffset = Math.PI / 2\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * Tugger attach point — rear hitch (where trailer kingpin couples).\n * Trailers are typically linked by data binding rather than scene-tree\n * containment, but if a trailer IS reparented under the tugger (e.g. for\n * a unified animation), it lands at the hitch position.\n *\n * The hitch sits at the rear of the chassis (component-local +Z, since\n * vehicle forward is -Z) at the chassis floor.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.object3d) return undefined\n\n const depth = (this.state.depth as number) ?? 600\n const height = (this.state.height as number) ?? 250\n return {\n attach: ro.object3d,\n localPosition: {\n x: 0,\n y: -depth / 2 + 5, // just above floor\n z: height * 0.45 // rear hitch\n }\n }\n }\n\n /**\n * 2D — render() draws the rounded body silhouette (auto-filled with\n * bodyColor); postrender() adds the hitch arm + LiDAR + status lamp.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const radius = Math.min(width, height) * 0.18\n ctx.beginPath()\n ctx.roundRect(left, top + height * 0.10, width, height * 0.80, radius)\n }\n\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const cx = left + width / 2\n const accentColor = (this.state.lampEmissive as string) || '#44ff44'\n\n ctx.save()\n\n // Hitch arm + coupler at rear (top in top-down convention)\n ctx.fillStyle = '#222233'\n ctx.strokeStyle = '#111'\n ctx.lineWidth = 1\n const hitchW = width * 0.12\n const hitchH = height * 0.10\n ctx.fillRect(cx - hitchW / 2, top, hitchW, hitchH)\n ctx.strokeRect(cx - hitchW / 2, top, hitchW, hitchH)\n // Coupler ball\n const ballR = Math.min(width, height) * 0.04\n ctx.fillStyle = '#444455'\n ctx.beginPath()\n ctx.ellipse(cx, top + ballR, ballR, ballR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // LiDAR sensor at front (bottom)\n const lidarR = Math.min(width, height) * 0.07\n ctx.fillStyle = '#222233'\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.85, lidarR, lidarR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.85, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n // Status lamp on top of control panel (center)\n const lampR = Math.min(width, height) * 0.06\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.45, lampR, lampR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#666'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Tugger3D(this)\n }\n}\n"]}
1
+ {"version":3,"file":"tugger.js","sourceRoot":"","sources":["../src/tugger.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 type { State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Tugger3D } from './tugger-3d.js'\n\nimport type { AgvStatus } from './agv.js'\n\n/**\n * Tugger — a towing AGV. Pulls trailers / carts behind it; has no cargo deck\n * of its own. Common in mail sorting, automotive line-side delivery, hospital\n * supply transport.\n *\n * Status enum is reused from {@link AgvStatus} — the tugger and the unit-load\n * AGV share the same operating-state semantics. The structural difference\n * (cargo deck vs hitch) is in geometry, not state.\n */\nexport type TuggerStatus = AgvStatus\n\n/** Tugger 컴포넌트 state */\nexport interface TuggerState extends State {\n // ── 운영 상태 ──\n status?: TuggerStatus\n\n // ── 시뮬레이션/모니터링 ──\n battery?: number\n speed?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst BODY_LEGEND = {\n idle: '#666',\n moving: '#777',\n charging: '#777',\n error: '#c66',\n default: '#666'\n}\n\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44',\n charging: '#ffaa00',\n error: '#ff3333',\n default: '#222222'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Moving', value: 'moving' },\n { display: 'Charging', value: 'charging' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'battery',\n name: 'battery',\n placeholder: '0..1'\n },\n {\n // Tugger travel speed in scene units / second. Used by Mover.moveTo\n // to derive motion duration. Tune per board scale.\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/tugger'\n}\n\n// Composition: ContainerAbstract → Placeable → Legendable → CarrierHolder → Mover → FloorBound.\n// Same chain as Forklift / AGV — Tugger is a wheeled tractor that pulls\n// trailers (CarrierHolder), drives via Mover.moveTo, and stays upright on\n// its wheels (FloorBound). Trailer attach is via hitch, encoded in\n// `attachPointFor`.\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Tugger lives only in the 3D scene graph.\nconst Base = FloorBound(Mover(CarrierHolder(Legendable(Placeable(ContainerAbstract))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n }\n}\n\n/**\n * Tugger has no cargo deck — just a powered tractor with a hitch. Trailers\n * are separate scene components and are linked by data binding (a\n * `currentTrailer` data field) rather than scene-tree containment, since\n * trailers swap dynamically and don't share the tractor's transform.\n *\n * Default depth is shorter than a payload AGV — there's no operation surface\n * to align to. ~600mm is enough for the motor housing + control panel.\n */\n@sceneComponent('tugger')\nexport default class Tugger extends Base {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): TuggerState {\n return super.state as TuggerState\n }\n\n /**\n * Phase H — Tugger 는 trailer 를 끌고 다님. carrier (trailer) 의\n * 'tugger-hitch' entry 가 있으면 매칭.\n * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아\n * TS4113 회피.)\n */\n get toolType(): string {\n return 'tugger-hitch'\n }\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = 600\n\n /**\n * Heading yaw offset (rad). things-scene's vehicle convention is\n * `vehicle forward = component-local -Z` (= \"rotation=0 → toward canvas\n * top edge\"); the framework default `Math.PI / 2` aligns with that.\n */\n static yawOffset = Math.PI / 2\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * Tugger attach point — rear hitch (where trailer kingpin couples).\n * Trailers are typically linked by data binding rather than scene-tree\n * containment, but if a trailer IS reparented under the tugger (e.g. for\n * a unified animation), it lands at the hitch position.\n *\n * The hitch sits at the rear of the chassis (component-local +Z, since\n * vehicle forward is -Z) at the chassis floor.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.object3d) return undefined\n\n const depth = (this.state.depth as number) ?? 600\n const height = (this.state.height as number) ?? 250\n return {\n attach: ro.object3d,\n localPosition: {\n x: 0,\n y: -depth / 2 + 5, // just above floor\n z: height * 0.45 // rear hitch\n }\n }\n }\n\n /**\n * 2D — render() draws the rounded body silhouette (auto-filled with\n * bodyColor); postrender() adds the hitch arm + LiDAR + status lamp.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const radius = Math.min(width, height) * 0.18\n ctx.beginPath()\n ctx.roundRect(left, top + height * 0.10, width, height * 0.80, radius)\n }\n\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const cx = left + width / 2\n const accentColor = (this.state.lampEmissive as string) || '#44ff44'\n\n ctx.save()\n\n // Hitch arm + coupler at rear (top in top-down convention)\n ctx.fillStyle = '#222233'\n ctx.strokeStyle = '#111'\n ctx.lineWidth = 1\n const hitchW = width * 0.12\n const hitchH = height * 0.10\n ctx.fillRect(cx - hitchW / 2, top, hitchW, hitchH)\n ctx.strokeRect(cx - hitchW / 2, top, hitchW, hitchH)\n // Coupler ball\n const ballR = Math.min(width, height) * 0.04\n ctx.fillStyle = '#444455'\n ctx.beginPath()\n ctx.ellipse(cx, top + ballR, ballR, ballR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // LiDAR sensor at front (bottom)\n const lidarR = Math.min(width, height) * 0.07\n ctx.fillStyle = '#222233'\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.85, lidarR, lidarR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.85, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n // Status lamp on top of control panel (center)\n const lampR = Math.min(width, height) * 0.06\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.45, lampR, lampR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#666'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Tugger3D(this)\n }\n}\n"]}
package/dist/worker.d.ts CHANGED
@@ -32,6 +32,14 @@ declare const Base: typeof Component & {
32
32
  };
33
33
  export default class Worker extends Base {
34
34
  get state(): WorkerState;
35
+ /**
36
+ * Phase H — 사람 작업자. 손으로 carrier 를 들어올림. 일반적인 carrier 의
37
+ * 어떤 진입점도 사용 가능 — 'manual' type. carrier 가 'manual' type entry 를
38
+ * 노출하면 매칭. 미노출 carrier 는 'manual' / 'gripper' 둘 다 매칭하도록 자체
39
+ * 호환성 logic 으로 override 가능.
40
+ * (override 키워드 미사용 — TS4113 회피.)
41
+ */
42
+ get toolType(): string;
35
43
  static legends: Record<string, LegendBinding>;
36
44
  /**
37
45
  * Worker stands on the floor — `floor` archetype with `bottom` alignment.
package/dist/worker.js CHANGED
@@ -75,6 +75,16 @@ let Worker = class Worker extends Base {
75
75
  get state() {
76
76
  return super.state;
77
77
  }
78
+ /**
79
+ * Phase H — 사람 작업자. 손으로 carrier 를 들어올림. 일반적인 carrier 의
80
+ * 어떤 진입점도 사용 가능 — 'manual' type. carrier 가 'manual' type entry 를
81
+ * 노출하면 매칭. 미노출 carrier 는 'manual' / 'gripper' 둘 다 매칭하도록 자체
82
+ * 호환성 logic 으로 override 가능.
83
+ * (override 키워드 미사용 — TS4113 회피.)
84
+ */
85
+ get toolType() {
86
+ return 'manual';
87
+ }
78
88
  static legends = {
79
89
  vestColor: { from: 'status', legend: VEST_LEGEND },
80
90
  helmetEmissive: { from: 'status', legend: HELMET_EMISSIVE_LEGEND }
@@ -1 +1 @@
1
- {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.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 type { State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Worker3D } from './worker-3d.js'\n\n/**\n * Worker status — what a human operator is currently doing.\n *\n * - `idle` — present at station but not actively working\n * - `walking` — moving between stations\n * - `working` — actively performing a task\n * - `alarm` — emergency / call-for-help (e.g. line-stop pull-cord)\n *\n * Notably no `loaded` or `running` — workers don't carry the same kind of\n * payload semantics as a forklift, and \"running\" is ambiguous for a person.\n */\nexport type WorkerStatus = 'idle' | 'walking' | 'working' | 'alarm'\n\n/** Worker 컴포넌트 state */\nexport interface WorkerState extends State {\n // ── 운영 상태 ──\n status?: WorkerStatus\n\n // ── 정체 ──\n workerName?: string\n\n // ── 시뮬레이션 ──\n speed?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\n/**\n * Vest color — the hi-vis safety vest is the most visible status indicator\n * for human workers in a smart factory floor view.\n */\nconst VEST_LEGEND = {\n idle: '#FFD700', // standard hi-vis yellow\n walking: '#FFD700',\n working: '#FF8C00', // orange tint (busy)\n alarm: '#FF1744', // bright red (emergency)\n default: '#FFD700'\n}\n\n/** Helmet emissive — small accent indicator visible from above (camera view). */\nconst HELMET_EMISSIVE_LEGEND = {\n idle: '#222222',\n walking: '#44ff44',\n working: '#44aaff',\n alarm: '#ff3333',\n default: '#222222'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Walking', value: 'walking' },\n { display: 'Working', value: 'working' },\n { display: 'Alarm', value: 'alarm' }\n ]\n }\n },\n {\n type: 'string',\n label: 'name',\n name: 'workerName'\n },\n {\n // Walking speed in scene units / second. Used by Mover.moveTo to\n // derive motion duration. Workers walk slower than vehicles.\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/worker'\n}\n\n// Composition: ContainerAbstract → Placeable → Legendable → CarrierHolder → Mover → FloorBound.\n// A worker is a person who walks (Mover.moveTo) between stations and can\n// carry items by hand (CarrierHolder). They stay upright (FloorBound — no\n// pitch/roll). Same composition as a vehicle because the logical capabilities\n// are the same; the visual rendering (humanoid vs chassis) differs.\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Worker exists only in the 3D scene graph.\nconst Base = FloorBound(Mover(CarrierHolder(Legendable(Placeable(ContainerAbstract))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n }\n}\n\n@sceneComponent('worker')\nexport default class Worker extends Base {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): WorkerState {\n return super.state as WorkerState\n }\n\n static legends: Record<string, LegendBinding> = {\n vestColor: { from: 'status', legend: VEST_LEGEND },\n helmetEmissive: { from: 'status', legend: HELMET_EMISSIVE_LEGEND }\n }\n\n /**\n * Worker stands on the floor — `floor` archetype with `bottom` alignment.\n * Default depth is ~1700mm (typical adult height); the head reaches just\n * above the operation surface, which makes a worker visually adjacent to a\n * conveyor in a side view but clearly above it in a front view.\n *\n * Note this is human height, not \"operation - floor\" — the worker is taller\n * than the conveyor by design. Use the explicit number form rather than the\n * heights-derived function form.\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = 1700\n\n /**\n * Heading yaw offset (rad). things-scene's vehicle convention is\n * `vehicle forward = component-local -Z` (= \"rotation=0 → toward canvas\n * top edge\"); the framework default `Math.PI / 2` aligns with that.\n * Workers face their walking direction.\n */\n static yawOffset = Math.PI / 2\n\n /**\n * Worker carry point — at chest level in front of the worker. Boxes/\n * parcels carried by hand land here. Approximation: 1.0m above floor,\n * 0.3m forward of body center.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.object3d) return undefined\n\n const depth = (this.state.depth as number) ?? 1700\n const height = (this.state.height as number) ?? 50\n return {\n attach: ro.object3d,\n localPosition: {\n x: 0,\n y: -depth / 2 + 1000, // ~1m above floor (chest level)\n z: -height * 0.3 // forward of body center\n }\n }\n }\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — top-down view of a person from above: shoulders / vest as the main\n * filled silhouette (auto-filled with vestColor), helmet circle drawn over\n * it in postrender(). The helmet sits at the *facing* direction (top of\n * the bounds), so the orientation is readable.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n // Shoulders: a rounded rect, wider than tall (chest seen from above)\n const shoulderH = height * 0.55\n const shoulderY = top + height * 0.30\n const radius = Math.min(width, shoulderH) * 0.30\n ctx.beginPath()\n ctx.roundRect(left, shoulderY, width, shoulderH, radius)\n }\n\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const cx = left + width / 2\n const helmetEmissive = (this.state.helmetEmissive as string) || '#222'\n\n ctx.save()\n\n // Reflective stripes across the vest (two horizontal bands)\n ctx.fillStyle = '#eaeaea'\n ctx.fillRect(left + width * 0.08, top + height * 0.45, width * 0.84, height * 0.04)\n ctx.fillRect(left + width * 0.08, top + height * 0.65, width * 0.84, height * 0.04)\n\n // Helmet circle at the top (facing direction)\n const helmetR = Math.min(width, height * 0.4) * 0.45\n ctx.fillStyle = (this.state.vestColor as string) || '#FFD700'\n ctx.strokeStyle = '#222'\n ctx.lineWidth = 1\n ctx.beginPath()\n ctx.ellipse(cx, top + helmetR + height * 0.02, helmetR, helmetR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // Emissive helmet status accent (small dot on helmet)\n ctx.fillStyle = helmetEmissive\n ctx.beginPath()\n ctx.ellipse(cx, top + helmetR + height * 0.02, helmetR * 0.4, helmetR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.vestColor as string) || '#FFD700'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Worker3D(this)\n }\n}\n"]}
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA8B,iBAAiB,EAAc,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAElH,OAAO,EACL,aAAa,EACb,UAAU,EACV,UAAU,EACV,KAAK,EACL,SAAS,EAOV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AA8BzC;;;GAGG;AACH,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,SAAS,EAAM,yBAAyB;IAC9C,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS,EAAG,qBAAqB;IAC1C,KAAK,EAAE,SAAS,EAAK,yBAAyB;IAC9C,OAAO,EAAE,SAAS;CACnB,CAAA;AAED,iFAAiF;AACjF,MAAM,sBAAsB,GAAG;IAC7B,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,SAAS;CACnB,CAAA;AAED,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,QAAQ;YACf,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE;gBACR,OAAO,EAAE;oBACP,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;oBAClC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;oBACxC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;oBACxC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;iBACrC;aACF;SACF;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,YAAY;SACnB;QACD;YACE,iEAAiE;YACjE,6DAA6D;YAC7D,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,OAAO;YACd,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,WAAW;SACzB;KACF;IACD,IAAI,EAAE,wBAAwB;CAC/B,CAAA;AAED,gGAAgG;AAChG,yEAAyE;AACzE,0EAA0E;AAC1E,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,2FAA2F;AAC3F,mEAAmE;AACnE,qEAAqE;AACrE,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CASrF,CAAA;AAGc,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,IAAI;IACtC,qEAAqE;IACrE,wEAAwE;IACxE,4EAA4E;IAC5E,IAAa,KAAK;QAChB,OAAO,KAAK,CAAC,KAAoB,CAAA;IACnC,CAAC;IAED;;;;;;OAMG;IACH,IAAI,QAAQ;QACV,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,MAAM,CAAC,OAAO,GAAkC;QAC9C,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE;QAClD,cAAc,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,sBAAsB,EAAE;KACnE,CAAA;IAED;;;;;;;;;OASG;IACH,MAAM,CAAC,SAAS,GAAuB,OAAO,CAAA;IAC9C,MAAM,CAAC,KAAK,GAAc,QAAQ,CAAA;IAClC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAA;IAE1B;;;;;OAKG;IACH,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAA;IAE9B;;;;OAIG;IACH,cAAc,CAAC,OAAkB;QAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAA;QAC3B,IAAI,CAAC,EAAE,EAAE,QAAQ;YAAE,OAAO,SAAS,CAAA;QAEnC,MAAM,KAAK,GAAI,IAAI,CAAC,KAAK,CAAC,KAAgB,IAAI,IAAI,CAAA;QAClD,MAAM,MAAM,GAAI,IAAI,CAAC,KAAK,CAAC,MAAiB,IAAI,EAAE,CAAA;QAClD,OAAO;YACL,MAAM,EAAE,EAAE,CAAC,QAAQ;YACnB,aAAa,EAAE;gBACb,CAAC,EAAE,CAAC;gBACJ,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,GAAG,IAAI,EAAE,gCAAgC;gBACtD,CAAC,EAAE,CAAC,MAAM,GAAG,GAAG,CAAC,yBAAyB;aAC3C;SACF,CAAA;IACH,CAAC;IAED,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,qEAAqE;QACrE,MAAM,SAAS,GAAG,MAAM,GAAG,IAAI,CAAA;QAC/B,MAAM,SAAS,GAAG,GAAG,GAAG,MAAM,GAAG,IAAI,CAAA;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,IAAI,CAAA;QAChD,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,CAAA;IAC1D,CAAC;IAED,UAAU,CAAC,GAA6B;QACtC,KAAK,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAA;QAEvB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,MAAM,EAAE,GAAG,IAAI,GAAG,KAAK,GAAG,CAAC,CAAA;QAC3B,MAAM,cAAc,GAAI,IAAI,CAAC,KAAK,CAAC,cAAyB,IAAI,MAAM,CAAA;QAEtE,GAAG,CAAC,IAAI,EAAE,CAAA;QAEV,4DAA4D;QAC5D,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;QACnF,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;QAEnF,8CAA8C;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAA;QACpD,GAAG,CAAC,SAAS,GAAI,IAAI,CAAC,KAAK,CAAC,SAAoB,IAAI,SAAS,CAAA;QAC7D,GAAG,CAAC,WAAW,GAAG,MAAM,CAAA;QACxB,GAAG,CAAC,SAAS,GAAG,CAAC,CAAA;QACjB,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACnF,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,MAAM,EAAE,CAAA;QAEZ,sDAAsD;QACtD,GAAG,CAAC,SAAS,GAAG,cAAc,CAAA;QAC9B,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,EAAE,OAAO,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QAC/F,GAAG,CAAC,IAAI,EAAE,CAAA;QAEV,GAAG,CAAC,OAAO,EAAE,CAAA;IACf,CAAC;IAED,IAAI,SAAS;QACX,OAAQ,IAAI,CAAC,KAAK,CAAC,SAAoB,IAAI,SAAS,CAAA;IACtD,CAAC;IAED,eAAe;QACb,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;;AAlIkB,MAAM;IAD1B,cAAc,CAAC,QAAQ,CAAC;GACJ,MAAM,CAmI1B;eAnIoB,MAAM","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Worker3D } from './worker-3d.js'\n\n/**\n * Worker status — what a human operator is currently doing.\n *\n * - `idle` — present at station but not actively working\n * - `walking` — moving between stations\n * - `working` — actively performing a task\n * - `alarm` — emergency / call-for-help (e.g. line-stop pull-cord)\n *\n * Notably no `loaded` or `running` — workers don't carry the same kind of\n * payload semantics as a forklift, and \"running\" is ambiguous for a person.\n */\nexport type WorkerStatus = 'idle' | 'walking' | 'working' | 'alarm'\n\n/** Worker 컴포넌트 state */\nexport interface WorkerState extends State {\n // ── 운영 상태 ──\n status?: WorkerStatus\n\n // ── 정체 ──\n workerName?: string\n\n // ── 시뮬레이션 ──\n speed?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\n/**\n * Vest color — the hi-vis safety vest is the most visible status indicator\n * for human workers in a smart factory floor view.\n */\nconst VEST_LEGEND = {\n idle: '#FFD700', // standard hi-vis yellow\n walking: '#FFD700',\n working: '#FF8C00', // orange tint (busy)\n alarm: '#FF1744', // bright red (emergency)\n default: '#FFD700'\n}\n\n/** Helmet emissive — small accent indicator visible from above (camera view). */\nconst HELMET_EMISSIVE_LEGEND = {\n idle: '#222222',\n walking: '#44ff44',\n working: '#44aaff',\n alarm: '#ff3333',\n default: '#222222'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Walking', value: 'walking' },\n { display: 'Working', value: 'working' },\n { display: 'Alarm', value: 'alarm' }\n ]\n }\n },\n {\n type: 'string',\n label: 'name',\n name: 'workerName'\n },\n {\n // Walking speed in scene units / second. Used by Mover.moveTo to\n // derive motion duration. Workers walk slower than vehicles.\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/worker'\n}\n\n// Composition: ContainerAbstract → Placeable → Legendable → CarrierHolder → Mover → FloorBound.\n// A worker is a person who walks (Mover.moveTo) between stations and can\n// carry items by hand (CarrierHolder). They stay upright (FloorBound — no\n// pitch/roll). Same composition as a vehicle because the logical capabilities\n// are the same; the visual rendering (humanoid vs chassis) differs.\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Worker exists only in the 3D scene graph.\nconst Base = FloorBound(Mover(CarrierHolder(Legendable(Placeable(ContainerAbstract))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n }\n}\n\n@sceneComponent('worker')\nexport default class Worker extends Base {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): WorkerState {\n return super.state as WorkerState\n }\n\n /**\n * Phase H — 사람 작업자. 손으로 carrier 를 들어올림. 일반적인 carrier 의\n * 어떤 진입점도 사용 가능 — 'manual' type. carrier 가 'manual' type entry 를\n * 노출하면 매칭. 미노출 carrier 는 'manual' / 'gripper' 둘 다 매칭하도록 자체\n * 호환성 logic 으로 override 가능.\n * (override 키워드 미사용 — TS4113 회피.)\n */\n get toolType(): string {\n return 'manual'\n }\n\n static legends: Record<string, LegendBinding> = {\n vestColor: { from: 'status', legend: VEST_LEGEND },\n helmetEmissive: { from: 'status', legend: HELMET_EMISSIVE_LEGEND }\n }\n\n /**\n * Worker stands on the floor — `floor` archetype with `bottom` alignment.\n * Default depth is ~1700mm (typical adult height); the head reaches just\n * above the operation surface, which makes a worker visually adjacent to a\n * conveyor in a side view but clearly above it in a front view.\n *\n * Note this is human height, not \"operation - floor\" — the worker is taller\n * than the conveyor by design. Use the explicit number form rather than the\n * heights-derived function form.\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = 1700\n\n /**\n * Heading yaw offset (rad). things-scene's vehicle convention is\n * `vehicle forward = component-local -Z` (= \"rotation=0 → toward canvas\n * top edge\"); the framework default `Math.PI / 2` aligns with that.\n * Workers face their walking direction.\n */\n static yawOffset = Math.PI / 2\n\n /**\n * Worker carry point — at chest level in front of the worker. Boxes/\n * parcels carried by hand land here. Approximation: 1.0m above floor,\n * 0.3m forward of body center.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.object3d) return undefined\n\n const depth = (this.state.depth as number) ?? 1700\n const height = (this.state.height as number) ?? 50\n return {\n attach: ro.object3d,\n localPosition: {\n x: 0,\n y: -depth / 2 + 1000, // ~1m above floor (chest level)\n z: -height * 0.3 // forward of body center\n }\n }\n }\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — top-down view of a person from above: shoulders / vest as the main\n * filled silhouette (auto-filled with vestColor), helmet circle drawn over\n * it in postrender(). The helmet sits at the *facing* direction (top of\n * the bounds), so the orientation is readable.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n // Shoulders: a rounded rect, wider than tall (chest seen from above)\n const shoulderH = height * 0.55\n const shoulderY = top + height * 0.30\n const radius = Math.min(width, shoulderH) * 0.30\n ctx.beginPath()\n ctx.roundRect(left, shoulderY, width, shoulderH, radius)\n }\n\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const cx = left + width / 2\n const helmetEmissive = (this.state.helmetEmissive as string) || '#222'\n\n ctx.save()\n\n // Reflective stripes across the vest (two horizontal bands)\n ctx.fillStyle = '#eaeaea'\n ctx.fillRect(left + width * 0.08, top + height * 0.45, width * 0.84, height * 0.04)\n ctx.fillRect(left + width * 0.08, top + height * 0.65, width * 0.84, height * 0.04)\n\n // Helmet circle at the top (facing direction)\n const helmetR = Math.min(width, height * 0.4) * 0.45\n ctx.fillStyle = (this.state.vestColor as string) || '#FFD700'\n ctx.strokeStyle = '#222'\n ctx.lineWidth = 1\n ctx.beginPath()\n ctx.ellipse(cx, top + helmetR + height * 0.02, helmetR, helmetR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // Emissive helmet status accent (small dot on helmet)\n ctx.fillStyle = helmetEmissive\n ctx.beginPath()\n ctx.ellipse(cx, top + helmetR + height * 0.02, helmetR * 0.4, helmetR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.vestColor as string) || '#FFD700'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Worker3D(this)\n }\n}\n"]}