@operato/scene-storage 10.0.0-beta.22 → 10.0.0-beta.24
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 +14 -0
- package/dist/asrs-rack.js +5 -2
- package/dist/asrs-rack.js.map +1 -1
- package/dist/pallet.d.ts +15 -1
- package/dist/pallet.js +70 -5
- package/dist/pallet.js.map +1 -1
- package/dist/spot.js +8 -4
- package/dist/spot.js.map +1 -1
- package/dist/templates/index.js +3 -3
- package/dist/templates/index.js.map +1 -1
- package/package.json +3 -3
- package/src/asrs-rack.ts +5 -2
- package/src/pallet.ts +74 -5
- package/src/spot.ts +8 -4
- package/src/templates/index.ts +3 -3
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
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.24](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.23...v10.0.0-beta.24) (2026-05-02)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :bug: Bug Fix
|
|
10
|
+
|
|
11
|
+
* **scene:** Container → ContainerAbstract for 3D-only components ([89bdea7](https://github.com/things-scene/operato-scene/commit/89bdea73b37247601932b92692ea5d5e7780976c))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### :mega: Other
|
|
15
|
+
|
|
16
|
+
* tune default template sizes for board overview ([582232a](https://github.com/things-scene/operato-scene/commit/582232ae078631e13754ba2e06320e211c3a1de1))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
## [10.0.0-beta.22](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.21...v10.0.0-beta.22) (2026-04-30)
|
|
7
21
|
|
|
8
22
|
|
package/dist/asrs-rack.js
CHANGED
|
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
/*
|
|
3
3
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { ContainerAbstract, sceneComponent } from '@hatiolab/things-scene';
|
|
6
6
|
import { Placeable } from '@operato/scene-base';
|
|
7
7
|
import { AsrsRack3D } from './asrs-rack-3d.js';
|
|
8
8
|
const NATURE = {
|
|
@@ -25,7 +25,10 @@ const NATURE = {
|
|
|
25
25
|
],
|
|
26
26
|
help: 'scene/component/asrs-rack'
|
|
27
27
|
};
|
|
28
|
-
|
|
28
|
+
// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
|
|
29
|
+
// which forces `isHTMLElement(): true` and trips the 3D pipeline's
|
|
30
|
+
// addObject DOM-skip gate. ASRS rack lives only in the 3D scene graph.
|
|
31
|
+
const Base = Placeable(ContainerAbstract);
|
|
29
32
|
/**
|
|
30
33
|
* AsrsRack — a multi-level high-bay storage rack, the structural backbone of
|
|
31
34
|
* an AS/RS (Automated Storage / Retrieval System).
|
package/dist/asrs-rack.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asrs-rack.js","sourceRoot":"","sources":["../src/asrs-rack.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA8B,
|
|
1
|
+
{"version":3,"file":"asrs-rack.js","sourceRoot":"","sources":["../src/asrs-rack.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA8B,iBAAiB,EAAc,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAClH,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,2FAA2F;AAC3F,mEAAmE;AACnE,uEAAuE;AACvE,MAAM,IAAI,GAAG,SAAS,CAAC,iBAAiB,CAAgC,CAAA;AAExE;;;;;;;;;;;;;;;;;;;;;;;;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, ContainerAbstract, 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\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. ASRS rack lives only in the 3D scene graph.\nconst Base = Placeable(ContainerAbstract) 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"]}
|
package/dist/pallet.d.ts
CHANGED
|
@@ -48,8 +48,22 @@ export default class Pallet extends Base {
|
|
|
48
48
|
get anchors(): never[];
|
|
49
49
|
/** Accept other operation-archetype cargo (boxes, parcels, smaller pallets) as stacked children. */
|
|
50
50
|
containable(component: Component): boolean;
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* 2D — top-down silhouette. Body is a flat rectangle (wood/plastic deck);
|
|
53
|
+
* `postrender()` adds the deck pattern + edge stroke so the pallet reads
|
|
54
|
+
* as a pallet instead of a featureless rectangle.
|
|
55
|
+
*/
|
|
52
56
|
render(ctx: CanvasRenderingContext2D): void;
|
|
57
|
+
/**
|
|
58
|
+
* Deck pattern + edge stroke. Wood: parallel slats with darker grooves
|
|
59
|
+
* between (typical EUR pallet deck). Plastic: cross-cutout pattern
|
|
60
|
+
* suggesting the molded reinforcement ribs.
|
|
61
|
+
*
|
|
62
|
+
* Slats run along the *short* axis of the rectangle (= along the longer
|
|
63
|
+
* stringer direction in real life), so a 1200×800 pallet shows multiple
|
|
64
|
+
* narrow slats across the 1200mm dimension — matching the EUR layout.
|
|
65
|
+
*/
|
|
66
|
+
postrender(ctx: CanvasRenderingContext2D): void;
|
|
53
67
|
get fillStyle(): string;
|
|
54
68
|
buildRealObject(): RealObject | undefined;
|
|
55
69
|
}
|
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 {
|
|
5
|
+
import { ContainerAbstract, 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 = {
|
|
@@ -30,9 +30,14 @@ const NATURE = {
|
|
|
30
30
|
help: 'scene/component/pallet'
|
|
31
31
|
};
|
|
32
32
|
// Carriable: a pallet can sit on AGV / Forklift / robot-arm gripper / Spot
|
|
33
|
-
// and also accept boxes / parcels as children (
|
|
34
|
-
// child-container behavior; Carriable only adds the
|
|
35
|
-
|
|
33
|
+
// and also accept boxes / parcels as children (ContainerAbstract base
|
|
34
|
+
// provides the child-container behavior; Carriable only adds the
|
|
35
|
+
// holder-mount hook).
|
|
36
|
+
//
|
|
37
|
+
// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
|
|
38
|
+
// which forces `isHTMLElement(): true` and trips the 3D pipeline's
|
|
39
|
+
// addObject DOM-skip gate. Pallet renders only as a 3D mesh.
|
|
40
|
+
const Base = Carriable(Legendable(Placeable(ContainerAbstract)));
|
|
36
41
|
/**
|
|
37
42
|
* Pallet — a flat transport structure that goods are stacked and stored on.
|
|
38
43
|
*
|
|
@@ -79,12 +84,72 @@ let Pallet = class Pallet extends Base {
|
|
|
79
84
|
return true;
|
|
80
85
|
return component.isDescendible(this);
|
|
81
86
|
}
|
|
82
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* 2D — top-down silhouette. Body is a flat rectangle (wood/plastic deck);
|
|
89
|
+
* `postrender()` adds the deck pattern + edge stroke so the pallet reads
|
|
90
|
+
* as a pallet instead of a featureless rectangle.
|
|
91
|
+
*/
|
|
83
92
|
render(ctx) {
|
|
84
93
|
const { width, height, left, top } = this.state;
|
|
85
94
|
ctx.beginPath();
|
|
86
95
|
ctx.rect(left, top, width, height);
|
|
87
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Deck pattern + edge stroke. Wood: parallel slats with darker grooves
|
|
99
|
+
* between (typical EUR pallet deck). Plastic: cross-cutout pattern
|
|
100
|
+
* suggesting the molded reinforcement ribs.
|
|
101
|
+
*
|
|
102
|
+
* Slats run along the *short* axis of the rectangle (= along the longer
|
|
103
|
+
* stringer direction in real life), so a 1200×800 pallet shows multiple
|
|
104
|
+
* narrow slats across the 1200mm dimension — matching the EUR layout.
|
|
105
|
+
*/
|
|
106
|
+
postrender(ctx) {
|
|
107
|
+
super.postrender?.(ctx);
|
|
108
|
+
const { width, height, left, top } = this.state;
|
|
109
|
+
const isPlastic = this.state.material === 'plastic';
|
|
110
|
+
ctx.save();
|
|
111
|
+
if (!isPlastic) {
|
|
112
|
+
// Wood — slats. Run them along the longer axis so the grooves are
|
|
113
|
+
// perpendicular to the longer side (typical pallet appearance).
|
|
114
|
+
const longAxisHorizontal = width >= height;
|
|
115
|
+
const slatCount = 5;
|
|
116
|
+
const grooveColor = '#7a4f25';
|
|
117
|
+
ctx.fillStyle = grooveColor;
|
|
118
|
+
if (longAxisHorizontal) {
|
|
119
|
+
// grooves vertical (X direction across the width)
|
|
120
|
+
const grooveW = Math.max(1, width * 0.012);
|
|
121
|
+
const slatW = (width - grooveW * (slatCount - 1)) / slatCount;
|
|
122
|
+
for (let i = 1; i < slatCount; i++) {
|
|
123
|
+
const x = left + i * slatW + (i - 1) * grooveW;
|
|
124
|
+
ctx.fillRect(x, top + height * 0.05, grooveW, height * 0.9);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
const grooveH = Math.max(1, height * 0.012);
|
|
129
|
+
const slatH = (height - grooveH * (slatCount - 1)) / slatCount;
|
|
130
|
+
for (let i = 1; i < slatCount; i++) {
|
|
131
|
+
const y = top + i * slatH + (i - 1) * grooveH;
|
|
132
|
+
ctx.fillRect(left + width * 0.05, y, width * 0.9, grooveH);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Plastic — cross + corner cutouts hint
|
|
138
|
+
ctx.strokeStyle = '#3a4956';
|
|
139
|
+
ctx.lineWidth = Math.max(1, Math.min(width, height) * 0.012);
|
|
140
|
+
ctx.beginPath();
|
|
141
|
+
ctx.moveTo(left + width * 0.5, top + height * 0.1);
|
|
142
|
+
ctx.lineTo(left + width * 0.5, top + height * 0.9);
|
|
143
|
+
ctx.moveTo(left + width * 0.1, top + height * 0.5);
|
|
144
|
+
ctx.lineTo(left + width * 0.9, top + height * 0.5);
|
|
145
|
+
ctx.stroke();
|
|
146
|
+
}
|
|
147
|
+
// Edge stroke
|
|
148
|
+
ctx.strokeStyle = isPlastic ? '#2a3946' : '#5e3818';
|
|
149
|
+
ctx.lineWidth = 1;
|
|
150
|
+
ctx.strokeRect(left, top, width, height);
|
|
151
|
+
ctx.restore();
|
|
152
|
+
}
|
|
88
153
|
get fillStyle() {
|
|
89
154
|
return this.state.bodyColor || '#a87644';
|
|
90
155
|
}
|
package/dist/pallet.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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 {\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 (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.\nconst Base = Carriable(Legendable(Placeable(ContainerAbstract))) 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 /**\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 as any).material as PalletMaterial) === '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 as any)\n }\n}\n"]}
|
package/dist/spot.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* the top face of the pad (overrides default attachPointFor).
|
|
30
30
|
*/
|
|
31
31
|
import { __decorate } from "tslib";
|
|
32
|
-
import {
|
|
32
|
+
import { ContainerAbstract, sceneComponent } from '@hatiolab/things-scene';
|
|
33
33
|
import { CarrierHolder, Placeable } from '@operato/scene-base';
|
|
34
34
|
import { Spot3D } from './spot-3d.js';
|
|
35
35
|
const NATURE = {
|
|
@@ -42,10 +42,14 @@ const NATURE = {
|
|
|
42
42
|
properties: [],
|
|
43
43
|
help: 'scene/component/spot'
|
|
44
44
|
};
|
|
45
|
-
//
|
|
45
|
+
// ContainerAbstract base — Spot accepts carrier children (parcel/box/pallet/...).
|
|
46
46
|
// CarrierHolder mixin only publishes the attach-point hook; the actual
|
|
47
|
-
// child-list management comes from
|
|
48
|
-
|
|
47
|
+
// child-list management comes from things-scene's container abstract.
|
|
48
|
+
//
|
|
49
|
+
// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
|
|
50
|
+
// which forces `isHTMLElement(): true` and trips the 3D pipeline's
|
|
51
|
+
// addObject DOM-skip gate. Spot is purely 3D.
|
|
52
|
+
const Base = CarrierHolder(Placeable(ContainerAbstract));
|
|
49
53
|
let Spot = class Spot extends Base {
|
|
50
54
|
static placement = 'floor';
|
|
51
55
|
static align = 'bottom';
|
package/dist/spot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spot.js","sourceRoot":"","sources":["../src/spot.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Spot — virtual pickup / drop zone.\n *\n * A modeling-time anchor for \"this is where things land\" — the destination\n * of a robot arm pick-and-place, the slot of an AGV stop, the staging\n * zone next to a conveyor. Spot itself does not move and does\n * not perform any logistics action; it only marks a location and accepts\n * carrier components as children.\n *\n * Visual identity:\n * - 2D: outlined rectangle with corner \"L\" marks (so it reads as a\n * virtual zone, not a solid object).\n * - 3D: a thin translucent floor pad — only the floor of the conceptual\n * box is rendered, the side walls are absent.\n *\n * Standard things-scene properties used (no component-specific extras —\n * keep the property-panel UX uniform with other components):\n * - `fillStyle` — pad / outline color (sole color source)\n * - `strokeStyle` — outline color override (defaults to fillStyle)\n * - `lineWidth` / `lineDash` — outline stroke style\n * - `alpha` — overall transparency, framework-applied\n * - `text` / `fontColor` / `fontSize` / `fontFamily` / `bold` / `italic`\n * — label rendered by the standard text pipeline\n * - `material3d` (3D) — metalness / roughness / castShadow / receiveShadow\n *\n * Role: `CarrierHolder` — accepts any Carrier as a child and lays it on\n * the top face of the pad (overrides default attachPointFor).\n */\n\nimport { Component, ComponentNature, Container, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n Placeable,\n type Alignment,\n type Heights,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Spot3D } from './spot-3d.js'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n // No component-specific properties — fillStyle / strokeStyle / lineWidth /\n // alpha / text / font* are framework-standard, surfaced by the property\n // panel automatically.\n properties: [],\n help: 'scene/component/spot'\n}\n\n// Container base — Spot accepts carrier children (parcel/box/pallet/...).\n// CarrierHolder mixin only publishes the attach-point hook; the actual\n// child-list management comes from the things-scene Container.\nconst Base = CarrierHolder(Placeable(Container)) as unknown as typeof Component\n\n@sceneComponent('spot')\nexport default class Spot extends Base {\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (_h: Heights) => 2 // a thin pad\n\n get nature(): ComponentNature {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — outlined rectangle + corner L marks. The pad body is drawn with a\n * fixed low alpha (0.15) on top of the user's `fillStyle` so the zone reads\n * as virtual even when fillStyle is fully opaque. Outline + corner marks\n * use `strokeStyle` (or fall back to `fillStyle`) at the user's `lineWidth`\n * and `lineDash`. The text label is drawn by the framework's standard\n * postrender pipeline using `text` / `fontColor` / `fontSize` / etc.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { left = 0, top = 0, width = 100, height = 100 } = this.state\n const fillStyle = (this.state.fillStyle as string) || '#3a8fbd'\n const strokeStyle = (this.state.strokeStyle as string) || fillStyle\n const lineWidth = numOr(this.state.lineWidth, 1)\n const lineDashStyle = String(this.state.lineDash ?? 'dash')\n\n // ── Pad body (fixed-low-alpha tint of fillStyle) ───────────────────\n ctx.save()\n ctx.fillStyle = fillStyle\n ctx.globalAlpha = 0.15\n ctx.fillRect(left, top, width, height)\n ctx.restore()\n\n // ── Outline ────────────────────────────────────────────────────────\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = lineWidth\n applyLineDash(ctx, lineDashStyle, lineWidth)\n ctx.strokeRect(\n left + lineWidth / 2,\n top + lineWidth / 2,\n width - lineWidth,\n height - lineWidth\n )\n ctx.setLineDash([])\n ctx.restore()\n\n // ── Corner L marks (solid, slightly heavier than outline) ──────────\n const ml = Math.min(width, height) * 0.18\n const cornerW = Math.max(lineWidth * 1.5, 1.5)\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = cornerW\n for (const [cx, cy, sx, sy] of [\n [left, top, 1, 1],\n [left + width, top, -1, 1],\n [left + width, top + height, -1, -1],\n [left, top + height, 1, -1]\n ] as [number, number, number, number][]) {\n ctx.beginPath()\n ctx.moveTo(cx + sx * ml, cy)\n ctx.lineTo(cx, cy)\n ctx.lineTo(cx, cy + sy * ml)\n ctx.stroke()\n }\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Spot3D(this as any)\n }\n\n /**\n * Mount carriers on the TOP of the pad (Spot3D's `getAttachFrame` is\n * already at pad-top in spot-local). Then lift the carrier by its\n * own halfDepth so the carrier's BOTTOM rests ON the pad surface, not\n * its volumetric center — without this lift, half the carrier would\n * sink below the pad / floor.\n *\n * Reads `_realObject.effectiveDepth` first (the framework-resolved\n * value, accounting for `static defaultDepth` and parent context),\n * falling back to raw `state.depth` for components built before\n * RealObject creation.\n */\n attachPointFor(carrier: Component) {\n const ro = (this as any)._realObject as Spot3D | undefined\n const frame = ro?.getAttachFrame?.()\n if (!frame) return undefined\n const carrierDepth = resolveDepth(carrier)\n return {\n attach: frame,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n}\n\nfunction resolveDepth(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 numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n\n/**\n * Map a things-scene `lineDash` string to a Canvas dash pattern. Mirrors\n * the keys understood by things-scene's `drawer/stroke.ts` so users see\n * consistent options across components. Unknown strings fall through to\n * a plain dashed pattern instead of throwing on setLineDash.\n */\nfunction applyLineDash(ctx: CanvasRenderingContext2D, style: string, lw: number) {\n switch (style) {\n case 'solid':\n ctx.setLineDash([])\n return\n case 'round-dot':\n ctx.setLineDash([0.1, lw * 2])\n ctx.lineCap = 'round'\n return\n case 'square-dot':\n ctx.setLineDash([lw, lw])\n return\n case 'long-dash':\n ctx.setLineDash([lw * 6, lw * 3])\n return\n case 'dash-dot':\n ctx.setLineDash([lw * 4, lw * 2, lw, lw * 2])\n return\n case 'dash':\n default:\n ctx.setLineDash([lw * 4, lw * 1.5])\n return\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"spot.js","sourceRoot":"","sources":["../src/spot.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Spot — virtual pickup / drop zone.\n *\n * A modeling-time anchor for \"this is where things land\" — the destination\n * of a robot arm pick-and-place, the slot of an AGV stop, the staging\n * zone next to a conveyor. Spot itself does not move and does\n * not perform any logistics action; it only marks a location and accepts\n * carrier components as children.\n *\n * Visual identity:\n * - 2D: outlined rectangle with corner \"L\" marks (so it reads as a\n * virtual zone, not a solid object).\n * - 3D: a thin translucent floor pad — only the floor of the conceptual\n * box is rendered, the side walls are absent.\n *\n * Standard things-scene properties used (no component-specific extras —\n * keep the property-panel UX uniform with other components):\n * - `fillStyle` — pad / outline color (sole color source)\n * - `strokeStyle` — outline color override (defaults to fillStyle)\n * - `lineWidth` / `lineDash` — outline stroke style\n * - `alpha` — overall transparency, framework-applied\n * - `text` / `fontColor` / `fontSize` / `fontFamily` / `bold` / `italic`\n * — label rendered by the standard text pipeline\n * - `material3d` (3D) — metalness / roughness / castShadow / receiveShadow\n *\n * Role: `CarrierHolder` — accepts any Carrier as a child and lays it on\n * the top face of the pad (overrides default attachPointFor).\n */\n\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n Placeable,\n type Alignment,\n type Heights,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Spot3D } from './spot-3d.js'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n // No component-specific properties — fillStyle / strokeStyle / lineWidth /\n // alpha / text / font* are framework-standard, surfaced by the property\n // panel automatically.\n properties: [],\n help: 'scene/component/spot'\n}\n\n// ContainerAbstract base — Spot accepts carrier children (parcel/box/pallet/...).\n// CarrierHolder mixin only publishes the attach-point hook; the actual\n// child-list management comes from things-scene's container abstract.\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Spot is purely 3D.\nconst Base = CarrierHolder(Placeable(ContainerAbstract)) as unknown as typeof Component\n\n@sceneComponent('spot')\nexport default class Spot extends Base {\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (_h: Heights) => 2 // a thin pad\n\n get nature(): ComponentNature {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — outlined rectangle + corner L marks. The pad body is drawn with a\n * fixed low alpha (0.15) on top of the user's `fillStyle` so the zone reads\n * as virtual even when fillStyle is fully opaque. Outline + corner marks\n * use `strokeStyle` (or fall back to `fillStyle`) at the user's `lineWidth`\n * and `lineDash`. The text label is drawn by the framework's standard\n * postrender pipeline using `text` / `fontColor` / `fontSize` / etc.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { left = 0, top = 0, width = 100, height = 100 } = this.state\n const fillStyle = (this.state.fillStyle as string) || '#3a8fbd'\n const strokeStyle = (this.state.strokeStyle as string) || fillStyle\n const lineWidth = numOr(this.state.lineWidth, 1)\n const lineDashStyle = String(this.state.lineDash ?? 'dash')\n\n // ── Pad body (fixed-low-alpha tint of fillStyle) ───────────────────\n ctx.save()\n ctx.fillStyle = fillStyle\n ctx.globalAlpha = 0.15\n ctx.fillRect(left, top, width, height)\n ctx.restore()\n\n // ── Outline ────────────────────────────────────────────────────────\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = lineWidth\n applyLineDash(ctx, lineDashStyle, lineWidth)\n ctx.strokeRect(\n left + lineWidth / 2,\n top + lineWidth / 2,\n width - lineWidth,\n height - lineWidth\n )\n ctx.setLineDash([])\n ctx.restore()\n\n // ── Corner L marks (solid, slightly heavier than outline) ──────────\n const ml = Math.min(width, height) * 0.18\n const cornerW = Math.max(lineWidth * 1.5, 1.5)\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = cornerW\n for (const [cx, cy, sx, sy] of [\n [left, top, 1, 1],\n [left + width, top, -1, 1],\n [left + width, top + height, -1, -1],\n [left, top + height, 1, -1]\n ] as [number, number, number, number][]) {\n ctx.beginPath()\n ctx.moveTo(cx + sx * ml, cy)\n ctx.lineTo(cx, cy)\n ctx.lineTo(cx, cy + sy * ml)\n ctx.stroke()\n }\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Spot3D(this as any)\n }\n\n /**\n * Mount carriers on the TOP of the pad (Spot3D's `getAttachFrame` is\n * already at pad-top in spot-local). Then lift the carrier by its\n * own halfDepth so the carrier's BOTTOM rests ON the pad surface, not\n * its volumetric center — without this lift, half the carrier would\n * sink below the pad / floor.\n *\n * Reads `_realObject.effectiveDepth` first (the framework-resolved\n * value, accounting for `static defaultDepth` and parent context),\n * falling back to raw `state.depth` for components built before\n * RealObject creation.\n */\n attachPointFor(carrier: Component) {\n const ro = (this as any)._realObject as Spot3D | undefined\n const frame = ro?.getAttachFrame?.()\n if (!frame) return undefined\n const carrierDepth = resolveDepth(carrier)\n return {\n attach: frame,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n}\n\nfunction resolveDepth(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 numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n\n/**\n * Map a things-scene `lineDash` string to a Canvas dash pattern. Mirrors\n * the keys understood by things-scene's `drawer/stroke.ts` so users see\n * consistent options across components. Unknown strings fall through to\n * a plain dashed pattern instead of throwing on setLineDash.\n */\nfunction applyLineDash(ctx: CanvasRenderingContext2D, style: string, lw: number) {\n switch (style) {\n case 'solid':\n ctx.setLineDash([])\n return\n case 'round-dot':\n ctx.setLineDash([0.1, lw * 2])\n ctx.lineCap = 'round'\n return\n case 'square-dot':\n ctx.setLineDash([lw, lw])\n return\n case 'long-dash':\n ctx.setLineDash([lw * 6, lw * 3])\n return\n case 'dash-dot':\n ctx.setLineDash([lw * 4, lw * 2, lw, lw * 2])\n return\n case 'dash':\n default:\n ctx.setLineDash([lw * 4, lw * 1.5])\n return\n }\n}\n"]}
|
package/dist/templates/index.js
CHANGED
|
@@ -20,7 +20,7 @@ export default [
|
|
|
20
20
|
type: 'pallet',
|
|
21
21
|
top: 100,
|
|
22
22
|
left: 100,
|
|
23
|
-
width:
|
|
23
|
+
width: 80,
|
|
24
24
|
height: 80,
|
|
25
25
|
material: 'wood'
|
|
26
26
|
}
|
|
@@ -34,7 +34,7 @@ export default [
|
|
|
34
34
|
type: 'pallet',
|
|
35
35
|
top: 100,
|
|
36
36
|
left: 250,
|
|
37
|
-
width:
|
|
37
|
+
width: 80,
|
|
38
38
|
height: 80,
|
|
39
39
|
material: 'plastic'
|
|
40
40
|
}
|
|
@@ -77,7 +77,7 @@ export default [
|
|
|
77
77
|
top: 100,
|
|
78
78
|
left: 640,
|
|
79
79
|
width: 60,
|
|
80
|
-
height:
|
|
80
|
+
height: 80
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,6BAA6B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AAC/E,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,gCAAgC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AACrF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,0BAA0B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AACzE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,6BAA6B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AAC/E,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,wBAAwB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AACtE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AAC3E,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,4BAA4B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AAE7E,eAAe;IACb;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,0BAA0B;QACvC,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE;YACL,IAAI,EAAE,QAAQ;YACd,GAAG,EAAE,GAAG;YACR,IAAI,EAAE,GAAG;YACT,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * things-scene catalog templates for the storage domain — pallet/box/parcel\n * variants, ASRS rack/crane, and the virtual `spot` placement marker.\n */\nimport spot from './spot.js'\nconst palletWood = new URL('../../icons/pallet-wood.png', import.meta.url).href\nconst palletPlastic = new URL('../../icons/pallet-plastic.png', import.meta.url).href\nconst boxWood = new URL('../../icons/box-wood.png', import.meta.url).href\nconst boxPlastic = new URL('../../icons/box-plastic.png', import.meta.url).href\nconst parcel = new URL('../../icons/parcel.png', import.meta.url).href\nconst asrsRack = new URL('../../icons/asrs-rack.png', import.meta.url).href\nconst asrsCrane = new URL('../../icons/asrs-crane.png', import.meta.url).href\n\nexport default [\n {\n type: 'pallet',\n description: 'wood pallet (EUR / EPAL)',\n group: 'storage',\n icon: palletWood,\n model: {\n type: 'pallet',\n top: 100,\n left: 100,\n width: 80,\n height: 80,\n material: 'wood'\n }\n },\n {\n type: 'pallet',\n description: 'plastic pallet',\n group: 'storage',\n icon: palletPlastic,\n model: {\n type: 'pallet',\n top: 100,\n left: 250,\n width: 80,\n height: 80,\n material: 'plastic'\n }\n },\n {\n type: 'box',\n description: 'wood crate',\n group: 'storage',\n icon: boxWood,\n model: {\n type: 'box',\n top: 100,\n left: 400,\n width: 80,\n height: 80,\n material: 'wood'\n }\n },\n {\n type: 'box',\n description: 'plastic tote',\n group: 'storage',\n icon: boxPlastic,\n model: {\n type: 'box',\n top: 100,\n left: 520,\n width: 80,\n height: 80,\n material: 'plastic'\n }\n },\n {\n type: 'parcel',\n description: 'cardboard parcel',\n group: 'storage',\n icon: parcel,\n model: {\n type: 'parcel',\n top: 100,\n left: 640,\n width: 60,\n height: 80\n }\n },\n {\n type: 'asrs-rack',\n description: 'AS/RS storage rack (multi-level)',\n group: 'storage',\n icon: asrsRack,\n model: {\n type: 'asrs-rack',\n top: 300,\n left: 100,\n width: 800,\n height: 200,\n levels: 4,\n bays: 5\n }\n },\n {\n type: 'asrs-crane',\n description: 'AS/RS stacker crane',\n group: 'storage',\n icon: asrsCrane,\n model: {\n type: 'asrs-crane',\n top: 550,\n left: 400,\n width: 100,\n height: 200,\n status: 'idle',\n carriageHeight: 100\n }\n },\n spot\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.24",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"module": "dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@hatiolab/things-scene": "^10.0.0-beta.1",
|
|
28
|
-
"@operato/scene-base": "^10.0.0-beta.
|
|
28
|
+
"@operato/scene-base": "^10.0.0-beta.24",
|
|
29
29
|
"three": "^0.183.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"typescript": "^5.0.4"
|
|
41
41
|
},
|
|
42
42
|
"prettier": "@hatiolab/prettier-config",
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "236ded8c545c4b172b2e86e4843b3221c4bf2fd8"
|
|
44
44
|
}
|
package/src/asrs-rack.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
-
import { Component, ComponentNature,
|
|
4
|
+
import { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'
|
|
5
5
|
import {
|
|
6
6
|
Placeable,
|
|
7
7
|
type Alignment,
|
|
@@ -32,7 +32,10 @@ const NATURE: ComponentNature = {
|
|
|
32
32
|
help: 'scene/component/asrs-rack'
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
|
|
36
|
+
// which forces `isHTMLElement(): true` and trips the 3D pipeline's
|
|
37
|
+
// addObject DOM-skip gate. ASRS rack lives only in the 3D scene graph.
|
|
38
|
+
const Base = Placeable(ContainerAbstract) as unknown as typeof Component
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* AsrsRack — a multi-level high-bay storage rack, the structural backbone of
|
package/src/pallet.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
-
import { Component, ComponentNature,
|
|
4
|
+
import { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'
|
|
5
5
|
import {
|
|
6
6
|
Carriable,
|
|
7
7
|
Legendable,
|
|
@@ -53,9 +53,14 @@ const NATURE: ComponentNature = {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// Carriable: a pallet can sit on AGV / Forklift / robot-arm gripper / Spot
|
|
56
|
-
// and also accept boxes / parcels as children (
|
|
57
|
-
// child-container behavior; Carriable only adds the
|
|
58
|
-
|
|
56
|
+
// and also accept boxes / parcels as children (ContainerAbstract base
|
|
57
|
+
// provides the child-container behavior; Carriable only adds the
|
|
58
|
+
// holder-mount hook).
|
|
59
|
+
//
|
|
60
|
+
// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
|
|
61
|
+
// which forces `isHTMLElement(): true` and trips the 3D pipeline's
|
|
62
|
+
// addObject DOM-skip gate. Pallet renders only as a 3D mesh.
|
|
63
|
+
const Base = Carriable(Legendable(Placeable(ContainerAbstract))) as unknown as typeof Component
|
|
59
64
|
|
|
60
65
|
/**
|
|
61
66
|
* Pallet — a flat transport structure that goods are stacked and stored on.
|
|
@@ -108,13 +113,77 @@ export default class Pallet extends Base {
|
|
|
108
113
|
return component.isDescendible(this as any)
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* 2D — top-down silhouette. Body is a flat rectangle (wood/plastic deck);
|
|
118
|
+
* `postrender()` adds the deck pattern + edge stroke so the pallet reads
|
|
119
|
+
* as a pallet instead of a featureless rectangle.
|
|
120
|
+
*/
|
|
112
121
|
render(ctx: CanvasRenderingContext2D) {
|
|
113
122
|
const { width, height, left, top } = this.state
|
|
114
123
|
ctx.beginPath()
|
|
115
124
|
ctx.rect(left, top, width, height)
|
|
116
125
|
}
|
|
117
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Deck pattern + edge stroke. Wood: parallel slats with darker grooves
|
|
129
|
+
* between (typical EUR pallet deck). Plastic: cross-cutout pattern
|
|
130
|
+
* suggesting the molded reinforcement ribs.
|
|
131
|
+
*
|
|
132
|
+
* Slats run along the *short* axis of the rectangle (= along the longer
|
|
133
|
+
* stringer direction in real life), so a 1200×800 pallet shows multiple
|
|
134
|
+
* narrow slats across the 1200mm dimension — matching the EUR layout.
|
|
135
|
+
*/
|
|
136
|
+
postrender(ctx: CanvasRenderingContext2D) {
|
|
137
|
+
super.postrender?.(ctx)
|
|
138
|
+
|
|
139
|
+
const { width, height, left, top } = this.state
|
|
140
|
+
const isPlastic = ((this.state as any).material as PalletMaterial) === 'plastic'
|
|
141
|
+
|
|
142
|
+
ctx.save()
|
|
143
|
+
|
|
144
|
+
if (!isPlastic) {
|
|
145
|
+
// Wood — slats. Run them along the longer axis so the grooves are
|
|
146
|
+
// perpendicular to the longer side (typical pallet appearance).
|
|
147
|
+
const longAxisHorizontal = width >= height
|
|
148
|
+
const slatCount = 5
|
|
149
|
+
const grooveColor = '#7a4f25'
|
|
150
|
+
ctx.fillStyle = grooveColor
|
|
151
|
+
if (longAxisHorizontal) {
|
|
152
|
+
// grooves vertical (X direction across the width)
|
|
153
|
+
const grooveW = Math.max(1, width * 0.012)
|
|
154
|
+
const slatW = (width - grooveW * (slatCount - 1)) / slatCount
|
|
155
|
+
for (let i = 1; i < slatCount; i++) {
|
|
156
|
+
const x = left + i * slatW + (i - 1) * grooveW
|
|
157
|
+
ctx.fillRect(x, top + height * 0.05, grooveW, height * 0.9)
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
const grooveH = Math.max(1, height * 0.012)
|
|
161
|
+
const slatH = (height - grooveH * (slatCount - 1)) / slatCount
|
|
162
|
+
for (let i = 1; i < slatCount; i++) {
|
|
163
|
+
const y = top + i * slatH + (i - 1) * grooveH
|
|
164
|
+
ctx.fillRect(left + width * 0.05, y, width * 0.9, grooveH)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
// Plastic — cross + corner cutouts hint
|
|
169
|
+
ctx.strokeStyle = '#3a4956'
|
|
170
|
+
ctx.lineWidth = Math.max(1, Math.min(width, height) * 0.012)
|
|
171
|
+
ctx.beginPath()
|
|
172
|
+
ctx.moveTo(left + width * 0.5, top + height * 0.1)
|
|
173
|
+
ctx.lineTo(left + width * 0.5, top + height * 0.9)
|
|
174
|
+
ctx.moveTo(left + width * 0.1, top + height * 0.5)
|
|
175
|
+
ctx.lineTo(left + width * 0.9, top + height * 0.5)
|
|
176
|
+
ctx.stroke()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Edge stroke
|
|
180
|
+
ctx.strokeStyle = isPlastic ? '#2a3946' : '#5e3818'
|
|
181
|
+
ctx.lineWidth = 1
|
|
182
|
+
ctx.strokeRect(left, top, width, height)
|
|
183
|
+
|
|
184
|
+
ctx.restore()
|
|
185
|
+
}
|
|
186
|
+
|
|
118
187
|
get fillStyle() {
|
|
119
188
|
return (this.state.bodyColor as string) || '#a87644'
|
|
120
189
|
}
|
package/src/spot.ts
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* the top face of the pad (overrides default attachPointFor).
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
import { Component, ComponentNature,
|
|
32
|
+
import { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'
|
|
33
33
|
import {
|
|
34
34
|
CarrierHolder,
|
|
35
35
|
Placeable,
|
|
@@ -51,10 +51,14 @@ const NATURE: ComponentNature = {
|
|
|
51
51
|
help: 'scene/component/spot'
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
//
|
|
54
|
+
// ContainerAbstract base — Spot accepts carrier children (parcel/box/pallet/...).
|
|
55
55
|
// CarrierHolder mixin only publishes the attach-point hook; the actual
|
|
56
|
-
// child-list management comes from
|
|
57
|
-
|
|
56
|
+
// child-list management comes from things-scene's container abstract.
|
|
57
|
+
//
|
|
58
|
+
// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
|
|
59
|
+
// which forces `isHTMLElement(): true` and trips the 3D pipeline's
|
|
60
|
+
// addObject DOM-skip gate. Spot is purely 3D.
|
|
61
|
+
const Base = CarrierHolder(Placeable(ContainerAbstract)) as unknown as typeof Component
|
|
58
62
|
|
|
59
63
|
@sceneComponent('spot')
|
|
60
64
|
export default class Spot extends Base {
|
package/src/templates/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ export default [
|
|
|
21
21
|
type: 'pallet',
|
|
22
22
|
top: 100,
|
|
23
23
|
left: 100,
|
|
24
|
-
width:
|
|
24
|
+
width: 80,
|
|
25
25
|
height: 80,
|
|
26
26
|
material: 'wood'
|
|
27
27
|
}
|
|
@@ -35,7 +35,7 @@ export default [
|
|
|
35
35
|
type: 'pallet',
|
|
36
36
|
top: 100,
|
|
37
37
|
left: 250,
|
|
38
|
-
width:
|
|
38
|
+
width: 80,
|
|
39
39
|
height: 80,
|
|
40
40
|
material: 'plastic'
|
|
41
41
|
}
|
|
@@ -78,7 +78,7 @@ export default [
|
|
|
78
78
|
top: 100,
|
|
79
79
|
left: 640,
|
|
80
80
|
width: 60,
|
|
81
|
-
height:
|
|
81
|
+
height: 80
|
|
82
82
|
}
|
|
83
83
|
},
|
|
84
84
|
{
|