@operato/scene-storage 10.0.0-beta.22

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +59 -0
  3. package/dist/asrs-crane-3d.d.ts +7 -0
  4. package/dist/asrs-crane-3d.js +164 -0
  5. package/dist/asrs-crane-3d.js.map +1 -0
  6. package/dist/asrs-crane.d.ts +47 -0
  7. package/dist/asrs-crane.js +104 -0
  8. package/dist/asrs-crane.js.map +1 -0
  9. package/dist/asrs-rack-3d.d.ts +7 -0
  10. package/dist/asrs-rack-3d.js +129 -0
  11. package/dist/asrs-rack-3d.js.map +1 -0
  12. package/dist/asrs-rack.d.ts +45 -0
  13. package/dist/asrs-rack.js +99 -0
  14. package/dist/asrs-rack.js.map +1 -0
  15. package/dist/box-3d.d.ts +11 -0
  16. package/dist/box-3d.js +166 -0
  17. package/dist/box-3d.js.map +1 -0
  18. package/dist/box.d.ts +36 -0
  19. package/dist/box.js +73 -0
  20. package/dist/box.js.map +1 -0
  21. package/dist/index.d.ts +10 -0
  22. package/dist/index.js +11 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/pallet-3d.d.ts +11 -0
  25. package/dist/pallet-3d.js +162 -0
  26. package/dist/pallet-3d.js.map +1 -0
  27. package/dist/pallet.d.ts +56 -0
  28. package/dist/pallet.js +99 -0
  29. package/dist/pallet.js.map +1 -0
  30. package/dist/parcel-3d.d.ts +7 -0
  31. package/dist/parcel-3d.js +82 -0
  32. package/dist/parcel-3d.js.map +1 -0
  33. package/dist/parcel.d.ts +30 -0
  34. package/dist/parcel.js +67 -0
  35. package/dist/parcel.js.map +1 -0
  36. package/dist/spot-3d.d.ts +30 -0
  37. package/dist/spot-3d.js +176 -0
  38. package/dist/spot-3d.js.map +1 -0
  39. package/dist/spot.d.ts +41 -0
  40. package/dist/spot.js +177 -0
  41. package/dist/spot.js.map +1 -0
  42. package/dist/templates/index.d.ts +92 -0
  43. package/dist/templates/index.js +115 -0
  44. package/dist/templates/index.js.map +1 -0
  45. package/dist/templates/spot.d.ts +24 -0
  46. package/dist/templates/spot.js +26 -0
  47. package/dist/templates/spot.js.map +1 -0
  48. package/icons/asrs-crane.png +0 -0
  49. package/icons/asrs-rack.png +0 -0
  50. package/icons/box-plastic.png +0 -0
  51. package/icons/box-wood.png +0 -0
  52. package/icons/pallet-plastic.png +0 -0
  53. package/icons/pallet-wood.png +0 -0
  54. package/icons/parcel.png +0 -0
  55. package/package.json +44 -0
  56. package/src/asrs-crane-3d.ts +191 -0
  57. package/src/asrs-crane.ts +130 -0
  58. package/src/asrs-rack-3d.ts +146 -0
  59. package/src/asrs-rack.ts +109 -0
  60. package/src/box-3d.ts +189 -0
  61. package/src/box.ts +99 -0
  62. package/src/index.ts +17 -0
  63. package/src/pallet-3d.ts +181 -0
  64. package/src/pallet.ts +125 -0
  65. package/src/parcel-3d.ts +90 -0
  66. package/src/parcel.ts +76 -0
  67. package/src/spot-3d.ts +200 -0
  68. package/src/spot.ts +197 -0
  69. package/src/templates/index.ts +115 -0
  70. package/src/templates/spot.ts +26 -0
  71. package/things-scene.config.js +5 -0
  72. package/translations/en.json +12 -0
  73. package/translations/ja.json +12 -0
  74. package/translations/ko.json +12 -0
  75. package/translations/ms.json +12 -0
  76. package/translations/zh.json +12 -0
  77. package/tsconfig.json +23 -0
  78. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,45 @@
