@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,176 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Spot 3D — translucent floor pad.
5
+ *
6
+ * Renders only the FLOOR face of the conceptual zone box; side walls are
7
+ * absent. Children (carriers) sit on the top face via an explicit attach
8
+ * frame; the pad doesn't occlude them (`depthWrite: false`).
9
+ *
10
+ * Standard things-scene properties read directly:
11
+ * - state.fillStyle → pad color (sole color source)
12
+ * - state.strokeStyle → outline color (defaults to fillStyle)
13
+ * - state.alpha → pad transparency, multiplied with the base 0.35 tint
14
+ * - state.text → label, rendered as a CanvasTexture quad on the pad
15
+ * - state.fontColor → label fill (defaults to fillStyle)
16
+ * - state.fontSize / fontFamily / bold / italic → label typography
17
+ * (composed via the things-scene fontStyle helper)
18
+ * - state.material3d → metalness / roughness / castShadow / receiveShadow
19
+ * (resolved + applied via the things-scene helpers,
20
+ * no hard-coded numbers)
21
+ */
22
+ import * as THREE from 'three';
23
+ import { RealObjectGroup, resolveMaterial3d, applyMaterial3dProps, fontStyle, opaqueColor } from '@hatiolab/things-scene';
24
+ const DEFAULT_PAD_COLOR = '#3a8fbd';
25
+ const BASE_PAD_OPACITY = 0.35; // multiplied by state.alpha
26
+ export class Spot3D extends RealObjectGroup {
27
+ build() {
28
+ super.build();
29
+ const state = this.component.state;
30
+ const w = Math.max(Math.abs(numOr(state.width, 100)), 1);
31
+ const h = Math.max(Math.abs(numOr(state.height, 100)), 1);
32
+ const d = this.effectiveDepth; // 2 by default (thin pad)
33
+ // opaqueColor strips alpha from rgba/hsla strings — THREE.Color doesn't
34
+ // honor alpha, would emit a console warning, and ignore the alpha bit.
35
+ // The actual transparency comes through the material.opacity below.
36
+ const padColor = opaqueColor(state.fillStyle || DEFAULT_PAD_COLOR);
37
+ const alpha = clamp(numOr(state.alpha, 1), 0, 1);
38
+ const padOpacity = clamp(BASE_PAD_OPACITY * alpha, 0.05, 1);
39
+ // material3d: pulls user-set metalness / roughness / shadow / side.
40
+ // Local defaults (transparent + DoubleSide + depthWrite:false) come
41
+ // from the constructor below; user values override via applyMaterial3dProps.
42
+ const resolved = resolveMaterial3d(state.material3d);
43
+ // ── Floor pad (the only visible surface of the conceptual zone box) ──
44
+ const padThickness = Math.max(d * 0.4, 0.5);
45
+ const padMat = new THREE.MeshStandardMaterial({
46
+ color: padColor,
47
+ transparent: true,
48
+ opacity: padOpacity,
49
+ side: THREE.DoubleSide,
50
+ depthWrite: false
51
+ });
52
+ applyMaterial3dProps(padMat, resolved);
53
+ const pad = new THREE.Mesh(new THREE.BoxGeometry(w, padThickness, h), padMat);
54
+ pad.position.set(0, -d / 2 + padThickness / 2, 0);
55
+ pad.castShadow = resolved.castShadow;
56
+ pad.receiveShadow = resolved.receiveShadow;
57
+ this.object3d.add(pad);
58
+ // ── Outline of the zone footprint (line on the floor) ──
59
+ const outlineGeo = new THREE.BufferGeometry().setFromPoints([
60
+ new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, -h / 2),
61
+ new THREE.Vector3(w / 2, -d / 2 + padThickness + 0.05, -h / 2),
62
+ new THREE.Vector3(w / 2, -d / 2 + padThickness + 0.05, h / 2),
63
+ new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, h / 2),
64
+ new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, -h / 2)
65
+ ]);
66
+ const outlineMat = new THREE.LineBasicMaterial({
67
+ color: opaqueColor(state.strokeStyle || padColor),
68
+ transparent: true,
69
+ opacity: 0.7 * alpha
70
+ });
71
+ const outline = new THREE.Line(outlineGeo, outlineMat);
72
+ this.object3d.add(outline);
73
+ // ── Label (uses standard text + font fields) ──
74
+ const text = state.text;
75
+ if (typeof text === 'string' && text.length > 0) {
76
+ const label = this._buildLabel(text, state, padColor, w, h);
77
+ if (label) {
78
+ label.position.set(0, -d / 2 + padThickness + Math.max(w, h) * 0.05, 0);
79
+ this.object3d.add(label);
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * Build the label as a canvas-textured quad. Uses the same `fontStyle`
85
+ * helper things-scene uses for its 2D text rendering, so the label
86
+ * here matches what the property panel previews.
87
+ */
88
+ _buildLabel(text, state, defaultColor, w, h) {
89
+ const fontSize = clamp(numOr(state.fontSize, 36), 8, 200);
90
+ const fontFamily = String(state.fontFamily ?? 'sans-serif');
91
+ const bold = !!state.bold;
92
+ const italic = !!state.italic;
93
+ const color = state.fontColor || defaultColor;
94
+ const canvas = document.createElement('canvas');
95
+ canvas.width = 512;
96
+ canvas.height = 128;
97
+ const ctx = canvas.getContext('2d');
98
+ if (!ctx)
99
+ return null;
100
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
101
+ ctx.font = fontStyle(bold, italic, fontSize, fontFamily);
102
+ ctx.fillStyle = color;
103
+ ctx.textAlign = 'center';
104
+ ctx.textBaseline = 'middle';
105
+ ctx.fillText(text, canvas.width / 2, canvas.height / 2);
106
+ const tex = new THREE.CanvasTexture(canvas);
107
+ tex.needsUpdate = true;
108
+ const labelMat = new THREE.MeshBasicMaterial({
109
+ map: tex,
110
+ transparent: true,
111
+ depthWrite: false,
112
+ side: THREE.DoubleSide
113
+ });
114
+ const labelW = Math.min(w, h) * 0.6;
115
+ const labelH = labelW * (canvas.height / canvas.width);
116
+ const mesh = new THREE.Mesh(new THREE.PlaneGeometry(labelW, labelH), labelMat);
117
+ mesh.rotation.x = -Math.PI / 2; // lay flat, readable from above
118
+ return mesh;
119
+ }
120
+ /**
121
+ * The sub-frame that carrier components should mount onto.
122
+ *
123
+ * Semantically Spot is a virtual cuboid SPACE — it marks "stuff goes
124
+ * here" — and carriers are placed INSIDE that space, resting on the
125
+ * cuboid's BOTTOM face. So the attach frame sits at the cuboid's
126
+ * bottom (`y = -d/2` in spot-local), NOT at the top of the rendered
127
+ * pad. The pad is just a translucent visual marker for the zone; the
128
+ * floor of the conceptual volume is what carriers stand on.
129
+ *
130
+ * The Spot.attachPointFor mixin lifts the carrier by its own halfDepth
131
+ * (in the +Y direction within this frame), placing the carrier's
132
+ * BOTTOM face exactly at the cuboid floor.
133
+ */
134
+ getAttachFrame() {
135
+ if (!this._attachFrame) {
136
+ const d = this.effectiveDepth;
137
+ this._attachFrame = new THREE.Object3D();
138
+ this._attachFrame.position.set(0, -d / 2, 0);
139
+ this.object3d.add(this._attachFrame);
140
+ }
141
+ return this._attachFrame;
142
+ }
143
+ _attachFrame;
144
+ updateDimension() { }
145
+ onchange(after, before) {
146
+ if ('width' in after ||
147
+ 'height' in after ||
148
+ 'depth' in after ||
149
+ 'fillStyle' in after ||
150
+ 'strokeStyle' in after ||
151
+ 'alpha' in after ||
152
+ 'text' in after ||
153
+ 'fontColor' in after ||
154
+ 'fontSize' in after ||
155
+ 'fontFamily' in after ||
156
+ 'bold' in after ||
157
+ 'italic' in after ||
158
+ 'material3d' in after) {
159
+ this._attachFrame = undefined;
160
+ this.update();
161
+ return;
162
+ }
163
+ super.onchange(after, before);
164
+ }
165
+ // alpha is rebuilt into the materials on every build; opt out of the
166
+ // base RealObject's "multiply existing material opacity" pass to avoid
167
+ // double-application.
168
+ updateAlpha() { }
169
+ }
170
+ function clamp(v, lo, hi) {
171
+ return Math.max(lo, Math.min(hi, v));
172
+ }
173
+ function numOr(v, dflt) {
174
+ return typeof v === 'number' && Number.isFinite(v) ? v : dflt;
175
+ }
176
+ //# sourceMappingURL=spot-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spot-3d.js","sourceRoot":"","sources":["../src/spot-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Spot 3D — translucent floor pad.\n *\n * Renders only the FLOOR face of the conceptual zone box; side walls are\n * absent. Children (carriers) sit on the top face via an explicit attach\n * frame; the pad doesn't occlude them (`depthWrite: false`).\n *\n * Standard things-scene properties read directly:\n * - state.fillStyle → pad color (sole color source)\n * - state.strokeStyle → outline color (defaults to fillStyle)\n * - state.alpha → pad transparency, multiplied with the base 0.35 tint\n * - state.text → label, rendered as a CanvasTexture quad on the pad\n * - state.fontColor → label fill (defaults to fillStyle)\n * - state.fontSize / fontFamily / bold / italic → label typography\n * (composed via the things-scene fontStyle helper)\n * - state.material3d → metalness / roughness / castShadow / receiveShadow\n * (resolved + applied via the things-scene helpers,\n * no hard-coded numbers)\n */\n\nimport * as THREE from 'three'\nimport {\n RealObjectGroup,\n resolveMaterial3d,\n applyMaterial3dProps,\n fontStyle,\n opaqueColor,\n type Material3D\n} from '@hatiolab/things-scene'\n\nconst DEFAULT_PAD_COLOR = '#3a8fbd'\nconst BASE_PAD_OPACITY = 0.35 // multiplied by state.alpha\n\nexport class Spot3D extends RealObjectGroup {\n build() {\n super.build()\n\n const state = this.component.state as any\n const w = Math.max(Math.abs(numOr(state.width, 100)), 1)\n const h = Math.max(Math.abs(numOr(state.height, 100)), 1)\n const d = this.effectiveDepth // 2 by default (thin pad)\n // opaqueColor strips alpha from rgba/hsla strings — THREE.Color doesn't\n // honor alpha, would emit a console warning, and ignore the alpha bit.\n // The actual transparency comes through the material.opacity below.\n const padColor = opaqueColor((state.fillStyle as string) || DEFAULT_PAD_COLOR)\n const alpha = clamp(numOr(state.alpha, 1), 0, 1)\n const padOpacity = clamp(BASE_PAD_OPACITY * alpha, 0.05, 1)\n\n // material3d: pulls user-set metalness / roughness / shadow / side.\n // Local defaults (transparent + DoubleSide + depthWrite:false) come\n // from the constructor below; user values override via applyMaterial3dProps.\n const resolved = resolveMaterial3d(state.material3d as Material3D | undefined)\n\n // ── Floor pad (the only visible surface of the conceptual zone box) ──\n const padThickness = Math.max(d * 0.4, 0.5)\n const padMat = new THREE.MeshStandardMaterial({\n color: padColor,\n transparent: true,\n opacity: padOpacity,\n side: THREE.DoubleSide,\n depthWrite: false\n })\n applyMaterial3dProps(padMat, resolved)\n const pad = new THREE.Mesh(new THREE.BoxGeometry(w, padThickness, h), padMat)\n pad.position.set(0, -d / 2 + padThickness / 2, 0)\n pad.castShadow = resolved.castShadow\n pad.receiveShadow = resolved.receiveShadow\n this.object3d.add(pad)\n\n // ── Outline of the zone footprint (line on the floor) ──\n const outlineGeo = new THREE.BufferGeometry().setFromPoints([\n new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, -h / 2),\n new THREE.Vector3(w / 2, -d / 2 + padThickness + 0.05, -h / 2),\n new THREE.Vector3(w / 2, -d / 2 + padThickness + 0.05, h / 2),\n new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, h / 2),\n new THREE.Vector3(-w / 2, -d / 2 + padThickness + 0.05, -h / 2)\n ])\n const outlineMat = new THREE.LineBasicMaterial({\n color: opaqueColor((state.strokeStyle as string) || padColor),\n transparent: true,\n opacity: 0.7 * alpha\n })\n const outline = new THREE.Line(outlineGeo, outlineMat)\n this.object3d.add(outline)\n\n // ── Label (uses standard text + font fields) ──\n const text = state.text\n if (typeof text === 'string' && text.length > 0) {\n const label = this._buildLabel(text, state, padColor, w, h)\n if (label) {\n label.position.set(0, -d / 2 + padThickness + Math.max(w, h) * 0.05, 0)\n this.object3d.add(label)\n }\n }\n }\n\n /**\n * Build the label as a canvas-textured quad. Uses the same `fontStyle`\n * helper things-scene uses for its 2D text rendering, so the label\n * here matches what the property panel previews.\n */\n private _buildLabel(text: string, state: any, defaultColor: string, w: number, h: number): THREE.Mesh | null {\n const fontSize = clamp(numOr(state.fontSize, 36), 8, 200)\n const fontFamily = String(state.fontFamily ?? 'sans-serif')\n const bold = !!state.bold\n const italic = !!state.italic\n const color = (state.fontColor as string) || defaultColor\n\n const canvas = document.createElement('canvas')\n canvas.width = 512\n canvas.height = 128\n const ctx = canvas.getContext('2d')\n if (!ctx) return null\n ctx.clearRect(0, 0, canvas.width, canvas.height)\n ctx.font = fontStyle(bold, italic, fontSize, fontFamily)\n ctx.fillStyle = color\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.fillText(text, canvas.width / 2, canvas.height / 2)\n const tex = new THREE.CanvasTexture(canvas)\n tex.needsUpdate = true\n const labelMat = new THREE.MeshBasicMaterial({\n map: tex,\n transparent: true,\n depthWrite: false,\n side: THREE.DoubleSide\n })\n const labelW = Math.min(w, h) * 0.6\n const labelH = labelW * (canvas.height / canvas.width)\n const mesh = new THREE.Mesh(new THREE.PlaneGeometry(labelW, labelH), labelMat)\n mesh.rotation.x = -Math.PI / 2 // lay flat, readable from above\n return mesh\n }\n\n /**\n * The sub-frame that carrier components should mount onto.\n *\n * Semantically Spot is a virtual cuboid SPACE — it marks \"stuff goes\n * here\" — and carriers are placed INSIDE that space, resting on the\n * cuboid's BOTTOM face. So the attach frame sits at the cuboid's\n * bottom (`y = -d/2` in spot-local), NOT at the top of the rendered\n * pad. The pad is just a translucent visual marker for the zone; the\n * floor of the conceptual volume is what carriers stand on.\n *\n * The Spot.attachPointFor mixin lifts the carrier by its own halfDepth\n * (in the +Y direction within this frame), placing the carrier's\n * BOTTOM face exactly at the cuboid floor.\n */\n getAttachFrame(): THREE.Object3D {\n if (!this._attachFrame) {\n const d = this.effectiveDepth\n this._attachFrame = new THREE.Object3D()\n this._attachFrame.position.set(0, -d / 2, 0)\n this.object3d.add(this._attachFrame)\n }\n return this._attachFrame\n }\n\n private _attachFrame?: THREE.Object3D\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'width' in after ||\n 'height' in after ||\n 'depth' in after ||\n 'fillStyle' in after ||\n 'strokeStyle' in after ||\n 'alpha' in after ||\n 'text' in after ||\n 'fontColor' in after ||\n 'fontSize' in after ||\n 'fontFamily' in after ||\n 'bold' in after ||\n 'italic' in after ||\n 'material3d' in after\n ) {\n this._attachFrame = undefined\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n // alpha is rebuilt into the materials on every build; opt out of the\n // base RealObject's \"multiply existing material opacity\" pass to avoid\n // double-application.\n updateAlpha() {}\n}\n\nfunction clamp(v: number, lo: number, hi: number) {\n return Math.max(lo, Math.min(hi, v))\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
package/dist/spot.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import { type Alignment, type Heights, type PlacementArchetype } from '@operato/scene-base';
3
+ declare const Base: typeof Component;
4
+ export default class Spot extends Base {
5
+ static placement: PlacementArchetype;
6
+ static align: Alignment;
7
+ static defaultDepth: (_h: Heights) => number;
8
+ get nature(): ComponentNature;
9
+ get anchors(): never[];
10
+ /**
11
+ * 2D — outlined rectangle + corner L marks. The pad body is drawn with a
12
+ * fixed low alpha (0.15) on top of the user's `fillStyle` so the zone reads
13
+ * as virtual even when fillStyle is fully opaque. Outline + corner marks
14
+ * use `strokeStyle` (or fall back to `fillStyle`) at the user's `lineWidth`
15
+ * and `lineDash`. The text label is drawn by the framework's standard
16
+ * postrender pipeline using `text` / `fontColor` / `fontSize` / etc.
17
+ */
18
+ render(ctx: CanvasRenderingContext2D): void;
19
+ buildRealObject(): RealObject | undefined;
20
+ /**
21
+ * Mount carriers on the TOP of the pad (Spot3D's `getAttachFrame` is
22
+ * already at pad-top in spot-local). Then lift the carrier by its
23
+ * own halfDepth so the carrier's BOTTOM rests ON the pad surface, not
24
+ * its volumetric center — without this lift, half the carrier would
25
+ * sink below the pad / floor.
26
+ *
27
+ * Reads `_realObject.effectiveDepth` first (the framework-resolved
28
+ * value, accounting for `static defaultDepth` and parent context),
29
+ * falling back to raw `state.depth` for components built before
30
+ * RealObject creation.
31
+ */
32
+ attachPointFor(carrier: Component): {
33
+ attach: import("three").Object3D<import("three").Object3DEventMap>;
34
+ localPosition: {
35
+ x: number;
36
+ y: number;
37
+ z: number;
38
+ };
39
+ } | undefined;
40
+ }
41
+ export {};
package/dist/spot.js ADDED
@@ -0,0 +1,177 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Spot — virtual pickup / drop zone.
5
+ *
6
+ * A modeling-time anchor for "this is where things land" — the destination
7
+ * of a robot arm pick-and-place, the slot of an AGV stop, the staging
8
+ * zone next to a conveyor. Spot itself does not move and does
9
+ * not perform any logistics action; it only marks a location and accepts
10
+ * carrier components as children.
11
+ *
12
+ * Visual identity:
13
+ * - 2D: outlined rectangle with corner "L" marks (so it reads as a
14
+ * virtual zone, not a solid object).
15
+ * - 3D: a thin translucent floor pad — only the floor of the conceptual
16
+ * box is rendered, the side walls are absent.
17
+ *
18
+ * Standard things-scene properties used (no component-specific extras —
19
+ * keep the property-panel UX uniform with other components):
20
+ * - `fillStyle` — pad / outline color (sole color source)
21
+ * - `strokeStyle` — outline color override (defaults to fillStyle)
22
+ * - `lineWidth` / `lineDash` — outline stroke style
23
+ * - `alpha` — overall transparency, framework-applied
24
+ * - `text` / `fontColor` / `fontSize` / `fontFamily` / `bold` / `italic`
25
+ * — label rendered by the standard text pipeline
26
+ * - `material3d` (3D) — metalness / roughness / castShadow / receiveShadow
27
+ *
28
+ * Role: `CarrierHolder` — accepts any Carrier as a child and lays it on
29
+ * the top face of the pad (overrides default attachPointFor).
30
+ */
31
+ import { __decorate } from "tslib";
32
+ import { Container, sceneComponent } from '@hatiolab/things-scene';
33
+ import { CarrierHolder, Placeable } from '@operato/scene-base';
34
+ import { Spot3D } from './spot-3d.js';
35
+ const NATURE = {
36
+ mutable: false,
37
+ resizable: true,
38
+ rotatable: true,
39
+ // No component-specific properties — fillStyle / strokeStyle / lineWidth /
40
+ // alpha / text / font* are framework-standard, surfaced by the property
41
+ // panel automatically.
42
+ properties: [],
43
+ help: 'scene/component/spot'
44
+ };
45
+ // Container base — Spot accepts carrier children (parcel/box/pallet/...).
46
+ // CarrierHolder mixin only publishes the attach-point hook; the actual
47
+ // child-list management comes from the things-scene Container.
48
+ const Base = CarrierHolder(Placeable(Container));
49
+ let Spot = class Spot extends Base {
50
+ static placement = 'floor';
51
+ static align = 'bottom';
52
+ static defaultDepth = (_h) => 2; // a thin pad
53
+ get nature() {
54
+ return NATURE;
55
+ }
56
+ get anchors() {
57
+ return [];
58
+ }
59
+ /**
60
+ * 2D — outlined rectangle + corner L marks. The pad body is drawn with a
61
+ * fixed low alpha (0.15) on top of the user's `fillStyle` so the zone reads
62
+ * as virtual even when fillStyle is fully opaque. Outline + corner marks
63
+ * use `strokeStyle` (or fall back to `fillStyle`) at the user's `lineWidth`
64
+ * and `lineDash`. The text label is drawn by the framework's standard
65
+ * postrender pipeline using `text` / `fontColor` / `fontSize` / etc.
66
+ */
67
+ render(ctx) {
68
+ const { left = 0, top = 0, width = 100, height = 100 } = this.state;
69
+ const fillStyle = this.state.fillStyle || '#3a8fbd';
70
+ const strokeStyle = this.state.strokeStyle || fillStyle;
71
+ const lineWidth = numOr(this.state.lineWidth, 1);
72
+ const lineDashStyle = String(this.state.lineDash ?? 'dash');
73
+ // ── Pad body (fixed-low-alpha tint of fillStyle) ───────────────────
74
+ ctx.save();
75
+ ctx.fillStyle = fillStyle;
76
+ ctx.globalAlpha = 0.15;
77
+ ctx.fillRect(left, top, width, height);
78
+ ctx.restore();
79
+ // ── Outline ────────────────────────────────────────────────────────
80
+ ctx.save();
81
+ ctx.strokeStyle = strokeStyle;
82
+ ctx.lineWidth = lineWidth;
83
+ applyLineDash(ctx, lineDashStyle, lineWidth);
84
+ ctx.strokeRect(left + lineWidth / 2, top + lineWidth / 2, width - lineWidth, height - lineWidth);
85
+ ctx.setLineDash([]);
86
+ ctx.restore();
87
+ // ── Corner L marks (solid, slightly heavier than outline) ──────────
88
+ const ml = Math.min(width, height) * 0.18;
89
+ const cornerW = Math.max(lineWidth * 1.5, 1.5);
90
+ ctx.save();
91
+ ctx.strokeStyle = strokeStyle;
92
+ ctx.lineWidth = cornerW;
93
+ for (const [cx, cy, sx, sy] of [
94
+ [left, top, 1, 1],
95
+ [left + width, top, -1, 1],
96
+ [left + width, top + height, -1, -1],
97
+ [left, top + height, 1, -1]
98
+ ]) {
99
+ ctx.beginPath();
100
+ ctx.moveTo(cx + sx * ml, cy);
101
+ ctx.lineTo(cx, cy);
102
+ ctx.lineTo(cx, cy + sy * ml);
103
+ ctx.stroke();
104
+ }
105
+ ctx.restore();
106
+ }
107
+ buildRealObject() {
108
+ return new Spot3D(this);
109
+ }
110
+ /**
111
+ * Mount carriers on the TOP of the pad (Spot3D's `getAttachFrame` is
112
+ * already at pad-top in spot-local). Then lift the carrier by its
113
+ * own halfDepth so the carrier's BOTTOM rests ON the pad surface, not
114
+ * its volumetric center — without this lift, half the carrier would
115
+ * sink below the pad / floor.
116
+ *
117
+ * Reads `_realObject.effectiveDepth` first (the framework-resolved
118
+ * value, accounting for `static defaultDepth` and parent context),
119
+ * falling back to raw `state.depth` for components built before
120
+ * RealObject creation.
121
+ */
122
+ attachPointFor(carrier) {
123
+ const ro = this._realObject;
124
+ const frame = ro?.getAttachFrame?.();
125
+ if (!frame)
126
+ return undefined;
127
+ const carrierDepth = resolveDepth(carrier);
128
+ return {
129
+ attach: frame,
130
+ localPosition: { x: 0, y: carrierDepth / 2, z: 0 }
131
+ };
132
+ }
133
+ };
134
+ Spot = __decorate([
135
+ sceneComponent('spot')
136
+ ], Spot);
137
+ export default Spot;
138
+ function resolveDepth(c) {
139
+ const eff = c._realObject?.effectiveDepth;
140
+ if (typeof eff === 'number' && Number.isFinite(eff))
141
+ return eff;
142
+ return numOr(c?.state?.depth, 0);
143
+ }
144
+ function numOr(v, dflt) {
145
+ return typeof v === 'number' && Number.isFinite(v) ? v : dflt;
146
+ }
147
+ /**
148
+ * Map a things-scene `lineDash` string to a Canvas dash pattern. Mirrors
149
+ * the keys understood by things-scene's `drawer/stroke.ts` so users see
150
+ * consistent options across components. Unknown strings fall through to
151
+ * a plain dashed pattern instead of throwing on setLineDash.
152
+ */
153
+ function applyLineDash(ctx, style, lw) {
154
+ switch (style) {
155
+ case 'solid':
156
+ ctx.setLineDash([]);
157
+ return;
158
+ case 'round-dot':
159
+ ctx.setLineDash([0.1, lw * 2]);
160
+ ctx.lineCap = 'round';
161
+ return;
162
+ case 'square-dot':
163
+ ctx.setLineDash([lw, lw]);
164
+ return;
165
+ case 'long-dash':
166
+ ctx.setLineDash([lw * 6, lw * 3]);
167
+ return;
168
+ case 'dash-dot':
169
+ ctx.setLineDash([lw * 4, lw * 2, lw, lw * 2]);
170
+ return;
171
+ case 'dash':
172
+ default:
173
+ ctx.setLineDash([lw * 4, lw * 1.5]);
174
+ return;
175
+ }
176
+ }
177
+ //# sourceMappingURL=spot.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,92 @@
1
+ declare const _default: ({
2
+ type: string;
3
+ description: string;
4
+ group: string;
5
+ icon: string;
6
+ model: {
7
+ type: string;
8
+ top: number;
9
+ left: number;
10
+ width: number;
11
+ height: number;
12
+ text: string;
13
+ fillStyle: string;
14
+ strokeStyle: string;
15
+ lineWidth: number;
16
+ lineDash: string;
17
+ alpha: number;
18
+ fontColor: string;
19
+ fontSize: number;
20
+ fontFamily: string;
21
+ bold: boolean;
22
+ };
23
+ } | {
24
+ type: string;
25
+ description: string;
26
+ group: string;
27
+ icon: string;
28
+ model: {
29
+ type: string;
30
+ top: number;
31
+ left: number;
32
+ width: number;
33
+ height: number;
34
+ material: string;
35
+ levels?: undefined;
36
+ bays?: undefined;
37
+ status?: undefined;
38
+ carriageHeight?: undefined;
39
+ };
40
+ } | {
41
+ type: string;
42
+ description: string;
43
+ group: string;
44
+ icon: string;
45
+ model: {
46
+ type: string;
47
+ top: number;
48
+ left: number;
49
+ width: number;
50
+ height: number;
51
+ material?: undefined;
52
+ levels?: undefined;
53
+ bays?: undefined;
54
+ status?: undefined;
55
+ carriageHeight?: undefined;
56
+ };
57
+ } | {
58
+ type: string;
59
+ description: string;
60
+ group: string;
61
+ icon: string;
62
+ model: {
63
+ type: string;
64
+ top: number;
65
+ left: number;
66
+ width: number;
67
+ height: number;
68
+ levels: number;
69
+ bays: number;
70
+ material?: undefined;
71
+ status?: undefined;
72
+ carriageHeight?: undefined;
73
+ };
74
+ } | {
75
+ type: string;
76
+ description: string;
77
+ group: string;
78
+ icon: string;
79
+ model: {
80
+ type: string;
81
+ top: number;
82
+ left: number;
83
+ width: number;
84
+ height: number;
85
+ status: string;
86
+ carriageHeight: number;
87
+ material?: undefined;
88
+ levels?: undefined;
89
+ bays?: undefined;
90
+ };
91
+ })[];
92
+ export default _default;
@@ -0,0 +1,115 @@
1
+ /*
2
+ * things-scene catalog templates for the storage domain — pallet/box/parcel
3
+ * variants, ASRS rack/crane, and the virtual `spot` placement marker.
4
+ */
5
+ import spot from './spot.js';
6
+ const palletWood = new URL('../../icons/pallet-wood.png', import.meta.url).href;
7
+ const palletPlastic = new URL('../../icons/pallet-plastic.png', import.meta.url).href;
8
+ const boxWood = new URL('../../icons/box-wood.png', import.meta.url).href;
9
+ const boxPlastic = new URL('../../icons/box-plastic.png', import.meta.url).href;
10
+ const parcel = new URL('../../icons/parcel.png', import.meta.url).href;
11
+ const asrsRack = new URL('../../icons/asrs-rack.png', import.meta.url).href;
12
+ const asrsCrane = new URL('../../icons/asrs-crane.png', import.meta.url).href;
13
+ export default [
14
+ {
15
+ type: 'pallet',
16
+ description: 'wood pallet (EUR / EPAL)',
17
+ group: 'storage',
18
+ icon: palletWood,
19
+ model: {
20
+ type: 'pallet',
21
+ top: 100,
22
+ left: 100,
23
+ width: 120,
24
+ height: 80,
25
+ material: 'wood'
26
+ }
27
+ },
28
+ {
29
+ type: 'pallet',
30
+ description: 'plastic pallet',
31
+ group: 'storage',
32
+ icon: palletPlastic,
33
+ model: {
34
+ type: 'pallet',
35
+ top: 100,
36
+ left: 250,
37
+ width: 120,
38
+ height: 80,
39
+ material: 'plastic'
40
+ }
41
+ },
42
+ {
43
+ type: 'box',
44
+ description: 'wood crate',
45
+ group: 'storage',
46
+ icon: boxWood,
47
+ model: {
48
+ type: 'box',
49
+ top: 100,
50
+ left: 400,
51
+ width: 80,
52
+ height: 80,
53
+ material: 'wood'
54
+ }
55
+ },
56
+ {
57
+ type: 'box',
58
+ description: 'plastic tote',
59
+ group: 'storage',
60
+ icon: boxPlastic,
61
+ model: {
62
+ type: 'box',
63
+ top: 100,
64
+ left: 520,
65
+ width: 80,
66
+ height: 80,
67
+ material: 'plastic'
68
+ }
69
+ },
70
+ {
71
+ type: 'parcel',
72
+ description: 'cardboard parcel',
73
+ group: 'storage',
74
+ icon: parcel,
75
+ model: {
76
+ type: 'parcel',
77
+ top: 100,
78
+ left: 640,
79
+ width: 60,
80
+ height: 90
81
+ }
82
+ },
83
+ {
84
+ type: 'asrs-rack',
85
+ description: 'AS/RS storage rack (multi-level)',
86
+ group: 'storage',
87
+ icon: asrsRack,
88
+ model: {
89
+ type: 'asrs-rack',
90
+ top: 300,
91
+ left: 100,
92
+ width: 800,
93
+ height: 200,
94
+ levels: 4,
95
+ bays: 5
96
+ }
97
+ },
98
+ {
99
+ type: 'asrs-crane',
100
+ description: 'AS/RS stacker crane',
101
+ group: 'storage',
102
+ icon: asrsCrane,
103
+ model: {
104
+ type: 'asrs-crane',
105
+ top: 550,
106
+ left: 400,
107
+ width: 100,
108
+ height: 200,
109
+ status: 'idle',
110
+ carriageHeight: 100
111
+ }
112
+ },
113
+ spot
114
+ ];
115
+ //# sourceMappingURL=index.js.map