@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,162 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Pallet 3D — wood and plastic variants.
5
+ *
6
+ * LO-POLY but structurally distinguishing the two materials:
7
+ *
8
+ * - wood: parallel top slats with gaps between + 3 perpendicular
9
+ * stringers (the classic EUR pallet silhouette) + parallel
10
+ * bottom slats. The forklift entry holes between stringers are
11
+ * the wood pallet's visual signature.
12
+ * - plastic: solid molded top deck (with a few suggestion cutouts as visual
13
+ * detail) + ribbed underside / feet. No discrete slats — the
14
+ * plastic pallet's signature is the seamless one-piece look.
15
+ *
16
+ * Color comes from `state.bodyColor` (Legendable, driven by `material`).
17
+ * Stringer / underside colors are slightly darker tints derived from bodyColor.
18
+ */
19
+ import * as THREE from 'three';
20
+ import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
21
+ import { RealObjectGroup } from '@hatiolab/things-scene';
22
+ export class Pallet3D extends RealObjectGroup {
23
+ build() {
24
+ super.build();
25
+ const { width, height, depth = 150 } = this.component.state;
26
+ const material = this.component.state.material || 'wood';
27
+ const bodyColor = this.component.state.bodyColor || '#a87644';
28
+ if (material === 'plastic') {
29
+ this.buildPlastic(width, height, depth, bodyColor);
30
+ }
31
+ else {
32
+ this.buildWood(width, height, depth, bodyColor);
33
+ }
34
+ }
35
+ /** Wood EUR-style: 7 top slats + 3 stringers + 5 bottom slats. */
36
+ buildWood(width, height, depth, bodyColor) {
37
+ const baseY = -depth / 2;
38
+ const slatThickness = depth * 0.15;
39
+ const stringerThickness = depth * 0.45;
40
+ const bottomSlatThickness = depth * 0.13;
41
+ const woodMaterial = new THREE.MeshStandardMaterial({
42
+ color: bodyColor,
43
+ metalness: 0.0,
44
+ roughness: 0.85
45
+ });
46
+ const stringerColor = new THREE.Color(bodyColor).multiplyScalar(0.85);
47
+ const stringerMaterial = new THREE.MeshStandardMaterial({
48
+ color: stringerColor,
49
+ metalness: 0.0,
50
+ roughness: 0.9
51
+ });
52
+ // ── Top + bottom slats — same count, same z-positions, paired vertically ─
53
+ // EUR-pallet style: 5 boards on top, 5 below (under the same z ranges so
54
+ // they read as a single skeleton rather than two unrelated grids).
55
+ const slatCount = 5;
56
+ const slatW = width;
57
+ const slatD = (height * 0.92) / (slatCount + (slatCount - 1) * 0.4);
58
+ const gapD = slatD * 0.4;
59
+ const totalSpan = slatCount * slatD + (slatCount - 1) * gapD;
60
+ const startZ = -totalSpan / 2;
61
+ const slatPositions = [];
62
+ for (let i = 0; i < slatCount; i++) {
63
+ slatPositions.push(startZ + i * (slatD + gapD) + slatD / 2);
64
+ }
65
+ const topSlatGeos = [];
66
+ for (const z of slatPositions) {
67
+ const slat = new THREE.BoxGeometry(slatW, slatThickness, slatD);
68
+ slat.translate(0, baseY + depth - slatThickness / 2, z);
69
+ topSlatGeos.push(slat);
70
+ }
71
+ const topSlatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(topSlatGeos), woodMaterial);
72
+ topSlatMesh.castShadow = true;
73
+ topSlatMesh.receiveShadow = true;
74
+ this.object3d.add(topSlatMesh);
75
+ // ── Stringers (3 perpendicular blocks between top and bottom decks) ─
76
+ const stringerCount = 3;
77
+ const stringerW = width * 0.07;
78
+ const stringerY = baseY + bottomSlatThickness + stringerThickness / 2;
79
+ const stringerGeos = [];
80
+ for (let i = 0; i < stringerCount; i++) {
81
+ const xFrac = i / (stringerCount - 1) - 0.5;
82
+ const x = xFrac * (width * 0.85);
83
+ const stringer = new THREE.BoxGeometry(stringerW, stringerThickness, height);
84
+ stringer.translate(x, stringerY, 0);
85
+ stringerGeos.push(stringer);
86
+ }
87
+ const stringerMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(stringerGeos), stringerMaterial);
88
+ stringerMesh.castShadow = true;
89
+ this.object3d.add(stringerMesh);
90
+ // ── Bottom slats — same z-positions as top so the deck reads as paired ─
91
+ const botSlatGeos = [];
92
+ for (const z of slatPositions) {
93
+ const slat = new THREE.BoxGeometry(width, bottomSlatThickness, slatD);
94
+ slat.translate(0, baseY + bottomSlatThickness / 2, z);
95
+ botSlatGeos.push(slat);
96
+ }
97
+ const botSlatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(botSlatGeos), woodMaterial);
98
+ botSlatMesh.receiveShadow = true;
99
+ this.object3d.add(botSlatMesh);
100
+ }
101
+ /** Plastic molded: solid top deck + ribbed underside / feet. */
102
+ buildPlastic(width, height, depth, bodyColor) {
103
+ const baseY = -depth / 2;
104
+ const deckThickness = depth * 0.30;
105
+ const footH = depth * 0.55;
106
+ const footW = width * 0.12;
107
+ const deckMaterial = new THREE.MeshStandardMaterial({
108
+ color: bodyColor,
109
+ metalness: 0.1,
110
+ roughness: 0.55
111
+ });
112
+ const footColor = new THREE.Color(bodyColor).multiplyScalar(0.85);
113
+ const footMaterial = new THREE.MeshStandardMaterial({
114
+ color: footColor,
115
+ metalness: 0.1,
116
+ roughness: 0.65
117
+ });
118
+ // ── Solid top deck ───────────────────────────────────────────────
119
+ const deckGeo = new THREE.BoxGeometry(width * 0.98, deckThickness, height * 0.98);
120
+ const deckMesh = new THREE.Mesh(deckGeo, deckMaterial);
121
+ deckMesh.position.set(0, baseY + depth - deckThickness / 2, 0);
122
+ deckMesh.castShadow = true;
123
+ deckMesh.receiveShadow = true;
124
+ this.object3d.add(deckMesh);
125
+ // ── 9 feet (3×3 grid — typical plastic pallet underside) ─────────
126
+ const footGeos = [];
127
+ for (let i = -1; i <= 1; i++) {
128
+ for (let j = -1; j <= 1; j++) {
129
+ const x = i * (width * 0.4);
130
+ const z = j * (height * 0.4);
131
+ const foot = new THREE.BoxGeometry(footW, footH, footW);
132
+ foot.translate(x, baseY + footH / 2, z);
133
+ footGeos.push(foot);
134
+ }
135
+ }
136
+ const footMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(footGeos), footMaterial);
137
+ footMesh.castShadow = true;
138
+ this.object3d.add(footMesh);
139
+ // ── Cross-bracing along underside (suggests molded reinforcement) ─
140
+ const braceH = depth * 0.10;
141
+ const braceGeo = new THREE.BoxGeometry(width * 0.95, braceH, height * 0.04);
142
+ for (const zSign of [-1, 0, 1]) {
143
+ const brace = new THREE.Mesh(braceGeo.clone(), footMaterial);
144
+ brace.position.set(0, baseY + footH - braceH / 2, zSign * height * 0.4);
145
+ this.object3d.add(brace);
146
+ }
147
+ }
148
+ updateDimension() { }
149
+ onchange(after, before) {
150
+ if ('material' in after ||
151
+ 'bodyColor' in after ||
152
+ 'width' in after ||
153
+ 'height' in after ||
154
+ 'depth' in after) {
155
+ this.update();
156
+ return;
157
+ }
158
+ super.onchange(after, before);
159
+ }
160
+ updateAlpha() { }
161
+ }
162
+ //# sourceMappingURL=pallet-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pallet-3d.js","sourceRoot":"","sources":["../src/pallet-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Pallet 3D — wood and plastic variants.\n *\n * LO-POLY but structurally distinguishing the two materials:\n *\n * - wood: parallel top slats with gaps between + 3 perpendicular\n * stringers (the classic EUR pallet silhouette) + parallel\n * bottom slats. The forklift entry holes between stringers are\n * the wood pallet's visual signature.\n * - plastic: solid molded top deck (with a few suggestion cutouts as visual\n * detail) + ribbed underside / feet. No discrete slats — the\n * plastic pallet's signature is the seamless one-piece look.\n *\n * Color comes from `state.bodyColor` (Legendable, driven by `material`).\n * Stringer / underside colors are slightly darker tints derived from bodyColor.\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 Pallet3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 150 } = 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.buildPlastic(width, height, depth, bodyColor)\n } else {\n this.buildWood(width, height, depth, bodyColor)\n }\n }\n\n /** Wood EUR-style: 7 top slats + 3 stringers + 5 bottom slats. */\n private buildWood(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const slatThickness = depth * 0.15\n const stringerThickness = depth * 0.45\n const bottomSlatThickness = depth * 0.13\n\n const woodMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0.0,\n roughness: 0.85\n })\n const stringerColor = new THREE.Color(bodyColor).multiplyScalar(0.85)\n const stringerMaterial = new THREE.MeshStandardMaterial({\n color: stringerColor,\n metalness: 0.0,\n roughness: 0.9\n })\n\n // ── Top + bottom slats — same count, same z-positions, paired vertically ─\n // EUR-pallet style: 5 boards on top, 5 below (under the same z ranges so\n // they read as a single skeleton rather than two unrelated grids).\n const slatCount = 5\n const slatW = width\n const slatD = (height * 0.92) / (slatCount + (slatCount - 1) * 0.4)\n const gapD = slatD * 0.4\n const totalSpan = slatCount * slatD + (slatCount - 1) * gapD\n const startZ = -totalSpan / 2\n\n const slatPositions: number[] = []\n for (let i = 0; i < slatCount; i++) {\n slatPositions.push(startZ + i * (slatD + gapD) + slatD / 2)\n }\n\n const topSlatGeos: THREE.BufferGeometry[] = []\n for (const z of slatPositions) {\n const slat = new THREE.BoxGeometry(slatW, slatThickness, slatD)\n slat.translate(0, baseY + depth - slatThickness / 2, z)\n topSlatGeos.push(slat)\n }\n const topSlatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(topSlatGeos), woodMaterial)\n topSlatMesh.castShadow = true\n topSlatMesh.receiveShadow = true\n this.object3d.add(topSlatMesh)\n\n // ── Stringers (3 perpendicular blocks between top and bottom decks) ─\n const stringerCount = 3\n const stringerW = width * 0.07\n const stringerY = baseY + bottomSlatThickness + stringerThickness / 2\n const stringerGeos: THREE.BufferGeometry[] = []\n for (let i = 0; i < stringerCount; i++) {\n const xFrac = i / (stringerCount - 1) - 0.5\n const x = xFrac * (width * 0.85)\n const stringer = new THREE.BoxGeometry(stringerW, stringerThickness, height)\n stringer.translate(x, stringerY, 0)\n stringerGeos.push(stringer)\n }\n const stringerMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(stringerGeos), stringerMaterial)\n stringerMesh.castShadow = true\n this.object3d.add(stringerMesh)\n\n // ── Bottom slats — same z-positions as top so the deck reads as paired ─\n const botSlatGeos: THREE.BufferGeometry[] = []\n for (const z of slatPositions) {\n const slat = new THREE.BoxGeometry(width, bottomSlatThickness, slatD)\n slat.translate(0, baseY + bottomSlatThickness / 2, z)\n botSlatGeos.push(slat)\n }\n const botSlatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(botSlatGeos), woodMaterial)\n botSlatMesh.receiveShadow = true\n this.object3d.add(botSlatMesh)\n }\n\n /** Plastic molded: solid top deck + ribbed underside / feet. */\n private buildPlastic(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const deckThickness = depth * 0.30\n const footH = depth * 0.55\n const footW = width * 0.12\n\n const deckMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0.1,\n roughness: 0.55\n })\n const footColor = new THREE.Color(bodyColor).multiplyScalar(0.85)\n const footMaterial = new THREE.MeshStandardMaterial({\n color: footColor,\n metalness: 0.1,\n roughness: 0.65\n })\n\n // ── Solid top deck ───────────────────────────────────────────────\n const deckGeo = new THREE.BoxGeometry(width * 0.98, deckThickness, height * 0.98)\n const deckMesh = new THREE.Mesh(deckGeo, deckMaterial)\n deckMesh.position.set(0, baseY + depth - deckThickness / 2, 0)\n deckMesh.castShadow = true\n deckMesh.receiveShadow = true\n this.object3d.add(deckMesh)\n\n // ── 9 feet (3×3 grid — typical plastic pallet underside) ─────────\n const footGeos: THREE.BufferGeometry[] = []\n for (let i = -1; i <= 1; i++) {\n for (let j = -1; j <= 1; j++) {\n const x = i * (width * 0.4)\n const z = j * (height * 0.4)\n const foot = new THREE.BoxGeometry(footW, footH, footW)\n foot.translate(x, baseY + footH / 2, z)\n footGeos.push(foot)\n }\n }\n const footMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(footGeos), footMaterial)\n footMesh.castShadow = true\n this.object3d.add(footMesh)\n\n // ── Cross-bracing along underside (suggests molded reinforcement) ─\n const braceH = depth * 0.10\n const braceGeo = new THREE.BoxGeometry(width * 0.95, braceH, height * 0.04)\n for (const zSign of [-1, 0, 1]) {\n const brace = new THREE.Mesh(braceGeo.clone(), footMaterial)\n brace.position.set(0, baseY + footH - braceH / 2, zSign * height * 0.4)\n this.object3d.add(brace)\n }\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"]}
@@ -0,0 +1,56 @@
1
+ import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import { type Alignment, type LegendBinding, type PlacementArchetype } from '@operato/scene-base';
3
+ /**
4
+ * Pallet material — drives both 2D fill color and 3D structure.
5
+ *
6
+ * - `wood` — traditional EUR / EPAL pallet: parallel slats on top and
7
+ * bottom, three perpendicular stringers between them.
8
+ * - `plastic` — molded one-piece pallet: solid top deck with cutouts,
9
+ * hollow underside with feet. Distinct ribbed underside.
10
+ *
11
+ * Adding a third material (e.g. metal, composite) is a one-line change to the
12
+ * legend + a 3D variant in pallet-3d.ts.
13
+ */
14
+ export type PalletMaterial = 'wood' | 'plastic';
15
+ declare const Base: typeof Component;
16
+ /**
17
+ * Pallet — a flat transport structure that goods are stacked and stored on.
18
+ *
19
+ * Standard EUR pallet is 1200 × 800mm × 144mm; we don't enforce these
20
+ * dimensions but they're a good starting point for the catalog templates.
21
+ *
22
+ * **Container-based.** Boxes / parcels stacked on the pallet are added as
23
+ * children — same `containable()` archetype-filter pattern as Forklift / Agv.
24
+ * Visual stacking (children rendering on top of the pallet rather than at
25
+ * absolute operation level) is a v2 concern; see ARCHITECTURE NOTES below.
26
+ *
27
+ * **Placement = `operation`.** A pallet's *normal* state is loaded and in
28
+ * transit on a conveyor / AGV / forklift fork — at operation level. Empty
29
+ * pallets in a floor-storage area are an exceptional state where the user
30
+ * sets `state.zPos = 0` explicitly. Default to the common case.
31
+ *
32
+ * ## ARCHITECTURE NOTES — visual stacking
33
+ *
34
+ * When a Box (also `placement: 'operation'`) is added as a child of a
35
+ * Pallet, both default to z = operation_height. They overlap visually
36
+ * rather than the box sitting on top of the pallet. Solving this cleanly
37
+ * (parent-relative z derivation when the parent is a structural carrier)
38
+ * is a follow-up — fmsim's pattern is to detect parent type at render time
39
+ * (machine-3d.ts:113-122). v1 accepts the visual overlap; v2 will add the
40
+ * detection.
41
+ */
42
+ export default class Pallet extends Base {
43
+ static legends: Record<string, LegendBinding>;
44
+ static placement: PlacementArchetype;
45
+ static align: Alignment;
46
+ static defaultDepth: number;
47
+ get nature(): ComponentNature;
48
+ get anchors(): never[];
49
+ /** Accept other operation-archetype cargo (boxes, parcels, smaller pallets) as stacked children. */
50
+ containable(component: Component): boolean;
51
+ /** 2D — top-down rectangle. */
52
+ render(ctx: CanvasRenderingContext2D): void;
53
+ get fillStyle(): string;
54
+ buildRealObject(): RealObject | undefined;
55
+ }
56
+ export {};
package/dist/pallet.js ADDED
@@ -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 { Carriable, Legendable, Placeable } from '@operato/scene-base';
7
+ import { Pallet3D } from './pallet-3d.js';
8
+ const BODY_LEGEND = {
9
+ wood: '#a87644',
10
+ plastic: '#5a6a78',
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/pallet'
31
+ };
32
+ // Carriable: a pallet can sit on AGV / Forklift / robot-arm gripper / Spot
33
+ // and also accept boxes / parcels as children (Container base provides the
34
+ // child-container behavior; Carriable only adds the holder-mount hook).
35
+ const Base = Carriable(Legendable(Placeable(Container)));
36
+ /**
37
+ * Pallet — a flat transport structure that goods are stacked and stored on.
38
+ *
39
+ * Standard EUR pallet is 1200 × 800mm × 144mm; we don't enforce these
40
+ * dimensions but they're a good starting point for the catalog templates.
41
+ *
42
+ * **Container-based.** Boxes / parcels stacked on the pallet are added as
43
+ * children — same `containable()` archetype-filter pattern as Forklift / Agv.
44
+ * Visual stacking (children rendering on top of the pallet rather than at
45
+ * absolute operation level) is a v2 concern; see ARCHITECTURE NOTES below.
46
+ *
47
+ * **Placement = `operation`.** A pallet's *normal* state is loaded and in
48
+ * transit on a conveyor / AGV / forklift fork — at operation level. Empty
49
+ * pallets in a floor-storage area are an exceptional state where the user
50
+ * sets `state.zPos = 0` explicitly. Default to the common case.
51
+ *
52
+ * ## ARCHITECTURE NOTES — visual stacking
53
+ *
54
+ * When a Box (also `placement: 'operation'`) is added as a child of a
55
+ * Pallet, both default to z = operation_height. They overlap visually
56
+ * rather than the box sitting on top of the pallet. Solving this cleanly
57
+ * (parent-relative z derivation when the parent is a structural carrier)
58
+ * is a follow-up — fmsim's pattern is to detect parent type at render time
59
+ * (machine-3d.ts:113-122). v1 accepts the visual overlap; v2 will add the
60
+ * detection.
61
+ */
62
+ let Pallet = class Pallet extends Base {
63
+ static legends = {
64
+ bodyColor: { from: 'material', legend: BODY_LEGEND }
65
+ };
66
+ static placement = 'operation';
67
+ static align = 'bottom';
68
+ static defaultDepth = 150; // EUR pallet is 144mm; 150 is the round number convention
69
+ get nature() {
70
+ return NATURE;
71
+ }
72
+ get anchors() {
73
+ return [];
74
+ }
75
+ /** Accept other operation-archetype cargo (boxes, parcels, smaller pallets) as stacked children. */
76
+ containable(component) {
77
+ const archetype = component.constructor.placement;
78
+ if (archetype === 'operation')
79
+ return true;
80
+ return component.isDescendible(this);
81
+ }
82
+ /** 2D — top-down rectangle. */
83
+ render(ctx) {
84
+ const { width, height, left, top } = this.state;
85
+ ctx.beginPath();
86
+ ctx.rect(left, top, width, height);
87
+ }
88
+ get fillStyle() {
89
+ return this.state.bodyColor || '#a87644';
90
+ }
91
+ buildRealObject() {
92
+ return new Pallet3D(this);
93
+ }
94
+ };
95
+ Pallet = __decorate([
96
+ sceneComponent('pallet')
97
+ ], Pallet);
98
+ export default Pallet;
99
+ //# sourceMappingURL=pallet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pallet.js","sourceRoot":"","sources":["../src/pallet.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA8B,SAAS,EAAc,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC1G,OAAO,EACL,SAAS,EACT,UAAU,EACV,SAAS,EAIV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAezC,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,wBAAwB;CAC/B,CAAA;AAED,2EAA2E;AAC3E,2EAA2E;AAC3E,wEAAwE;AACxE,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAgC,CAAA;AAEvF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEY,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,IAAI;IACtC,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,CAAC,0DAA0D;IAEpF,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED,oGAAoG;IACpG,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,+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,QAAQ,CAAC,IAAW,CAAC,CAAA;IAClC,CAAC;;AArCkB,MAAM;IAD1B,cAAc,CAAC,QAAQ,CAAC;GACJ,MAAM,CAsC1B;eAtCoB,MAAM","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, Container, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Legendable,\n Placeable,\n type Alignment,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Pallet3D } from './pallet-3d.js'\n\n/**\n * Pallet material — drives both 2D fill color and 3D structure.\n *\n * - `wood` — traditional EUR / EPAL pallet: parallel slats on top and\n * bottom, three perpendicular stringers between them.\n * - `plastic` — molded one-piece pallet: solid top deck with cutouts,\n * hollow underside with feet. Distinct ribbed underside.\n *\n * Adding a third material (e.g. metal, composite) is a one-line change to the\n * legend + a 3D variant in pallet-3d.ts.\n */\nexport type PalletMaterial = 'wood' | 'plastic'\n\nconst BODY_LEGEND = {\n wood: '#a87644',\n plastic: '#5a6a78',\n default: '#a87644'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'material',\n name: 'material',\n property: {\n options: [\n { display: 'Wood', value: 'wood' },\n { display: 'Plastic', value: 'plastic' }\n ]\n }\n }\n ],\n help: 'scene/component/pallet'\n}\n\n// Carriable: a pallet can sit on AGV / Forklift / robot-arm gripper / Spot\n// and also accept boxes / parcels as children (Container base provides the\n// child-container behavior; Carriable only adds the holder-mount hook).\nconst Base = Carriable(Legendable(Placeable(Container))) as unknown as typeof Component\n\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 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 = 150 // EUR pallet is 144mm; 150 is the round number convention\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** Accept other operation-archetype cargo (boxes, parcels, smaller pallets) as stacked children. */\n containable(component: Component) {\n const archetype = (component.constructor as any).placement\n if (archetype === 'operation') return true\n return component.isDescendible(this as any)\n }\n\n /** 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 Pallet3D(this as any)\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { RealObjectGroup } from '@hatiolab/things-scene';
2
+ export declare class Parcel3D extends RealObjectGroup {
3
+ build(): void;
4
+ updateDimension(): void;
5
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
6
+ updateAlpha(): void;
7
+ }
@@ -0,0 +1,82 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Parcel 3D — a cardboard package.
5
+ *
6
+ * Structure:
7
+ * - main body box (cardboard color)
8
+ * - tape line running across the top (the visual signature — what makes
9
+ * this read as a "shipping parcel" rather than a generic box)
10
+ * - small label area on top (white rectangle suggesting a shipping label)
11
+ *
12
+ * Kept very simple — parcels in a logistics scene are typically present in
13
+ * large numbers (sortation lines, fulfillment bays), so polygon count
14
+ * matters more than it does for one-off equipment.
15
+ */
16
+ import * as THREE from 'three';
17
+ import { RealObjectGroup } from '@hatiolab/things-scene';
18
+ const CARDBOARD_COLOR = 0xc8a878;
19
+ const TAPE_COLOR = 0xddc899;
20
+ const LABEL_COLOR = 0xeeeeee;
21
+ export class Parcel3D extends RealObjectGroup {
22
+ build() {
23
+ super.build();
24
+ const { width, height, depth = 150 } = this.component.state;
25
+ const baseY = -depth / 2;
26
+ // ── Main body ────────────────────────────────────────────────────
27
+ const bodyGeo = new THREE.BoxGeometry(width, depth, height);
28
+ const bodyMaterial = new THREE.MeshStandardMaterial({
29
+ color: CARDBOARD_COLOR,
30
+ metalness: 0,
31
+ roughness: 0.9
32
+ });
33
+ const bodyMesh = new THREE.Mesh(bodyGeo, bodyMaterial);
34
+ bodyMesh.position.set(0, 0, 0);
35
+ bodyMesh.castShadow = true;
36
+ bodyMesh.receiveShadow = true;
37
+ this.object3d.add(bodyMesh);
38
+ // ── Tape line on top (running along the long axis) ───────────────
39
+ const tapeW = Math.min(width, height) * 0.10;
40
+ const tapeT = depth * 0.02;
41
+ const tapeAlongLong = width >= height;
42
+ const tapeGeo = tapeAlongLong
43
+ ? new THREE.BoxGeometry(width * 1.005, tapeT, tapeW)
44
+ : new THREE.BoxGeometry(tapeW, tapeT, height * 1.005);
45
+ const tapeMaterial = new THREE.MeshStandardMaterial({
46
+ color: TAPE_COLOR,
47
+ metalness: 0.05,
48
+ roughness: 0.5
49
+ });
50
+ const tapeMesh = new THREE.Mesh(tapeGeo, tapeMaterial);
51
+ tapeMesh.position.set(0, baseY + depth + tapeT / 2 - 0.01, 0);
52
+ this.object3d.add(tapeMesh);
53
+ // ── Shipping label (small white rectangle on top) ────────────────
54
+ const labelW = Math.min(width, height) * 0.35;
55
+ const labelH = labelW * 0.6;
56
+ const labelGeo = new THREE.BoxGeometry(labelW, depth * 0.005, labelH);
57
+ const labelMaterial = new THREE.MeshStandardMaterial({
58
+ color: LABEL_COLOR,
59
+ metalness: 0,
60
+ roughness: 0.4
61
+ });
62
+ const labelMesh = new THREE.Mesh(labelGeo, labelMaterial);
63
+ // Position on top, off-center by ~25% of long axis
64
+ if (tapeAlongLong) {
65
+ labelMesh.position.set(width * 0.2, baseY + depth + depth * 0.0025, -height * 0.15);
66
+ }
67
+ else {
68
+ labelMesh.position.set(width * 0.15, baseY + depth + depth * 0.0025, height * 0.2);
69
+ }
70
+ this.object3d.add(labelMesh);
71
+ }
72
+ updateDimension() { }
73
+ onchange(after, before) {
74
+ if ('width' in after || 'height' in after || 'depth' in after) {
75
+ this.update();
76
+ return;
77
+ }
78
+ super.onchange(after, before);
79
+ }
80
+ updateAlpha() { }
81
+ }
82
+ //# sourceMappingURL=parcel-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parcel-3d.js","sourceRoot":"","sources":["../src/parcel-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Parcel 3D — a cardboard package.\n *\n * Structure:\n * - main body box (cardboard color)\n * - tape line running across the top (the visual signature — what makes\n * this read as a \"shipping parcel\" rather than a generic box)\n * - small label area on top (white rectangle suggesting a shipping label)\n *\n * Kept very simple — parcels in a logistics scene are typically present in\n * large numbers (sortation lines, fulfillment bays), so polygon count\n * matters more than it does for one-off equipment.\n */\n\nimport * as THREE from 'three'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nconst CARDBOARD_COLOR = 0xc8a878\nconst TAPE_COLOR = 0xddc899\nconst LABEL_COLOR = 0xeeeeee\n\nexport class Parcel3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 150 } = this.component.state\n const baseY = -depth / 2\n\n // ── Main body ────────────────────────────────────────────────────\n const bodyGeo = new THREE.BoxGeometry(width, depth, height)\n const bodyMaterial = new THREE.MeshStandardMaterial({\n color: CARDBOARD_COLOR,\n metalness: 0,\n roughness: 0.9\n })\n const bodyMesh = new THREE.Mesh(bodyGeo, bodyMaterial)\n bodyMesh.position.set(0, 0, 0)\n bodyMesh.castShadow = true\n bodyMesh.receiveShadow = true\n this.object3d.add(bodyMesh)\n\n // ── Tape line on top (running along the long axis) ───────────────\n const tapeW = Math.min(width, height) * 0.10\n const tapeT = depth * 0.02\n const tapeAlongLong = width >= height\n const tapeGeo = tapeAlongLong\n ? new THREE.BoxGeometry(width * 1.005, tapeT, tapeW)\n : new THREE.BoxGeometry(tapeW, tapeT, height * 1.005)\n const tapeMaterial = new THREE.MeshStandardMaterial({\n color: TAPE_COLOR,\n metalness: 0.05,\n roughness: 0.5\n })\n const tapeMesh = new THREE.Mesh(tapeGeo, tapeMaterial)\n tapeMesh.position.set(0, baseY + depth + tapeT / 2 - 0.01, 0)\n this.object3d.add(tapeMesh)\n\n // ── Shipping label (small white rectangle on top) ────────────────\n const labelW = Math.min(width, height) * 0.35\n const labelH = labelW * 0.6\n const labelGeo = new THREE.BoxGeometry(labelW, depth * 0.005, labelH)\n const labelMaterial = new THREE.MeshStandardMaterial({\n color: LABEL_COLOR,\n metalness: 0,\n roughness: 0.4\n })\n const labelMesh = new THREE.Mesh(labelGeo, labelMaterial)\n // Position on top, off-center by ~25% of long axis\n if (tapeAlongLong) {\n labelMesh.position.set(width * 0.2, baseY + depth + depth * 0.0025, -height * 0.15)\n } else {\n labelMesh.position.set(width * 0.15, baseY + depth + depth * 0.0025, height * 0.2)\n }\n this.object3d.add(labelMesh)\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if ('width' in after || 'height' in after || 'depth' in after) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import { type Alignment, type PlacementArchetype } from '@operato/scene-base';
3
+ declare const Base: typeof Component;
4
+ /**
5
+ * Parcel — a cardboard package, the typical e-commerce / parcel-sortation unit.
6
+ *
7
+ * Distinct from `Box` because parcels have:
8
+ * - cardboard appearance (tan/brown corrugate, not wood / plastic)
9
+ * - tape line down the center (the visual signature that says "package")
10
+ * - typically a label on top (where shipping info goes)
11
+ * - flatter / more elongated proportions in real-world parcel networks
12
+ *
13
+ * No `material` prop — parcels are always cardboard. If a future shipping
14
+ * domain needs metal cases or polybags, those become separate components.
15
+ *
16
+ * No Legendable for v1 — parcel color is fixed cardboard. Future damaged /
17
+ * inspected indicators would add a status legend then.
18
+ */
19
+ export default class Parcel extends Base {
20
+ static placement: PlacementArchetype;
21
+ static align: Alignment;
22
+ static defaultDepth: number;
23
+ get nature(): ComponentNature;
24
+ get anchors(): never[];
25
+ /** 2D — top-down rectangle in cardboard tan. */
26
+ render(ctx: CanvasRenderingContext2D): void;
27
+ get fillStyle(): string;
28
+ buildRealObject(): RealObject | undefined;
29
+ }
30
+ export {};
package/dist/parcel.js ADDED
@@ -0,0 +1,67 @@
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, Placeable } from '@operato/scene-base';
7
+ import { Parcel3D } from './parcel-3d.js';
8
+ const NATURE = {
9
+ mutable: false,
10
+ resizable: true,
11
+ rotatable: true,
12
+ properties: [
13
+ {
14
+ type: 'string',
15
+ label: 'tracking-id',
16
+ name: 'trackingId'
17
+ }
18
+ ],
19
+ help: 'scene/component/parcel'
20
+ };
21
+ // Carriable: parcel can be a child of any CarrierHolder (Spot, robot-arm
22
+ // gripper, AGV deck, …). Mixin wraps add() so the parcel's 3D object3d
23
+ // is reattached to the holder's chosen mount frame.
24
+ const Base = Carriable(Placeable(RectPath(Shape)));
25
+ /**
26
+ * Parcel — a cardboard package, the typical e-commerce / parcel-sortation unit.
27
+ *
28
+ * Distinct from `Box` because parcels have:
29
+ * - cardboard appearance (tan/brown corrugate, not wood / plastic)
30
+ * - tape line down the center (the visual signature that says "package")
31
+ * - typically a label on top (where shipping info goes)
32
+ * - flatter / more elongated proportions in real-world parcel networks
33
+ *
34
+ * No `material` prop — parcels are always cardboard. If a future shipping
35
+ * domain needs metal cases or polybags, those become separate components.
36
+ *
37
+ * No Legendable for v1 — parcel color is fixed cardboard. Future damaged /
38
+ * inspected indicators would add a status legend then.
39
+ */
40
+ let Parcel = class Parcel extends Base {
41
+ static placement = 'operation';
42
+ static align = 'bottom';
43
+ static defaultDepth = 150;
44
+ get nature() {
45
+ return NATURE;
46
+ }
47
+ get anchors() {
48
+ return [];
49
+ }
50
+ /** 2D — top-down rectangle in cardboard tan. */
51
+ render(ctx) {
52
+ const { width, height, left, top } = this.state;
53
+ ctx.beginPath();
54
+ ctx.rect(left, top, width, height);
55
+ }
56
+ get fillStyle() {
57
+ return '#c8a878';
58
+ }
59
+ buildRealObject() {
60
+ return new Parcel3D(this);
61
+ }
62
+ };
63
+ Parcel = __decorate([
64
+ sceneComponent('parcel')
65
+ ], Parcel);
66
+ export default Parcel;
67
+ //# sourceMappingURL=parcel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parcel.js","sourceRoot":"","sources":["../src/parcel.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA0C,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAChH,OAAO,EACL,SAAS,EACT,SAAS,EAGV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,UAAU,EAAE;QACV;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,aAAa;YACpB,IAAI,EAAE,YAAY;SACnB;KACF;IACD,IAAI,EAAE,wBAAwB;CAC/B,CAAA;AAED,yEAAyE;AACzE,uEAAuE;AACvE,oDAAoD;AACpD,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAgC,CAAA;AAEjF;;;;;;;;;;;;;;GAcG;AAEY,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,IAAI;IACtC,MAAM,CAAC,SAAS,GAAuB,WAAW,CAAA;IAClD,MAAM,CAAC,KAAK,GAAc,QAAQ,CAAA;IAClC,MAAM,CAAC,YAAY,GAAG,GAAG,CAAA;IAEzB,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED,gDAAgD;IAChD,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;IACpC,CAAC;IAED,IAAI,SAAS;QACX,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,eAAe;QACb,OAAO,IAAI,QAAQ,CAAC,IAAW,CAAC,CAAA;IAClC,CAAC;;AA1BkB,MAAM;IAD1B,cAAc,CAAC,QAAQ,CAAC;GACJ,MAAM,CA2B1B;eA3BoB,MAAM","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, RealObject, RectPath, Shape, sceneComponent } from '@hatiolab/things-scene'\nimport {\n Carriable,\n Placeable,\n type Alignment,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Parcel3D } from './parcel-3d.js'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'string',\n label: 'tracking-id',\n name: 'trackingId'\n }\n ],\n help: 'scene/component/parcel'\n}\n\n// Carriable: parcel can be a child of any CarrierHolder (Spot, robot-arm\n// gripper, AGV deck, …). Mixin wraps add() so the parcel's 3D object3d\n// is reattached to the holder's chosen mount frame.\nconst Base = Carriable(Placeable(RectPath(Shape))) as unknown as typeof Component\n\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 Base {\n static placement: PlacementArchetype = 'operation'\n static align: Alignment = 'bottom'\n static defaultDepth = 150\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** 2D — top-down rectangle in cardboard tan. */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n get fillStyle() {\n return '#c8a878'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Parcel3D(this as any)\n }\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import * as THREE from 'three';
2
+ import { RealObjectGroup } from '@hatiolab/things-scene';
3
+ export declare class Spot3D extends RealObjectGroup {
4
+ build(): void;
5
+ /**
6
+ * Build the label as a canvas-textured quad. Uses the same `fontStyle`
7
+ * helper things-scene uses for its 2D text rendering, so the label
8
+ * here matches what the property panel previews.
9
+ */
10
+ private _buildLabel;
11
+ /**
12
+ * The sub-frame that carrier components should mount onto.
13
+ *
14
+ * Semantically Spot is a virtual cuboid SPACE — it marks "stuff goes
15
+ * here" — and carriers are placed INSIDE that space, resting on the
16
+ * cuboid's BOTTOM face. So the attach frame sits at the cuboid's
17
+ * bottom (`y = -d/2` in spot-local), NOT at the top of the rendered
18
+ * pad. The pad is just a translucent visual marker for the zone; the
19
+ * floor of the conceptual volume is what carriers stand on.
20
+ *
21
+ * The Spot.attachPointFor mixin lifts the carrier by its own halfDepth
22
+ * (in the +Y direction within this frame), placing the carrier's
23
+ * BOTTOM face exactly at the cuboid floor.
24
+ */
25
+ getAttachFrame(): THREE.Object3D;
26
+ private _attachFrame?;
27
+ updateDimension(): void;
28
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
29
+ updateAlpha(): void;
30
+ }