1
+ import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import { type Alignment, type Heights, type PlacementArchetype } from '@operato/scene-base';
3
+ declare const Base: typeof Component;
4
+ /**
5
+ * AsrsRack — a multi-level high-bay storage rack, the structural backbone of
6
+ * an AS/RS (Automated Storage / Retrieval System).
7
+ *
8
+ * `levels` × `bays` cells form a vertical grid. Each cell holds one logistics
9
+ * package (Pallet / Box / Parcel). A pair of AsrsRacks separated by an aisle
10
+ * (where an AsrsCrane runs) is the typical AS/RS configuration; v1 ships the
11
+ * single-rack unit and lets users compose multi-rack systems by placing them
12
+ * side by side. A future `AsrsAisle` composite may bundle the pair + crane.
13
+ *
14
+ * **Placement**: `floor` archetype, full ceiling depth by default — AS/RS
15
+ * racks typically span floor to ceiling, with levels sized to fit the tallest
16
+ * pallet load. Users can shorten via explicit `state.depth` for warehouses
17
+ * with smaller envelopes.
18
+ *
19
+ * **Container-based**. Cells host stored cargo as children — each child's
20
+ * left/top within the rack's bounds determines which cell it occupies. The
21
+ * stacking pass in `Placeable.computeDefaultZPos` ensures each child cargo's
22
+ * z lands on the rack's overall bottom (parent.zPos + parent.depth = ceiling),
23
+ * which isn't quite cell-level resolution — true per-cell z positioning is
24
+ * a v3 concern (the cargo would need to know which cell-row it's in).
25
+ *
26
+ * No Legendable for v1 — racks are passive structures; their per-cell
27
+ * occupancy state is implicit in the children, not a status flag.
28
+ */
29
+ export default class AsrsRack extends Base {
30
+ static placement: PlacementArchetype;
31
+ static align: Alignment;
32
+ static defaultDepth: (h: Heights) => number;
33
+ get nature(): ComponentNature;
34
+ get anchors(): never[];
35
+ /** Operation cargo (pallets / boxes / parcels) goes in the rack's cells. */
36
+ containable(component: Component): boolean;
37
+ /**
38
+ * 2D — top-down rectangle showing the rack footprint, with subdivisions
39
+ * suggesting the bay layout (lines parallel to the aisle).
40
+ */
41
+ render(ctx: CanvasRenderingContext2D): void;
42
+ get fillStyle(): string;
43
+ buildRealObject(): RealObject | undefined;
44
+ }
45
+ export {};
@@ -0,0 +1,99 @@
1
+ import { __decorate } from "tslib";
2
+ /*
3
+ * Copyright © HatioLab Inc. All rights reserved.
4
+ */
5
+ import { Container, sceneComponent } from '@hatiolab/things-scene';
6
+ import { Placeable } from '@operato/scene-base';
7
+ import { AsrsRack3D } from './asrs-rack-3d.js';
8
+ const NATURE = {
9
+ mutable: false,
10
+ resizable: true,
11
+ rotatable: true,
12
+ properties: [
13
+ {
14
+ type: 'number',
15
+ label: 'levels',
16
+ name: 'levels',
17
+ placeholder: '# of vertical levels (default 4)'
18
+ },
19
+ {
20
+ type: 'number',
21
+ label: 'bays',
22
+ name: 'bays',
23
+ placeholder: '# of horizontal bays (default 5)'
24
+ }
25
+ ],
26
+ help: 'scene/component/asrs-rack'
27
+ };
28
+ const Base = Placeable(Container);
29
+ /**
30
+ * AsrsRack — a multi-level high-bay storage rack, the structural backbone of
31
+ * an AS/RS (Automated Storage / Retrieval System).
32
+ *
33
+ * `levels` × `bays` cells form a vertical grid. Each cell holds one logistics
34
+ * package (Pallet / Box / Parcel). A pair of AsrsRacks separated by an aisle
35
+ * (where an AsrsCrane runs) is the typical AS/RS configuration; v1 ships the
36
+ * single-rack unit and lets users compose multi-rack systems by placing them
37
+ * side by side. A future `AsrsAisle` composite may bundle the pair + crane.
38
+ *
39
+ * **Placement**: `floor` archetype, full ceiling depth by default — AS/RS
40
+ * racks typically span floor to ceiling, with levels sized to fit the tallest
41
+ * pallet load. Users can shorten via explicit `state.depth` for warehouses
42
+ * with smaller envelopes.
43
+ *
44
+ * **Container-based**. Cells host stored cargo as children — each child's
45
+ * left/top within the rack's bounds determines which cell it occupies. The
46
+ * stacking pass in `Placeable.computeDefaultZPos` ensures each child cargo's
47
+ * z lands on the rack's overall bottom (parent.zPos + parent.depth = ceiling),
48
+ * which isn't quite cell-level resolution — true per-cell z positioning is
49
+ * a v3 concern (the cargo would need to know which cell-row it's in).
50
+ *
51
+ * No Legendable for v1 — racks are passive structures; their per-cell
52
+ * occupancy state is implicit in the children, not a status flag.
53
+ */
54
+ let AsrsRack = class AsrsRack extends Base {
55
+ static placement = 'floor';
56
+ static align = 'bottom';
57
+ static defaultDepth = (h) => h.ceiling - h.floor;
58
+ get nature() {
59
+ return NATURE;
60
+ }
61
+ get anchors() {
62
+ return [];
63
+ }
64
+ /** Operation cargo (pallets / boxes / parcels) goes in the rack's cells. */
65
+ containable(component) {
66
+ const archetype = component.constructor.placement;
67
+ if (archetype === 'operation')
68
+ return true;
69
+ return component.isDescendible(this);
70
+ }
71
+ /**
72
+ * 2D — top-down rectangle showing the rack footprint, with subdivisions
73
+ * suggesting the bay layout (lines parallel to the aisle).
74
+ */
75
+ render(ctx) {
76
+ const { width, height, left, top } = this.state;
77
+ const bays = Math.max(1, Math.floor(this.state.bays || 5));
78
+ ctx.beginPath();
79
+ // Outer rectangle
80
+ ctx.rect(left, top, width, height);
81
+ // Bay subdivisions (vertical lines)
82
+ for (let i = 1; i < bays; i++) {
83
+ const x = left + (width * i) / bays;
84
+ ctx.moveTo(x, top);
85
+ ctx.lineTo(x, top + height);
86
+ }
87
+ }
88
+ get fillStyle() {
89
+ return '#a0a0a8';
90
+ }
91
+ buildRealObject() {
92
+ return new AsrsRack3D(this);
93
+ }
94
+ };
95
+ AsrsRack = __decorate([
96
+ sceneComponent('asrs-rack')
97
+ ], AsrsRack);
98
+ export default AsrsRack;
99
+ //# sourceMappingURL=asrs-rack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asrs-rack.js","sourceRoot":"","sources":["../src/asrs-rack.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA8B,SAAS,EAAc,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC1G,OAAO,EACL,SAAS,EAIV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,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,WAAW,EAAE,kCAAkC;SAChD;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,MAAM;YACZ,WAAW,EAAE,kCAAkC;SAChD;KACF;IACD,IAAI,EAAE,2BAA2B;CAClC,CAAA;AAED,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAgC,CAAA;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEY,IAAM,QAAQ,GAAd,MAAM,QAAS,SAAQ,IAAI;IACxC,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,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED,4EAA4E;IAC5E,WAAW,CAAC,SAAoB;QAC9B,MAAM,SAAS,GAAI,SAAS,CAAC,WAAmB,CAAC,SAAS,CAAA;QAC1D,IAAI,SAAS,KAAK,WAAW;YAAE,OAAO,IAAI,CAAA;QAC1C,OAAO,SAAS,CAAC,aAAa,CAAC,IAAW,CAAC,CAAA;IAC7C,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAE,IAAI,CAAC,KAAK,CAAC,IAAe,IAAI,CAAC,CAAC,CAAC,CAAA;QAEtE,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,kBAAkB;QAClB,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;QAClC,oCAAoC;QACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAA;YACnC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;YAClB,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;IAED,IAAI,SAAS;QACX,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,eAAe;QACb,OAAO,IAAI,UAAU,CAAC,IAAW,CAAC,CAAA;IACpC,CAAC;;AA7CkB,QAAQ;IAD5B,cAAc,CAAC,WAAW,CAAC;GACP,QAAQ,CA8C5B;eA9CoB,QAAQ","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, Container, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport {\n Placeable,\n type Alignment,\n type Heights,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { AsrsRack3D } from './asrs-rack-3d.js'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'number',\n label: 'levels',\n name: 'levels',\n placeholder: '# of vertical levels (default 4)'\n },\n {\n type: 'number',\n label: 'bays',\n name: 'bays',\n placeholder: '# of horizontal bays (default 5)'\n }\n ],\n help: 'scene/component/asrs-rack'\n}\n\nconst Base = Placeable(Container) as unknown as typeof Component\n\n/**\n * AsrsRack — a multi-level high-bay storage rack, the structural backbone of\n * an AS/RS (Automated Storage / Retrieval System).\n *\n * `levels` × `bays` cells form a vertical grid. Each cell holds one logistics\n * package (Pallet / Box / Parcel). A pair of AsrsRacks separated by an aisle\n * (where an AsrsCrane runs) is the typical AS/RS configuration; v1 ships the\n * single-rack unit and lets users compose multi-rack systems by placing them\n * side by side. A future `AsrsAisle` composite may bundle the pair + crane.\n *\n * **Placement**: `floor` archetype, full ceiling depth by default — AS/RS\n * racks typically span floor to ceiling, with levels sized to fit the tallest\n * pallet load. Users can shorten via explicit `state.depth` for warehouses\n * with smaller envelopes.\n *\n * **Container-based**. Cells host stored cargo as children — each child's\n * left/top within the rack's bounds determines which cell it occupies. The\n * stacking pass in `Placeable.computeDefaultZPos` ensures each child cargo's\n * z lands on the rack's overall bottom (parent.zPos + parent.depth = ceiling),\n * which isn't quite cell-level resolution — true per-cell z positioning is\n * a v3 concern (the cargo would need to know which cell-row it's in).\n *\n * No Legendable for v1 — racks are passive structures; their per-cell\n * occupancy state is implicit in the children, not a status flag.\n */\n@sceneComponent('asrs-rack')\nexport default class AsrsRack extends Base {\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.ceiling - h.floor\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** Operation cargo (pallets / boxes / parcels) goes in the rack's cells. */\n containable(component: Component) {\n const archetype = (component.constructor as any).placement\n if (archetype === 'operation') return true\n return component.isDescendible(this as any)\n }\n\n /**\n * 2D — top-down rectangle showing the rack footprint, with subdivisions\n * suggesting the bay layout (lines parallel to the aisle).\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const bays = Math.max(1, Math.floor((this.state.bays as number) || 5))\n\n ctx.beginPath()\n // Outer rectangle\n ctx.rect(left, top, width, height)\n // Bay subdivisions (vertical lines)\n for (let i = 1; i < bays; i++) {\n const x = left + (width * i) / bays\n ctx.moveTo(x, top)\n ctx.lineTo(x, top + height)\n }\n }\n\n get fillStyle() {\n return '#a0a0a8'\n }\n\n buildRealObject(): RealObject | undefined {\n return new AsrsRack3D(this as any)\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { RealObjectGroup } from '@hatiolab/things-scene';
2
+ export declare class Box3D extends RealObjectGroup {
3
+ build(): void;
4
+ /** Wood crate — visible slats, 4 corner posts, open top. */
5
+ private buildWoodCrate;
6
+ /** Plastic tote — solid molded walls + top stackable lip. */
7
+ private buildPlasticTote;
8
+ updateDimension(): void;
9
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
10
+ updateAlpha(): void;
11
+ }
package/dist/box-3d.js ADDED
@@ -0,0 +1,166 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Box 3D — wood crate and plastic tote variants.
5
+ *
6
+ * Wood crate: 4 vertical corner posts + horizontal slats with gaps → the
7
+ * typical industrial wooden crate look. Forklift-friendly.
8
+ * Plastic tote: solid 4 walls + visible top lip / handle cutouts. Stackable.
9
+ *
10
+ * Both have a defined floor (so they look like containers, not just walls)
11
+ * and an opening at top — as you'd expect from a real crate / tote that's
12
+ * open or has a removable lid.
13
+ */
14
+ import * as THREE from 'three';
15
+ import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
16
+ import { RealObjectGroup } from '@hatiolab/things-scene';
17
+ export class Box3D extends RealObjectGroup {
18
+ build() {
19
+ super.build();
20
+ const { width, height, depth = 300 } = this.component.state;
21
+ const material = this.component.state.material || 'wood';
22
+ const bodyColor = this.component.state.bodyColor || '#a87644';
23
+ if (material === 'plastic') {
24
+ this.buildPlasticTote(width, height, depth, bodyColor);
25
+ }
26
+ else {
27
+ this.buildWoodCrate(width, height, depth, bodyColor);
28
+ }
29
+ }
30
+ /** Wood crate — visible slats, 4 corner posts, open top. */
31
+ buildWoodCrate(width, height, depth, bodyColor) {
32
+ const baseY = -depth / 2;
33
+ const wallThickness = Math.min(width, height) * 0.04;
34
+ const postW = wallThickness * 1.6;
35
+ const slatH = depth * 0.10;
36
+ const slatGap = slatH * 0.6;
37
+ const floorH = depth * 0.05;
38
+ const woodMaterial = new THREE.MeshStandardMaterial({
39
+ color: bodyColor,
40
+ metalness: 0,
41
+ roughness: 0.85
42
+ });
43
+ const postColor = new THREE.Color(bodyColor).multiplyScalar(0.8);
44
+ const postMaterial = new THREE.MeshStandardMaterial({
45
+ color: postColor,
46
+ metalness: 0,
47
+ roughness: 0.9
48
+ });
49
+ // ── 4 corner posts ───────────────────────────────────────────────
50
+ const postGeos = [];
51
+ for (const xSign of [-1, 1]) {
52
+ for (const zSign of [-1, 1]) {
53
+ const post = new THREE.BoxGeometry(postW, depth, postW);
54
+ post.translate(xSign * (width / 2 - postW / 2), baseY + depth / 2, zSign * (height / 2 - postW / 2));
55
+ postGeos.push(post);
56
+ }
57
+ }
58
+ const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos), postMaterial);
59
+ postMesh.castShadow = true;
60
+ this.object3d.add(postMesh);
61
+ // ── Slatted walls (horizontal slats with gaps) ───────────────────
62
+ const slatRowCount = Math.max(2, Math.floor((depth - floorH) / (slatH + slatGap)));
63
+ const slatGeos = [];
64
+ for (let row = 0; row < slatRowCount; row++) {
65
+ const y = baseY + floorH + slatGap + row * (slatH + slatGap) + slatH / 2;
66
+ // Long walls (front / back)
67
+ for (const zSign of [-1, 1]) {
68
+ const slat = new THREE.BoxGeometry(width - postW * 2, slatH, wallThickness);
69
+ slat.translate(0, y, zSign * (height / 2 - wallThickness / 2));
70
+ slatGeos.push(slat);
71
+ }
72
+ // Short walls (left / right)
73
+ for (const xSign of [-1, 1]) {
74
+ const slat = new THREE.BoxGeometry(wallThickness, slatH, height - postW * 2);
75
+ slat.translate(xSign * (width / 2 - wallThickness / 2), y, 0);
76
+ slatGeos.push(slat);
77
+ }
78
+ }
79
+ const slatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(slatGeos), woodMaterial);
80
+ slatMesh.castShadow = true;
81
+ slatMesh.receiveShadow = true;
82
+ this.object3d.add(slatMesh);
83
+ // ── Floor (bottom panel) ─────────────────────────────────────────
84
+ const floorGeo = new THREE.BoxGeometry(width - postW * 2, floorH, height - postW * 2);
85
+ const floorMesh = new THREE.Mesh(floorGeo, woodMaterial);
86
+ floorMesh.position.set(0, baseY + floorH / 2, 0);
87
+ floorMesh.receiveShadow = true;
88
+ this.object3d.add(floorMesh);
89
+ }
90
+ /** Plastic tote — solid molded walls + top stackable lip. */
91
+ buildPlasticTote(width, height, depth, bodyColor) {
92
+ const baseY = -depth / 2;
93
+ const wallThickness = Math.min(width, height) * 0.05;
94
+ const lipH = depth * 0.06;
95
+ const floorH = depth * 0.06;
96
+ const totMaterial = new THREE.MeshStandardMaterial({
97
+ color: bodyColor,
98
+ metalness: 0.05,
99
+ roughness: 0.55
100
+ });
101
+ const lipColor = new THREE.Color(bodyColor).multiplyScalar(0.85);
102
+ const lipMaterial = new THREE.MeshStandardMaterial({
103
+ color: lipColor,
104
+ metalness: 0.05,
105
+ roughness: 0.55
106
+ });
107
+ // ── 4 solid walls ────────────────────────────────────────────────
108
+ const wallGeos = [];
109
+ const wallH = depth - lipH - floorH;
110
+ const wallY = baseY + floorH + wallH / 2;
111
+ // Long walls
112
+ for (const zSign of [-1, 1]) {
113
+ const wall = new THREE.BoxGeometry(width, wallH, wallThickness);
114
+ wall.translate(0, wallY, zSign * (height / 2 - wallThickness / 2));
115
+ wallGeos.push(wall);
116
+ }
117
+ // Short walls
118
+ for (const xSign of [-1, 1]) {
119
+ const wall = new THREE.BoxGeometry(wallThickness, wallH, height - 2 * wallThickness);
120
+ wall.translate(xSign * (width / 2 - wallThickness / 2), wallY, 0);
121
+ wallGeos.push(wall);
122
+ }
123
+ const wallMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wallGeos), totMaterial);
124
+ wallMesh.castShadow = true;
125
+ wallMesh.receiveShadow = true;
126
+ this.object3d.add(wallMesh);
127
+ // ── Top lip (stackable rim — slightly wider than walls) ──────────
128
+ const lipGeos = [];
129
+ const lipY = baseY + depth - lipH / 2;
130
+ // Long sides
131
+ for (const zSign of [-1, 1]) {
132
+ const lip = new THREE.BoxGeometry(width * 1.02, lipH, wallThickness * 1.5);
133
+ lip.translate(0, lipY, zSign * (height / 2 - wallThickness * 0.75));
134
+ lipGeos.push(lip);
135
+ }
136
+ // Short sides
137
+ for (const xSign of [-1, 1]) {
138
+ const lip = new THREE.BoxGeometry(wallThickness * 1.5, lipH, height * 1.02 - 2 * wallThickness * 1.5);
139
+ lip.translate(xSign * (width / 2 - wallThickness * 0.75), lipY, 0);
140
+ lipGeos.push(lip);
141
+ }
142
+ const lipMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(lipGeos), lipMaterial);
143
+ lipMesh.castShadow = true;
144
+ this.object3d.add(lipMesh);
145
+ // ── Floor (solid bottom) ─────────────────────────────────────────
146
+ const floorGeo = new THREE.BoxGeometry(width, floorH, height);
147
+ const floorMesh = new THREE.Mesh(floorGeo, totMaterial);
148
+ floorMesh.position.set(0, baseY + floorH / 2, 0);
149
+ floorMesh.receiveShadow = true;
150
+ this.object3d.add(floorMesh);
151
+ }
152
+ updateDimension() { }
153
+ onchange(after, before) {
154
+ if ('material' in after ||
155
+ 'bodyColor' in after ||
156
+ 'width' in after ||
157
+ 'height' in after ||
158
+ 'depth' in after) {
159
+ this.update();
160
+ return;
161
+ }
162
+ super.onchange(after, before);
163
+ }
164
+ updateAlpha() { }
165
+ }
166
+ //# sourceMappingURL=box-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"box-3d.js","sourceRoot":"","sources":["../src/box-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Box 3D — wood crate and plastic tote variants.\n *\n * Wood crate: 4 vertical corner posts + horizontal slats with gaps → the\n * typical industrial wooden crate look. Forklift-friendly.\n * Plastic tote: solid 4 walls + visible top lip / handle cutouts. Stackable.\n *\n * Both have a defined floor (so they look like containers, not just walls)\n * and an opening at top — as you'd expect from a real crate / tote that's\n * open or has a removable lid.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nexport class Box3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 300 } = this.component.state\n const material = (this.component.state.material as string) || 'wood'\n const bodyColor = (this.component.state.bodyColor as string) || '#a87644'\n\n if (material === 'plastic') {\n this.buildPlasticTote(width, height, depth, bodyColor)\n } else {\n this.buildWoodCrate(width, height, depth, bodyColor)\n }\n }\n\n /** Wood crate — visible slats, 4 corner posts, open top. */\n private buildWoodCrate(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const wallThickness = Math.min(width, height) * 0.04\n const postW = wallThickness * 1.6\n const slatH = depth * 0.10\n const slatGap = slatH * 0.6\n const floorH = depth * 0.05\n\n const woodMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0,\n roughness: 0.85\n })\n const postColor = new THREE.Color(bodyColor).multiplyScalar(0.8)\n const postMaterial = new THREE.MeshStandardMaterial({\n color: postColor,\n metalness: 0,\n roughness: 0.9\n })\n\n // ── 4 corner posts ───────────────────────────────────────────────\n const postGeos: THREE.BufferGeometry[] = []\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const post = new THREE.BoxGeometry(postW, depth, postW)\n post.translate(\n xSign * (width / 2 - postW / 2),\n baseY + depth / 2,\n zSign * (height / 2 - postW / 2)\n )\n postGeos.push(post)\n }\n }\n const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos), postMaterial)\n postMesh.castShadow = true\n this.object3d.add(postMesh)\n\n // ── Slatted walls (horizontal slats with gaps) ───────────────────\n const slatRowCount = Math.max(2, Math.floor((depth - floorH) / (slatH + slatGap)))\n const slatGeos: THREE.BufferGeometry[] = []\n\n for (let row = 0; row < slatRowCount; row++) {\n const y = baseY + floorH + slatGap + row * (slatH + slatGap) + slatH / 2\n // Long walls (front / back)\n for (const zSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(width - postW * 2, slatH, wallThickness)\n slat.translate(0, y, zSign * (height / 2 - wallThickness / 2))\n slatGeos.push(slat)\n }\n // Short walls (left / right)\n for (const xSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(wallThickness, slatH, height - postW * 2)\n slat.translate(xSign * (width / 2 - wallThickness / 2), y, 0)\n slatGeos.push(slat)\n }\n }\n const slatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(slatGeos), woodMaterial)\n slatMesh.castShadow = true\n slatMesh.receiveShadow = true\n this.object3d.add(slatMesh)\n\n // ── Floor (bottom panel) ─────────────────────────────────────────\n const floorGeo = new THREE.BoxGeometry(width - postW * 2, floorH, height - postW * 2)\n const floorMesh = new THREE.Mesh(floorGeo, woodMaterial)\n floorMesh.position.set(0, baseY + floorH / 2, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\n }\n\n /** Plastic tote — solid molded walls + top stackable lip. */\n private buildPlasticTote(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const wallThickness = Math.min(width, height) * 0.05\n const lipH = depth * 0.06\n const floorH = depth * 0.06\n\n const totMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0.05,\n roughness: 0.55\n })\n const lipColor = new THREE.Color(bodyColor).multiplyScalar(0.85)\n const lipMaterial = new THREE.MeshStandardMaterial({\n color: lipColor,\n metalness: 0.05,\n roughness: 0.55\n })\n\n // ── 4 solid walls ────────────────────────────────────────────────\n const wallGeos: THREE.BufferGeometry[] = []\n const wallH = depth - lipH - floorH\n const wallY = baseY + floorH + wallH / 2\n\n // Long walls\n for (const zSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(width, wallH, wallThickness)\n wall.translate(0, wallY, zSign * (height / 2 - wallThickness / 2))\n wallGeos.push(wall)\n }\n // Short walls\n for (const xSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(wallThickness, wallH, height - 2 * wallThickness)\n wall.translate(xSign * (width / 2 - wallThickness / 2), wallY, 0)\n wallGeos.push(wall)\n }\n const wallMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wallGeos), totMaterial)\n wallMesh.castShadow = true\n wallMesh.receiveShadow = true\n this.object3d.add(wallMesh)\n\n // ── Top lip (stackable rim — slightly wider than walls) ──────────\n const lipGeos: THREE.BufferGeometry[] = []\n const lipY = baseY + depth - lipH / 2\n // Long sides\n for (const zSign of [-1, 1]) {\n const lip = new THREE.BoxGeometry(width * 1.02, lipH, wallThickness * 1.5)\n lip.translate(0, lipY, zSign * (height / 2 - wallThickness * 0.75))\n lipGeos.push(lip)\n }\n // Short sides\n for (const xSign of [-1, 1]) {\n const lip = new THREE.BoxGeometry(wallThickness * 1.5, lipH, height * 1.02 - 2 * wallThickness * 1.5)\n lip.translate(xSign * (width / 2 - wallThickness * 0.75), lipY, 0)\n lipGeos.push(lip)\n }\n const lipMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(lipGeos), lipMaterial)\n lipMesh.castShadow = true\n this.object3d.add(lipMesh)\n\n // ── Floor (solid bottom) ─────────────────────────────────────────\n const floorGeo = new THREE.BoxGeometry(width, floorH, height)\n const floorMesh = new THREE.Mesh(floorGeo, totMaterial)\n floorMesh.position.set(0, baseY + floorH / 2, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'material' in after ||\n 'bodyColor' in after ||\n 'width' in after ||\n 'height' in after ||\n 'depth' in after\n ) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n"]}
package/dist/box.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import { type Alignment, type LegendBinding, type PlacementArchetype } from '@operato/scene-base';
3
+ /**
4
+ * Box material — drives 3D structure and color.
5
+ *
6
+ * - `wood` — wood crate: visible vertical slats, gaps between, open or
7
+ * semi-open top. Used for heavy / industrial parts.
8
+ * - `plastic` — plastic tote / bin: solid molded walls with stackable lip
9
+ * at top. Used for fulfillment, parts kitting.
10
+ *
11
+ * Cardboard parcels are a separate component (see `parcel.ts`) — they have
12
+ * different proportions, taping, and labels that warrant a distinct class.
13
+ */
14
+ export type BoxMaterial = 'wood' | 'plastic';
15
+ declare const Base: typeof Component;
16
+ /**
17
+ * Box — a generic stackable container for goods. Wood crate or plastic tote
18
+ * variants distinguished by `material` prop.
19
+ *
20
+ * Shape-based (not Container) — boxes nesting other components is rare in
21
+ * logistics visualization (a *case* of items inside a box is data, not
22
+ * scene-tree). If a future use case needs nested boxes, extend Container.
23
+ */
24
+ export default class Box extends Base {
25
+ static legends: Record<string, LegendBinding>;
26
+ static placement: PlacementArchetype;
27
+ static align: Alignment;
28
+ static defaultDepth: number;
29
+ get nature(): ComponentNature;
30
+ get anchors(): never[];
31
+ /** 2D — top-down rectangle. */
32
+ render(ctx: CanvasRenderingContext2D): void;
33
+ get fillStyle(): string;
34
+ buildRealObject(): RealObject | undefined;
35
+ }
36
+ export {};
package/dist/box.js ADDED
@@ -0,0 +1,73 @@
1
+ import { __decorate } from "tslib";
2
+ /*
3
+ * Copyright © HatioLab Inc. All rights reserved.
4
+ */
5
+ import { RectPath, Shape, sceneComponent } from '@hatiolab/things-scene';
6
+ import { Carriable, Legendable, Placeable } from '@operato/scene-base';
7
+ import { Box3D } from './box-3d.js';
8
+ const BODY_LEGEND = {
9
+ wood: '#a87644',
10
+ plastic: '#3a5078',
11
+ default: '#a87644'
12
+ };
13
+ const NATURE = {
14
+ mutable: false,
15
+ resizable: true,
16
+ rotatable: true,
17
+ properties: [
18
+ {
19
+ type: 'select',
20
+ label: 'material',
21
+ name: 'material',
22
+ property: {
23
+ options: [
24
+ { display: 'Wood', value: 'wood' },
25
+ { display: 'Plastic', value: 'plastic' }
26
+ ]
27
+ }
28
+ }
29
+ ],
30
+ help: 'scene/component/box'
31
+ };
32
+ // Carriable: a box can be a child of any CarrierHolder (Pallet for stacking,
33
+ // AGV deck, robot-arm gripper, Spot for staging).
34
+ const Base = Carriable(Legendable(Placeable(RectPath(Shape))));
35
+ /**
36
+ * Box — a generic stackable container for goods. Wood crate or plastic tote
37
+ * variants distinguished by `material` prop.
38
+ *
39
+ * Shape-based (not Container) — boxes nesting other components is rare in
40
+ * logistics visualization (a *case* of items inside a box is data, not
41
+ * scene-tree). If a future use case needs nested boxes, extend Container.
42
+ */
43
+ let Box = class Box extends Base {
44
+ static legends = {
45
+ bodyColor: { from: 'material', legend: BODY_LEGEND }
46
+ };
47
+ static placement = 'operation';
48
+ static align = 'bottom';
49
+ static defaultDepth = 300;
50
+ get nature() {
51
+ return NATURE;
52
+ }
53
+ get anchors() {
54
+ return [];
55
+ }
56
+ /** 2D — top-down rectangle. */
57
+ render(ctx) {
58
+ const { width, height, left, top } = this.state;
59
+ ctx.beginPath();
60
+ ctx.rect(left, top, width, height);
61
+ }
62
+ get fillStyle() {
63
+ return this.state.bodyColor || '#a87644';
64
+ }
65
+ buildRealObject() {
66
+ return new Box3D(this);
67
+ }
68
+ };
69
+ Box = __decorate([
70
+ sceneComponent('box')
71
+ ], Box);
72
+ export default Box;
73
+ //# sourceMappingURL=box.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"box.js","sourceRoot":"","sources":["../src/box.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA0C,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAChH,OAAO,EACL,SAAS,EACT,UAAU,EACV,SAAS,EAIV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAenC,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,SAAS;IAClB,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,UAAU;YACjB,IAAI,EAAE,UAAU;YAChB,QAAQ,EAAE;gBACR,OAAO,EAAE;oBACP,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;oBAClC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;iBACzC;aACF;SACF;KACF;IACD,IAAI,EAAE,qBAAqB;CAC5B,CAAA;AAED,6EAA6E;AAC7E,kDAAkD;AAClD,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAgC,CAAA;AAE7F;;;;;;;GAOG;AAEY,IAAM,GAAG,GAAT,MAAM,GAAI,SAAQ,IAAI;IACnC,MAAM,CAAC,OAAO,GAAkC;QAC9C,SAAS,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE;KACrD,CAAA;IAED,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,+BAA+B;IAC/B,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,OAAQ,IAAI,CAAC,KAAK,CAAC,SAAoB,IAAI,SAAS,CAAA;IACtD,CAAC;IAED,eAAe;QACb,OAAO,IAAI,KAAK,CAAC,IAAW,CAAC,CAAA;IAC/B,CAAC;;AA9BkB,GAAG;IADvB,cAAc,CAAC,KAAK,CAAC;GACD,GAAG,CA+BvB;eA/BoB,GAAG","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, RealObject, RectPath, Shape, sceneComponent } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Legendable,\n Placeable,\n type Alignment,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { 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\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).\nconst Base = Carriable(Legendable(Placeable(RectPath(Shape)))) as unknown as typeof Component\n\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 Base {\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 as any)\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ export { default as Pallet } from './pallet.js';
2
+ export type { PalletMaterial } from './pallet.js';
3
+ export { default as Box } from './box.js';
4
+ export type { BoxMaterial } from './box.js';
5
+ export { default as Parcel } from './parcel.js';
6
+ export { default as AsrsRack } from './asrs-rack.js';
7
+ export { default as AsrsCrane } from './asrs-crane.js';
8
+ export type { AsrsCraneStatus } from './asrs-crane.js';
9
+ export { default as Spot } from './spot.js';
10
+ export { Spot3D } from './spot-3d.js';
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ */
4
+ export { default as Pallet } from './pallet.js';
5
+ export { default as Box } from './box.js';
6
+ export { default as Parcel } from './parcel.js';
7
+ export { default as AsrsRack } from './asrs-rack.js';
8
+ export { default as AsrsCrane } from './asrs-crane.js';
9
+ export { default as Spot } from './spot.js';
10
+ export { Spot3D } from './spot-3d.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAG/C,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAA;AAGzC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAGtD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nexport { default as Pallet } from './pallet.js'\nexport type { PalletMaterial } from './pallet.js'\n\nexport { default as Box } from './box.js'\nexport type { BoxMaterial } from './box.js'\n\nexport { default as Parcel } from './parcel.js'\n\nexport { default as AsrsRack } from './asrs-rack.js'\nexport { default as AsrsCrane } from './asrs-crane.js'\nexport type { AsrsCraneStatus } from './asrs-crane.js'\n\nexport { default as Spot } from './spot.js'\nexport { Spot3D } from './spot-3d.js'\n"]}
@@ -0,0 +1,11 @@
1
+ import { RealObjectGroup } from '@hatiolab/things-scene';
2
+ export declare class Pallet3D extends RealObjectGroup {
3
+ build(): void;
4
+ /** Wood EUR-style: 7 top slats + 3 stringers + 5 bottom slats. */
5
+ private buildWood;
6
+ /** Plastic molded: solid top deck + ribbed underside / feet. */
7
+ private buildPlastic;
8
+ updateDimension(): void;
9
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
10
+ updateAlpha(): void;
11
+ }