-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathEmbeded_2D_Sketcher.html
More file actions
440 lines (401 loc) · 16.9 KB
/
Embeded_2D_Sketcher.html
File metadata and controls
440 lines (401 loc) · 16.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>BREP API Example: Embeded 2D Sketcher</title>
<link rel="stylesheet" href="./example.css" />
</head>
<body>
<main>
<header class="example-header">
<div>
<h1>Embeded 2D Sketcher</h1>
<p class="lead">
Mounts the iframe-based <code>Sketcher2DEmbed</code> API and exports SVG, DXF, and per-curve 3D polylines from the host page.
</p>
</div>
<nav class="example-nav">
<a href="./index.html">All Examples</a>
<a href="../index.html">BREP CAD App</a>
<a href="https://github.com/mmiscool/BREP/blob/master/apiExamples/Embeded_2D_Sketcher.html" target="_blank" rel="noopener noreferrer">Source on GitHub</a>
</nav>
</header>
<div class="row">
<button id="btn-create">Create Sketcher Iframe</button>
<button id="btn-destroy" disabled>Destroy Sketcher</button>
<button id="btn-apply-css" disabled>Apply CSS</button>
<button id="btn-apply-theme" disabled>Apply Theme</button>
<button id="btn-export-svg" disabled>Export SVG Paths</button>
<button id="btn-export-dxf" disabled>Export DXF</button>
<button id="btn-download-dxf" disabled>Download DXF</button>
<button id="btn-export-polylines" disabled>Export 3D Polylines</button>
</div>
<div class="control-grid">
<div>
<label for="geometry-color">Geometry Color</label>
<input id="geometry-color" type="color" value="#d9f99d" />
</div>
<div>
<label for="point-color">Point Color</label>
<input id="point-color" type="color" value="#93c5fd" />
</div>
<div>
<label for="constraint-color">Constraint Color</label>
<input id="constraint-color" type="color" value="#60a5fa" />
</div>
<div>
<label for="background-color">Background Color</label>
<input id="background-color" type="color" value="#ffffff" />
</div>
<div>
<label for="point-size-px">Point Size (px)</label>
<input id="point-size-px" type="number" value="10" min="1" max="128" step="1" />
</div>
<div>
<label for="curve-thickness-px">Curve Thickness (px)</label>
<input id="curve-thickness-px" type="number" value="2" min="0.5" max="48" step="0.5" />
</div>
<label class="toggle" for="sidebar-expanded">
<input id="sidebar-expanded" type="checkbox" />
Sidebar Expanded
</label>
<label class="toggle" for="grid-visible">
<input id="grid-visible" type="checkbox" checked />
Show Grid
</label>
<div>
<label for="grid-spacing">Grid Spacing</label>
<input id="grid-spacing" type="number" value="1" min="0.0001" step="0.1" />
</div>
<div>
<label for="curve-resolution">Curve Resolution</label>
<input id="curve-resolution" type="number" value="64" min="3" max="2048" step="1" />
</div>
</div>
<label for="css-input">Custom CSS injected into iframe:</label>
<textarea id="css-input">.sketch-dims .dim-label {
background: rgba(10, 18, 32, 0.92) !important;
border-color: #4f8cff !important;
color: #dce9ff !important;
}
#main-toolbar {
background: rgba(12, 16, 24, 0.92) !important;
}
#main-toolbar .mtb-btn.is-active {
border-color: #0aa36c !important;
color: #e8fff6 !important;
}</textarea>
<div id="sketch-status">Sketcher not created.</div>
<div id="sketch-host"></div>
<pre id="event-output">(No sketch events yet)</pre>
<div id="svg-preview"></div>
<pre id="path-output">(No SVG exported yet)</pre>
<pre id="dxf-output">(No DXF exported yet)</pre>
<pre id="polyline-output">(No 3D polylines exported yet)</pre>
</main>
<script type="module">
import { Sketcher2DEmbed } from '../dist-kernel/brep-kernel.js';
const btnCreate = document.getElementById('btn-create');
const btnDestroy = document.getElementById('btn-destroy');
const btnApplyCss = document.getElementById('btn-apply-css');
const btnApplyTheme = document.getElementById('btn-apply-theme');
const btnExportSvg = document.getElementById('btn-export-svg');
const btnExportDxf = document.getElementById('btn-export-dxf');
const btnDownloadDxf = document.getElementById('btn-download-dxf');
const btnExportPolylines = document.getElementById('btn-export-polylines');
const geometryColorInput = document.getElementById('geometry-color');
const pointColorInput = document.getElementById('point-color');
const constraintColorInput = document.getElementById('constraint-color');
const backgroundColorInput = document.getElementById('background-color');
const pointSizePxInput = document.getElementById('point-size-px');
const curveThicknessPxInput = document.getElementById('curve-thickness-px');
const sidebarExpandedInput = document.getElementById('sidebar-expanded');
const gridVisibleInput = document.getElementById('grid-visible');
const gridSpacingInput = document.getElementById('grid-spacing');
const curveResolutionInput = document.getElementById('curve-resolution');
const cssInput = document.getElementById('css-input');
const sketchStatusEl = document.getElementById('sketch-status');
const sketchHost = document.getElementById('sketch-host');
const eventOutput = document.getElementById('event-output');
const svgPreview = document.getElementById('svg-preview');
const pathOutput = document.getElementById('path-output');
const dxfOutput = document.getElementById('dxf-output');
const polylineOutput = document.getElementById('polyline-output');
let sketcher = null;
let latestSketch = null;
let changeCount = 0;
let finishCount = 0;
let cancelCount = 0;
let dxfDownloadUrl = null;
const maxEventLines = 6;
const setSketchStatus = (text) => {
sketchStatusEl.textContent = text;
};
const revokeDxfDownload = () => {
if (dxfDownloadUrl) {
URL.revokeObjectURL(dxfDownloadUrl);
dxfDownloadUrl = null;
}
};
const setDxfDownload = (text) => {
revokeDxfDownload();
if (typeof text !== 'string' || !text.length) {
btnDownloadDxf.disabled = true;
return;
}
const blob = new Blob([text], { type: 'application/dxf' });
dxfDownloadUrl = URL.createObjectURL(blob);
btnDownloadDxf.disabled = false;
};
const setSketchButtons = (mounted) => {
btnCreate.disabled = mounted;
btnDestroy.disabled = !mounted;
btnApplyCss.disabled = !mounted;
btnApplyTheme.disabled = !mounted;
btnExportSvg.disabled = !mounted;
btnExportDxf.disabled = !mounted;
btnExportPolylines.disabled = !mounted;
btnDownloadDxf.disabled = !mounted || !dxfDownloadUrl;
};
const pushEvent = (label, payload = null) => {
const now = new Date().toLocaleTimeString();
const line = payload == null
? `[${now}] ${label}`
: `[${now}] ${label} ${JSON.stringify(payload)}`;
const existing = eventOutput.textContent === '(No sketch events yet)'
? []
: eventOutput.textContent.split('\n').filter(Boolean);
const next = [line, ...existing].slice(0, maxEventLines);
eventOutput.textContent = next.length ? next.join('\n') : '(No sketch events yet)';
};
const currentTheme = () => ({
geometryColor: geometryColorInput.value,
pointColor: pointColorInput.value,
constraintColor: constraintColorInput.value,
backgroundColor: backgroundColorInput.value,
pointSizePx: Math.max(1, Number(pointSizePxInput.value) || 10),
curveThicknessPx: Math.max(0.5, Number(curveThicknessPxInput.value) || 2),
});
const currentGridSpacing = () => {
const parsed = Number(gridSpacingInput.value);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
};
const currentCurveResolution = () => {
const parsed = Number(curveResolutionInput.value);
if (!Number.isFinite(parsed)) return 64;
return Math.max(3, Math.min(2048, Math.floor(parsed)));
};
const attachSketcher = async () => {
if (sketcher) return;
sketcher = new Sketcher2DEmbed({
cssText: cssInput.value,
...currentTheme(),
sidebarExpanded: sidebarExpandedInput.checked,
gridVisible: gridVisibleInput.checked,
gridSpacing: currentGridSpacing(),
onChange: (sketch) => {
latestSketch = sketch;
changeCount += 1;
const pathCount = Array.isArray(sketch?.geometries) ? sketch.geometries.length : 0;
setSketchStatus(`Sketch updated (${changeCount}). Geometries: ${pathCount}`);
pushEvent('onChange', { geometries: pathCount });
},
onFinished: (sketch) => {
latestSketch = sketch;
finishCount += 1;
const pathCount = Array.isArray(sketch?.geometries) ? sketch.geometries.length : 0;
setSketchStatus(`Sketch finished (${finishCount}). Geometries: ${pathCount}`);
pushEvent('onFinished', { geometries: pathCount });
exportSvg().catch((error) => {
console.error(error);
setSketchStatus(`Failed to export SVG after Finish: ${error?.message || String(error)}`);
});
},
onCancelled: () => {
cancelCount += 1;
setSketchStatus(`Sketch cancelled (${cancelCount}).`);
pushEvent('onCancelled');
},
});
await sketcher.mount(sketchHost);
latestSketch = await sketcher.getSketch();
setSketchButtons(true);
setSketchStatus('Sketcher iframe mounted. Draw geometry and run any export action.');
};
const detachSketcher = async () => {
if (!sketcher) return;
await sketcher.destroy();
sketcher = null;
latestSketch = null;
changeCount = 0;
finishCount = 0;
cancelCount = 0;
setSketchButtons(false);
setSketchStatus('Sketcher destroyed.');
eventOutput.textContent = '(No sketch events yet)';
svgPreview.innerHTML = '';
pathOutput.textContent = '(No SVG exported yet)';
dxfOutput.textContent = '(No DXF exported yet)';
polylineOutput.textContent = '(No 3D polylines exported yet)';
revokeDxfDownload();
};
const applySketchCss = async () => {
if (!sketcher) return;
await sketcher.setCss(cssInput.value);
setSketchStatus('Custom CSS applied to iframe.');
};
const applySketchTheme = async () => {
if (!sketcher) return;
await sketcher.setTheme(currentTheme());
await sketcher.setSidebarExpanded(sidebarExpandedInput.checked);
if (typeof sketcher.setGrid === 'function') {
await sketcher.setGrid({
visible: gridVisibleInput.checked,
spacing: currentGridSpacing(),
});
} else {
if (typeof sketcher.setGridVisible === 'function') {
await sketcher.setGridVisible(gridVisibleInput.checked);
}
if (typeof sketcher.setGridSpacing === 'function') {
await sketcher.setGridSpacing(currentGridSpacing());
}
}
setSketchStatus('Theme + sidebar + grid state applied to iframe.');
};
const exportSvg = async () => {
if (!sketcher) return;
const result = await sketcher.exportSVG({
flipY: true,
precision: 3,
stroke: '#111111',
strokeWidth: 1.5,
fill: 'none',
padding: 12,
curveResolution: currentCurveResolution(),
});
latestSketch = await sketcher.getSketch({ preferCached: true });
svgPreview.innerHTML = result.svg;
pathOutput.textContent = result.paths.length
? result.paths.map((row) => `id=${row.id} type=${row.type} d="${row.d}"`).join('\n')
: '(No sketch geometry to export)';
setSketchStatus(`Exported ${result.paths.length} SVG paths.`);
svgPreview.scrollIntoView({ behavior: 'smooth', block: 'start' });
};
const exportDxf = async () => {
if (!sketcher) return;
const result = await sketcher.exportDXF({
units: 'mm',
curveResolution: currentCurveResolution(),
includeConstruction: false,
});
dxfOutput.textContent = result?.dxf || '(No DXF payload returned)';
setDxfDownload(result?.dxf || '');
setSketchStatus(`Exported DXF with ${result?.polylines?.length || 0} polylines.`);
};
const export3DPolylines = async () => {
if (!sketcher) return;
const result = await sketcher.export3DPolylines({
curveResolution: currentCurveResolution(),
includeConstruction: false,
origin: [0, 0, 0],
xAxis: [1, 0, 0],
yAxis: [0, 1, 0],
});
polylineOutput.textContent = JSON.stringify(result, null, 2);
setSketchStatus(`Exported ${result?.polylines?.length || 0} 3D polylines.`);
};
btnCreate.addEventListener('click', () => {
attachSketcher().catch((error) => {
console.error(error);
setSketchStatus(`Failed to create sketcher: ${error?.message || String(error)}`);
});
});
btnDestroy.addEventListener('click', () => {
detachSketcher().catch((error) => {
console.error(error);
setSketchStatus(`Failed to destroy sketcher: ${error?.message || String(error)}`);
});
});
btnApplyCss.addEventListener('click', () => {
applySketchCss().catch((error) => {
console.error(error);
setSketchStatus(`Failed to apply CSS: ${error?.message || String(error)}`);
});
});
btnApplyTheme.addEventListener('click', () => {
applySketchTheme().catch((error) => {
console.error(error);
setSketchStatus(`Failed to apply theme: ${error?.message || String(error)}`);
});
});
btnExportSvg.addEventListener('click', () => {
exportSvg().catch((error) => {
console.error(error);
setSketchStatus(`Failed to export SVG: ${error?.message || String(error)}`);
});
});
btnExportDxf.addEventListener('click', () => {
exportDxf().catch((error) => {
console.error(error);
setSketchStatus(`Failed to export DXF: ${error?.message || String(error)}`);
});
});
btnDownloadDxf.addEventListener('click', () => {
if (!dxfDownloadUrl) return;
const link = document.createElement('a');
link.href = dxfDownloadUrl;
link.download = 'sketch-export.dxf';
link.click();
});
btnExportPolylines.addEventListener('click', () => {
export3DPolylines().catch((error) => {
console.error(error);
setSketchStatus(`Failed to export 3D polylines: ${error?.message || String(error)}`);
});
});
[geometryColorInput, pointColorInput, constraintColorInput, backgroundColorInput, pointSizePxInput, curveThicknessPxInput].forEach((input) => {
input.addEventListener('input', () => {
if (!sketcher) return;
applySketchTheme().catch((error) => {
console.error(error);
setSketchStatus(`Failed to apply theme: ${error?.message || String(error)}`);
});
});
});
sidebarExpandedInput.addEventListener('change', () => {
if (!sketcher) return;
applySketchTheme().catch((error) => {
console.error(error);
setSketchStatus(`Failed to apply sidebar state: ${error?.message || String(error)}`);
});
});
gridVisibleInput.addEventListener('change', () => {
if (!sketcher) return;
applySketchTheme().catch((error) => {
console.error(error);
setSketchStatus(`Failed to apply grid visibility: ${error?.message || String(error)}`);
});
});
gridSpacingInput.addEventListener('input', () => {
if (!sketcher) return;
applySketchTheme().catch((error) => {
console.error(error);
setSketchStatus(`Failed to apply grid spacing: ${error?.message || String(error)}`);
});
});
window.addEventListener('beforeunload', () => {
if (sketcher) {
sketcher.destroy().catch(() => {});
}
revokeDxfDownload();
});
setSketchButtons(false);
if (latestSketch && !Array.isArray(latestSketch?.geometries)) {
console.warn('Unexpected sketch payload shape', latestSketch);
}
</script>
</body>
</html>