pluton-2d 0.1.0
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/LICENSE +21 -0
- package/README.md +403 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +359 -0
- package/dist/index.js +1119 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1119 @@
|
|
|
1
|
+
const c = "http://www.w3.org/2000/svg";
|
|
2
|
+
class w {
|
|
3
|
+
listeners = /* @__PURE__ */ new Map();
|
|
4
|
+
on(t, e) {
|
|
5
|
+
return this.listeners.has(t) || this.listeners.set(t, /* @__PURE__ */ new Set()), this.listeners.get(t).add(e), () => this.off(t, e);
|
|
6
|
+
}
|
|
7
|
+
off(t, e) {
|
|
8
|
+
this.listeners.get(t)?.delete(e);
|
|
9
|
+
}
|
|
10
|
+
emit(t, e) {
|
|
11
|
+
this.listeners.get(t)?.forEach((s) => {
|
|
12
|
+
try {
|
|
13
|
+
s(e);
|
|
14
|
+
} catch (i) {
|
|
15
|
+
console.error("EventBus listener error:", i);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
clear() {
|
|
20
|
+
this.listeners.clear();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
class S {
|
|
24
|
+
svg;
|
|
25
|
+
defs;
|
|
26
|
+
cameraRef;
|
|
27
|
+
cachedViewport = null;
|
|
28
|
+
resizeObserver;
|
|
29
|
+
onResize;
|
|
30
|
+
constructor(t, e, s, i) {
|
|
31
|
+
this.svg = t, this.defs = e, this.cameraRef = s, this.onResize = i, this.resizeObserver = new ResizeObserver(() => {
|
|
32
|
+
this.invalidateViewport(), this.onResize?.();
|
|
33
|
+
}), this.resizeObserver.observe(t);
|
|
34
|
+
}
|
|
35
|
+
dispose() {
|
|
36
|
+
this.resizeObserver.disconnect();
|
|
37
|
+
}
|
|
38
|
+
invalidateViewport() {
|
|
39
|
+
this.cachedViewport = null;
|
|
40
|
+
}
|
|
41
|
+
viewport = () => {
|
|
42
|
+
if (this.cachedViewport) return this.cachedViewport;
|
|
43
|
+
const t = this.svg.viewBox?.baseVal;
|
|
44
|
+
if (t && t.width && t.height)
|
|
45
|
+
return this.cachedViewport = { x: t.x, y: t.y, width: t.width, height: t.height }, this.cachedViewport;
|
|
46
|
+
const e = Number(this.svg.getAttribute("width")) || 0, s = Number(this.svg.getAttribute("height")) || 0;
|
|
47
|
+
if (e && s)
|
|
48
|
+
return this.cachedViewport = { x: 0, y: 0, width: e, height: s }, this.cachedViewport;
|
|
49
|
+
const i = this.svg.getBoundingClientRect();
|
|
50
|
+
return this.cachedViewport = { x: 0, y: 0, width: i.width, height: i.height }, this.cachedViewport;
|
|
51
|
+
};
|
|
52
|
+
camera = () => this.cameraRef?.state() ?? null;
|
|
53
|
+
}
|
|
54
|
+
const A = /* @__PURE__ */ new WeakMap();
|
|
55
|
+
function $(l) {
|
|
56
|
+
let t = A.get(l);
|
|
57
|
+
return t || (t = /* @__PURE__ */ new Map(), A.set(l, t)), t;
|
|
58
|
+
}
|
|
59
|
+
function m(l, t) {
|
|
60
|
+
const e = t.getAttribute("id");
|
|
61
|
+
if (!e) {
|
|
62
|
+
l.appendChild(t);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const s = $(l), i = s.get(e);
|
|
66
|
+
i ? i.replaceWith(t) : l.appendChild(t), s.set(e, t);
|
|
67
|
+
}
|
|
68
|
+
class v {
|
|
69
|
+
hatchFill45Id = "pluton-pattern-fill-hatch-45";
|
|
70
|
+
graphPaperPatternId = "pluton-pattern-graph-paper";
|
|
71
|
+
defsEl;
|
|
72
|
+
constructor(t) {
|
|
73
|
+
this.defsEl = t;
|
|
74
|
+
}
|
|
75
|
+
sync() {
|
|
76
|
+
m(this.defsEl, this.createHatchFill45Pattern()), m(this.defsEl, this.createGraphPaperPattern());
|
|
77
|
+
}
|
|
78
|
+
createHatchFill45Pattern() {
|
|
79
|
+
const t = document.createElementNS(c, "pattern");
|
|
80
|
+
t.setAttribute("id", this.hatchFill45Id), t.setAttribute("patternUnits", "userSpaceOnUse"), t.setAttribute("width", "8"), t.setAttribute("height", "8"), t.setAttribute("patternTransform", "rotate(-45)");
|
|
81
|
+
const e = document.createElementNS(c, "line");
|
|
82
|
+
return e.setAttribute("x1", "0"), e.setAttribute("y1", "0"), e.setAttribute("x2", "0"), e.setAttribute("y2", "8"), e.setAttribute("stroke-width", "12.5"), e.classList.add("pluton-pattern-hatch"), t.appendChild(e), t;
|
|
83
|
+
}
|
|
84
|
+
createGraphPaperPattern() {
|
|
85
|
+
const s = document.createElementNS(c, "pattern");
|
|
86
|
+
s.setAttribute("id", this.graphPaperPatternId), s.setAttribute("patternUnits", "userSpaceOnUse"), s.setAttribute("x", "0"), s.setAttribute("y", "0"), s.setAttribute("width", String(50)), s.setAttribute("height", String(50));
|
|
87
|
+
const i = document.createElementNS(c, "path");
|
|
88
|
+
i.classList.add("pluton-pattern-graph-paper-minor");
|
|
89
|
+
const n = Math.max(1, Math.floor(50 / 10)), r = [];
|
|
90
|
+
for (let a = 1; a < n; a++) {
|
|
91
|
+
const h = a * 10;
|
|
92
|
+
r.push(`M ${h} 0 L ${h} 50`);
|
|
93
|
+
}
|
|
94
|
+
for (let a = 1; a < n; a++) {
|
|
95
|
+
const h = a * 10;
|
|
96
|
+
r.push(`M 0 ${h} L 50 ${h}`);
|
|
97
|
+
}
|
|
98
|
+
i.setAttribute("d", r.join(" ")), s.appendChild(i);
|
|
99
|
+
const o = document.createElementNS(c, "path");
|
|
100
|
+
return o.classList.add("pluton-pattern-graph-paper-major"), o.setAttribute(
|
|
101
|
+
"d",
|
|
102
|
+
"M 50 0 L 50 50 M 0 50 L 50 50"
|
|
103
|
+
), s.appendChild(o), s;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
class P {
|
|
107
|
+
graphPaperGradientId = "pluton-gradient-graph-paper";
|
|
108
|
+
graphPaperMaskId = "pluton-mask-graph-paper";
|
|
109
|
+
defsEl;
|
|
110
|
+
constructor(t) {
|
|
111
|
+
this.defsEl = t;
|
|
112
|
+
}
|
|
113
|
+
syncForViewport(t) {
|
|
114
|
+
m(this.defsEl, this.createGraphPaperFadeGradient(t)), m(this.defsEl, this.createGraphPaperFadeMask(t));
|
|
115
|
+
}
|
|
116
|
+
createGraphPaperFadeGradient(t) {
|
|
117
|
+
const i = t.width / 2, n = t.height / 2, r = Math.sqrt(i * i + n * n), o = r * 0.65, a = r * 1, h = document.createElementNS(c, "radialGradient");
|
|
118
|
+
h.setAttribute("id", this.graphPaperGradientId), h.setAttribute("gradientUnits", "userSpaceOnUse"), h.setAttribute("cx", String(i)), h.setAttribute("cy", String(n)), h.setAttribute("r", String(a));
|
|
119
|
+
const d = document.createElementNS(c, "stop");
|
|
120
|
+
d.setAttribute("offset", "0"), d.setAttribute("stop-color", "white");
|
|
121
|
+
const u = document.createElementNS(c, "stop");
|
|
122
|
+
u.setAttribute(
|
|
123
|
+
"offset",
|
|
124
|
+
a === 0 ? "0" : String(o / a)
|
|
125
|
+
), u.setAttribute("stop-color", "white");
|
|
126
|
+
const p = document.createElementNS(c, "stop");
|
|
127
|
+
return p.setAttribute("offset", "1"), p.setAttribute("stop-color", "black"), h.append(d, u, p), h;
|
|
128
|
+
}
|
|
129
|
+
createGraphPaperFadeMask(t) {
|
|
130
|
+
const e = document.createElementNS(c, "mask");
|
|
131
|
+
e.setAttribute("id", this.graphPaperMaskId), e.setAttribute("maskUnits", "userSpaceOnUse"), e.setAttribute("x", "0"), e.setAttribute("y", "0"), e.setAttribute("width", String(t.width)), e.setAttribute("height", String(t.height));
|
|
132
|
+
const s = document.createElementNS(c, "rect");
|
|
133
|
+
return s.setAttribute("x", "0"), s.setAttribute("y", "0"), s.setAttribute("width", String(t.width)), s.setAttribute("height", String(t.height)), s.setAttribute("fill", `url(#${this.graphPaperGradientId})`), e.appendChild(s), e;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
class E {
|
|
137
|
+
pencilFilterId = "pluton-filter-pencil";
|
|
138
|
+
defsEl;
|
|
139
|
+
intensity;
|
|
140
|
+
constructor(t, e = 1) {
|
|
141
|
+
this.defsEl = t, this.intensity = e;
|
|
142
|
+
}
|
|
143
|
+
sync() {
|
|
144
|
+
m(this.defsEl, this.createPencilFilter());
|
|
145
|
+
}
|
|
146
|
+
createPencilFilter() {
|
|
147
|
+
const s = document.createElementNS(c, "filter");
|
|
148
|
+
s.setAttribute("id", this.pencilFilterId), s.setAttribute("x", "-50%"), s.setAttribute("y", "-50%"), s.setAttribute("width", "200%"), s.setAttribute("height", "200%");
|
|
149
|
+
const i = document.createElementNS(c, "feTurbulence");
|
|
150
|
+
i.setAttribute("type", "fractalNoise"), i.setAttribute("baseFrequency", String(0.22)), i.setAttribute("numOctaves", String(3)), i.setAttribute("seed", "1"), i.setAttribute("result", "turbulence");
|
|
151
|
+
const n = document.createElementNS(
|
|
152
|
+
c,
|
|
153
|
+
"feDisplacementMap"
|
|
154
|
+
);
|
|
155
|
+
return n.setAttribute("in", "SourceGraphic"), n.setAttribute("in2", "turbulence"), n.setAttribute("scale", String(this.intensity)), n.setAttribute("xChannelSelector", "R"), n.setAttribute("yChannelSelector", "G"), s.appendChild(i), s.appendChild(n), s;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
class L {
|
|
159
|
+
patterns;
|
|
160
|
+
gradients;
|
|
161
|
+
filters;
|
|
162
|
+
lastWidth = 0;
|
|
163
|
+
lastHeight = 0;
|
|
164
|
+
constructor(t, e = 1.25) {
|
|
165
|
+
this.patterns = new v(t), this.gradients = new P(t), this.filters = new E(t, e), this.patterns.sync(), this.filters.sync();
|
|
166
|
+
}
|
|
167
|
+
syncForViewport(t) {
|
|
168
|
+
this.lastWidth === t.width && this.lastHeight === t.height || (this.lastWidth = t.width, this.lastHeight = t.height, this.gradients.syncForViewport(t));
|
|
169
|
+
}
|
|
170
|
+
get hatchFill45Id() {
|
|
171
|
+
return this.patterns.hatchFill45Id;
|
|
172
|
+
}
|
|
173
|
+
get graphPaperPatternId() {
|
|
174
|
+
return this.patterns.graphPaperPatternId;
|
|
175
|
+
}
|
|
176
|
+
get graphPaperGradientId() {
|
|
177
|
+
return this.gradients.graphPaperGradientId;
|
|
178
|
+
}
|
|
179
|
+
get graphPaperMaskId() {
|
|
180
|
+
return this.gradients.graphPaperMaskId;
|
|
181
|
+
}
|
|
182
|
+
get pencilFilterId() {
|
|
183
|
+
return this.filters.pencilFilterId;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
class C {
|
|
187
|
+
svg;
|
|
188
|
+
events;
|
|
189
|
+
requestFrame;
|
|
190
|
+
panX = 0;
|
|
191
|
+
panY = 0;
|
|
192
|
+
scale = 1;
|
|
193
|
+
targetPanX = 0;
|
|
194
|
+
targetPanY = 0;
|
|
195
|
+
targetScale = 1;
|
|
196
|
+
minScale = 1;
|
|
197
|
+
maxScale = 20;
|
|
198
|
+
dampingFactor = 0.2;
|
|
199
|
+
isPanning = !1;
|
|
200
|
+
isResetting = !1;
|
|
201
|
+
lastX = 0;
|
|
202
|
+
lastY = 0;
|
|
203
|
+
cachedRect = null;
|
|
204
|
+
panEnabled = !1;
|
|
205
|
+
zoomEnabled = !1;
|
|
206
|
+
panListeners = [];
|
|
207
|
+
zoomListeners = [];
|
|
208
|
+
constructor(t, e, s) {
|
|
209
|
+
this.svg = t, this.events = e, this.requestFrame = s;
|
|
210
|
+
}
|
|
211
|
+
dispose() {
|
|
212
|
+
this.enablePan(!1), this.enableZoom(!1);
|
|
213
|
+
}
|
|
214
|
+
state() {
|
|
215
|
+
return { panX: this.panX, panY: this.panY, scale: this.scale };
|
|
216
|
+
}
|
|
217
|
+
reset() {
|
|
218
|
+
this.isResetting || (this.isResetting = !0, this.targetPanX = 0, this.targetPanY = 0, this.targetScale = 1, this.requestFrame());
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Interpolate current values toward targets.
|
|
222
|
+
* Called every frame by Engine's loop. Returns true if still animating.
|
|
223
|
+
*/
|
|
224
|
+
tick() {
|
|
225
|
+
const e = this.panX, s = this.panY, i = this.scale;
|
|
226
|
+
this.panX = this.smoothStep(this.panX, this.targetPanX), this.panY = this.smoothStep(this.panY, this.targetPanY), this.scale = this.smoothStep(this.scale, this.targetScale);
|
|
227
|
+
const n = Math.abs(this.targetPanX - this.panX) < 0.01 && Math.abs(this.targetPanY - this.panY) < 0.01 && Math.abs(this.targetScale - this.scale) < 0.01;
|
|
228
|
+
return n && (this.panX = this.targetPanX, this.panY = this.targetPanY, this.scale = this.targetScale, this.isResetting && this.panX === 0 && this.panY === 0 && this.scale === 1 && (this.isResetting = !1)), (e !== this.panX || s !== this.panY || i !== this.scale) && this.events.emit("camera:changed", this.state()), !n;
|
|
229
|
+
}
|
|
230
|
+
smoothStep(t, e) {
|
|
231
|
+
return t + (e - t) * this.dampingFactor;
|
|
232
|
+
}
|
|
233
|
+
updateCachedRect() {
|
|
234
|
+
this.cachedRect = this.svg.getBoundingClientRect();
|
|
235
|
+
}
|
|
236
|
+
getRect() {
|
|
237
|
+
return this.cachedRect || this.updateCachedRect(), this.cachedRect;
|
|
238
|
+
}
|
|
239
|
+
enablePan(t) {
|
|
240
|
+
t !== this.panEnabled && (this.panEnabled = t, t ? this.addPanListeners() : this.removePanListeners());
|
|
241
|
+
}
|
|
242
|
+
enableZoom(t) {
|
|
243
|
+
t !== this.zoomEnabled && (this.zoomEnabled = t, t ? this.addZoomListeners() : this.removeZoomListeners());
|
|
244
|
+
}
|
|
245
|
+
addPanListeners() {
|
|
246
|
+
if (this.panListeners.length > 0) return;
|
|
247
|
+
const t = (i) => {
|
|
248
|
+
(i.button === 1 || i.button === 0 && i.shiftKey) && (i.preventDefault(), this.isPanning = !0, this.updateCachedRect(), this.lastX = i.clientX, this.lastY = i.clientY);
|
|
249
|
+
}, e = (i) => {
|
|
250
|
+
if (!this.isPanning || this.isResetting) return;
|
|
251
|
+
const n = i.clientX - this.lastX, r = i.clientY - this.lastY;
|
|
252
|
+
this.targetPanX += n, this.targetPanY += r;
|
|
253
|
+
const o = this.getRect(), a = o.width * 0.5 * this.targetScale, h = o.height * 0.5 * this.targetScale;
|
|
254
|
+
this.targetPanX = Math.max(-a, Math.min(a, this.targetPanX)), this.targetPanY = Math.max(-h, Math.min(h, this.targetPanY)), this.lastX = i.clientX, this.lastY = i.clientY, this.requestFrame();
|
|
255
|
+
}, s = (i) => {
|
|
256
|
+
(i.button === 1 || i.button === 0) && (this.isPanning = !1, this.cachedRect = null);
|
|
257
|
+
};
|
|
258
|
+
this.svg.addEventListener("mousedown", t), window.addEventListener("mousemove", e), window.addEventListener("mouseup", s), this.panListeners = [
|
|
259
|
+
{
|
|
260
|
+
target: this.svg,
|
|
261
|
+
type: "mousedown",
|
|
262
|
+
handler: t
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
target: window,
|
|
266
|
+
type: "mousemove",
|
|
267
|
+
handler: e
|
|
268
|
+
},
|
|
269
|
+
{ target: window, type: "mouseup", handler: s }
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
addZoomListeners() {
|
|
273
|
+
if (this.zoomListeners.length > 0) return;
|
|
274
|
+
const t = (e) => {
|
|
275
|
+
if (this.isResetting) return;
|
|
276
|
+
e.preventDefault(), this.updateCachedRect();
|
|
277
|
+
const s = this.cachedRect, i = e.clientX - s.left - s.width * 0.5, n = e.clientY - s.top - s.height * 0.5, r = e.deltaY < 0 ? 1.1 : 1 / 1.1, o = Math.max(
|
|
278
|
+
this.minScale,
|
|
279
|
+
Math.min(this.maxScale, this.targetScale * r)
|
|
280
|
+
), a = o / this.targetScale;
|
|
281
|
+
this.targetPanX = i + (this.targetPanX - i) * a, this.targetPanY = n + (this.targetPanY - n) * a, this.targetScale = o, this.requestFrame();
|
|
282
|
+
};
|
|
283
|
+
this.svg.addEventListener("wheel", t, {
|
|
284
|
+
passive: !1
|
|
285
|
+
}), this.zoomListeners = [
|
|
286
|
+
{ target: this.svg, type: "wheel", handler: t }
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
removePanListeners() {
|
|
290
|
+
for (const { target: t, type: e, handler: s } of this.panListeners)
|
|
291
|
+
t.removeEventListener(e, s);
|
|
292
|
+
this.panListeners = [], this.isPanning = !1, this.cachedRect = null;
|
|
293
|
+
}
|
|
294
|
+
removeZoomListeners() {
|
|
295
|
+
for (const { target: t, type: e, handler: s } of this.zoomListeners)
|
|
296
|
+
t.removeEventListener(e, s);
|
|
297
|
+
this.zoomListeners = [];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
class R {
|
|
301
|
+
root;
|
|
302
|
+
patternRect;
|
|
303
|
+
axesGroup;
|
|
304
|
+
xAxis;
|
|
305
|
+
yAxis;
|
|
306
|
+
constructor(t, e) {
|
|
307
|
+
this.root = document.createElementNS(c, "g"), this.root.classList.add("pluton-background"), t.appendChild(this.root);
|
|
308
|
+
const s = e.viewport(), i = this.computeExtent(s);
|
|
309
|
+
this.patternRect = document.createElementNS(c, "rect"), this.patternRect.classList.add("pluton-graph-paper"), this.patternRect.setAttribute("fill", `url(#${e.defs.graphPaperPatternId})`), this.root.appendChild(this.patternRect), this.updatePatternRect(i);
|
|
310
|
+
const { axes: n, xAxis: r, yAxis: o } = this.createAxes();
|
|
311
|
+
this.axesGroup = n, this.xAxis = r, this.yAxis = o, this.updateAxes(i);
|
|
312
|
+
}
|
|
313
|
+
updateForViewport(t) {
|
|
314
|
+
const e = this.computeExtent(t);
|
|
315
|
+
this.updatePatternRect(e), this.updateAxes(e);
|
|
316
|
+
}
|
|
317
|
+
enableGrid(t) {
|
|
318
|
+
this.patternRect.style.display = t ? "" : "none";
|
|
319
|
+
}
|
|
320
|
+
enableAxes(t) {
|
|
321
|
+
this.axesGroup.style.display = t ? "" : "none";
|
|
322
|
+
}
|
|
323
|
+
computeExtent(t) {
|
|
324
|
+
return Math.sqrt(t.width ** 2 + t.height ** 2) * 3;
|
|
325
|
+
}
|
|
326
|
+
updatePatternRect(t) {
|
|
327
|
+
this.patternRect.setAttribute("x", String(-t / 2)), this.patternRect.setAttribute("y", String(-t / 2)), this.patternRect.setAttribute("width", String(t)), this.patternRect.setAttribute("height", String(t));
|
|
328
|
+
}
|
|
329
|
+
updateAxes(t) {
|
|
330
|
+
this.xAxis.setAttribute("x1", String(-t)), this.xAxis.setAttribute("x2", String(t)), this.xAxis.setAttribute("y1", "0"), this.xAxis.setAttribute("y2", "0"), this.yAxis.setAttribute("x1", "0"), this.yAxis.setAttribute("x2", "0"), this.yAxis.setAttribute("y1", String(-t)), this.yAxis.setAttribute("y2", String(t));
|
|
331
|
+
}
|
|
332
|
+
createAxes() {
|
|
333
|
+
const t = document.createElementNS(c, "g");
|
|
334
|
+
t.classList.add("pluton-axes");
|
|
335
|
+
const e = document.createElementNS(c, "line");
|
|
336
|
+
e.classList.add("pluton-axis", "pluton-axis-x");
|
|
337
|
+
const s = document.createElementNS(c, "line");
|
|
338
|
+
return s.classList.add("pluton-axis", "pluton-axis-y"), t.append(e, s), this.root.appendChild(t), { axes: t, xAxis: e, yAxis: s };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
class y {
|
|
342
|
+
root;
|
|
343
|
+
groups = [];
|
|
344
|
+
constructor(t, e) {
|
|
345
|
+
this.root = document.createElementNS(c, "g"), this.root.classList.add("pluton-layer", e), t.appendChild(this.root);
|
|
346
|
+
}
|
|
347
|
+
group() {
|
|
348
|
+
const t = this.createGroup(this.root);
|
|
349
|
+
return this.groups.push(t), t;
|
|
350
|
+
}
|
|
351
|
+
beginRecord() {
|
|
352
|
+
for (const t of this.groups) t.beginRecord();
|
|
353
|
+
}
|
|
354
|
+
commit() {
|
|
355
|
+
for (const t of this.groups) t.commit();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
class M {
|
|
359
|
+
commands = [];
|
|
360
|
+
/**
|
|
361
|
+
* Move to a position relative to current position
|
|
362
|
+
* @param dx - horizontal offset
|
|
363
|
+
* @param dy - vertical offset
|
|
364
|
+
* @returns this builder for chaining
|
|
365
|
+
*/
|
|
366
|
+
moveTo(t, e) {
|
|
367
|
+
return this.commands.push(`m ${t} ${e}`), this;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Move to an absolute position
|
|
371
|
+
* @param x - absolute x coordinate
|
|
372
|
+
* @param y - absolute y coordinate
|
|
373
|
+
* @returns this builder for chaining
|
|
374
|
+
*/
|
|
375
|
+
moveToAbs(t, e) {
|
|
376
|
+
return this.commands.push(`M ${t} ${e}`), this;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Draw a line relative to current position
|
|
380
|
+
* @param dx - horizontal offset
|
|
381
|
+
* @param dy - vertical offset
|
|
382
|
+
* @returns this builder for chaining
|
|
383
|
+
*/
|
|
384
|
+
lineTo(t, e) {
|
|
385
|
+
return this.commands.push(`l ${t} ${e}`), this;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Draw a line to an absolute position
|
|
389
|
+
* @param x - absolute x coordinate
|
|
390
|
+
* @param y - absolute y coordinate
|
|
391
|
+
* @returns this builder for chaining
|
|
392
|
+
*/
|
|
393
|
+
lineToAbs(t, e) {
|
|
394
|
+
return this.commands.push(`L ${t} ${e}`), this;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Draw an arc relative to current position
|
|
398
|
+
* @param dx - horizontal offset to end point
|
|
399
|
+
* @param dy - vertical offset to end point
|
|
400
|
+
* @param r - arc radius
|
|
401
|
+
* @param clockwise - whether the arc sweeps clockwise
|
|
402
|
+
* @defaultValue false
|
|
403
|
+
* @param largeArc - whether to take the arc > 180°
|
|
404
|
+
* @defaultValue false
|
|
405
|
+
* @returns this builder for chaining
|
|
406
|
+
*/
|
|
407
|
+
arcTo(t, e, s, i = !1, n = !1) {
|
|
408
|
+
if (s <= 0)
|
|
409
|
+
return this.commands.push(`l ${t} ${e}`), this;
|
|
410
|
+
const r = i ? 0 : 1, o = n ? 1 : 0;
|
|
411
|
+
return this.commands.push(`a ${s} ${s} 0 ${o} ${r} ${t} ${e}`), this;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Draw an arc to an absolute position
|
|
415
|
+
* @param x - absolute x coordinate of end point
|
|
416
|
+
* @param y - absolute y coordinate of end point
|
|
417
|
+
* @param r - arc radius
|
|
418
|
+
* @param clockwise - whether the arc sweeps clockwise
|
|
419
|
+
* @defaultValue false
|
|
420
|
+
* @param largeArc - whether to take the arc > 180°
|
|
421
|
+
* @defaultValue false
|
|
422
|
+
* @returns this builder for chaining
|
|
423
|
+
*/
|
|
424
|
+
arcToAbs(t, e, s, i = !1, n = !1) {
|
|
425
|
+
if (s <= 0)
|
|
426
|
+
return this.commands.push(`L ${t} ${e}`), this;
|
|
427
|
+
const r = i ? 0 : 1, o = n ? 1 : 0;
|
|
428
|
+
return this.commands.push(`A ${s} ${s} 0 ${o} ${r} ${t} ${e}`), this;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Close the current path
|
|
432
|
+
* @returns this builder for chaining
|
|
433
|
+
*/
|
|
434
|
+
close() {
|
|
435
|
+
return this.commands.push("z"), this;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Reset the builder
|
|
439
|
+
*/
|
|
440
|
+
reset() {
|
|
441
|
+
this.commands.length = 0;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get the SVG path data string
|
|
445
|
+
* @returns SVG path data
|
|
446
|
+
*/
|
|
447
|
+
toString() {
|
|
448
|
+
return this.commands.join(" ");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
class Y {
|
|
452
|
+
g;
|
|
453
|
+
paths = [];
|
|
454
|
+
// tracks current write position during record cycle
|
|
455
|
+
activeIndex = 0;
|
|
456
|
+
translateX = 0;
|
|
457
|
+
translateY = 0;
|
|
458
|
+
lastTransform = "";
|
|
459
|
+
drawUsage = "dynamic";
|
|
460
|
+
hasCommitted = !1;
|
|
461
|
+
constructor(t) {
|
|
462
|
+
this.g = document.createElementNS(c, "g"), t.appendChild(this.g);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Begin recording phase
|
|
466
|
+
*/
|
|
467
|
+
beginRecord() {
|
|
468
|
+
this.activeIndex = 0;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Commit recorded paths to the DOM
|
|
472
|
+
*/
|
|
473
|
+
commit() {
|
|
474
|
+
if (!(this.drawUsage === "static" && this.hasCommitted)) {
|
|
475
|
+
for (let t = 0; t < this.activeIndex; t++) {
|
|
476
|
+
const e = this.paths[t], s = e.builder.toString();
|
|
477
|
+
s !== e.lastD && (e.path.setAttribute("d", s), e.lastD = s);
|
|
478
|
+
}
|
|
479
|
+
if (this.paths.length > this.activeIndex)
|
|
480
|
+
for (let t = this.paths.length - 1; t >= this.activeIndex; t--)
|
|
481
|
+
this.paths[t].path.remove(), this.paths.pop();
|
|
482
|
+
this.drawUsage === "static" && (this.hasCommitted = !0);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
translate(t, e) {
|
|
486
|
+
this.translateX = t, this.translateY = e, this.applyTransform();
|
|
487
|
+
}
|
|
488
|
+
setDrawUsage(t) {
|
|
489
|
+
this.drawUsage = t, t === "dynamic" && (this.hasCommitted = !1);
|
|
490
|
+
}
|
|
491
|
+
path(t) {
|
|
492
|
+
const e = this.activeIndex++, s = t?.className ?? "";
|
|
493
|
+
if (e < this.paths.length) {
|
|
494
|
+
const r = this.paths[e];
|
|
495
|
+
return r.builder.reset(), s !== r.lastClass && (s ? r.path.setAttribute("class", s) : r.path.removeAttribute("class"), r.lastClass = s), r.builder;
|
|
496
|
+
}
|
|
497
|
+
const i = new M(), n = document.createElementNS(c, "path");
|
|
498
|
+
return s && n.setAttribute("class", s), this.g.appendChild(n), this.paths.push({ builder: i, path: n, lastD: "", lastClass: s }), i;
|
|
499
|
+
}
|
|
500
|
+
clear() {
|
|
501
|
+
this.paths.length = 0, this.activeIndex = 0, this.g.replaceChildren(), this.hasCommitted = !1, this.translateX = 0, this.translateY = 0, this.applyTransform();
|
|
502
|
+
}
|
|
503
|
+
applyTransform() {
|
|
504
|
+
const t = `translate(${this.translateX}, ${this.translateY})`;
|
|
505
|
+
t !== this.lastTransform && (this.g.setAttribute("transform", t), this.lastTransform = t);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
class X extends y {
|
|
509
|
+
unsubscribeBegin;
|
|
510
|
+
unsubscribeEnd;
|
|
511
|
+
constructor(t, e) {
|
|
512
|
+
super(t, "pluton-geometry"), this.unsubscribeBegin = e.on(
|
|
513
|
+
"engine:commit-start",
|
|
514
|
+
() => this.beginRecord()
|
|
515
|
+
), this.unsubscribeEnd = e.on("engine:commit-end", () => this.commit());
|
|
516
|
+
}
|
|
517
|
+
createGroup(t) {
|
|
518
|
+
return new Y(t);
|
|
519
|
+
}
|
|
520
|
+
dispose() {
|
|
521
|
+
this.unsubscribeBegin(), this.unsubscribeEnd();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
class F {
|
|
525
|
+
commands = [];
|
|
526
|
+
filledCommands = [];
|
|
527
|
+
textRecords = [];
|
|
528
|
+
currentX = 0;
|
|
529
|
+
currentY = 0;
|
|
530
|
+
/**
|
|
531
|
+
* Reset builder state
|
|
532
|
+
*/
|
|
533
|
+
reset() {
|
|
534
|
+
this.commands.length = 0, this.textRecords.length = 0, this.filledCommands.length = 0, this.currentX = 0, this.currentY = 0;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Get SVG path data for dimension lines
|
|
538
|
+
* @returns SVG path data string
|
|
539
|
+
*/
|
|
540
|
+
toPathData() {
|
|
541
|
+
return this.commands.join(" ");
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Get SVG path data for filled shapes
|
|
545
|
+
* @returns SVG path data string
|
|
546
|
+
*/
|
|
547
|
+
toFilledPathData() {
|
|
548
|
+
return this.filledCommands.join(" ");
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Get all text records
|
|
552
|
+
* @returns array of text records with positions and alignment
|
|
553
|
+
*/
|
|
554
|
+
getTexts() {
|
|
555
|
+
return this.textRecords;
|
|
556
|
+
}
|
|
557
|
+
// ------------------------------------
|
|
558
|
+
// positioning
|
|
559
|
+
// ------------------------------------
|
|
560
|
+
/**
|
|
561
|
+
* Move to a position relative to current position
|
|
562
|
+
* @param dx - horizontal offset
|
|
563
|
+
* @param dy - vertical offset
|
|
564
|
+
* @returns this builder for chaining
|
|
565
|
+
*/
|
|
566
|
+
moveTo(t, e) {
|
|
567
|
+
return this.currentX += t, this.currentY += e, this.commands.push(`M ${this.currentX} ${this.currentY}`), this;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Move to an absolute position
|
|
571
|
+
* @param x - absolute x coordinate
|
|
572
|
+
* @param y - absolute y coordinate
|
|
573
|
+
* @returns this builder for chaining
|
|
574
|
+
*/
|
|
575
|
+
moveToAbs(t, e) {
|
|
576
|
+
return this.currentX = t, this.currentY = e, this.commands.push(`M ${t} ${e}`), this;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Draw a line to a position relative to current position
|
|
580
|
+
* @param dx - horizontal offset
|
|
581
|
+
* @param dy - vertical offset
|
|
582
|
+
* @returns this builder for chaining
|
|
583
|
+
*/
|
|
584
|
+
lineTo(t, e) {
|
|
585
|
+
return this.currentX += t, this.currentY += e, this.commands.push(`l ${t} ${e}`), this;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Draw a line to an absolute position
|
|
589
|
+
* @param x - absolute x coordinate
|
|
590
|
+
* @param y - absolute y coordinate
|
|
591
|
+
* @returns this builder for chaining
|
|
592
|
+
*/
|
|
593
|
+
lineToAbs(t, e) {
|
|
594
|
+
return this.currentX = t, this.currentY = e, this.commands.push(`L ${t} ${e}`), this;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Draw an arc centered at current position
|
|
598
|
+
* @param r - arc radius
|
|
599
|
+
* @param startAngle - start angle in radians (0 = right, π/2 = up)
|
|
600
|
+
* @param endAngle - end angle in radians
|
|
601
|
+
* @returns this builder for chaining
|
|
602
|
+
*/
|
|
603
|
+
arc(t, e, s) {
|
|
604
|
+
const i = this.currentX, n = this.currentY, r = i + t * Math.cos(e), o = n + t * Math.sin(e), a = i + t * Math.cos(s), h = n + t * Math.sin(s), d = s - e, u = d > 0 ? 1 : 0, g = Math.abs(d) > Math.PI ? 1 : 0;
|
|
605
|
+
this.commands.push(`M ${r} ${o}`);
|
|
606
|
+
const f = a - r, b = h - o;
|
|
607
|
+
return this.commands.push(`a ${t} ${t} 0 ${g} ${u} ${f} ${b}`), this.currentX = a, this.currentY = h, this;
|
|
608
|
+
}
|
|
609
|
+
// ------------------------------------
|
|
610
|
+
// primitives
|
|
611
|
+
// ------------------------------------
|
|
612
|
+
/**
|
|
613
|
+
* Draw an open arrow at current position
|
|
614
|
+
* @param angleRad - direction the arrow points in radians (0 = right, π/2 = up)
|
|
615
|
+
* @returns this builder for chaining
|
|
616
|
+
*/
|
|
617
|
+
arrow(t, e = 8) {
|
|
618
|
+
const s = Math.PI / 4, i = this.currentX, n = this.currentY, r = i - Math.cos(t) * e, o = n - Math.sin(t) * e, a = this.rotateAround(r, o, i, n, +s), h = this.rotateAround(r, o, i, n, -s);
|
|
619
|
+
return this.commands.push(
|
|
620
|
+
`M ${a.x} ${a.y} L ${i} ${n} L ${h.x} ${h.y}`,
|
|
621
|
+
`M ${i} ${n}`
|
|
622
|
+
), this;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Draw a filled arrow at current position
|
|
626
|
+
* @param angleRad - direction the arrow points in radians (0 = right, π/2 = up)
|
|
627
|
+
* @returns this builder for chaining
|
|
628
|
+
*/
|
|
629
|
+
arrowFilled(t, e = 8) {
|
|
630
|
+
const s = Math.PI / 4, i = this.currentX, n = this.currentY, r = i - Math.cos(t) * e, o = n - Math.sin(t) * e, a = this.rotateAround(r, o, i, n, +s), h = this.rotateAround(r, o, i, n, -s);
|
|
631
|
+
return this.filledCommands.push(
|
|
632
|
+
`M ${a.x} ${a.y} L ${i} ${n} L ${h.x} ${h.y} Z`,
|
|
633
|
+
`M ${i} ${n}`
|
|
634
|
+
), this;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Draw a tick mark at current position
|
|
638
|
+
* @param angleRad - orientation angle in radians (dimension line direction)
|
|
639
|
+
* @returns this builder for chaining
|
|
640
|
+
*/
|
|
641
|
+
tick(t, e = 15) {
|
|
642
|
+
const s = this.currentX, i = this.currentY, n = e / 2, r = s, o = i - n, a = s, h = i + n, d = -Math.PI / 4, u = this.rotateAround(
|
|
643
|
+
r,
|
|
644
|
+
o,
|
|
645
|
+
s,
|
|
646
|
+
i,
|
|
647
|
+
d
|
|
648
|
+
), p = this.rotateAround(
|
|
649
|
+
a,
|
|
650
|
+
h,
|
|
651
|
+
s,
|
|
652
|
+
i,
|
|
653
|
+
d
|
|
654
|
+
), g = this.rotateAround(
|
|
655
|
+
r,
|
|
656
|
+
o,
|
|
657
|
+
s,
|
|
658
|
+
i,
|
|
659
|
+
t
|
|
660
|
+
), f = this.rotateAround(
|
|
661
|
+
a,
|
|
662
|
+
h,
|
|
663
|
+
s,
|
|
664
|
+
i,
|
|
665
|
+
t
|
|
666
|
+
), b = this.rotateAround(
|
|
667
|
+
u.x,
|
|
668
|
+
u.y,
|
|
669
|
+
s,
|
|
670
|
+
i,
|
|
671
|
+
t
|
|
672
|
+
), x = this.rotateAround(
|
|
673
|
+
p.x,
|
|
674
|
+
p.y,
|
|
675
|
+
s,
|
|
676
|
+
i,
|
|
677
|
+
t
|
|
678
|
+
);
|
|
679
|
+
return this.commands.push(
|
|
680
|
+
`M ${g.x} ${g.y} L ${f.x} ${f.y}`,
|
|
681
|
+
`M ${b.x} ${b.y} L ${x.x} ${x.y}`,
|
|
682
|
+
`M ${s} ${i}`
|
|
683
|
+
), this;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Draw a center mark (crosshair with center dot) at current position
|
|
687
|
+
* @param size - total size of the crosshair
|
|
688
|
+
* @defaultValue 10
|
|
689
|
+
* @returns this builder for chaining
|
|
690
|
+
*/
|
|
691
|
+
centerMark(t = 10) {
|
|
692
|
+
const e = t / 2, s = this.currentX, i = this.currentY;
|
|
693
|
+
this.commands.push(
|
|
694
|
+
`M ${s - e} ${i} L ${s + e} ${i}`,
|
|
695
|
+
`M ${s} ${i - e} L ${s} ${i + e}`,
|
|
696
|
+
`M ${s} ${i}`
|
|
697
|
+
);
|
|
698
|
+
const n = t / 10;
|
|
699
|
+
return this.filledCommands.push(
|
|
700
|
+
`M ${s - n} ${i} a ${n} ${n} 0 1 0 ${n * 2} 0 a ${n} ${n} 0 1 0 ${-(n * 2)} 0`
|
|
701
|
+
), this;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Rotate a point around a center point by a given angle
|
|
705
|
+
* @param x - point x coordinate
|
|
706
|
+
* @param y - point y coordinate
|
|
707
|
+
* @param centerX - rotation center x
|
|
708
|
+
* @param centerY - rotation center y
|
|
709
|
+
* @param radians - rotation angle in radians
|
|
710
|
+
* @returns rotated point coordinates
|
|
711
|
+
*/
|
|
712
|
+
rotateAround = (t, e, s, i, n) => {
|
|
713
|
+
const r = t - s, o = e - i, a = Math.cos(n), h = Math.sin(n);
|
|
714
|
+
return {
|
|
715
|
+
x: s + r * a - o * h,
|
|
716
|
+
y: i + r * h + o * a
|
|
717
|
+
};
|
|
718
|
+
};
|
|
719
|
+
/**
|
|
720
|
+
* Place text at position relative to current position
|
|
721
|
+
* @param dx - horizontal offset from current position
|
|
722
|
+
* @param dy - vertical offset from current position
|
|
723
|
+
* @param text - text content to display
|
|
724
|
+
* @param align - text alignment (maps to SVG text-anchor: start, middle, end)
|
|
725
|
+
* @defaultValue "middle"
|
|
726
|
+
* @param className - optional class name for the text element
|
|
727
|
+
* @returns this builder for chaining
|
|
728
|
+
*/
|
|
729
|
+
textAt(t, e, s, i = "middle", n) {
|
|
730
|
+
return this.textRecords.push({
|
|
731
|
+
x: this.currentX + t,
|
|
732
|
+
y: this.currentY + e,
|
|
733
|
+
text: s,
|
|
734
|
+
align: i,
|
|
735
|
+
className: n
|
|
736
|
+
}), this;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Place text at absolute position
|
|
740
|
+
* @param x - absolute x coordinate
|
|
741
|
+
* @param y - absolute y coordinate
|
|
742
|
+
* @param text - text content to display
|
|
743
|
+
* @param align - text alignment (maps to SVG text-anchor: start, middle, end)
|
|
744
|
+
* @defaultValue "middle"
|
|
745
|
+
* @param className - optional class name for the text element
|
|
746
|
+
* @returns this builder for chaining
|
|
747
|
+
*/
|
|
748
|
+
textAtAbs(t, e, s, i = "middle", n) {
|
|
749
|
+
return this.textRecords.push({ x: t, y: e, text: s, align: i, className: n }), this;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Close the current path
|
|
753
|
+
* @returns this builder for chaining
|
|
754
|
+
*/
|
|
755
|
+
close() {
|
|
756
|
+
return this.commands.push("z"), this;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
class I {
|
|
760
|
+
g;
|
|
761
|
+
entries = [];
|
|
762
|
+
// tracks current dimension write position during record cycle
|
|
763
|
+
activeIndex = 0;
|
|
764
|
+
translateX = 0;
|
|
765
|
+
translateY = 0;
|
|
766
|
+
lastTransform = "";
|
|
767
|
+
drawUsage = "dynamic";
|
|
768
|
+
hasCommitted = !1;
|
|
769
|
+
constructor(t) {
|
|
770
|
+
this.g = document.createElementNS(c, "g"), t.appendChild(this.g);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Begin recording phase
|
|
774
|
+
*/
|
|
775
|
+
beginRecord() {
|
|
776
|
+
this.activeIndex = 0;
|
|
777
|
+
for (const t of this.entries) t.activeTextCursor = 0;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Commit recorded dimensions to the DOM
|
|
781
|
+
*/
|
|
782
|
+
commit() {
|
|
783
|
+
if (!(this.drawUsage === "static" && this.hasCommitted)) {
|
|
784
|
+
for (let t = 0; t < this.activeIndex; t++) {
|
|
785
|
+
const e = this.entries[t], s = e.builder.toPathData() ?? "";
|
|
786
|
+
s !== e.lastD && (e.path.setAttribute("d", s), e.lastD = s);
|
|
787
|
+
const i = e.builder.toFilledPathData() ?? "";
|
|
788
|
+
i !== e.lastFd && (e.filledPath.setAttribute("d", i), e.lastFd = i);
|
|
789
|
+
const n = e.builder.getTexts();
|
|
790
|
+
for (const r of n) {
|
|
791
|
+
const o = e.activeTextCursor++, a = o < e.texts.length ? e.texts[o] : this.createText(e), h = `translate(${r.x} ${r.y}) scale(1,-1)`;
|
|
792
|
+
r.align !== a.lastAnchor && (a.el.setAttribute("text-anchor", r.align), a.lastAnchor = r.align);
|
|
793
|
+
const d = r.className ?? "";
|
|
794
|
+
d !== a.lastClass && (d ? a.el.setAttribute("class", d) : a.el.removeAttribute("class"), a.lastClass = d), h !== a.lastTransform && (a.el.setAttribute("transform", h), a.lastTransform = h), r.text !== a.lastText && (a.el.textContent = r.text, a.lastText = r.text);
|
|
795
|
+
}
|
|
796
|
+
for (; e.texts.length > e.activeTextCursor; )
|
|
797
|
+
e.texts.pop()?.el.remove();
|
|
798
|
+
}
|
|
799
|
+
if (this.entries.length > this.activeIndex)
|
|
800
|
+
for (let t = this.entries.length - 1; t >= this.activeIndex; t--)
|
|
801
|
+
this.entries[t].root.remove(), this.entries.pop();
|
|
802
|
+
this.drawUsage === "static" && (this.hasCommitted = !0);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
clear() {
|
|
806
|
+
this.entries.length = 0, this.activeIndex = 0, this.g.replaceChildren(), this.hasCommitted = !1, this.translateX = 0, this.translateY = 0, this.applyTransform();
|
|
807
|
+
}
|
|
808
|
+
translate(t, e) {
|
|
809
|
+
this.translateX = t, this.translateY = e, this.applyTransform();
|
|
810
|
+
}
|
|
811
|
+
setDrawUsage(t) {
|
|
812
|
+
this.drawUsage = t, t === "dynamic" && (this.hasCommitted = !1);
|
|
813
|
+
}
|
|
814
|
+
dimension(t) {
|
|
815
|
+
const { className: e = "" } = t ?? {}, s = this.activeIndex++;
|
|
816
|
+
if (s < this.entries.length) {
|
|
817
|
+
const h = this.entries[s];
|
|
818
|
+
return h.builder.reset(), e !== h.lastClass && (h.path.setAttribute("class", `pluton-dim-stroke ${e}`), h.filledPath.setAttribute("class", `pluton-dim-filled ${e}`), h.lastClass = e), h.builder;
|
|
819
|
+
}
|
|
820
|
+
const i = document.createElementNS(c, "g");
|
|
821
|
+
this.g.appendChild(i);
|
|
822
|
+
const n = document.createElementNS(c, "path");
|
|
823
|
+
n.setAttribute("class", `pluton-dim-stroke ${e}`), i.appendChild(n);
|
|
824
|
+
const r = document.createElementNS(c, "path");
|
|
825
|
+
r.setAttribute("class", `pluton-dim-filled ${e}`), i.appendChild(r);
|
|
826
|
+
const o = new F(), a = {
|
|
827
|
+
root: i,
|
|
828
|
+
path: n,
|
|
829
|
+
filledPath: r,
|
|
830
|
+
builder: o,
|
|
831
|
+
lastD: "",
|
|
832
|
+
lastFd: "",
|
|
833
|
+
lastClass: e,
|
|
834
|
+
texts: [],
|
|
835
|
+
activeTextCursor: 0
|
|
836
|
+
};
|
|
837
|
+
return this.entries.push(a), o;
|
|
838
|
+
}
|
|
839
|
+
applyTransform() {
|
|
840
|
+
const t = `translate(${this.translateX}, ${this.translateY})`;
|
|
841
|
+
t !== this.lastTransform && (this.g.setAttribute("transform", t), this.lastTransform = t);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Create a new text element for a dimension entry
|
|
845
|
+
* @param entry - dimension entry to add text to
|
|
846
|
+
* @returns newly created text cache
|
|
847
|
+
*/
|
|
848
|
+
createText(t) {
|
|
849
|
+
const e = document.createElementNS(c, "text");
|
|
850
|
+
e.setAttribute("x", "0"), e.setAttribute("y", "0"), e.setAttribute("dominant-baseline", "middle"), t.root.appendChild(e);
|
|
851
|
+
const s = {
|
|
852
|
+
el: e,
|
|
853
|
+
lastTransform: "",
|
|
854
|
+
lastAnchor: "",
|
|
855
|
+
lastText: "",
|
|
856
|
+
lastClass: ""
|
|
857
|
+
};
|
|
858
|
+
return t.texts.push(s), s;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
class k extends y {
|
|
862
|
+
unsubscribeBegin;
|
|
863
|
+
unsubscribeEnd;
|
|
864
|
+
constructor(t, e) {
|
|
865
|
+
super(t, "pluton-dimensions"), this.unsubscribeBegin = e.on(
|
|
866
|
+
"engine:commit-start",
|
|
867
|
+
() => this.beginRecord()
|
|
868
|
+
), this.unsubscribeEnd = e.on("engine:commit-end", () => this.commit());
|
|
869
|
+
}
|
|
870
|
+
createGroup(t) {
|
|
871
|
+
return new I(t);
|
|
872
|
+
}
|
|
873
|
+
dispose() {
|
|
874
|
+
this.unsubscribeBegin(), this.unsubscribeEnd();
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
class T {
|
|
878
|
+
context;
|
|
879
|
+
backgroundLayer;
|
|
880
|
+
worldLayer;
|
|
881
|
+
geometryLayer;
|
|
882
|
+
dimensionsLayer;
|
|
883
|
+
pencilFilterEnabled = !1;
|
|
884
|
+
background;
|
|
885
|
+
lastCameraTransform = "";
|
|
886
|
+
constructor(t, e) {
|
|
887
|
+
this.context = t;
|
|
888
|
+
const s = t.svg, i = document.createElementNS(c, "g");
|
|
889
|
+
i.classList.add("pluton-background-container"), i.setAttribute(
|
|
890
|
+
"mask",
|
|
891
|
+
`url(#${t.defs.graphPaperMaskId})`
|
|
892
|
+
), s.appendChild(i), this.backgroundLayer = document.createElementNS(c, "g"), this.backgroundLayer.classList.add("pluton-background-layer"), i.appendChild(this.backgroundLayer), this.background = new R(this.backgroundLayer, t);
|
|
893
|
+
const n = document.createElementNS(c, "g");
|
|
894
|
+
n.classList.add("pluton-content-container"), s.appendChild(n), this.worldLayer = document.createElementNS(c, "g"), this.worldLayer.classList.add("pluton-world-layer"), n.appendChild(this.worldLayer), this.geometryLayer = new X(this.worldLayer, e), this.dimensionsLayer = new k(this.worldLayer, e), this.applyFilter(), this.updateTransforms();
|
|
895
|
+
}
|
|
896
|
+
get geometry() {
|
|
897
|
+
return this.geometryLayer;
|
|
898
|
+
}
|
|
899
|
+
get dimensions() {
|
|
900
|
+
return this.dimensionsLayer;
|
|
901
|
+
}
|
|
902
|
+
dispose() {
|
|
903
|
+
this.geometryLayer.dispose(), this.dimensionsLayer.dispose(), this.backgroundLayer.parentElement?.remove(), this.worldLayer.parentElement?.remove();
|
|
904
|
+
}
|
|
905
|
+
updateTransforms() {
|
|
906
|
+
const t = this.context.viewport(), e = this.context.camera(), s = t.x + t.width * 0.5, i = t.y + t.height * 0.5;
|
|
907
|
+
if (e) {
|
|
908
|
+
const n = s + e.panX, r = i + e.panY, o = e.scale, a = `translate(${n}px, ${r}px) scale(${o}, ${-o})`;
|
|
909
|
+
a !== this.lastCameraTransform && (this.backgroundLayer.style.transform = a, this.worldLayer.style.transform = a, this.lastCameraTransform = a);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
onViewportChanged(t) {
|
|
913
|
+
this.background.updateForViewport(t);
|
|
914
|
+
}
|
|
915
|
+
enableGrid(t) {
|
|
916
|
+
this.background.enableGrid(t);
|
|
917
|
+
}
|
|
918
|
+
enableAxes(t) {
|
|
919
|
+
this.background.enableAxes(t);
|
|
920
|
+
}
|
|
921
|
+
enableFilter(t) {
|
|
922
|
+
this.pencilFilterEnabled = t, this.applyFilter();
|
|
923
|
+
}
|
|
924
|
+
applyFilter() {
|
|
925
|
+
const t = this.context.defs.pencilFilterId, e = this.pencilFilterEnabled ? `url(#${t})` : "none";
|
|
926
|
+
this.geometryLayer.root.style.filter = e, this.dimensionsLayer.root.style.filter = e;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
class N {
|
|
930
|
+
drawCallbacks = [];
|
|
931
|
+
paramsState;
|
|
932
|
+
autoRenderEnabled = !1;
|
|
933
|
+
events;
|
|
934
|
+
frameBudget = 1e3 / 60;
|
|
935
|
+
// 60 FPS cap (~16.67ms)
|
|
936
|
+
lastFrameTime = 0;
|
|
937
|
+
renderPending = !1;
|
|
938
|
+
rafId;
|
|
939
|
+
tickFn = null;
|
|
940
|
+
constructor(t, e) {
|
|
941
|
+
this.events = t;
|
|
942
|
+
for (const s of Object.keys(e)) {
|
|
943
|
+
const i = e[s];
|
|
944
|
+
if (i !== null && typeof i == "object")
|
|
945
|
+
throw new Error(
|
|
946
|
+
`Pluton2D params must be flat. "${s}" is an object — nested mutations won't trigger redraws.`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
this.paramsState = new Proxy(e, {
|
|
950
|
+
set: (s, i, n) => {
|
|
951
|
+
if (n !== null && typeof n == "object")
|
|
952
|
+
throw new Error(
|
|
953
|
+
`Pluton2D params must be flat. "${String(i)}" cannot be an object.`
|
|
954
|
+
);
|
|
955
|
+
return s[i] = n, this.autoRenderEnabled && this.scheduleRender(), !0;
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
getParams() {
|
|
960
|
+
return this.paramsState;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Set a per-frame tick function (e.g. camera interpolation).
|
|
964
|
+
* Called every loop iteration. Return true to keep the loop alive.
|
|
965
|
+
*/
|
|
966
|
+
setTickFn(t) {
|
|
967
|
+
this.tickFn = t;
|
|
968
|
+
}
|
|
969
|
+
draw(t) {
|
|
970
|
+
return this.drawCallbacks.push(t), this.autoRenderEnabled || (this.autoRenderEnabled = !0, this.scheduleRender()), () => {
|
|
971
|
+
const e = this.drawCallbacks.indexOf(t);
|
|
972
|
+
e >= 0 && this.drawCallbacks.splice(e, 1);
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
dispose() {
|
|
976
|
+
this.drawCallbacks.length = 0, this.autoRenderEnabled = !1, this.renderPending = !1, this.rafId !== void 0 && (cancelAnimationFrame(this.rafId), this.rafId = void 0);
|
|
977
|
+
}
|
|
978
|
+
/** Mark dirty + keep loop alive. Called on params mutation or resize. */
|
|
979
|
+
scheduleRender() {
|
|
980
|
+
this.renderPending = !0, this.ensureLoop();
|
|
981
|
+
}
|
|
982
|
+
/** Keep loop alive without marking dirty. Called by camera when targets change. */
|
|
983
|
+
requestFrame() {
|
|
984
|
+
this.ensureLoop();
|
|
985
|
+
}
|
|
986
|
+
ensureLoop() {
|
|
987
|
+
this.rafId === void 0 && (this.rafId = requestAnimationFrame((t) => this.loop(t)));
|
|
988
|
+
}
|
|
989
|
+
loop(t) {
|
|
990
|
+
this.rafId = void 0;
|
|
991
|
+
let s = this.tickFn?.() ?? !1;
|
|
992
|
+
if (this.renderPending) {
|
|
993
|
+
const i = t - this.lastFrameTime;
|
|
994
|
+
i >= this.frameBudget ? (this.lastFrameTime = t - i % this.frameBudget, this.renderPending = !1, this.commit()) : s = !0;
|
|
995
|
+
}
|
|
996
|
+
s && this.ensureLoop();
|
|
997
|
+
}
|
|
998
|
+
commit() {
|
|
999
|
+
this.events.emit("engine:commit-start", void 0);
|
|
1000
|
+
for (const t of this.drawCallbacks)
|
|
1001
|
+
t(this.paramsState);
|
|
1002
|
+
this.events.emit("engine:commit-end", void 0);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
class z {
|
|
1006
|
+
context;
|
|
1007
|
+
events;
|
|
1008
|
+
scene;
|
|
1009
|
+
engine;
|
|
1010
|
+
camera;
|
|
1011
|
+
defsEl;
|
|
1012
|
+
/**
|
|
1013
|
+
* Reactive parameters that trigger redraw when modified or reassigned
|
|
1014
|
+
*/
|
|
1015
|
+
params;
|
|
1016
|
+
/**
|
|
1017
|
+
* Create a new Pluton2D instance
|
|
1018
|
+
* @param svg - SVG element to render into
|
|
1019
|
+
* @param initialParams - initial reactive parameters
|
|
1020
|
+
*/
|
|
1021
|
+
constructor(t, e, s) {
|
|
1022
|
+
this.events = new w(), t.classList.add("pluton-root"), this.defsEl = document.createElementNS(c, "defs"), t.insertBefore(this.defsEl, t.firstChild);
|
|
1023
|
+
const i = new L(this.defsEl, s?.filterIntensity);
|
|
1024
|
+
this.engine = new N(this.events, e), this.params = this.engine.getParams(), this.camera = new C(t, this.events, () => this.engine.requestFrame()), this.engine.setTickFn(() => this.camera.tick());
|
|
1025
|
+
let n = null;
|
|
1026
|
+
this.context = new S(t, i, this.camera, () => {
|
|
1027
|
+
n?.();
|
|
1028
|
+
}), i.syncForViewport(this.context.viewport()), this.scene = new T(this.context, this.events), n = () => {
|
|
1029
|
+
this.context.invalidateViewport();
|
|
1030
|
+
const r = this.context.viewport();
|
|
1031
|
+
i.syncForViewport(r), this.scene.onViewportChanged(r), this.scene.updateTransforms(), this.engine.scheduleRender();
|
|
1032
|
+
}, this.events.on("camera:changed", () => {
|
|
1033
|
+
this.scene.updateTransforms();
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Access the geometry layer for drawing shapes
|
|
1038
|
+
*/
|
|
1039
|
+
get geometry() {
|
|
1040
|
+
return this.scene.geometry;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Access the dimensions layer for annotations
|
|
1044
|
+
*/
|
|
1045
|
+
get dimensions() {
|
|
1046
|
+
return this.scene.dimensions;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Register a reactive drawing callback
|
|
1050
|
+
* @param callback - drawing function receiving current params
|
|
1051
|
+
* @returns unsubscribe function to remove the callback
|
|
1052
|
+
*/
|
|
1053
|
+
draw(t) {
|
|
1054
|
+
return this.engine.draw(t);
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Enable or disable the hand-drawn pencil filter effect
|
|
1058
|
+
* @param enabled - whether the filter is active
|
|
1059
|
+
* @defaultValue false
|
|
1060
|
+
*/
|
|
1061
|
+
enableFilter(t) {
|
|
1062
|
+
this.scene.enableFilter(t);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Enable or disable the built-in graph-paper background
|
|
1066
|
+
* @param enabled - whether the graph-paper is visible
|
|
1067
|
+
* @defaultValue true
|
|
1068
|
+
*/
|
|
1069
|
+
enableGrid(t) {
|
|
1070
|
+
this.scene.enableGrid(t);
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Enable or disable the built-in axes
|
|
1074
|
+
* @param enabled - whether the axes are visible
|
|
1075
|
+
* @defaultValue true
|
|
1076
|
+
*/
|
|
1077
|
+
enableAxes(t) {
|
|
1078
|
+
this.scene.enableAxes(t);
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Enable or disable camera panning
|
|
1082
|
+
* @param enabled - whether pan input is active
|
|
1083
|
+
* @defaultValue false
|
|
1084
|
+
*/
|
|
1085
|
+
enablePan(t) {
|
|
1086
|
+
this.camera.enablePan(t);
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Enable or disable camera zooming
|
|
1090
|
+
* @param enabled - whether zoom input is active
|
|
1091
|
+
* @defaultValue false
|
|
1092
|
+
*/
|
|
1093
|
+
enableZoom(t) {
|
|
1094
|
+
this.camera.enableZoom(t);
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Enable or disable the built‑in hatch fill on geometry
|
|
1098
|
+
* @param enabled - whether hatch fill is active
|
|
1099
|
+
* @defaultValue false
|
|
1100
|
+
*/
|
|
1101
|
+
enableHatchFill(t) {
|
|
1102
|
+
t ? this.context.svg.classList.add("pluton-fill-hatch") : this.context.svg.classList.remove("pluton-fill-hatch");
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Reset camera to initial position and zoom
|
|
1106
|
+
*/
|
|
1107
|
+
resetCamera() {
|
|
1108
|
+
this.camera.reset();
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Clean up resources and remove event listeners
|
|
1112
|
+
*/
|
|
1113
|
+
dispose() {
|
|
1114
|
+
this.camera.dispose(), this.scene.dispose(), this.engine.dispose(), this.context.dispose(), this.events.clear(), this.defsEl.remove(), this.context.svg.classList.remove("pluton-root");
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
export {
|
|
1118
|
+
z as Pluton2D
|
|
1119
|
+
};
|