@operato/scene-storage 10.0.0-beta.31 → 10.0.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/asrs-crane.d.ts +6 -0
- package/dist/asrs-crane.js +8 -0
- package/dist/asrs-crane.js.map +1 -1
- package/dist/box.d.ts +10 -1
- package/dist/box.js +28 -1
- package/dist/box.js.map +1 -1
- package/dist/pallet.d.ts +14 -1
- package/dist/pallet.js +39 -1
- package/dist/pallet.js.map +1 -1
- package/dist/parcel.d.ts +7 -1
- package/dist/parcel.js +25 -1
- package/dist/parcel.js.map +1 -1
- package/package.json +3 -3
- package/src/asrs-crane.ts +9 -0
- package/src/box.ts +39 -2
- package/src/pallet.ts +53 -2
- package/src/parcel.ts +36 -2
- package/test/test-phase-h-carrier-pickable.ts +105 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
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.32](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.31...v10.0.0-beta.32) (2026-05-09)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
* **manufacturing/storage:** Phase G9 + H — RobotArm SCAFFOLDING 정상 승격 + Parcel Pickable ([05e4d95](https://github.com/things-scene/operato-scene/commit/05e4d954b939dd295927322d683463a3f7bf4e82))
|
|
12
|
+
* **storage:** Phase H — AsrsCrane toolType 'forklift-fork' ([5596848](https://github.com/things-scene/operato-scene/commit/5596848954543888aa3feb80598548fd5f935beb))
|
|
13
|
+
* **storage:** Phase H — Pallet / Box 의 Pickable 구현 ([0149709](https://github.com/things-scene/operato-scene/commit/0149709d20dfa56d27a5823d75c9e2c57bba5890))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
## [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
18
|
|
|
8
19
|
|
package/dist/asrs-crane.d.ts
CHANGED
|
@@ -45,6 +45,12 @@ export default class AsrsCrane extends AsrsCrane_base {
|
|
|
45
45
|
static defaultDepth: (h: Heights) => number;
|
|
46
46
|
/** Yaw offset: crane model is drawn with the aisle axis along X (right = forward). */
|
|
47
47
|
static yawOffset: number;
|
|
48
|
+
/**
|
|
49
|
+
* Phase H — ASRS crane 은 telescoping forks 로 rack cell 안의 carrier 픽업.
|
|
50
|
+
* forklift-fork 와 동일 mechanism — pallet 의 fork pocket 진입.
|
|
51
|
+
* (Mover mixin chain 이 :any 라 override 키워드 불가, getter 만 정의.)
|
|
52
|
+
*/
|
|
53
|
+
get toolType(): string;
|
|
48
54
|
get nature(): ComponentNature;
|
|
49
55
|
get anchors(): never[];
|
|
50
56
|
/** Stacker crane carries at most one load at a time on its forks. */
|
package/dist/asrs-crane.js
CHANGED
|
@@ -88,6 +88,14 @@ let AsrsCrane = class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Le
|
|
|
88
88
|
static defaultDepth = (h) => h.ceiling - h.floor;
|
|
89
89
|
/** Yaw offset: crane model is drawn with the aisle axis along X (right = forward). */
|
|
90
90
|
static yawOffset = 0;
|
|
91
|
+
/**
|
|
92
|
+
* Phase H — ASRS crane 은 telescoping forks 로 rack cell 안의 carrier 픽업.
|
|
93
|
+
* forklift-fork 와 동일 mechanism — pallet 의 fork pocket 진입.
|
|
94
|
+
* (Mover mixin chain 이 :any 라 override 키워드 불가, getter 만 정의.)
|
|
95
|
+
*/
|
|
96
|
+
get toolType() {
|
|
97
|
+
return 'forklift-fork';
|
|
98
|
+
}
|
|
91
99
|
get nature() {
|
|
92
100
|
return NATURE;
|
|
93
101
|
}
|
package/dist/asrs-crane.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asrs-crane.js","sourceRoot":"","sources":["../src/asrs-crane.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA8B,iBAAiB,EAAE,iBAAiB,EAAc,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAErI,OAAO,EACL,aAAa,EACb,UAAU,EACV,KAAK,EACL,SAAS,EAOV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AA0BhD,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,MAAM;IACb,OAAO,EAAE,MAAM;CAChB,CAAA;AAED,MAAM,oBAAoB,GAAG;IAC3B,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,SAAS;IACpB,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,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;oBACxC,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE;oBAC5C,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;iBACrC;aACF;SACF;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,iBAAiB;YACxB,IAAI,EAAE,gBAAgB;YACtB,WAAW,EAAE,iCAAiC;SAC/C;KACF;IACD,IAAI,EAAE,4BAA4B;CACnC,CAAA;AAED,sGAAsG;AACtG,EAAE;AACF,gFAAgF;AAChF,8FAA8F;AAC9F,uFAAuF;AACvF,oEAAoE;AACpE,wEAAwE;AACxE,sDAAsD;AACtD,mFAAmF;AACnF,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,yCAAyC;AACzC;;;;;;;;;;;;;;;GAeG;AAEY,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,UAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IAItH,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,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,KAAK,CAAA;IAEzD,sFAAsF;IACtF,MAAM,CAAC,SAAS,GAAG,CAAC,CAAA;IAEpB,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED,6EAA6E;IAE7E,qEAAqE;IACrE,IAAI,KAAK;QACP,OAAO,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;IACvC,CAAC;IAED,6EAA6E;IAE7E;;;;;;;;OAQG;IACH,cAAc,CAAC,OAAkB;QAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAA;QAC3B,MAAM,KAAK,GAAG,EAAE,EAAE,gBAAgB,EAAE,EAAE,CAAA;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;YACjD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,YAAY,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAA;QAC9E,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAA;QACvC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QACtB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IACzB,CAAC;IAED,6EAA6E;IAE7E;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,MAAM,CACV,MAAiB,EACjB,IAAsB,EACtB,WAAwB,EAAE;QAE1B,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,SAA4B,EAAE,CAAC,CAAA;YACvD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAA;YAC9C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,IAAI,CAAC,QAAQ,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC7C,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,WAA8B,EAAE,CAAC,CAAA;QAC3D,CAAC;QACD,0DAA0D;IAC5D,CAAC;IAED,6EAA6E;IAE7E,8DAA8D;IAC9D,KAAK,CAAC,OAAkB,EAAE,OAAqB;QAC7C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IACpC,CAAC;IAED,iEAAiE;IACjE,OAAO,CAAC,OAAkB,EAAE,IAAe,EAAE,OAAqB;QAChE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IAC3C,CAAC;IAED,4EAA4E;IAE5E;;;OAGG;IACH,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,MAAM,SAAS,GAAI,IAAI,CAAC,KAAK,CAAC,SAAoB,IAAI,MAAM,CAAA;QAC5D,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;QAClC,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,OAAO,EAAE,CAAA;IACf,CAAC;IAED,4EAA4E;IAE5E,eAAe;QACb,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,CAAA;IAC9B,CAAC;;AA5HkB,SAAS;IAD7B,cAAc,CAAC,YAAY,CAAC;GACR,SAAS,CA6H7B;eA7HoB,SAAS;AA+H9B,SAAS,mBAAmB,CAAC,CAAY;IACvC,MAAM,GAAG,GAAI,CAAS,CAAC,WAAW,EAAE,cAAc,CAAA;IAClD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAA;IAC/D,OAAO,KAAK,CAAE,CAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;AAC3C,CAAC;AAED,SAAS,qBAAqB,CAAC,CAAY;IACzC,MAAM,GAAG,GAAI,CAAS,CAAC,KAAK,CAAA;IAC5B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,mEAAmE;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACpC,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,KAAK,CAAC,CAAU,EAAE,IAAY;IACrC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAC/D,CAAC","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { SlotDef, State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n Legendable,\n Mover,\n Placeable,\n type AttachFrame,\n type Alignment,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { AsrsCrane3D } from './asrs-crane-3d.js'\n\n/**\n * AsrsCrane status — the operating state of a stacker crane in an AS/RS aisle.\n *\n * - `idle` — parked at home position\n * - `moving` — translating along the aisle (horizontal) or along the\n * mast (vertical); the two motions are typically combined\n * - `loading` — extracting a pallet from a rack cell\n * - `unloading` — placing a pallet into a rack cell\n * - `error` — fault / e-stop / collision warning\n */\nexport type AsrsCraneStatus = 'idle' | 'moving' | 'loading' | 'unloading' | 'error'\n\n/** AsrsCrane 컴포넌트 state */\nexport interface AsrsCraneState extends State {\n // ── 운영 상태 ──\n status?: AsrsCraneStatus\n\n // ── 액추에이터 ──\n carriageHeight?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst BODY_LEGEND = {\n idle: '#888',\n moving: '#aabbcc',\n loading: '#ffaa00',\n unloading: '#ffaa00',\n error: '#c66',\n default: '#888'\n}\n\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44',\n loading: '#ffaa00',\n unloading: '#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: 'Loading', value: 'loading' },\n { display: 'Unloading', value: 'unloading' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'carriage-height',\n name: 'carriageHeight',\n placeholder: 'mm — height of carriage on mast'\n }\n ],\n help: 'scene/component/asrs-crane'\n}\n\n// Mixin chain: Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract\n//\n// Mover: pick / place / pickAndPlace / moveTo / engage primitives\n// CarrierHolder: attachPointFor() — where the carrier sits on the crane (carriage fork)\n// ContainerCapacity: receive() / dispatch() / canReceive() / slots — slot tracking +\n// TRANSFER_SLOT_KEY bookkeeping during transit\n// Legendable: status → bodyColor / lampEmissive colour mapping\n// Placeable: floor-archetype 3D positioning\n// ContainerAbstract: child management — carrier becomes a child while in transit\n//\n// Note: ContainerAbstract replaces Shape. The 2D outline is drawn manually in\n// render() below (a simple top-down rectangle), matching the old Shape output\n// without the Shape base-class overhead.\n/**\n * AsrsCrane — the stacker / retrieval crane that runs in the aisle of an\n * AS/RS, moving cargo between the load port and the rack cells.\n *\n * Structure: a tall vertical mast that translates along a floor + ceiling\n * rail (the aisle), with a carriage that slides up/down the mast carrying a\n * shuttle / forks.\n *\n * **Monitoring mode**: crane status is driven by data binding\n * (`state.status`, `state.carriageHeight`). The carrier is referenced\n * via data binding — it is NOT a child of the crane in monitoring mode.\n *\n * **Simulation mode**: call `crane.pick(carrier)` / `crane.place(carrier, rackCell)`\n * (or `crane.pickAndPlace(carrier, rackCell)`). Mover handles navigation +\n * engage + reparent. During transit the carrier IS a child of the crane.\n */\n@sceneComponent('asrs-crane')\nexport default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract))))) {\n declare state: AsrsCraneState\n declare _realObject?: AsrsCrane3D\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 = (h: Heights) => h.ceiling - h.floor\n\n /** Yaw offset: crane model is drawn with the aisle axis along X (right = forward). */\n static yawOffset = 0\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n // ── ContainerCapacity ─────────────────────────────────────────────────────\n\n /** Stacker crane carries at most one load at a time on its forks. */\n get slots(): SlotDef[] {\n return [{ id: 'forks', maxCount: 1 }]\n }\n\n // ── CarrierHolder — attach frame (carriage fork position) ─────────────────\n\n /**\n * Return the 3D attach frame on the crane's carriage (fork tip).\n * Carriers are attached here while the crane is in transit (pick phase).\n *\n * The AsrsCrane3D exposes `getCarriageFrame()` — a sub-Object3D that\n * tracks the carriage height and sits at the fork TCP. If the 3D object\n * isn't built yet (e.g. before scene initialization), fall back to the\n * crane's own object3d centre.\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const ro = this._realObject\n const frame = ro?.getCarriageFrame?.()\n if (frame) {\n const carrierDepth = resolveCarrierDepth(carrier)\n return { attach: frame, localPosition: { x: 0, y: carrierDepth / 2, z: 0 } }\n }\n const root = this._realObject?.object3d\n if (!root) return null\n return { attach: root }\n }\n\n // ── Mover overrides ───────────────────────────────────────────────────────\n\n /**\n * Domain-specific actuation between arrival and reparent.\n *\n * Simulation sequence for PICK:\n * 1. Mover.pick() navigates crane to carrier position (moveTo).\n * 2. engage('pick') → snap carriage height + status 'loading'.\n * 3. Carrier is reparented to crane (becomes child).\n *\n * For now: set status and snap carriage height. A full ASRS simulation\n * would tween the carriageHeight here (animate AsrsCrane3D).\n *\n * Status lifecycle:\n * idle → (moveTo running) → engage fires → loading/unloading → (reparent) → idle\n * The 'moving' state is not set from Mover.moveTo() because TypeScript\n * can't call super.moveTo() on an `: any`-typed mixin. WCS data binding\n * sets 'moving' in monitoring mode; override pick()/place() to set it\n * in full simulation environments.\n */\n async engage(\n target: Component,\n kind: 'pick' | 'place',\n _options: MoveOptions = {}\n ): Promise<void> {\n if (kind === 'pick') {\n this.setState({ status: 'loading' as AsrsCraneStatus })\n const carrierY = resolveCarrierCenterY(target)\n if (carrierY !== null) {\n this.setState({ carriageHeight: carrierY })\n }\n } else {\n this.setState({ status: 'unloading' as AsrsCraneStatus })\n }\n // In a full simulation: await carriage-motion tween here.\n }\n\n // ── Domain aliases ────────────────────────────────────────────────────────\n\n /** Fetch a carrier from a rack cell (semantically = pick). */\n fetch(carrier: Component, options?: MoveOptions): Promise<void> {\n return this.pick(carrier, options)\n }\n\n /** Deposit a carrier into a rack cell (semantically = place). */\n deposit(carrier: Component, cell: Component, options?: MoveOptions): Promise<void> {\n return this.place(carrier, cell, options)\n }\n\n // ── 2D rendering ─────────────────────────────────────────────────────────\n\n /**\n * 2D — top-down rectangle showing the crane's footprint along the aisle.\n * The crane is much taller than wide, so the 2D mark is small.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const fillColor = (this.state.bodyColor as string) || '#888'\n ctx.save()\n ctx.fillStyle = fillColor\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n ctx.fill()\n ctx.restore()\n }\n\n // ── 3D ───────────────────────────────────────────────────────────────────\n\n buildRealObject(): RealObject | undefined {\n return new AsrsCrane3D(this)\n }\n}\n\nfunction resolveCarrierDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction resolveCarrierCenterY(c: Component): number | null {\n const pos = (c as any).state\n if (!pos) return null\n // zPos is the 3D Y center of a Placeable component in things-scene\n const zPos = numOr(pos.zPos, NaN)\n if (!Number.isNaN(zPos)) return zPos\n return null\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
|
|
1
|
+
{"version":3,"file":"asrs-crane.js","sourceRoot":"","sources":["../src/asrs-crane.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 Legendable,\n Mover,\n Placeable,\n type AttachFrame,\n type Alignment,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { AsrsCrane3D } from './asrs-crane-3d.js'\n\n/**\n * AsrsCrane status — the operating state of a stacker crane in an AS/RS aisle.\n *\n * - `idle` — parked at home position\n * - `moving` — translating along the aisle (horizontal) or along the\n * mast (vertical); the two motions are typically combined\n * - `loading` — extracting a pallet from a rack cell\n * - `unloading` — placing a pallet into a rack cell\n * - `error` — fault / e-stop / collision warning\n */\nexport type AsrsCraneStatus = 'idle' | 'moving' | 'loading' | 'unloading' | 'error'\n\n/** AsrsCrane 컴포넌트 state */\nexport interface AsrsCraneState extends State {\n // ── 운영 상태 ──\n status?: AsrsCraneStatus\n\n // ── 액추에이터 ──\n carriageHeight?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst BODY_LEGEND = {\n idle: '#888',\n moving: '#aabbcc',\n loading: '#ffaa00',\n unloading: '#ffaa00',\n error: '#c66',\n default: '#888'\n}\n\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44',\n loading: '#ffaa00',\n unloading: '#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: 'Loading', value: 'loading' },\n { display: 'Unloading', value: 'unloading' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'carriage-height',\n name: 'carriageHeight',\n placeholder: 'mm — height of carriage on mast'\n }\n ],\n help: 'scene/component/asrs-crane'\n}\n\n// Mixin chain: Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract\n//\n// Mover: pick / place / pickAndPlace / moveTo / engage primitives\n// CarrierHolder: attachPointFor() — where the carrier sits on the crane (carriage fork)\n// ContainerCapacity: receive() / dispatch() / canReceive() / slots — slot tracking +\n// TRANSFER_SLOT_KEY bookkeeping during transit\n// Legendable: status → bodyColor / lampEmissive colour mapping\n// Placeable: floor-archetype 3D positioning\n// ContainerAbstract: child management — carrier becomes a child while in transit\n//\n// Note: ContainerAbstract replaces Shape. The 2D outline is drawn manually in\n// render() below (a simple top-down rectangle), matching the old Shape output\n// without the Shape base-class overhead.\n/**\n * AsrsCrane — the stacker / retrieval crane that runs in the aisle of an\n * AS/RS, moving cargo between the load port and the rack cells.\n *\n * Structure: a tall vertical mast that translates along a floor + ceiling\n * rail (the aisle), with a carriage that slides up/down the mast carrying a\n * shuttle / forks.\n *\n * **Monitoring mode**: crane status is driven by data binding\n * (`state.status`, `state.carriageHeight`). The carrier is referenced\n * via data binding — it is NOT a child of the crane in monitoring mode.\n *\n * **Simulation mode**: call `crane.pick(carrier)` / `crane.place(carrier, rackCell)`\n * (or `crane.pickAndPlace(carrier, rackCell)`). Mover handles navigation +\n * engage + reparent. During transit the carrier IS a child of the crane.\n */\n@sceneComponent('asrs-crane')\nexport default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract))))) {\n declare state: AsrsCraneState\n declare _realObject?: AsrsCrane3D\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 = (h: Heights) => h.ceiling - h.floor\n\n /** Yaw offset: crane model is drawn with the aisle axis along X (right = forward). */\n static yawOffset = 0\n\n /**\n * Phase H — ASRS crane 은 telescoping forks 로 rack cell 안의 carrier 픽업.\n * forklift-fork 와 동일 mechanism — pallet 의 fork pocket 진입.\n * (Mover mixin chain 이 :any 라 override 키워드 불가, getter 만 정의.)\n */\n get toolType(): string {\n return 'forklift-fork'\n }\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n // ── ContainerCapacity ─────────────────────────────────────────────────────\n\n /** Stacker crane carries at most one load at a time on its forks. */\n get slots(): SlotDef[] {\n return [{ id: 'forks', maxCount: 1 }]\n }\n\n // ── CarrierHolder — attach frame (carriage fork position) ─────────────────\n\n /**\n * Return the 3D attach frame on the crane's carriage (fork tip).\n * Carriers are attached here while the crane is in transit (pick phase).\n *\n * The AsrsCrane3D exposes `getCarriageFrame()` — a sub-Object3D that\n * tracks the carriage height and sits at the fork TCP. If the 3D object\n * isn't built yet (e.g. before scene initialization), fall back to the\n * crane's own object3d centre.\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const ro = this._realObject\n const frame = ro?.getCarriageFrame?.()\n if (frame) {\n const carrierDepth = resolveCarrierDepth(carrier)\n return { attach: frame, localPosition: { x: 0, y: carrierDepth / 2, z: 0 } }\n }\n const root = this._realObject?.object3d\n if (!root) return null\n return { attach: root }\n }\n\n // ── Mover overrides ───────────────────────────────────────────────────────\n\n /**\n * Domain-specific actuation between arrival and reparent.\n *\n * Simulation sequence for PICK:\n * 1. Mover.pick() navigates crane to carrier position (moveTo).\n * 2. engage('pick') → snap carriage height + status 'loading'.\n * 3. Carrier is reparented to crane (becomes child).\n *\n * For now: set status and snap carriage height. A full ASRS simulation\n * would tween the carriageHeight here (animate AsrsCrane3D).\n *\n * Status lifecycle:\n * idle → (moveTo running) → engage fires → loading/unloading → (reparent) → idle\n * The 'moving' state is not set from Mover.moveTo() because TypeScript\n * can't call super.moveTo() on an `: any`-typed mixin. WCS data binding\n * sets 'moving' in monitoring mode; override pick()/place() to set it\n * in full simulation environments.\n */\n async engage(\n target: Component,\n kind: 'pick' | 'place',\n _options: MoveOptions = {}\n ): Promise<void> {\n if (kind === 'pick') {\n this.setState({ status: 'loading' as AsrsCraneStatus })\n const carrierY = resolveCarrierCenterY(target)\n if (carrierY !== null) {\n this.setState({ carriageHeight: carrierY })\n }\n } else {\n this.setState({ status: 'unloading' as AsrsCraneStatus })\n }\n // In a full simulation: await carriage-motion tween here.\n }\n\n // ── Domain aliases ────────────────────────────────────────────────────────\n\n /** Fetch a carrier from a rack cell (semantically = pick). */\n fetch(carrier: Component, options?: MoveOptions): Promise<void> {\n return this.pick(carrier, options)\n }\n\n /** Deposit a carrier into a rack cell (semantically = place). */\n deposit(carrier: Component, cell: Component, options?: MoveOptions): Promise<void> {\n return this.place(carrier, cell, options)\n }\n\n // ── 2D rendering ─────────────────────────────────────────────────────────\n\n /**\n * 2D — top-down rectangle showing the crane's footprint along the aisle.\n * The crane is much taller than wide, so the 2D mark is small.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const fillColor = (this.state.bodyColor as string) || '#888'\n ctx.save()\n ctx.fillStyle = fillColor\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n ctx.fill()\n ctx.restore()\n }\n\n // ── 3D ───────────────────────────────────────────────────────────────────\n\n buildRealObject(): RealObject | undefined {\n return new AsrsCrane3D(this)\n }\n}\n\nfunction resolveCarrierDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction resolveCarrierCenterY(c: Component): number | null {\n const pos = (c as any).state\n if (!pos) return null\n // zPos is the 3D Y center of a Placeable component in things-scene\n const zPos = numOr(pos.zPos, NaN)\n if (!Number.isNaN(zPos)) return zPos\n return null\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
|
package/dist/box.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComponentNature, RealObject } from '@hatiolab/things-scene';
|
|
2
|
-
import type { State, Material3D } from '@hatiolab/things-scene';
|
|
2
|
+
import type { State, Material3D, PickupFrame } from '@hatiolab/things-scene';
|
|
3
3
|
import { type Alignment, type LegendBinding, type PlacementArchetype } from '@operato/scene-base';
|
|
4
4
|
/**
|
|
5
5
|
* Box material — drives 3D structure and color.
|
|
@@ -39,5 +39,14 @@ export default class Box extends Box_base {
|
|
|
39
39
|
render(ctx: CanvasRenderingContext2D): void;
|
|
40
40
|
get fillStyle(): string;
|
|
41
41
|
buildRealObject(): RealObject | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Phase H — pickup contract. Box 는 위에서 gripper / vacuum cup 으로 집기 —
|
|
44
|
+
* 단일 entry (top center). Box 의 dimensions 가 작아서 forklift fork 보다는
|
|
45
|
+
* gripper 가 일반적. forklift 로 들어올릴 box 는 통상 pallet 위에 stacking
|
|
46
|
+
* 후 pallet 째로 운반.
|
|
47
|
+
*
|
|
48
|
+
* tolerance 가 pallet 보다 빡빡 (gripper 정밀도 vs forklift pocket 폭).
|
|
49
|
+
*/
|
|
50
|
+
pickupFrames(): PickupFrame[];
|
|
42
51
|
}
|
|
43
52
|
export {};
|
package/dist/box.js
CHANGED
|
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
/*
|
|
3
3
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
4
4
|
*/
|
|
5
|
-
import { RectPath, Shape, sceneComponent } from '@hatiolab/things-scene';
|
|
5
|
+
import { RectPath, Shape, topApproachFrame, getWorldPose, sceneComponent } from '@hatiolab/things-scene';
|
|
6
6
|
import { Carriable, Legendable, Placeable } from '@operato/scene-base';
|
|
7
7
|
import { Box3D } from './box-3d.js';
|
|
8
8
|
const BODY_LEGEND = {
|
|
@@ -64,6 +64,33 @@ let Box = class Box extends Carriable(Legendable(Placeable(RectPath(Shape)))) {
|
|
|
64
64
|
buildRealObject() {
|
|
65
65
|
return new Box3D(this);
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Phase H — pickup contract. Box 는 위에서 gripper / vacuum cup 으로 집기 —
|
|
69
|
+
* 단일 entry (top center). Box 의 dimensions 가 작아서 forklift fork 보다는
|
|
70
|
+
* gripper 가 일반적. forklift 로 들어올릴 box 는 통상 pallet 위에 stacking
|
|
71
|
+
* 후 pallet 째로 운반.
|
|
72
|
+
*
|
|
73
|
+
* tolerance 가 pallet 보다 빡빡 (gripper 정밀도 vs forklift pocket 폭).
|
|
74
|
+
*/
|
|
75
|
+
pickupFrames() {
|
|
76
|
+
const wp = getWorldPose(this);
|
|
77
|
+
const me = {
|
|
78
|
+
position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },
|
|
79
|
+
rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }
|
|
80
|
+
};
|
|
81
|
+
const boxDepth = this.constructor.defaultDepth ?? 300;
|
|
82
|
+
return [
|
|
83
|
+
topApproachFrame({
|
|
84
|
+
carrierWorld: me,
|
|
85
|
+
topY: boxDepth, // Box top in carrier-local Y (depth = full height; top at depth)
|
|
86
|
+
approachDistance: 50, // gripper 가 hover 하는 거리
|
|
87
|
+
toolType: 'gripper',
|
|
88
|
+
tolerance: { positionMm: 5, angleDeg: 1 },
|
|
89
|
+
priority: 0,
|
|
90
|
+
id: 'top-gripper'
|
|
91
|
+
})
|
|
92
|
+
];
|
|
93
|
+
}
|
|
67
94
|
};
|
|
68
95
|
Box = __decorate([
|
|
69
96
|
sceneComponent('box')
|
package/dist/box.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"box.js","sourceRoot":"","sources":["../src/box.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"box.js","sourceRoot":"","sources":["../src/box.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport {\n ComponentNature,\n RealObject,\n RectPath,\n Shape,\n topApproachFrame,\n getWorldPose,\n sceneComponent\n} from '@hatiolab/things-scene'\nimport type { State, Material3D, PickupFrame, PoseSerialized } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Legendable,\n Placeable,\n type Alignment,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Box3D } from './box-3d.js'\n\n/**\n * Box material — drives 3D structure and color.\n *\n * - `wood` — wood crate: visible vertical slats, gaps between, open or\n * semi-open top. Used for heavy / industrial parts.\n * - `plastic` — plastic tote / bin: solid molded walls with stackable lip\n * at top. Used for fulfillment, parts kitting.\n *\n * Cardboard parcels are a separate component (see `parcel.ts`) — they have\n * different proportions, taping, and labels that warrant a distinct class.\n */\nexport type BoxMaterial = 'wood' | 'plastic'\n\n/** Box 컴포넌트 state */\nexport interface BoxState extends State {\n // ── 외관 ──\n material?: BoxMaterial\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst BODY_LEGEND = {\n wood: '#a87644',\n plastic: '#3a5078',\n default: '#a87644'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'material',\n name: 'material',\n property: {\n options: [\n { display: 'Wood', value: 'wood' },\n { display: 'Plastic', value: 'plastic' }\n ]\n }\n }\n ],\n help: 'scene/component/box'\n}\n\n// Carriable: a box can be a child of any CarrierHolder (Pallet for stacking,\n// AGV deck, robot-arm gripper, Spot for staging).\n/**\n * Box — a generic stackable container for goods. Wood crate or plastic tote\n * variants distinguished by `material` prop.\n *\n * Shape-based (not Container) — boxes nesting other components is rare in\n * logistics visualization (a *case* of items inside a box is data, not\n * scene-tree). If a future use case needs nested boxes, extend Container.\n */\n@sceneComponent('box')\nexport default class Box extends Carriable(Legendable(Placeable(RectPath(Shape)))) {\n declare state: BoxState\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'material', legend: BODY_LEGEND }\n }\n\n static placement: PlacementArchetype = 'operation'\n static align: Alignment = 'bottom'\n static defaultDepth = 300\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** 2D — top-down rectangle. */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#a87644'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Box3D(this)\n }\n\n /**\n * Phase H — pickup contract. Box 는 위에서 gripper / vacuum cup 으로 집기 —\n * 단일 entry (top center). Box 의 dimensions 가 작아서 forklift fork 보다는\n * gripper 가 일반적. forklift 로 들어올릴 box 는 통상 pallet 위에 stacking\n * 후 pallet 째로 운반.\n *\n * tolerance 가 pallet 보다 빡빡 (gripper 정밀도 vs forklift pocket 폭).\n */\n pickupFrames(): PickupFrame[] {\n const wp = getWorldPose(this)\n const me: PoseSerialized = {\n position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },\n rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }\n }\n const boxDepth = (this.constructor as any).defaultDepth ?? 300\n\n return [\n topApproachFrame({\n carrierWorld: me,\n topY: boxDepth, // Box top in carrier-local Y (depth = full height; top at depth)\n approachDistance: 50, // gripper 가 hover 하는 거리\n toolType: 'gripper',\n tolerance: { positionMm: 5, angleDeg: 1 },\n priority: 0,\n id: 'top-gripper'\n })\n ]\n }\n}\n"]}
|
package/dist/pallet.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
|
|
2
|
-
import type { State, Material3D } from '@hatiolab/things-scene';
|
|
2
|
+
import type { State, Material3D, PickupFrame } from '@hatiolab/things-scene';
|
|
3
3
|
import { type Alignment, type LegendBinding, type PlacementArchetype } from '@operato/scene-base';
|
|
4
4
|
/**
|
|
5
5
|
* Pallet material — drives both 2D fill color and 3D structure.
|
|
@@ -73,5 +73,18 @@ export default class Pallet extends Pallet_base {
|
|
|
73
73
|
postrender(ctx: CanvasRenderingContext2D): void;
|
|
74
74
|
get fillStyle(): string;
|
|
75
75
|
buildRealObject(): RealObject | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Phase H — pickup contract. EUR/EPAL pallet 의 fork 진입은 양 단면 (긴 변
|
|
78
|
+
* 두 군데). 'east' / 'west' = pallet 의 짧은 축 방향에서 fork 가 들어감.
|
|
79
|
+
*
|
|
80
|
+
* fork 진입 높이 (entryHeight) 는 pallet 의 fork pocket 위치 — pallet 의
|
|
81
|
+
* 바닥에서 약 50mm (top deck 와 bottom deck 사이의 stringer 영역). 표준
|
|
82
|
+
* EUR pallet 의 fork pocket 은 144mm 두께의 약 50% 지점.
|
|
83
|
+
*
|
|
84
|
+
* approachDistance 는 forklift 가 fork 끝을 pallet 에 닿기 직전 hover 자세 —
|
|
85
|
+
* pallet 길이만큼 떨어져서 fork 길이 (보통 1100mm) 가 다 들어가도록 한다.
|
|
86
|
+
* 여기선 conservative 하게 pallet 너비 + 200mm.
|
|
87
|
+
*/
|
|
88
|
+
pickupFrames(): PickupFrame[];
|
|
76
89
|
}
|
|
77
90
|
export {};
|
package/dist/pallet.js
CHANGED
|
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
/*
|
|
3
3
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
4
4
|
*/
|
|
5
|
-
import { ContainerAbstract, sceneComponent } from '@hatiolab/things-scene';
|
|
5
|
+
import { ContainerAbstract, rectangularFootprintFrames, getWorldPose, sceneComponent } from '@hatiolab/things-scene';
|
|
6
6
|
import { Carriable, Legendable, Placeable } from '@operato/scene-base';
|
|
7
7
|
import { Pallet3D } from './pallet-3d.js';
|
|
8
8
|
const BODY_LEGEND = {
|
|
@@ -155,6 +155,44 @@ let Pallet = class Pallet extends Carriable(Legendable(Placeable(ContainerAbstra
|
|
|
155
155
|
buildRealObject() {
|
|
156
156
|
return new Pallet3D(this);
|
|
157
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Phase H — pickup contract. EUR/EPAL pallet 의 fork 진입은 양 단면 (긴 변
|
|
160
|
+
* 두 군데). 'east' / 'west' = pallet 의 짧은 축 방향에서 fork 가 들어감.
|
|
161
|
+
*
|
|
162
|
+
* fork 진입 높이 (entryHeight) 는 pallet 의 fork pocket 위치 — pallet 의
|
|
163
|
+
* 바닥에서 약 50mm (top deck 와 bottom deck 사이의 stringer 영역). 표준
|
|
164
|
+
* EUR pallet 의 fork pocket 은 144mm 두께의 약 50% 지점.
|
|
165
|
+
*
|
|
166
|
+
* approachDistance 는 forklift 가 fork 끝을 pallet 에 닿기 직전 hover 자세 —
|
|
167
|
+
* pallet 길이만큼 떨어져서 fork 길이 (보통 1100mm) 가 다 들어가도록 한다.
|
|
168
|
+
* 여기선 conservative 하게 pallet 너비 + 200mm.
|
|
169
|
+
*/
|
|
170
|
+
pickupFrames() {
|
|
171
|
+
const { width = 1200, height = 800 } = this.state;
|
|
172
|
+
const palletDepth = this.constructor.defaultDepth ?? 150;
|
|
173
|
+
// 4-way pallet: 모든 면에서 fork 진입 가능. 2-way 는 sides 를 ['east','west']
|
|
174
|
+
// 로 한정 — pallet 의 stringer 방향에 따라 다르나 default 는 4-way 가정.
|
|
175
|
+
// (state.palletType 같은 속성 추가 시 분기 가능 — 현재는 4-way 가정.)
|
|
176
|
+
const me = (() => {
|
|
177
|
+
const wp = getWorldPose(this);
|
|
178
|
+
return {
|
|
179
|
+
position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },
|
|
180
|
+
rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }
|
|
181
|
+
};
|
|
182
|
+
})();
|
|
183
|
+
const longerAxis = Math.max(width, height);
|
|
184
|
+
return rectangularFootprintFrames({
|
|
185
|
+
carrierWorld: me,
|
|
186
|
+
width,
|
|
187
|
+
depth: height,
|
|
188
|
+
entryHeight: palletDepth * 0.4, // fork pocket 중심
|
|
189
|
+
approachDistance: longerAxis + 200,
|
|
190
|
+
sides: ['east', 'west', 'north', 'south'],
|
|
191
|
+
toolType: 'forklift-fork',
|
|
192
|
+
tolerance: { positionMm: 50, angleDeg: 5 },
|
|
193
|
+
priority: 0
|
|
194
|
+
});
|
|
195
|
+
}
|
|
158
196
|
};
|
|
159
197
|
Pallet = __decorate([
|
|
160
198
|
sceneComponent('pallet')
|
package/dist/pallet.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pallet.js","sourceRoot":"","sources":["../src/pallet.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { State, Material3D } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Legendable,\n Placeable,\n type Alignment,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Pallet3D } from './pallet-3d.js'\n\n/**\n * Pallet material — drives both 2D fill color and 3D structure.\n *\n * - `wood` — traditional EUR / EPAL pallet: parallel slats on top and\n * bottom, three perpendicular stringers between them.\n * - `plastic` — molded one-piece pallet: solid top deck with cutouts,\n * hollow underside with feet. Distinct ribbed underside.\n *\n * Adding a third material (e.g. metal, composite) is a one-line change to the\n * legend + a 3D variant in pallet-3d.ts.\n */\nexport type PalletMaterial = 'wood' | 'plastic'\n\n/** Pallet 컴포넌트 state */\nexport interface PalletState extends State {\n // ── 외관 ──\n material?: PalletMaterial\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst BODY_LEGEND = {\n wood: '#a87644',\n plastic: '#5a6a78',\n default: '#a87644'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'material',\n name: 'material',\n property: {\n options: [\n { display: 'Wood', value: 'wood' },\n { display: 'Plastic', value: 'plastic' }\n ]\n }\n }\n ],\n help: 'scene/component/pallet'\n}\n\n// Carriable: a pallet can sit on AGV / Forklift / robot-arm gripper / Spot\n// and also accept boxes / parcels as children (ContainerAbstract base\n// provides the child-container behavior; Carriable only adds the\n// holder-mount hook).\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Pallet renders only as a 3D mesh.\n/**\n * Pallet — a flat transport structure that goods are stacked and stored on.\n *\n * Standard EUR pallet is 1200 × 800mm × 144mm; we don't enforce these\n * dimensions but they're a good starting point for the catalog templates.\n *\n * **Container-based.** Boxes / parcels stacked on the pallet are added as\n * children — same `containable()` archetype-filter pattern as Forklift / Agv.\n * Visual stacking (children rendering on top of the pallet rather than at\n * absolute operation level) is a v2 concern; see ARCHITECTURE NOTES below.\n *\n * **Placement = `operation`.** A pallet's *normal* state is loaded and in\n * transit on a conveyor / AGV / forklift fork — at operation level. Empty\n * pallets in a floor-storage area are an exceptional state where the user\n * sets `state.zPos = 0` explicitly. Default to the common case.\n *\n * ## ARCHITECTURE NOTES — visual stacking\n *\n * When a Box (also `placement: 'operation'`) is added as a child of a\n * Pallet, both default to z = operation_height. They overlap visually\n * rather than the box sitting on top of the pallet. Solving this cleanly\n * (parent-relative z derivation when the parent is a structural carrier)\n * is a follow-up — fmsim's pattern is to detect parent type at render time\n * (machine-3d.ts:113-122). v1 accepts the visual overlap; v2 will add the\n * detection.\n */\n@sceneComponent('pallet')\nexport default class Pallet extends Carriable(Legendable(Placeable(ContainerAbstract))) {\n declare state: PalletState\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'material', legend: BODY_LEGEND }\n }\n\n static placement: PlacementArchetype = 'operation'\n static align: Alignment = 'bottom'\n static defaultDepth = 150 // EUR pallet is 144mm; 150 is the round number convention\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** Accept other operation-archetype cargo (boxes, parcels, smaller pallets) as stacked children. */\n containable(component: Component) {\n const archetype = (component.constructor as any).placement\n if (archetype === 'operation') return true\n return component.isDescendible(this)\n }\n\n /**\n * 2D — top-down silhouette. Body is a flat rectangle (wood/plastic deck);\n * `postrender()` adds the deck pattern + edge stroke so the pallet reads\n * as a pallet instead of a featureless rectangle.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n /**\n * Deck pattern + edge stroke. Wood: parallel slats with darker grooves\n * between (typical EUR pallet deck). Plastic: cross-cutout pattern\n * suggesting the molded reinforcement ribs.\n *\n * Slats run along the *short* axis of the rectangle (= along the longer\n * stringer direction in real life), so a 1200×800 pallet shows multiple\n * narrow slats across the 1200mm dimension — matching the EUR layout.\n */\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const isPlastic = this.state.material === 'plastic'\n\n ctx.save()\n\n if (!isPlastic) {\n // Wood — slats. Run them along the longer axis so the grooves are\n // perpendicular to the longer side (typical pallet appearance).\n const longAxisHorizontal = width >= height\n const slatCount = 5\n const grooveColor = '#7a4f25'\n ctx.fillStyle = grooveColor\n if (longAxisHorizontal) {\n // grooves vertical (X direction across the width)\n const grooveW = Math.max(1, width * 0.012)\n const slatW = (width - grooveW * (slatCount - 1)) / slatCount\n for (let i = 1; i < slatCount; i++) {\n const x = left + i * slatW + (i - 1) * grooveW\n ctx.fillRect(x, top + height * 0.05, grooveW, height * 0.9)\n }\n } else {\n const grooveH = Math.max(1, height * 0.012)\n const slatH = (height - grooveH * (slatCount - 1)) / slatCount\n for (let i = 1; i < slatCount; i++) {\n const y = top + i * slatH + (i - 1) * grooveH\n ctx.fillRect(left + width * 0.05, y, width * 0.9, grooveH)\n }\n }\n } else {\n // Plastic — cross + corner cutouts hint\n ctx.strokeStyle = '#3a4956'\n ctx.lineWidth = Math.max(1, Math.min(width, height) * 0.012)\n ctx.beginPath()\n ctx.moveTo(left + width * 0.5, top + height * 0.1)\n ctx.lineTo(left + width * 0.5, top + height * 0.9)\n ctx.moveTo(left + width * 0.1, top + height * 0.5)\n ctx.lineTo(left + width * 0.9, top + height * 0.5)\n ctx.stroke()\n }\n\n // Edge stroke\n ctx.strokeStyle = isPlastic ? '#2a3946' : '#5e3818'\n ctx.lineWidth = 1\n ctx.strokeRect(left, top, width, height)\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#a87644'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Pallet3D(this)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pallet.js","sourceRoot":"","sources":["../src/pallet.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport {\n Component,\n ComponentNature,\n ContainerAbstract,\n RealObject,\n Pose6DOF,\n rectangularFootprintFrames,\n getWorldPose,\n sceneComponent\n} from '@hatiolab/things-scene'\nimport type { State, Material3D, PickupFrame, PoseSerialized } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Legendable,\n Placeable,\n type Alignment,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Pallet3D } from './pallet-3d.js'\n\n/**\n * Pallet material — drives both 2D fill color and 3D structure.\n *\n * - `wood` — traditional EUR / EPAL pallet: parallel slats on top and\n * bottom, three perpendicular stringers between them.\n * - `plastic` — molded one-piece pallet: solid top deck with cutouts,\n * hollow underside with feet. Distinct ribbed underside.\n *\n * Adding a third material (e.g. metal, composite) is a one-line change to the\n * legend + a 3D variant in pallet-3d.ts.\n */\nexport type PalletMaterial = 'wood' | 'plastic'\n\n/** Pallet 컴포넌트 state */\nexport interface PalletState extends State {\n // ── 외관 ──\n material?: PalletMaterial\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst BODY_LEGEND = {\n wood: '#a87644',\n plastic: '#5a6a78',\n default: '#a87644'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'material',\n name: 'material',\n property: {\n options: [\n { display: 'Wood', value: 'wood' },\n { display: 'Plastic', value: 'plastic' }\n ]\n }\n }\n ],\n help: 'scene/component/pallet'\n}\n\n// Carriable: a pallet can sit on AGV / Forklift / robot-arm gripper / Spot\n// and also accept boxes / parcels as children (ContainerAbstract base\n// provides the child-container behavior; Carriable only adds the\n// holder-mount hook).\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Pallet renders only as a 3D mesh.\n/**\n * Pallet — a flat transport structure that goods are stacked and stored on.\n *\n * Standard EUR pallet is 1200 × 800mm × 144mm; we don't enforce these\n * dimensions but they're a good starting point for the catalog templates.\n *\n * **Container-based.** Boxes / parcels stacked on the pallet are added as\n * children — same `containable()` archetype-filter pattern as Forklift / Agv.\n * Visual stacking (children rendering on top of the pallet rather than at\n * absolute operation level) is a v2 concern; see ARCHITECTURE NOTES below.\n *\n * **Placement = `operation`.** A pallet's *normal* state is loaded and in\n * transit on a conveyor / AGV / forklift fork — at operation level. Empty\n * pallets in a floor-storage area are an exceptional state where the user\n * sets `state.zPos = 0` explicitly. Default to the common case.\n *\n * ## ARCHITECTURE NOTES — visual stacking\n *\n * When a Box (also `placement: 'operation'`) is added as a child of a\n * Pallet, both default to z = operation_height. They overlap visually\n * rather than the box sitting on top of the pallet. Solving this cleanly\n * (parent-relative z derivation when the parent is a structural carrier)\n * is a follow-up — fmsim's pattern is to detect parent type at render time\n * (machine-3d.ts:113-122). v1 accepts the visual overlap; v2 will add the\n * detection.\n */\n@sceneComponent('pallet')\nexport default class Pallet extends Carriable(Legendable(Placeable(ContainerAbstract))) {\n declare state: PalletState\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'material', legend: BODY_LEGEND }\n }\n\n static placement: PlacementArchetype = 'operation'\n static align: Alignment = 'bottom'\n static defaultDepth = 150 // EUR pallet is 144mm; 150 is the round number convention\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** Accept other operation-archetype cargo (boxes, parcels, smaller pallets) as stacked children. */\n containable(component: Component) {\n const archetype = (component.constructor as any).placement\n if (archetype === 'operation') return true\n return component.isDescendible(this)\n }\n\n /**\n * 2D — top-down silhouette. Body is a flat rectangle (wood/plastic deck);\n * `postrender()` adds the deck pattern + edge stroke so the pallet reads\n * as a pallet instead of a featureless rectangle.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n /**\n * Deck pattern + edge stroke. Wood: parallel slats with darker grooves\n * between (typical EUR pallet deck). Plastic: cross-cutout pattern\n * suggesting the molded reinforcement ribs.\n *\n * Slats run along the *short* axis of the rectangle (= along the longer\n * stringer direction in real life), so a 1200×800 pallet shows multiple\n * narrow slats across the 1200mm dimension — matching the EUR layout.\n */\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const isPlastic = this.state.material === 'plastic'\n\n ctx.save()\n\n if (!isPlastic) {\n // Wood — slats. Run them along the longer axis so the grooves are\n // perpendicular to the longer side (typical pallet appearance).\n const longAxisHorizontal = width >= height\n const slatCount = 5\n const grooveColor = '#7a4f25'\n ctx.fillStyle = grooveColor\n if (longAxisHorizontal) {\n // grooves vertical (X direction across the width)\n const grooveW = Math.max(1, width * 0.012)\n const slatW = (width - grooveW * (slatCount - 1)) / slatCount\n for (let i = 1; i < slatCount; i++) {\n const x = left + i * slatW + (i - 1) * grooveW\n ctx.fillRect(x, top + height * 0.05, grooveW, height * 0.9)\n }\n } else {\n const grooveH = Math.max(1, height * 0.012)\n const slatH = (height - grooveH * (slatCount - 1)) / slatCount\n for (let i = 1; i < slatCount; i++) {\n const y = top + i * slatH + (i - 1) * grooveH\n ctx.fillRect(left + width * 0.05, y, width * 0.9, grooveH)\n }\n }\n } else {\n // Plastic — cross + corner cutouts hint\n ctx.strokeStyle = '#3a4956'\n ctx.lineWidth = Math.max(1, Math.min(width, height) * 0.012)\n ctx.beginPath()\n ctx.moveTo(left + width * 0.5, top + height * 0.1)\n ctx.lineTo(left + width * 0.5, top + height * 0.9)\n ctx.moveTo(left + width * 0.1, top + height * 0.5)\n ctx.lineTo(left + width * 0.9, top + height * 0.5)\n ctx.stroke()\n }\n\n // Edge stroke\n ctx.strokeStyle = isPlastic ? '#2a3946' : '#5e3818'\n ctx.lineWidth = 1\n ctx.strokeRect(left, top, width, height)\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#a87644'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Pallet3D(this)\n }\n\n /**\n * Phase H — pickup contract. EUR/EPAL pallet 의 fork 진입은 양 단면 (긴 변\n * 두 군데). 'east' / 'west' = pallet 의 짧은 축 방향에서 fork 가 들어감.\n *\n * fork 진입 높이 (entryHeight) 는 pallet 의 fork pocket 위치 — pallet 의\n * 바닥에서 약 50mm (top deck 와 bottom deck 사이의 stringer 영역). 표준\n * EUR pallet 의 fork pocket 은 144mm 두께의 약 50% 지점.\n *\n * approachDistance 는 forklift 가 fork 끝을 pallet 에 닿기 직전 hover 자세 —\n * pallet 길이만큼 떨어져서 fork 길이 (보통 1100mm) 가 다 들어가도록 한다.\n * 여기선 conservative 하게 pallet 너비 + 200mm.\n */\n pickupFrames(): PickupFrame[] {\n const { width = 1200, height = 800 } = this.state\n const palletDepth = (this.constructor as any).defaultDepth ?? 150\n\n // 4-way pallet: 모든 면에서 fork 진입 가능. 2-way 는 sides 를 ['east','west']\n // 로 한정 — pallet 의 stringer 방향에 따라 다르나 default 는 4-way 가정.\n // (state.palletType 같은 속성 추가 시 분기 가능 — 현재는 4-way 가정.)\n const me: PoseSerialized = (() => {\n const wp = getWorldPose(this)\n return {\n position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },\n rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }\n }\n })()\n\n const longerAxis = Math.max(width, height)\n\n return rectangularFootprintFrames({\n carrierWorld: me,\n width,\n depth: height,\n entryHeight: palletDepth * 0.4, // fork pocket 중심\n approachDistance: longerAxis + 200,\n sides: ['east', 'west', 'north', 'south'],\n toolType: 'forklift-fork',\n tolerance: { positionMm: 50, angleDeg: 5 },\n priority: 0\n })\n }\n}\n"]}
|
package/dist/parcel.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComponentNature, RealObject } from '@hatiolab/things-scene';
|
|
2
|
-
import type { State, Material3D } from '@hatiolab/things-scene';
|
|
2
|
+
import type { State, Material3D, PickupFrame } from '@hatiolab/things-scene';
|
|
3
3
|
import { type Alignment, type PlacementArchetype } from '@operato/scene-base';
|
|
4
4
|
/** Parcel 컴포넌트 state */
|
|
5
5
|
export interface ParcelState extends State {
|
|
@@ -33,5 +33,11 @@ export default class Parcel extends Parcel_base {
|
|
|
33
33
|
render(ctx: CanvasRenderingContext2D): void;
|
|
34
34
|
get fillStyle(): string;
|
|
35
35
|
buildRealObject(): RealObject | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Phase H — pickup contract. Parcel 은 위에서 vacuum gripper / suction cup 으로
|
|
38
|
+
* 집기 — Box 와 동일한 패턴이지만 cardboard 표면이라 더 큰 흡착 면 필요.
|
|
39
|
+
* tolerance 도 약간 완화 (cardboard 변형 가능성).
|
|
40
|
+
*/
|
|
41
|
+
pickupFrames(): PickupFrame[];
|
|
36
42
|
}
|
|
37
43
|
export {};
|
package/dist/parcel.js
CHANGED
|
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
/*
|
|
3
3
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
4
4
|
*/
|
|
5
|
-
import { RectPath, Shape, sceneComponent } from '@hatiolab/things-scene';
|
|
5
|
+
import { RectPath, Shape, topApproachFrame, getWorldPose, sceneComponent } from '@hatiolab/things-scene';
|
|
6
6
|
import { Carriable, Placeable } from '@operato/scene-base';
|
|
7
7
|
import { Parcel3D } from './parcel-3d.js';
|
|
8
8
|
const NATURE = {
|
|
@@ -58,6 +58,30 @@ let Parcel = class Parcel extends Carriable(Placeable(RectPath(Shape))) {
|
|
|
58
58
|
buildRealObject() {
|
|
59
59
|
return new Parcel3D(this);
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Phase H — pickup contract. Parcel 은 위에서 vacuum gripper / suction cup 으로
|
|
63
|
+
* 집기 — Box 와 동일한 패턴이지만 cardboard 표면이라 더 큰 흡착 면 필요.
|
|
64
|
+
* tolerance 도 약간 완화 (cardboard 변형 가능성).
|
|
65
|
+
*/
|
|
66
|
+
pickupFrames() {
|
|
67
|
+
const wp = getWorldPose(this);
|
|
68
|
+
const me = {
|
|
69
|
+
position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },
|
|
70
|
+
rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }
|
|
71
|
+
};
|
|
72
|
+
const parcelDepth = this.constructor.defaultDepth ?? 150;
|
|
73
|
+
return [
|
|
74
|
+
topApproachFrame({
|
|
75
|
+
carrierWorld: me,
|
|
76
|
+
topY: parcelDepth,
|
|
77
|
+
approachDistance: 80, // gripper hover 거리 (Box 보다 더 — vacuum 펼침)
|
|
78
|
+
toolType: 'gripper',
|
|
79
|
+
tolerance: { positionMm: 10, angleDeg: 2 }, // cardboard 변형 감안
|
|
80
|
+
priority: 0,
|
|
81
|
+
id: 'top-suction'
|
|
82
|
+
})
|
|
83
|
+
];
|
|
84
|
+
}
|
|
61
85
|
};
|
|
62
86
|
Parcel = __decorate([
|
|
63
87
|
sceneComponent('parcel')
|
package/dist/parcel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parcel.js","sourceRoot":"","sources":["../src/parcel.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"parcel.js","sourceRoot":"","sources":["../src/parcel.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAGL,QAAQ,EACR,KAAK,EACL,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACf,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EACL,SAAS,EACT,SAAS,EAGV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAWzC,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,UAAU,EAAE;QACV;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,aAAa;YACpB,IAAI,EAAE,YAAY;SACnB;KACF;IACD,IAAI,EAAE,wBAAwB;CAC/B,CAAA;AAED,yEAAyE;AACzE,uEAAuE;AACvE,oDAAoD;AACpD;;;;;;;;;;;;;;GAcG;AAEY,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAGvE,MAAM,CAAC,SAAS,GAAuB,WAAW,CAAA;IAClD,MAAM,CAAC,KAAK,GAAc,QAAQ,CAAA;IAClC,MAAM,CAAC,YAAY,GAAG,GAAG,CAAA;IAEzB,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED,gDAAgD;IAChD,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;IACpC,CAAC;IAED,IAAI,SAAS;QACX,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,eAAe;QACb,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IAED;;;;OAIG;IACH,YAAY;QACV,MAAM,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;QAC7B,MAAM,EAAE,GAAmB;YACzB,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE;YAClE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE;SACrF,CAAA;QACD,MAAM,WAAW,GAAI,IAAI,CAAC,WAAmB,CAAC,YAAY,IAAI,GAAG,CAAA;QAEjE,OAAO;YACL,gBAAgB,CAAC;gBACf,YAAY,EAAE,EAAE;gBAChB,IAAI,EAAE,WAAW;gBACjB,gBAAgB,EAAE,EAAE,EAAa,0CAA0C;gBAC3E,QAAQ,EAAE,SAAS;gBACnB,SAAS,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAG,kBAAkB;gBAC/D,QAAQ,EAAE,CAAC;gBACX,EAAE,EAAE,aAAa;aAClB,CAAC;SACH,CAAA;IACH,CAAC;;AAtDkB,MAAM;IAD1B,cAAc,CAAC,QAAQ,CAAC;GACJ,MAAM,CAuD1B;eAvDoB,MAAM","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport {\n ComponentNature,\n RealObject,\n RectPath,\n Shape,\n topApproachFrame,\n getWorldPose,\n sceneComponent\n} from '@hatiolab/things-scene'\nimport type { State, Material3D, PickupFrame, PoseSerialized } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Placeable,\n type Alignment,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Parcel3D } from './parcel-3d.js'\n\n/** Parcel 컴포넌트 state */\nexport interface ParcelState extends State {\n // ── 정체 ──\n trackingId?: string\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'string',\n label: 'tracking-id',\n name: 'trackingId'\n }\n ],\n help: 'scene/component/parcel'\n}\n\n// Carriable: parcel can be a child of any CarrierHolder (Spot, robot-arm\n// gripper, AGV deck, …). Mixin wraps add() so the parcel's 3D object3d\n// is reattached to the holder's chosen mount frame.\n/**\n * Parcel — a cardboard package, the typical e-commerce / parcel-sortation unit.\n *\n * Distinct from `Box` because parcels have:\n * - cardboard appearance (tan/brown corrugate, not wood / plastic)\n * - tape line down the center (the visual signature that says \"package\")\n * - typically a label on top (where shipping info goes)\n * - flatter / more elongated proportions in real-world parcel networks\n *\n * No `material` prop — parcels are always cardboard. If a future shipping\n * domain needs metal cases or polybags, those become separate components.\n *\n * No Legendable for v1 — parcel color is fixed cardboard. Future damaged /\n * inspected indicators would add a status legend then.\n */\n@sceneComponent('parcel')\nexport default class Parcel extends Carriable(Placeable(RectPath(Shape))) {\n declare state: ParcelState\n\n static placement: PlacementArchetype = 'operation'\n static align: Alignment = 'bottom'\n static defaultDepth = 150\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** 2D — top-down rectangle in cardboard tan. */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n get fillStyle() {\n return '#c8a878'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Parcel3D(this)\n }\n\n /**\n * Phase H — pickup contract. Parcel 은 위에서 vacuum gripper / suction cup 으로\n * 집기 — Box 와 동일한 패턴이지만 cardboard 표면이라 더 큰 흡착 면 필요.\n * tolerance 도 약간 완화 (cardboard 변형 가능성).\n */\n pickupFrames(): PickupFrame[] {\n const wp = getWorldPose(this)\n const me: PoseSerialized = {\n position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },\n rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }\n }\n const parcelDepth = (this.constructor as any).defaultDepth ?? 150\n\n return [\n topApproachFrame({\n carrierWorld: me,\n topY: parcelDepth,\n approachDistance: 80, // gripper hover 거리 (Box 보다 더 — vacuum 펼침)\n toolType: 'gripper',\n tolerance: { positionMm: 10, angleDeg: 2 }, // cardboard 변형 감안\n priority: 0,\n id: 'top-suction'\n })\n ]\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@operato/scene-storage",
|
|
3
3
|
"description": "Storage-domain components for things-scene (smart factory / logistics) — pallet, box, parcel; AS/RS and shelves planned.",
|
|
4
4
|
"author": "heartyoh",
|
|
5
|
-
"version": "10.0.0-beta.
|
|
5
|
+
"version": "10.0.0-beta.32",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"module": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@hatiolab/things-scene": "^10.0.0-beta.1",
|
|
29
|
-
"@operato/scene-base": "^10.0.0-beta.
|
|
29
|
+
"@operato/scene-base": "^10.0.0-beta.32",
|
|
30
30
|
"three": "^0.183.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"typescript": "^5.0.4"
|
|
46
46
|
},
|
|
47
47
|
"prettier": "@hatiolab/prettier-config",
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "9c9fec2b993ccda805cdd53fe11e15fb4b71254e"
|
|
49
49
|
}
|
package/src/asrs-crane.ts
CHANGED
|
@@ -135,6 +135,15 @@ export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Leg
|
|
|
135
135
|
/** Yaw offset: crane model is drawn with the aisle axis along X (right = forward). */
|
|
136
136
|
static yawOffset = 0
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Phase H — ASRS crane 은 telescoping forks 로 rack cell 안의 carrier 픽업.
|
|
140
|
+
* forklift-fork 와 동일 mechanism — pallet 의 fork pocket 진입.
|
|
141
|
+
* (Mover mixin chain 이 :any 라 override 키워드 불가, getter 만 정의.)
|
|
142
|
+
*/
|
|
143
|
+
get toolType(): string {
|
|
144
|
+
return 'forklift-fork'
|
|
145
|
+
}
|
|
146
|
+
|
|
138
147
|
get nature() {
|
|
139
148
|
return NATURE
|
|
140
149
|
}
|
package/src/box.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
ComponentNature,
|
|
6
|
+
RealObject,
|
|
7
|
+
RectPath,
|
|
8
|
+
Shape,
|
|
9
|
+
topApproachFrame,
|
|
10
|
+
getWorldPose,
|
|
11
|
+
sceneComponent
|
|
12
|
+
} from '@hatiolab/things-scene'
|
|
13
|
+
import type { State, Material3D, PickupFrame, PoseSerialized } from '@hatiolab/things-scene'
|
|
6
14
|
import {
|
|
7
15
|
Carriable,
|
|
8
16
|
Legendable,
|
|
@@ -106,4 +114,33 @@ export default class Box extends Carriable(Legendable(Placeable(RectPath(Shape))
|
|
|
106
114
|
buildRealObject(): RealObject | undefined {
|
|
107
115
|
return new Box3D(this)
|
|
108
116
|
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Phase H — pickup contract. Box 는 위에서 gripper / vacuum cup 으로 집기 —
|
|
120
|
+
* 단일 entry (top center). Box 의 dimensions 가 작아서 forklift fork 보다는
|
|
121
|
+
* gripper 가 일반적. forklift 로 들어올릴 box 는 통상 pallet 위에 stacking
|
|
122
|
+
* 후 pallet 째로 운반.
|
|
123
|
+
*
|
|
124
|
+
* tolerance 가 pallet 보다 빡빡 (gripper 정밀도 vs forklift pocket 폭).
|
|
125
|
+
*/
|
|
126
|
+
pickupFrames(): PickupFrame[] {
|
|
127
|
+
const wp = getWorldPose(this)
|
|
128
|
+
const me: PoseSerialized = {
|
|
129
|
+
position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },
|
|
130
|
+
rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }
|
|
131
|
+
}
|
|
132
|
+
const boxDepth = (this.constructor as any).defaultDepth ?? 300
|
|
133
|
+
|
|
134
|
+
return [
|
|
135
|
+
topApproachFrame({
|
|
136
|
+
carrierWorld: me,
|
|
137
|
+
topY: boxDepth, // Box top in carrier-local Y (depth = full height; top at depth)
|
|
138
|
+
approachDistance: 50, // gripper 가 hover 하는 거리
|
|
139
|
+
toolType: 'gripper',
|
|
140
|
+
tolerance: { positionMm: 5, angleDeg: 1 },
|
|
141
|
+
priority: 0,
|
|
142
|
+
id: 'top-gripper'
|
|
143
|
+
})
|
|
144
|
+
]
|
|
145
|
+
}
|
|
109
146
|
}
|
package/src/pallet.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
Component,
|
|
6
|
+
ComponentNature,
|
|
7
|
+
ContainerAbstract,
|
|
8
|
+
RealObject,
|
|
9
|
+
Pose6DOF,
|
|
10
|
+
rectangularFootprintFrames,
|
|
11
|
+
getWorldPose,
|
|
12
|
+
sceneComponent
|
|
13
|
+
} from '@hatiolab/things-scene'
|
|
14
|
+
import type { State, Material3D, PickupFrame, PoseSerialized } from '@hatiolab/things-scene'
|
|
6
15
|
import {
|
|
7
16
|
Carriable,
|
|
8
17
|
Legendable,
|
|
@@ -201,4 +210,46 @@ export default class Pallet extends Carriable(Legendable(Placeable(ContainerAbst
|
|
|
201
210
|
buildRealObject(): RealObject | undefined {
|
|
202
211
|
return new Pallet3D(this)
|
|
203
212
|
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Phase H — pickup contract. EUR/EPAL pallet 의 fork 진입은 양 단면 (긴 변
|
|
216
|
+
* 두 군데). 'east' / 'west' = pallet 의 짧은 축 방향에서 fork 가 들어감.
|
|
217
|
+
*
|
|
218
|
+
* fork 진입 높이 (entryHeight) 는 pallet 의 fork pocket 위치 — pallet 의
|
|
219
|
+
* 바닥에서 약 50mm (top deck 와 bottom deck 사이의 stringer 영역). 표준
|
|
220
|
+
* EUR pallet 의 fork pocket 은 144mm 두께의 약 50% 지점.
|
|
221
|
+
*
|
|
222
|
+
* approachDistance 는 forklift 가 fork 끝을 pallet 에 닿기 직전 hover 자세 —
|
|
223
|
+
* pallet 길이만큼 떨어져서 fork 길이 (보통 1100mm) 가 다 들어가도록 한다.
|
|
224
|
+
* 여기선 conservative 하게 pallet 너비 + 200mm.
|
|
225
|
+
*/
|
|
226
|
+
pickupFrames(): PickupFrame[] {
|
|
227
|
+
const { width = 1200, height = 800 } = this.state
|
|
228
|
+
const palletDepth = (this.constructor as any).defaultDepth ?? 150
|
|
229
|
+
|
|
230
|
+
// 4-way pallet: 모든 면에서 fork 진입 가능. 2-way 는 sides 를 ['east','west']
|
|
231
|
+
// 로 한정 — pallet 의 stringer 방향에 따라 다르나 default 는 4-way 가정.
|
|
232
|
+
// (state.palletType 같은 속성 추가 시 분기 가능 — 현재는 4-way 가정.)
|
|
233
|
+
const me: PoseSerialized = (() => {
|
|
234
|
+
const wp = getWorldPose(this)
|
|
235
|
+
return {
|
|
236
|
+
position: { x: wp.position.x, y: wp.position.y, z: wp.position.z },
|
|
237
|
+
rotation: { x: wp.rotation.x, y: wp.rotation.y, z: wp.rotation.z, w: wp.rotation.w }
|
|
238
|
+
}
|
|
239
|
+
})()
|
|
240
|
+
|
|
241
|
+
const longerAxis = Math.max(width, height)
|
|
242
|
+
|
|
243
|
+
return rectangularFootprintFrames({
|
|
244
|
+
carrierWorld: me,
|
|
245
|
+
width,
|
|
246
|
+
depth: height,
|
|
247
|
+
entryHeight: palletDepth * 0.4, // fork pocket 중심
|
|
248
|
+
approachDistance: longerAxis + 200,
|
|
249
|
+
sides: ['east', 'west', 'north', 'south'],
|
|
250
|
+
toolType: 'forklift-fork',
|
|
251
|
+
tolerance: { positionMm: 50, angleDeg: 5 },
|
|
252
|
+
priority: 0
|
|
253
|
+
})
|
|
254
|
+
}
|
|
204
255
|
}
|