@operato/scene-transport 10.0.0-beta.32 → 10.0.0-beta.37
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 +17 -0
- package/dist/forklift.js +16 -2
- package/dist/forklift.js.map +1 -1
- package/package.json +3 -3
- package/src/forklift.ts +11 -2
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
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.37](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.36...v10.0.0-beta.37) (2026-05-15)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :bug: Bug Fix
|
|
10
|
+
|
|
11
|
+
* **scene-base/conveyance/transport/manufacturing:** dispose leak 광범위 정리 ([163c433](https://github.com/things-scene/operato-scene/commit/163c433ef3adddc9f6af7ed8c16971c486590b7a))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [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)
|
|
16
|
+
|
|
17
|
+
**Note:** Version bump only for package @operato/scene-transport
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
6
23
|
## [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)
|
|
7
24
|
|
|
8
25
|
|
package/dist/forklift.js
CHANGED
|
@@ -255,17 +255,31 @@ let Forklift = class Forklift extends Base {
|
|
|
255
255
|
this.setState?.({ forkHeight: targetForkHeight });
|
|
256
256
|
// mast 재구성 + attach point 재계산 안정화 시간 — sim-time 기준 (frameClock).
|
|
257
257
|
// speed 변경 시에도 sim animation 과 같은 시간축 유지.
|
|
258
|
+
// dispose 시 즉시 unsub — Mover.removed/dispose 가 _moverActiveUnsubs 일괄 정리.
|
|
258
259
|
await new Promise(resolve => {
|
|
259
260
|
let elapsed = 0;
|
|
260
261
|
let unsub = null;
|
|
262
|
+
const cleanup = () => {
|
|
263
|
+
if (unsub) {
|
|
264
|
+
;
|
|
265
|
+
this._moverActiveUnsubs?.delete(unsub);
|
|
266
|
+
unsub();
|
|
267
|
+
unsub = null;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
261
270
|
unsub = frameClock.subscribe(simDt => {
|
|
271
|
+
if (this._moverDisposed) {
|
|
272
|
+
cleanup();
|
|
273
|
+
resolve();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
262
276
|
elapsed += simDt;
|
|
263
277
|
if (elapsed >= 200) {
|
|
264
|
-
|
|
265
|
-
unsub = null;
|
|
278
|
+
cleanup();
|
|
266
279
|
resolve();
|
|
267
280
|
}
|
|
268
281
|
}, { description: 'forklift mast settle' });
|
|
282
|
+
this._moverActiveUnsubs?.add(unsub);
|
|
269
283
|
});
|
|
270
284
|
}
|
|
271
285
|
/**
|
package/dist/forklift.js.map
CHANGED
|
@@ -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, 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"]}
|
|
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 // dispose 시 즉시 unsub — Mover.removed/dispose 가 _moverActiveUnsubs 일괄 정리.\n await new Promise<void>(resolve => {\n let elapsed = 0\n let unsub: (() => void) | null = null\n const cleanup = () => {\n if (unsub) {\n ;(this as any)._moverActiveUnsubs?.delete(unsub)\n unsub()\n unsub = null\n }\n }\n unsub = frameClock.subscribe(\n simDt => {\n if ((this as any)._moverDisposed) { cleanup(); resolve(); return }\n elapsed += simDt\n if (elapsed >= 200) {\n cleanup()\n resolve()\n }\n },\n { description: 'forklift mast settle' }\n )\n ;(this as any)._moverActiveUnsubs?.add(unsub)\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/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@operato/scene-transport",
|
|
3
3
|
"description": "Transport-domain components for things-scene (smart factory / logistics) — forklift, worker, AGV.",
|
|
4
4
|
"author": "heartyoh",
|
|
5
|
-
"version": "10.0.0-beta.
|
|
5
|
+
"version": "10.0.0-beta.37",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"module": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@hatiolab/things-scene": "^10.0.0-beta.1",
|
|
29
|
-
"@operato/scene-base": "^10.0.0-beta.
|
|
29
|
+
"@operato/scene-base": "^10.0.0-beta.37",
|
|
30
30
|
"three": "^0.183.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"typescript": "^5.0.4"
|
|
46
46
|
},
|
|
47
47
|
"prettier": "@hatiolab/prettier-config",
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "73ed7b04e2ac536818f9c113d52fdf7d4a4c44bd"
|
|
49
49
|
}
|
package/src/forklift.ts
CHANGED
|
@@ -328,20 +328,29 @@ export default class Forklift extends Base {
|
|
|
328
328
|
this.setState?.({ forkHeight: targetForkHeight })
|
|
329
329
|
// mast 재구성 + attach point 재계산 안정화 시간 — sim-time 기준 (frameClock).
|
|
330
330
|
// speed 변경 시에도 sim animation 과 같은 시간축 유지.
|
|
331
|
+
// dispose 시 즉시 unsub — Mover.removed/dispose 가 _moverActiveUnsubs 일괄 정리.
|
|
331
332
|
await new Promise<void>(resolve => {
|
|
332
333
|
let elapsed = 0
|
|
333
334
|
let unsub: (() => void) | null = null
|
|
335
|
+
const cleanup = () => {
|
|
336
|
+
if (unsub) {
|
|
337
|
+
;(this as any)._moverActiveUnsubs?.delete(unsub)
|
|
338
|
+
unsub()
|
|
339
|
+
unsub = null
|
|
340
|
+
}
|
|
341
|
+
}
|
|
334
342
|
unsub = frameClock.subscribe(
|
|
335
343
|
simDt => {
|
|
344
|
+
if ((this as any)._moverDisposed) { cleanup(); resolve(); return }
|
|
336
345
|
elapsed += simDt
|
|
337
346
|
if (elapsed >= 200) {
|
|
338
|
-
|
|
339
|
-
unsub = null
|
|
347
|
+
cleanup()
|
|
340
348
|
resolve()
|
|
341
349
|
}
|
|
342
350
|
},
|
|
343
351
|
{ description: 'forklift mast settle' }
|
|
344
352
|
)
|
|
353
|
+
;(this as any)._moverActiveUnsubs?.add(unsub)
|
|
345
354
|
})
|
|
346
355
|
}
|
|
347
356
|
|