@xom11/whiteboard 0.24.0 → 0.24.2
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/README.md +1 -1
- package/dist/{ExcalidrawWithMenus-KBLDWPM2.mjs → ExcalidrawWithMenus-WENZRYYE.mjs} +2 -2
- package/dist/{ExcalidrawWithMenus-KBLDWPM2.mjs.map → ExcalidrawWithMenus-WENZRYYE.mjs.map} +1 -1
- package/dist/ai.d.mts +616 -0
- package/dist/ai.d.ts +616 -0
- package/dist/ai.js +1036 -0
- package/dist/ai.js.map +1 -0
- package/dist/ai.mjs +999 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/catalog.json +4 -4
- package/dist/{chunk-TOOHCAWP.mjs → chunk-2WF6KIGF.mjs} +5 -4
- package/dist/chunk-2WF6KIGF.mjs.map +1 -0
- package/dist/{chunk-2SKXRBGS.mjs → chunk-45CGKJ7S.mjs} +4 -4
- package/dist/{chunk-2SKXRBGS.mjs.map → chunk-45CGKJ7S.mjs.map} +1 -1
- package/dist/{chunk-O6QTYAKE.mjs → chunk-4DS3MKID.mjs} +4 -4
- package/dist/{chunk-O6QTYAKE.mjs.map → chunk-4DS3MKID.mjs.map} +1 -1
- package/dist/chunk-73Q7ADVL.mjs +35 -0
- package/dist/chunk-73Q7ADVL.mjs.map +1 -0
- package/dist/{chunk-XVSO7FBM.mjs → chunk-7WQXXEVR.mjs} +5 -5
- package/dist/{chunk-XVSO7FBM.mjs.map → chunk-7WQXXEVR.mjs.map} +1 -1
- package/dist/{chunk-VBJLUHCY.mjs → chunk-AZIARTGX.mjs} +3 -3
- package/dist/{chunk-VBJLUHCY.mjs.map → chunk-AZIARTGX.mjs.map} +1 -1
- package/dist/{chunk-RBUILBX3.mjs → chunk-BEZSQKPY.mjs} +6 -5
- package/dist/chunk-BEZSQKPY.mjs.map +1 -0
- package/dist/{chunk-6XUPIGVD.mjs → chunk-BKSXPNPQ.mjs} +4 -123
- package/dist/chunk-BKSXPNPQ.mjs.map +1 -0
- package/dist/{chunk-FZY33J6Z.mjs → chunk-CGZZO4BX.mjs} +7 -6
- package/dist/chunk-CGZZO4BX.mjs.map +1 -0
- package/dist/{chunk-RD34F5PM.mjs → chunk-ICR4CVOE.mjs} +2 -2
- package/dist/chunk-ICR4CVOE.mjs.map +1 -0
- package/dist/{chunk-7WG2KDRF.mjs → chunk-KRC2XOIG.mjs} +3 -3
- package/dist/{chunk-7WG2KDRF.mjs.map → chunk-KRC2XOIG.mjs.map} +1 -1
- package/dist/{chunk-33PEN2WC.mjs → chunk-LVNCYP4J.mjs} +6 -6
- package/dist/{chunk-33PEN2WC.mjs.map → chunk-LVNCYP4J.mjs.map} +1 -1
- package/dist/{chunk-TQYQVXNW.mjs → chunk-QGNU34T7.mjs} +2 -2
- package/dist/chunk-QGNU34T7.mjs.map +1 -0
- package/dist/{chunk-RXOFO64U.mjs → chunk-SGFJLHHG.mjs} +3 -3
- package/dist/{chunk-RXOFO64U.mjs.map → chunk-SGFJLHHG.mjs.map} +1 -1
- package/dist/chunk-WM2VDYQA.mjs +1771 -0
- package/dist/chunk-WM2VDYQA.mjs.map +1 -0
- package/dist/{chunk-VRWZILTG.mjs → chunk-ZBJBQKJ2.mjs} +128 -3
- package/dist/chunk-ZBJBQKJ2.mjs.map +1 -0
- package/dist/geometry-2d.d.mts +2 -1
- package/dist/geometry-2d.d.ts +2 -1
- package/dist/geometry-2d.js +2056 -100
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +7 -6
- package/dist/geometry-3d.d.mts +2 -1
- package/dist/geometry-3d.d.ts +2 -1
- package/dist/geometry-3d.js +2051 -15
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +6 -5
- package/dist/graph-2d.d.mts +2 -1
- package/dist/graph-2d.d.ts +2 -1
- package/dist/graph-2d.js +1938 -8
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +9 -8
- package/dist/{host-EVJT3LIF.mjs → host-EPZCNFLH.mjs} +31 -29
- package/dist/host-EPZCNFLH.mjs.map +1 -0
- package/dist/{host-3N4E4KJH.mjs → host-LKCMYEAV.mjs} +12 -11
- package/dist/host-LKCMYEAV.mjs.map +1 -0
- package/dist/{host-6SNSZ332.mjs → host-QS2EOTRJ.mjs} +3 -3
- package/dist/{host-6SNSZ332.mjs.map → host-QS2EOTRJ.mjs.map} +1 -1
- package/dist/{host-HN4X3TBC.mjs → host-ZIQ77W33.mjs} +9 -8
- package/dist/host-ZIQ77W33.mjs.map +1 -0
- package/dist/index.d.mts +4 -616
- package/dist/index.d.ts +4 -616
- package/dist/index.js +2095 -1171
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +25 -1025
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +2 -1
- package/dist/latex.d.ts +2 -1
- package/dist/latex.js.map +1 -1
- package/dist/latex.mjs +1 -1
- package/dist/render-SA4JTOW3.mjs +8 -0
- package/dist/{render-OCVGDKK6.mjs.map → render-SA4JTOW3.mjs.map} +1 -1
- package/dist/serialize-JAVOU22E.mjs +7 -0
- package/dist/{serialize-GKN6OVPM.mjs.map → serialize-JAVOU22E.mjs.map} +1 -1
- package/dist/{types-rA4slL08.d.mts → types-Crbefnfe.d.ts} +2 -49
- package/dist/types-DxlMPh-6.d.mts +49 -0
- package/dist/types-DxlMPh-6.d.ts +49 -0
- package/dist/{types-rA4slL08.d.ts → types-vtvyKGAA.d.mts} +2 -49
- package/package.json +16 -2
- package/dist/chunk-3KBL77M6.mjs +0 -127
- package/dist/chunk-3KBL77M6.mjs.map +0 -1
- package/dist/chunk-6XUPIGVD.mjs.map +0 -1
- package/dist/chunk-FZY33J6Z.mjs.map +0 -1
- package/dist/chunk-RBUILBX3.mjs.map +0 -1
- package/dist/chunk-RD34F5PM.mjs.map +0 -1
- package/dist/chunk-TOOHCAWP.mjs.map +0 -1
- package/dist/chunk-TQYQVXNW.mjs.map +0 -1
- package/dist/chunk-VRWZILTG.mjs.map +0 -1
- package/dist/host-3N4E4KJH.mjs.map +0 -1
- package/dist/host-EVJT3LIF.mjs.map +0 -1
- package/dist/host-HN4X3TBC.mjs.map +0 -1
- package/dist/render-OCVGDKK6.mjs +0 -8
- package/dist/serialize-GKN6OVPM.mjs +0 -6
package/dist/index.js
CHANGED
|
@@ -8,11 +8,6 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
8
8
|
var reactDom = require('react-dom');
|
|
9
9
|
var excalidraw = require('@excalidraw/excalidraw');
|
|
10
10
|
require('@excalidraw/excalidraw/index.css');
|
|
11
|
-
var zod = require('zod');
|
|
12
|
-
var Anthropic = require('@anthropic-ai/sdk');
|
|
13
|
-
var zodToJsonSchema = require('zod-to-json-schema');
|
|
14
|
-
|
|
15
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
11
|
|
|
17
12
|
function _interopNamespace(e) {
|
|
18
13
|
if (e && e.__esModule) return e;
|
|
@@ -33,7 +28,6 @@ function _interopNamespace(e) {
|
|
|
33
28
|
}
|
|
34
29
|
|
|
35
30
|
var React18__namespace = /*#__PURE__*/_interopNamespace(React18);
|
|
36
|
-
var Anthropic__default = /*#__PURE__*/_interopDefault(Anthropic);
|
|
37
31
|
|
|
38
32
|
var __defProp = Object.defineProperty;
|
|
39
33
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -82,10 +76,16 @@ var init_types = __esm({
|
|
|
82
76
|
});
|
|
83
77
|
|
|
84
78
|
// src/core/scene/registry.ts
|
|
79
|
+
function registerKind(def22) {
|
|
80
|
+
if (registry.has(def22.type)) {
|
|
81
|
+
console.warn(`[scene] kind "${def22.type}" \u0111\xE3 \u0111\u01B0\u1EE3c \u0111\u0103ng k\xFD \u2014 ghi \u0111\xE8 \u0111\u1ECBnh ngh\u0129a c\u0169`);
|
|
82
|
+
}
|
|
83
|
+
registry.set(def22.type, def22);
|
|
84
|
+
}
|
|
85
85
|
function getKind(type) {
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
88
|
-
return
|
|
86
|
+
const def22 = registry.get(type);
|
|
87
|
+
if (!def22) throw new Error(`[scene] unknown kind: ${type}`);
|
|
88
|
+
return def22;
|
|
89
89
|
}
|
|
90
90
|
var registry;
|
|
91
91
|
var init_registry = __esm({
|
|
@@ -109,7 +109,7 @@ function collectDependents(state, rootId) {
|
|
|
109
109
|
continue;
|
|
110
110
|
}
|
|
111
111
|
const refs = kindDef.dependsOn(obj.attrs);
|
|
112
|
-
if (refs.some((
|
|
112
|
+
if (refs.some((r) => dependents.has(r))) {
|
|
113
113
|
dependents.add(obj.id);
|
|
114
114
|
grew = true;
|
|
115
115
|
}
|
|
@@ -279,98 +279,1652 @@ function createStore(initial, options = {}) {
|
|
|
279
279
|
} finally {
|
|
280
280
|
suspendHistory = prevSuspend;
|
|
281
281
|
}
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
var HISTORY_DEFAULT, UNDO_ACTION, REDO_ACTION;
|
|
286
|
-
var init_store = __esm({
|
|
287
|
-
"src/core/scene/store.ts"() {
|
|
288
|
-
init_reducer();
|
|
289
|
-
HISTORY_DEFAULT = 100;
|
|
290
|
-
UNDO_ACTION = { type: "TRANSACTION", payload: { actions: [] } };
|
|
291
|
-
REDO_ACTION = { type: "TRANSACTION", payload: { actions: [] } };
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
var HISTORY_DEFAULT, UNDO_ACTION, REDO_ACTION;
|
|
286
|
+
var init_store = __esm({
|
|
287
|
+
"src/core/scene/store.ts"() {
|
|
288
|
+
init_reducer();
|
|
289
|
+
HISTORY_DEFAULT = 100;
|
|
290
|
+
UNDO_ACTION = { type: "TRANSACTION", payload: { actions: [] } };
|
|
291
|
+
REDO_ACTION = { type: "TRANSACTION", payload: { actions: [] } };
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// src/core/scene/selectors.ts
|
|
296
|
+
function listObjects(state) {
|
|
297
|
+
return state.order.map((id) => state.objects[id]).filter((o) => o !== void 0);
|
|
298
|
+
}
|
|
299
|
+
function nextLabel(state, kind) {
|
|
300
|
+
const kinds = LABEL_GROUPS[kind] ?? [kind];
|
|
301
|
+
const used = new Set(
|
|
302
|
+
listObjects(state).filter((o) => kinds.includes(o.kind)).map((o) => o.label)
|
|
303
|
+
);
|
|
304
|
+
for (const c of ALPHABET) if (!used.has(c)) return c;
|
|
305
|
+
let idx = 1;
|
|
306
|
+
while (true) {
|
|
307
|
+
for (const c of ALPHABET) {
|
|
308
|
+
const candidate = `${c}${idx}`;
|
|
309
|
+
if (!used.has(candidate)) return candidate;
|
|
310
|
+
}
|
|
311
|
+
idx += 1;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
var ALPHABET, LABEL_GROUPS;
|
|
315
|
+
var init_selectors = __esm({
|
|
316
|
+
"src/core/scene/selectors.ts"() {
|
|
317
|
+
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
318
|
+
LABEL_GROUPS = {
|
|
319
|
+
point: ["point", "intersection"],
|
|
320
|
+
intersection: ["point", "intersection"]
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
function useEditorState(opts) {
|
|
325
|
+
const { store, initialState, onHistoryChange, bindKeyboardShortcuts = true } = opts;
|
|
326
|
+
const onHistoryChangeRef = React18__namespace.useRef(onHistoryChange);
|
|
327
|
+
onHistoryChangeRef.current = onHistoryChange;
|
|
328
|
+
React18__namespace.useEffect(() => {
|
|
329
|
+
if (initialState?.state) {
|
|
330
|
+
const loaded = initialState.state;
|
|
331
|
+
store.withoutHistory(() => {
|
|
332
|
+
store.dispatch({ type: "LOAD", payload: { state: loaded } });
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}, []);
|
|
336
|
+
React18__namespace.useEffect(() => {
|
|
337
|
+
onHistoryChangeRef.current?.(store.canUndo(), store.canRedo());
|
|
338
|
+
const unsub = store.subscribe(() => {
|
|
339
|
+
onHistoryChangeRef.current?.(store.canUndo(), store.canRedo());
|
|
340
|
+
});
|
|
341
|
+
return unsub;
|
|
342
|
+
}, [store]);
|
|
343
|
+
React18__namespace.useEffect(() => {
|
|
344
|
+
if (!bindKeyboardShortcuts) return;
|
|
345
|
+
const onKey = (e) => {
|
|
346
|
+
const ae = document.activeElement;
|
|
347
|
+
const inField = !!(ae && (ae.tagName === "INPUT" || ae.tagName === "TEXTAREA" || ae.isContentEditable));
|
|
348
|
+
if (inField) return;
|
|
349
|
+
if (!(e.metaKey || e.ctrlKey)) return;
|
|
350
|
+
const key = e.key.toLowerCase();
|
|
351
|
+
if (key === "z" && !e.shiftKey) {
|
|
352
|
+
e.preventDefault();
|
|
353
|
+
e.stopPropagation();
|
|
354
|
+
store.undo();
|
|
355
|
+
} else if (key === "z" && e.shiftKey || key === "y" && !e.shiftKey) {
|
|
356
|
+
e.preventDefault();
|
|
357
|
+
e.stopPropagation();
|
|
358
|
+
store.redo();
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
window.addEventListener("keydown", onKey, { capture: true });
|
|
362
|
+
return () => window.removeEventListener("keydown", onKey, { capture: true });
|
|
363
|
+
}, [store, bindKeyboardShortcuts]);
|
|
364
|
+
}
|
|
365
|
+
var init_useEditorState = __esm({
|
|
366
|
+
"src/core/scene/hooks/useEditorState.ts"() {
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// src/core/scene/hooks/index.ts
|
|
371
|
+
var init_hooks = __esm({
|
|
372
|
+
"src/core/scene/hooks/index.ts"() {
|
|
373
|
+
init_useEditorState();
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// src/core/scene/kinds/3d-constraint.ts
|
|
378
|
+
function constraintRefs(c) {
|
|
379
|
+
switch (c.kind) {
|
|
380
|
+
case "onPlane":
|
|
381
|
+
return [c.planeId];
|
|
382
|
+
case "onLine":
|
|
383
|
+
return [c.lineId];
|
|
384
|
+
case "onPolygon":
|
|
385
|
+
return [c.polygonId];
|
|
386
|
+
case "onSphere":
|
|
387
|
+
return [c.sphereId];
|
|
388
|
+
default:
|
|
389
|
+
return [];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
var init_d_constraint = __esm({
|
|
393
|
+
"src/core/scene/kinds/3d-constraint.ts"() {
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// src/core/scene/kinds/point3d.ts
|
|
398
|
+
var def;
|
|
399
|
+
var init_point3d = __esm({
|
|
400
|
+
"src/core/scene/kinds/point3d.ts"() {
|
|
401
|
+
init_registry();
|
|
402
|
+
init_d_constraint();
|
|
403
|
+
def = {
|
|
404
|
+
type: "point3d",
|
|
405
|
+
schemaVersion: 1,
|
|
406
|
+
migrate: {},
|
|
407
|
+
validate: (a) => {
|
|
408
|
+
if (!a || !a.constraint || !a.constraint.kind) {
|
|
409
|
+
throw new Error("point3d: constraint required");
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
dependsOn: (a) => constraintRefs(a.constraint),
|
|
413
|
+
measure: (obj) => {
|
|
414
|
+
const c = obj.attrs.constraint;
|
|
415
|
+
if (c.kind === "free") {
|
|
416
|
+
return [
|
|
417
|
+
{ label: "x", value: c.x },
|
|
418
|
+
{ label: "y", value: c.y },
|
|
419
|
+
{ label: "z", value: c.z }
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
if (c.kind === "onGround") {
|
|
423
|
+
return [
|
|
424
|
+
{ label: "x", value: c.x },
|
|
425
|
+
{ label: "y", value: c.y },
|
|
426
|
+
{ label: "z", value: 0 }
|
|
427
|
+
];
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
},
|
|
431
|
+
describe: (obj) => {
|
|
432
|
+
const c = obj.attrs.constraint;
|
|
433
|
+
if (c.kind === "free") return `${obj.label} = (${c.x.toFixed(2)}, ${c.y.toFixed(2)}, ${c.z.toFixed(2)})`;
|
|
434
|
+
if (c.kind === "onGround") return `${obj.label} = (${c.x.toFixed(2)}, ${c.y.toFixed(2)}, 0)`;
|
|
435
|
+
if (c.kind === "onAxis") return `${obj.label} tr\xEAn tr\u1EE5c ${c.axis} (t=${c.t.toFixed(2)})`;
|
|
436
|
+
if (c.kind === "onPlane") return `${obj.label} tr\xEAn m\u1EB7t ${c.planeId}`;
|
|
437
|
+
if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${c.lineId}`;
|
|
438
|
+
if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${c.polygonId}`;
|
|
439
|
+
if (c.kind === "onSphere") return `${obj.label} tr\xEAn m\u1EB7t c\u1EA7u ${c.sphereId}`;
|
|
440
|
+
return obj.label;
|
|
441
|
+
},
|
|
442
|
+
render: (obj, ctx) => {
|
|
443
|
+
const view = ctx.jxg;
|
|
444
|
+
const c = obj.attrs.constraint;
|
|
445
|
+
const opts = {
|
|
446
|
+
name: obj.label,
|
|
447
|
+
visible: obj.visible,
|
|
448
|
+
fixed: obj.locked,
|
|
449
|
+
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
450
|
+
fillColor: obj.attrs.color ?? "#1e40af",
|
|
451
|
+
size: 4
|
|
452
|
+
};
|
|
453
|
+
if (c.kind === "free") {
|
|
454
|
+
return view.create("point3d", [c.x, c.y, c.z], opts);
|
|
455
|
+
} else if (c.kind === "onGround") {
|
|
456
|
+
return view.create("point3d", [c.x, c.y, 0], opts);
|
|
457
|
+
} else if (c.kind === "onAxis") {
|
|
458
|
+
const coords = c.axis === "x" ? [c.t, 0, 0] : c.axis === "y" ? [0, c.t, 0] : [0, 0, c.t];
|
|
459
|
+
return view.create("point3d", coords, opts);
|
|
460
|
+
} else if (c.kind === "onPlane") {
|
|
461
|
+
const plane = ctx.resolveRef(c.planeId);
|
|
462
|
+
return view.create("point3d", [
|
|
463
|
+
() => plane.F(c.u, c.v)[0],
|
|
464
|
+
() => plane.F(c.u, c.v)[1],
|
|
465
|
+
() => plane.F(c.u, c.v)[2]
|
|
466
|
+
], opts);
|
|
467
|
+
} else if (c.kind === "onLine") {
|
|
468
|
+
const line = ctx.resolveRef(c.lineId);
|
|
469
|
+
return view.create("point3d", [
|
|
470
|
+
() => line.F(c.t)[0],
|
|
471
|
+
() => line.F(c.t)[1],
|
|
472
|
+
() => line.F(c.t)[2]
|
|
473
|
+
], opts);
|
|
474
|
+
} else if (c.kind === "onPolygon") {
|
|
475
|
+
const poly = ctx.resolveRef(c.polygonId);
|
|
476
|
+
return view.create("point3d", [
|
|
477
|
+
() => poly.F(c.u, c.v)[0],
|
|
478
|
+
() => poly.F(c.u, c.v)[1],
|
|
479
|
+
() => poly.F(c.u, c.v)[2]
|
|
480
|
+
], opts);
|
|
481
|
+
} else if (c.kind === "onSphere") {
|
|
482
|
+
const sph = ctx.resolveRef(c.sphereId);
|
|
483
|
+
return view.create("point3d", [
|
|
484
|
+
() => sph.F(c.theta, c.phi)[0],
|
|
485
|
+
() => sph.F(c.theta, c.phi)[1],
|
|
486
|
+
() => sph.F(c.theta, c.phi)[2]
|
|
487
|
+
], opts);
|
|
488
|
+
}
|
|
489
|
+
return view.create("point3d", [0, 0, 0], opts);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
registerKind(def);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// src/core/scene/kinds/labelOf.ts
|
|
497
|
+
function labelOf(id, state) {
|
|
498
|
+
return state?.objects[id]?.label ?? id;
|
|
499
|
+
}
|
|
500
|
+
var init_labelOf = __esm({
|
|
501
|
+
"src/core/scene/kinds/labelOf.ts"() {
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// src/core/scene/kinds/segment3d.ts
|
|
506
|
+
var def2;
|
|
507
|
+
var init_segment3d = __esm({
|
|
508
|
+
"src/core/scene/kinds/segment3d.ts"() {
|
|
509
|
+
init_registry();
|
|
510
|
+
init_labelOf();
|
|
511
|
+
def2 = {
|
|
512
|
+
type: "segment3d",
|
|
513
|
+
schemaVersion: 1,
|
|
514
|
+
migrate: {},
|
|
515
|
+
validate: (a) => {
|
|
516
|
+
if (!a?.p1 || !a?.p2) throw new Error("segment3d: p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
517
|
+
},
|
|
518
|
+
dependsOn: (a) => [a.p1, a.p2],
|
|
519
|
+
measure: (obj, state) => {
|
|
520
|
+
const p1 = state.objects[obj.attrs.p1];
|
|
521
|
+
const p2 = state.objects[obj.attrs.p2];
|
|
522
|
+
if (!p1 || !p2) return null;
|
|
523
|
+
const c1 = p1.attrs.constraint;
|
|
524
|
+
const c2 = p2.attrs.constraint;
|
|
525
|
+
if (c1?.kind !== "free" || c2?.kind !== "free") return null;
|
|
526
|
+
const dx = (c2.x ?? 0) - (c1.x ?? 0);
|
|
527
|
+
const dy = (c2.y ?? 0) - (c1.y ?? 0);
|
|
528
|
+
const dz = (c2.z ?? 0) - (c1.z ?? 0);
|
|
529
|
+
return [{ label: "length", value: Math.hypot(dx, dy, dz) }];
|
|
530
|
+
},
|
|
531
|
+
describe: (obj, state) => `\u0110o\u1EA1n th\u1EB3ng ${labelOf(obj.attrs.p1, state)}${labelOf(obj.attrs.p2, state)}`,
|
|
532
|
+
render: (obj, ctx) => {
|
|
533
|
+
const view = ctx.jxg;
|
|
534
|
+
const pA = ctx.resolveRef(obj.attrs.p1);
|
|
535
|
+
const pB = ctx.resolveRef(obj.attrs.p2);
|
|
536
|
+
return view.create("line3d", [pA, pB], {
|
|
537
|
+
straightFirst: false,
|
|
538
|
+
straightLast: false,
|
|
539
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
540
|
+
strokeWidth: 2,
|
|
541
|
+
visible: obj.visible
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
registerKind(def2);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// src/core/scene/kinds/line3d.ts
|
|
550
|
+
var init_line3d = __esm({
|
|
551
|
+
"src/core/scene/kinds/line3d.ts"() {
|
|
552
|
+
init_registry();
|
|
553
|
+
init_labelOf();
|
|
554
|
+
registerKind({
|
|
555
|
+
type: "line3d",
|
|
556
|
+
schemaVersion: 1,
|
|
557
|
+
migrate: {},
|
|
558
|
+
validate: (a) => {
|
|
559
|
+
if (!a?.p1 || !a?.p2) throw new Error("line3d: p1/p2 required");
|
|
560
|
+
},
|
|
561
|
+
dependsOn: (a) => [a.p1, a.p2],
|
|
562
|
+
describe: (obj, state) => `\u0110\u01B0\u1EDDng ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}`,
|
|
563
|
+
render: (obj, ctx) => {
|
|
564
|
+
const view = ctx.jxg;
|
|
565
|
+
const pA = ctx.resolveRef(obj.attrs.p1);
|
|
566
|
+
const pB = ctx.resolveRef(obj.attrs.p2);
|
|
567
|
+
return view.create("line3d", [pA, pB], {
|
|
568
|
+
straightFirst: true,
|
|
569
|
+
straightLast: true,
|
|
570
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
571
|
+
strokeWidth: 2,
|
|
572
|
+
visible: obj.visible
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// src/core/scene/kinds/ray3d.ts
|
|
580
|
+
var init_ray3d = __esm({
|
|
581
|
+
"src/core/scene/kinds/ray3d.ts"() {
|
|
582
|
+
init_registry();
|
|
583
|
+
init_labelOf();
|
|
584
|
+
registerKind({
|
|
585
|
+
type: "ray3d",
|
|
586
|
+
schemaVersion: 1,
|
|
587
|
+
migrate: {},
|
|
588
|
+
validate: (a) => {
|
|
589
|
+
if (!a?.origin || !a?.through) throw new Error("ray3d: origin/through required");
|
|
590
|
+
},
|
|
591
|
+
dependsOn: (a) => [a.origin, a.through],
|
|
592
|
+
describe: (obj, state) => `Tia ${obj.label} t\u1EEB ${labelOf(obj.attrs.origin, state)} qua ${labelOf(obj.attrs.through, state)}`,
|
|
593
|
+
render: (obj, ctx) => {
|
|
594
|
+
const view = ctx.jxg;
|
|
595
|
+
const pOrigin = ctx.resolveRef(obj.attrs.origin);
|
|
596
|
+
const pThrough = ctx.resolveRef(obj.attrs.through);
|
|
597
|
+
return view.create("line3d", [pOrigin, pThrough], {
|
|
598
|
+
straightFirst: false,
|
|
599
|
+
straightLast: true,
|
|
600
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
601
|
+
strokeWidth: 2,
|
|
602
|
+
visible: obj.visible
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
// src/core/scene/kinds/vector3d.ts
|
|
610
|
+
var init_vector3d = __esm({
|
|
611
|
+
"src/core/scene/kinds/vector3d.ts"() {
|
|
612
|
+
init_registry();
|
|
613
|
+
init_labelOf();
|
|
614
|
+
registerKind({
|
|
615
|
+
type: "vector3d",
|
|
616
|
+
schemaVersion: 1,
|
|
617
|
+
migrate: {},
|
|
618
|
+
validate: (a) => {
|
|
619
|
+
if (!a?.from || !a?.to) throw new Error("vector3d: from/to required");
|
|
620
|
+
},
|
|
621
|
+
dependsOn: (a) => [a.from, a.to],
|
|
622
|
+
describe: (obj, state) => `V\xE9c-t\u01A1 ${obj.label}: ${labelOf(obj.attrs.from, state)} \u2192 ${labelOf(obj.attrs.to, state)}`,
|
|
623
|
+
render: (obj, ctx) => {
|
|
624
|
+
const view = ctx.jxg;
|
|
625
|
+
const pFrom = ctx.resolveRef(obj.attrs.from);
|
|
626
|
+
const pTo = ctx.resolveRef(obj.attrs.to);
|
|
627
|
+
return view.create("line3d", [pFrom, pTo], {
|
|
628
|
+
straightFirst: false,
|
|
629
|
+
straightLast: false,
|
|
630
|
+
lastArrow: { type: 1 },
|
|
631
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
632
|
+
strokeWidth: 2,
|
|
633
|
+
visible: obj.visible
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
// src/core/scene/kinds/plane3d.ts
|
|
641
|
+
var init_plane3d = __esm({
|
|
642
|
+
"src/core/scene/kinds/plane3d.ts"() {
|
|
643
|
+
init_registry();
|
|
644
|
+
init_labelOf();
|
|
645
|
+
registerKind({
|
|
646
|
+
type: "plane3d",
|
|
647
|
+
schemaVersion: 1,
|
|
648
|
+
migrate: {},
|
|
649
|
+
validate: (a) => {
|
|
650
|
+
if (!a?.p1 || !a?.p2 || !a?.p3) throw new Error("plane3d: c\u1EA7n 3 \u0111i\u1EC3m");
|
|
651
|
+
},
|
|
652
|
+
dependsOn: (a) => [a.p1, a.p2, a.p3],
|
|
653
|
+
describe: (obj, state) => `M\u1EB7t ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}, ${labelOf(obj.attrs.p3, state)}`,
|
|
654
|
+
render: (obj, ctx) => {
|
|
655
|
+
const view = ctx.jxg;
|
|
656
|
+
return view.create("plane3d", [
|
|
657
|
+
ctx.resolveRef(obj.attrs.p1),
|
|
658
|
+
ctx.resolveRef(obj.attrs.p2),
|
|
659
|
+
ctx.resolveRef(obj.attrs.p3)
|
|
660
|
+
], {
|
|
661
|
+
fillOpacity: 0.15,
|
|
662
|
+
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
663
|
+
strokeColor: obj.attrs.color ?? "#60a5fa",
|
|
664
|
+
visible: obj.visible
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// src/core/scene/kinds/polygon3d.ts
|
|
672
|
+
var init_polygon3d = __esm({
|
|
673
|
+
"src/core/scene/kinds/polygon3d.ts"() {
|
|
674
|
+
init_registry();
|
|
675
|
+
registerKind({
|
|
676
|
+
type: "polygon3d",
|
|
677
|
+
schemaVersion: 1,
|
|
678
|
+
migrate: {},
|
|
679
|
+
validate: (a) => {
|
|
680
|
+
if (!a?.vertices || a.vertices.length < 3) throw new Error("polygon3d: c\u1EA7n \u22653 vertices");
|
|
681
|
+
},
|
|
682
|
+
dependsOn: (a) => [...a.vertices],
|
|
683
|
+
describe: (obj) => `\u0110a gi\xE1c ${obj.label} (${obj.attrs.vertices.length} \u0111\u1EC9nh)`,
|
|
684
|
+
render: (obj, ctx) => {
|
|
685
|
+
const view = ctx.jxg;
|
|
686
|
+
const refs = obj.attrs.vertices.map((id) => ctx.resolveRef(id));
|
|
687
|
+
return view.create("polygon3d", [refs], {
|
|
688
|
+
fillOpacity: 0.3,
|
|
689
|
+
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
690
|
+
visible: obj.visible
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// src/core/scene/kinds/sphere3d.ts
|
|
698
|
+
var init_sphere3d = __esm({
|
|
699
|
+
"src/core/scene/kinds/sphere3d.ts"() {
|
|
700
|
+
init_registry();
|
|
701
|
+
init_labelOf();
|
|
702
|
+
registerKind({
|
|
703
|
+
type: "sphere3d",
|
|
704
|
+
schemaVersion: 1,
|
|
705
|
+
migrate: {},
|
|
706
|
+
validate: (a) => {
|
|
707
|
+
if (!a?.center || !a?.surfacePoint) throw new Error("sphere3d: center/surfacePoint required");
|
|
708
|
+
},
|
|
709
|
+
dependsOn: (a) => [a.center, a.surfacePoint],
|
|
710
|
+
describe: (obj, state) => `M\u1EB7t c\u1EA7u ${obj.label} t\xE2m ${labelOf(obj.attrs.center, state)}`,
|
|
711
|
+
render: (obj, ctx) => {
|
|
712
|
+
const view = ctx.jxg;
|
|
713
|
+
return view.create("sphere3d", [
|
|
714
|
+
ctx.resolveRef(obj.attrs.center),
|
|
715
|
+
ctx.resolveRef(obj.attrs.surfacePoint)
|
|
716
|
+
], {
|
|
717
|
+
fillOpacity: 0.25,
|
|
718
|
+
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
719
|
+
visible: obj.visible
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// src/core/scene/kinds/polyhedron3d.ts
|
|
727
|
+
var FLAVOR_LABEL;
|
|
728
|
+
var init_polyhedron3d = __esm({
|
|
729
|
+
"src/core/scene/kinds/polyhedron3d.ts"() {
|
|
730
|
+
init_registry();
|
|
731
|
+
FLAVOR_LABEL = {
|
|
732
|
+
pyramid: "ch\xF3p",
|
|
733
|
+
prism: "l\u0103ng tr\u1EE5",
|
|
734
|
+
tetrahedron: "t\u1EE9 di\u1EC7n",
|
|
735
|
+
cube: "l\u1EADp ph\u01B0\u01A1ng"
|
|
736
|
+
};
|
|
737
|
+
registerKind({
|
|
738
|
+
type: "polyhedron3d",
|
|
739
|
+
schemaVersion: 1,
|
|
740
|
+
migrate: {},
|
|
741
|
+
validate: (a) => {
|
|
742
|
+
if (!a?.vertices || a.vertices.length < 4) throw new Error("polyhedron3d: c\u1EA7n \u22654 vertices");
|
|
743
|
+
if (!a?.faces || a.faces.length < 4) throw new Error("polyhedron3d: c\u1EA7n \u22654 faces");
|
|
744
|
+
},
|
|
745
|
+
dependsOn: (a) => [...a.vertices],
|
|
746
|
+
describe: (obj) => `Kh\u1ED1i ${FLAVOR_LABEL[obj.attrs.flavor]} ${obj.label}`,
|
|
747
|
+
render: (obj, ctx) => {
|
|
748
|
+
const view = ctx.jxg;
|
|
749
|
+
const verts = obj.attrs.vertices.map((id) => ctx.resolveRef(id));
|
|
750
|
+
const faces = obj.attrs.faces.map(
|
|
751
|
+
(faceIndices, fi) => view.create("polygon3d", [faceIndices.map((i) => verts[i])], {
|
|
752
|
+
id: `${obj.id}.face${faceIndices.join("-")}.${fi}`,
|
|
753
|
+
fillOpacity: 0.25,
|
|
754
|
+
fillColor: obj.attrs.color ?? "#fbbf24",
|
|
755
|
+
strokeColor: "#0066cc",
|
|
756
|
+
strokeWidth: 1.5,
|
|
757
|
+
visible: obj.visible
|
|
758
|
+
})
|
|
759
|
+
);
|
|
760
|
+
return { faces };
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
// src/core/scene/kinds/cylinder3d.ts
|
|
767
|
+
var CURVED_SEGMENTS;
|
|
768
|
+
var init_cylinder3d = __esm({
|
|
769
|
+
"src/core/scene/kinds/cylinder3d.ts"() {
|
|
770
|
+
init_registry();
|
|
771
|
+
CURVED_SEGMENTS = 16;
|
|
772
|
+
registerKind({
|
|
773
|
+
type: "cylinder3d",
|
|
774
|
+
schemaVersion: 1,
|
|
775
|
+
migrate: {},
|
|
776
|
+
validate: (a) => {
|
|
777
|
+
if (!a?.baseCenter || !a?.topCenter) throw new Error("cylinder3d: baseCenter/topCenter required");
|
|
778
|
+
if (!(a.radius > 0)) throw new Error("cylinder3d: radius > 0");
|
|
779
|
+
},
|
|
780
|
+
dependsOn: (a) => [a.baseCenter, a.topCenter],
|
|
781
|
+
describe: (obj) => `Tr\u1EE5 ${obj.label} R=${obj.attrs.radius.toFixed(2)}`,
|
|
782
|
+
render: (obj, ctx) => {
|
|
783
|
+
const view = ctx.jxg;
|
|
784
|
+
const a = ctx.resolveRef(obj.attrs.baseCenter);
|
|
785
|
+
const b = ctx.resolveRef(obj.attrs.topCenter);
|
|
786
|
+
const r = obj.attrs.radius;
|
|
787
|
+
const ax = a.X?.() ?? 0, ay = a.Y?.() ?? 0, az = a.Z?.() ?? 0;
|
|
788
|
+
const bx = b.X?.() ?? 0, by = b.Y?.() ?? 0, bz = b.Z?.() ?? 0;
|
|
789
|
+
const baseRing = [];
|
|
790
|
+
const topRing = [];
|
|
791
|
+
for (let i = 0; i < CURVED_SEGMENTS; i++) {
|
|
792
|
+
const theta = i / CURVED_SEGMENTS * Math.PI * 2;
|
|
793
|
+
const dx = r * Math.cos(theta);
|
|
794
|
+
const dy = r * Math.sin(theta);
|
|
795
|
+
baseRing.push([ax + dx, ay + dy, az]);
|
|
796
|
+
topRing.push([bx + dx, by + dy, bz]);
|
|
797
|
+
}
|
|
798
|
+
const vertices = [...baseRing, ...topRing];
|
|
799
|
+
const faces = [];
|
|
800
|
+
faces.push(baseRing.map((_, i) => i));
|
|
801
|
+
faces.push(topRing.map((_, i) => CURVED_SEGMENTS + i));
|
|
802
|
+
for (let i = 0; i < CURVED_SEGMENTS; i++) {
|
|
803
|
+
const next = (i + 1) % CURVED_SEGMENTS;
|
|
804
|
+
faces.push([i, next, CURVED_SEGMENTS + next, CURVED_SEGMENTS + i]);
|
|
805
|
+
}
|
|
806
|
+
const vertJxgs = vertices.map(
|
|
807
|
+
(v, i) => view.create("point3d", v, {
|
|
808
|
+
id: `${obj.id}.v${i}`,
|
|
809
|
+
visible: false,
|
|
810
|
+
fixed: true,
|
|
811
|
+
withLabel: false
|
|
812
|
+
})
|
|
813
|
+
);
|
|
814
|
+
const faceJxgs = faces.map(
|
|
815
|
+
(face, fi) => view.create("polygon3d", [face.map((idx) => vertJxgs[idx])], {
|
|
816
|
+
id: `${obj.id}.face${fi}`,
|
|
817
|
+
fillOpacity: 0.25,
|
|
818
|
+
fillColor: obj.attrs.color ?? "#f97316",
|
|
819
|
+
strokeColor: "#0066cc",
|
|
820
|
+
strokeWidth: 1.5,
|
|
821
|
+
visible: obj.visible
|
|
822
|
+
})
|
|
823
|
+
);
|
|
824
|
+
return { _verts: vertJxgs, faces: faceJxgs };
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// src/core/scene/kinds/cone3d.ts
|
|
831
|
+
var CURVED_SEGMENTS2;
|
|
832
|
+
var init_cone3d = __esm({
|
|
833
|
+
"src/core/scene/kinds/cone3d.ts"() {
|
|
834
|
+
init_registry();
|
|
835
|
+
CURVED_SEGMENTS2 = 16;
|
|
836
|
+
registerKind({
|
|
837
|
+
type: "cone3d",
|
|
838
|
+
schemaVersion: 1,
|
|
839
|
+
migrate: {},
|
|
840
|
+
validate: (a) => {
|
|
841
|
+
if (!a?.baseCenter || !a?.apex) throw new Error("cone3d: baseCenter/apex required");
|
|
842
|
+
if (!(a.radius > 0)) throw new Error("cone3d: radius > 0");
|
|
843
|
+
},
|
|
844
|
+
dependsOn: (a) => [a.baseCenter, a.apex],
|
|
845
|
+
describe: (obj) => `N\xF3n ${obj.label} R=${obj.attrs.radius.toFixed(2)}`,
|
|
846
|
+
render: (obj, ctx) => {
|
|
847
|
+
const view = ctx.jxg;
|
|
848
|
+
const base = ctx.resolveRef(obj.attrs.baseCenter);
|
|
849
|
+
const apexPt = ctx.resolveRef(obj.attrs.apex);
|
|
850
|
+
const r = obj.attrs.radius;
|
|
851
|
+
const bx = base.X?.() ?? 0, by = base.Y?.() ?? 0, bz = base.Z?.() ?? 0;
|
|
852
|
+
const apexCoords = [
|
|
853
|
+
apexPt.X?.() ?? 0,
|
|
854
|
+
apexPt.Y?.() ?? 0,
|
|
855
|
+
apexPt.Z?.() ?? 0
|
|
856
|
+
];
|
|
857
|
+
const baseRing = [];
|
|
858
|
+
for (let i = 0; i < CURVED_SEGMENTS2; i++) {
|
|
859
|
+
const theta = i / CURVED_SEGMENTS2 * Math.PI * 2;
|
|
860
|
+
baseRing.push([bx + r * Math.cos(theta), by + r * Math.sin(theta), bz]);
|
|
861
|
+
}
|
|
862
|
+
const apexIdx = baseRing.length;
|
|
863
|
+
const vertices = [...baseRing, apexCoords];
|
|
864
|
+
const faces = [baseRing.map((_, i) => i)];
|
|
865
|
+
for (let i = 0; i < CURVED_SEGMENTS2; i++) {
|
|
866
|
+
faces.push([i, (i + 1) % CURVED_SEGMENTS2, apexIdx]);
|
|
867
|
+
}
|
|
868
|
+
const vertJxgs = vertices.map(
|
|
869
|
+
(v, i) => view.create("point3d", v, {
|
|
870
|
+
id: `${obj.id}.v${i}`,
|
|
871
|
+
visible: false,
|
|
872
|
+
fixed: true,
|
|
873
|
+
withLabel: false
|
|
874
|
+
})
|
|
875
|
+
);
|
|
876
|
+
const faceJxgs = faces.map(
|
|
877
|
+
(face, fi) => view.create("polygon3d", [face.map((idx) => vertJxgs[idx])], {
|
|
878
|
+
id: `${obj.id}.face${fi}`,
|
|
879
|
+
fillOpacity: 0.25,
|
|
880
|
+
fillColor: obj.attrs.color ?? "#f59e0b",
|
|
881
|
+
strokeColor: "#0066cc",
|
|
882
|
+
strokeWidth: 1.5,
|
|
883
|
+
visible: obj.visible
|
|
884
|
+
})
|
|
885
|
+
);
|
|
886
|
+
return { _verts: vertJxgs, faces: faceJxgs };
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
// src/core/scene/kinds/2d-constraint.ts
|
|
893
|
+
function transformRefs(t) {
|
|
894
|
+
switch (t.kind) {
|
|
895
|
+
case "translate":
|
|
896
|
+
return [];
|
|
897
|
+
case "rotate":
|
|
898
|
+
case "reflectPoint":
|
|
899
|
+
case "dilate":
|
|
900
|
+
return [t.center];
|
|
901
|
+
case "reflectLine":
|
|
902
|
+
return [t.line];
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function constraintRefs2D(c) {
|
|
906
|
+
switch (c.kind) {
|
|
907
|
+
case "onLine":
|
|
908
|
+
return [c.lineId];
|
|
909
|
+
case "onSegment":
|
|
910
|
+
return [c.segmentId];
|
|
911
|
+
case "onCircle":
|
|
912
|
+
return [c.circleId];
|
|
913
|
+
case "onPolygon":
|
|
914
|
+
return [c.polygonId];
|
|
915
|
+
case "midpoint":
|
|
916
|
+
return [c.p1, c.p2];
|
|
917
|
+
case "transformed":
|
|
918
|
+
return [c.source, ...transformRefs(c.transform)];
|
|
919
|
+
case "perpFoot":
|
|
920
|
+
return [c.from, c.onLine];
|
|
921
|
+
case "circumcenter":
|
|
922
|
+
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
923
|
+
case "incenter":
|
|
924
|
+
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
925
|
+
case "centroid":
|
|
926
|
+
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
927
|
+
case "orthocenter":
|
|
928
|
+
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
929
|
+
default:
|
|
930
|
+
return [];
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
var init_d_constraint2 = __esm({
|
|
934
|
+
"src/core/scene/kinds/2d-constraint.ts"() {
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
// src/core/scene/kinds/point.ts
|
|
939
|
+
function buildJxgTransforms(board, ctx, t) {
|
|
940
|
+
switch (t.kind) {
|
|
941
|
+
case "translate":
|
|
942
|
+
return [board.create("transform", [t.dx, t.dy], { type: "translate" })];
|
|
943
|
+
case "rotate": {
|
|
944
|
+
const c = ctx.resolveRef(t.center);
|
|
945
|
+
return [board.create("transform", [t.angleRad, c], { type: "rotate" })];
|
|
946
|
+
}
|
|
947
|
+
case "reflectPoint": {
|
|
948
|
+
const c = ctx.resolveRef(t.center);
|
|
949
|
+
return [board.create("transform", [Math.PI, c], { type: "rotate" })];
|
|
950
|
+
}
|
|
951
|
+
case "reflectLine": {
|
|
952
|
+
const l = ctx.resolveRef(t.line);
|
|
953
|
+
return [board.create("transform", [l], { type: "reflect" })];
|
|
954
|
+
}
|
|
955
|
+
case "dilate": {
|
|
956
|
+
const c = ctx.resolveRef(t.center);
|
|
957
|
+
return [
|
|
958
|
+
board.create("transform", [() => -c.X(), () => -c.Y()], { type: "translate" }),
|
|
959
|
+
board.create("transform", [t.k, t.k], { type: "scale" }),
|
|
960
|
+
board.create("transform", [() => c.X(), () => c.Y()], { type: "translate" })
|
|
961
|
+
];
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
var def3;
|
|
966
|
+
var init_point = __esm({
|
|
967
|
+
"src/core/scene/kinds/point.ts"() {
|
|
968
|
+
init_registry();
|
|
969
|
+
init_d_constraint2();
|
|
970
|
+
def3 = {
|
|
971
|
+
type: "point",
|
|
972
|
+
schemaVersion: 1,
|
|
973
|
+
migrate: {},
|
|
974
|
+
validate: (a) => {
|
|
975
|
+
if (!a || !a.constraint || !a.constraint.kind) {
|
|
976
|
+
throw new Error("point: constraint required");
|
|
977
|
+
}
|
|
978
|
+
const c = a.constraint;
|
|
979
|
+
if (c.kind === "perpFoot") {
|
|
980
|
+
if (!c.from || !c.onLine) {
|
|
981
|
+
throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (c.kind === "circumcenter") {
|
|
985
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
986
|
+
throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
987
|
+
}
|
|
988
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
989
|
+
throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (c.kind === "incenter") {
|
|
993
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
994
|
+
throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
995
|
+
}
|
|
996
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
997
|
+
throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (c.kind === "centroid") {
|
|
1001
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1002
|
+
throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1003
|
+
}
|
|
1004
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1005
|
+
throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (c.kind === "orthocenter") {
|
|
1009
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1010
|
+
throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1011
|
+
}
|
|
1012
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1013
|
+
throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
dependsOn: (a) => constraintRefs2D(a.constraint),
|
|
1018
|
+
measure: (obj) => {
|
|
1019
|
+
const c = obj.attrs.constraint;
|
|
1020
|
+
if (c.kind === "free") {
|
|
1021
|
+
return [
|
|
1022
|
+
{ label: "x", value: c.x },
|
|
1023
|
+
{ label: "y", value: c.y }
|
|
1024
|
+
];
|
|
1025
|
+
}
|
|
1026
|
+
return null;
|
|
1027
|
+
},
|
|
1028
|
+
describe: (obj, state) => {
|
|
1029
|
+
const c = obj.attrs.constraint;
|
|
1030
|
+
if (c.kind === "free") return `\u0110i\u1EC3m ${obj.label}`;
|
|
1031
|
+
if (c.kind === "onAxis") return `${obj.label} tr\xEAn tr\u1EE5c ${c.axis}`;
|
|
1032
|
+
if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`;
|
|
1033
|
+
if (c.kind === "onSegment") return `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`;
|
|
1034
|
+
if (c.kind === "onCircle") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`;
|
|
1035
|
+
if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`;
|
|
1036
|
+
if (c.kind === "midpoint") {
|
|
1037
|
+
const l1 = state?.objects[c.p1]?.label ?? c.p1;
|
|
1038
|
+
const l2 = state?.objects[c.p2]?.label ?? c.p2;
|
|
1039
|
+
return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
|
|
1040
|
+
}
|
|
1041
|
+
if (c.kind === "transformed") {
|
|
1042
|
+
const t = c.transform;
|
|
1043
|
+
const labelRef = (id) => state?.objects[id]?.label ?? id;
|
|
1044
|
+
const op = t.kind === "translate" ? `t\u1ECBnh ti\u1EBFn (${t.dx.toFixed(2)}, ${t.dy.toFixed(2)})` : t.kind === "rotate" ? `quay ${(t.angleRad * 180 / Math.PI).toFixed(0)}\xB0 quanh ${labelRef(t.center)}` : t.kind === "reflectLine" ? `\u0111\u1ED1i x\u1EE9ng qua ${labelRef(t.line)}` : t.kind === "reflectPoint" ? `\u0111\u1ED1i x\u1EE9ng qua \u0111i\u1EC3m ${labelRef(t.center)}` : t.kind === "dilate" ? `v\u1ECB t\u1EF1 k=${t.k} quanh ${labelRef(t.center)}` : "";
|
|
1045
|
+
return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
|
|
1046
|
+
}
|
|
1047
|
+
if (c.kind === "perpFoot") {
|
|
1048
|
+
const fromLabel = state?.objects[c.from]?.label ?? c.from;
|
|
1049
|
+
const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
|
|
1050
|
+
return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
|
|
1051
|
+
}
|
|
1052
|
+
if (c.kind === "circumcenter") {
|
|
1053
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1054
|
+
return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
|
|
1055
|
+
}
|
|
1056
|
+
if (c.kind === "incenter") {
|
|
1057
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1058
|
+
return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
|
|
1059
|
+
}
|
|
1060
|
+
if (c.kind === "centroid") {
|
|
1061
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1062
|
+
return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
|
|
1063
|
+
}
|
|
1064
|
+
if (c.kind === "orthocenter") {
|
|
1065
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1066
|
+
return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
|
|
1067
|
+
}
|
|
1068
|
+
return `\u0110i\u1EC3m ${obj.label}`;
|
|
1069
|
+
},
|
|
1070
|
+
render: (obj, ctx) => {
|
|
1071
|
+
const board = ctx.jxg;
|
|
1072
|
+
const c = obj.attrs.constraint;
|
|
1073
|
+
const opts = {
|
|
1074
|
+
name: obj.label,
|
|
1075
|
+
withLabel: obj.attrs.showLabel ?? true,
|
|
1076
|
+
visible: obj.visible,
|
|
1077
|
+
fixed: obj.locked,
|
|
1078
|
+
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
1079
|
+
fillColor: obj.attrs.color ?? "#1e40af",
|
|
1080
|
+
face: obj.attrs.face ?? "o",
|
|
1081
|
+
size: obj.attrs.size ?? 4
|
|
1082
|
+
};
|
|
1083
|
+
if (c.kind === "free") return board.create("point", [c.x, c.y], opts);
|
|
1084
|
+
if (c.kind === "onAxis") {
|
|
1085
|
+
const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
|
|
1086
|
+
return board.create("point", coords, opts);
|
|
1087
|
+
}
|
|
1088
|
+
if (c.kind === "onLine") {
|
|
1089
|
+
const line = ctx.resolveRef(c.lineId);
|
|
1090
|
+
return board.create("glider", [c.t, c.t, line], opts);
|
|
1091
|
+
}
|
|
1092
|
+
if (c.kind === "onSegment") {
|
|
1093
|
+
const seg = ctx.resolveRef(c.segmentId);
|
|
1094
|
+
return board.create("glider", [c.t, c.t, seg], opts);
|
|
1095
|
+
}
|
|
1096
|
+
if (c.kind === "onCircle") {
|
|
1097
|
+
const circle = ctx.resolveRef(c.circleId);
|
|
1098
|
+
return board.create("glider", [Math.cos(c.theta), Math.sin(c.theta), circle], opts);
|
|
1099
|
+
}
|
|
1100
|
+
if (c.kind === "onPolygon") {
|
|
1101
|
+
const poly = ctx.resolveRef(c.polygonId);
|
|
1102
|
+
return board.create("glider", [c.u, c.v, poly], opts);
|
|
1103
|
+
}
|
|
1104
|
+
if (c.kind === "midpoint") {
|
|
1105
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1106
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1107
|
+
return board.create("midpoint", [p1, p2], opts);
|
|
1108
|
+
}
|
|
1109
|
+
if (c.kind === "transformed") {
|
|
1110
|
+
const src = ctx.resolveRef(c.source);
|
|
1111
|
+
const transforms = buildJxgTransforms(board, ctx, c.transform);
|
|
1112
|
+
const parent = transforms.length === 1 ? transforms[0] : transforms;
|
|
1113
|
+
const pt = board.create("point", [src, parent], opts);
|
|
1114
|
+
pt._helpers = transforms;
|
|
1115
|
+
return pt;
|
|
1116
|
+
}
|
|
1117
|
+
if (c.kind === "perpFoot") {
|
|
1118
|
+
const from = ctx.resolveRef(c.from);
|
|
1119
|
+
const onLine = ctx.resolveRef(c.onLine);
|
|
1120
|
+
return board.create("perpendicularpoint", [onLine, from], opts);
|
|
1121
|
+
}
|
|
1122
|
+
if (c.kind === "circumcenter") {
|
|
1123
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1124
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1125
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1126
|
+
return board.create("circumcenter", [a, b, c3], opts);
|
|
1127
|
+
}
|
|
1128
|
+
if (c.kind === "incenter") {
|
|
1129
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1130
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1131
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1132
|
+
return board.create("incenter", [a, b, c3], opts);
|
|
1133
|
+
}
|
|
1134
|
+
if (c.kind === "centroid") {
|
|
1135
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1136
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1137
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1138
|
+
return board.create("point", [
|
|
1139
|
+
() => (a.X() + b.X() + c3.X()) / 3,
|
|
1140
|
+
() => (a.Y() + b.Y() + c3.Y()) / 3
|
|
1141
|
+
], opts);
|
|
1142
|
+
}
|
|
1143
|
+
if (c.kind === "orthocenter") {
|
|
1144
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1145
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1146
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1147
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
1148
|
+
const lineBC = board.create("line", [b, c3], hide);
|
|
1149
|
+
const altA = board.create("perpendicular", [lineBC, a], hide);
|
|
1150
|
+
const lineAC = board.create("line", [a, c3], hide);
|
|
1151
|
+
const altB = board.create("perpendicular", [lineAC, b], hide);
|
|
1152
|
+
const ortho = board.create("intersection", [altA, altB, 0], opts);
|
|
1153
|
+
ortho._helpers = [lineBC, altA, lineAC, altB];
|
|
1154
|
+
return ortho;
|
|
1155
|
+
}
|
|
1156
|
+
return board.create("point", [0, 0], opts);
|
|
1157
|
+
},
|
|
1158
|
+
/**
|
|
1159
|
+
* Free → Free update giữ nguyên JxgObj identity (gọi setPositionDirectly +
|
|
1160
|
+
* setAttribute) để các object phụ thuộc (line/segment/...) không bị stale
|
|
1161
|
+
* parent ref. Đổi constraint kind → throw để renderer fallback recreate.
|
|
1162
|
+
*
|
|
1163
|
+
* Đây cũng là endpoint cho drag-sync dispatch trong JxgRenderer: khi user
|
|
1164
|
+
* kéo điểm, listener dispatch UPDATE_ATTRS → update hook chạy, vị trí đã
|
|
1165
|
+
* đúng sẵn nên setPositionDirectly là no-op nhưng vẫn cần để sync các attrs
|
|
1166
|
+
* khác (label/color/...).
|
|
1167
|
+
*/
|
|
1168
|
+
update: (obj, prev, ctx, existing) => {
|
|
1169
|
+
const c = obj.attrs.constraint;
|
|
1170
|
+
const oldC = prev.attrs.constraint;
|
|
1171
|
+
if (c.kind === "free" && oldC.kind === "free") {
|
|
1172
|
+
const el = existing;
|
|
1173
|
+
if (typeof el.setPositionDirectly === "function") {
|
|
1174
|
+
try {
|
|
1175
|
+
el.setPositionDirectly(1, [c.x, c.y]);
|
|
1176
|
+
} catch {
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (typeof el.setAttribute === "function") {
|
|
1180
|
+
try {
|
|
1181
|
+
el.setAttribute({
|
|
1182
|
+
name: obj.label,
|
|
1183
|
+
withLabel: obj.attrs.showLabel ?? true,
|
|
1184
|
+
visible: obj.visible,
|
|
1185
|
+
fixed: obj.locked,
|
|
1186
|
+
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
1187
|
+
fillColor: obj.attrs.color ?? "#1e40af",
|
|
1188
|
+
face: obj.attrs.face ?? "o",
|
|
1189
|
+
size: obj.attrs.size ?? 4
|
|
1190
|
+
});
|
|
1191
|
+
} catch {
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
throw new Error("point: constraint kind changed \u2014 recreate");
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
registerKind(def3);
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
// src/core/scene/kinds/segment.ts
|
|
1204
|
+
var def4;
|
|
1205
|
+
var init_segment = __esm({
|
|
1206
|
+
"src/core/scene/kinds/segment.ts"() {
|
|
1207
|
+
init_registry();
|
|
1208
|
+
init_labelOf();
|
|
1209
|
+
def4 = {
|
|
1210
|
+
type: "segment",
|
|
1211
|
+
schemaVersion: 1,
|
|
1212
|
+
migrate: {},
|
|
1213
|
+
validate: (a) => {
|
|
1214
|
+
if (!a?.p1 || !a?.p2) throw new Error("segment: p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
1215
|
+
},
|
|
1216
|
+
dependsOn: (a) => [a.p1, a.p2],
|
|
1217
|
+
measure: (obj, state) => {
|
|
1218
|
+
const p1 = state.objects[obj.attrs.p1];
|
|
1219
|
+
const p2 = state.objects[obj.attrs.p2];
|
|
1220
|
+
if (!p1 || !p2) return null;
|
|
1221
|
+
const c1 = p1.attrs.constraint;
|
|
1222
|
+
const c2 = p2.attrs.constraint;
|
|
1223
|
+
if (c1?.kind !== "free" || c2?.kind !== "free") return null;
|
|
1224
|
+
const dx = (c2.x ?? 0) - (c1.x ?? 0);
|
|
1225
|
+
const dy = (c2.y ?? 0) - (c1.y ?? 0);
|
|
1226
|
+
return [{ label: "length", value: Math.hypot(dx, dy) }];
|
|
1227
|
+
},
|
|
1228
|
+
describe: (obj, state) => `\u0110o\u1EA1n th\u1EB3ng ${labelOf(obj.attrs.p1, state)}${labelOf(obj.attrs.p2, state)}`,
|
|
1229
|
+
render: (obj, ctx) => {
|
|
1230
|
+
const board = ctx.jxg;
|
|
1231
|
+
const p1 = ctx.resolveRef(obj.attrs.p1);
|
|
1232
|
+
const p2 = ctx.resolveRef(obj.attrs.p2);
|
|
1233
|
+
return board.create("segment", [p1, p2], {
|
|
1234
|
+
name: obj.label,
|
|
1235
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
1236
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1237
|
+
strokeWidth: obj.attrs.width ?? 2,
|
|
1238
|
+
dash: obj.attrs.dash ?? 0,
|
|
1239
|
+
visible: obj.visible,
|
|
1240
|
+
fixed: obj.locked
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
registerKind(def4);
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
// src/core/scene/kinds/line.ts
|
|
1249
|
+
function stripBorderSuffix(id) {
|
|
1250
|
+
const m = /^(.+):border:\d+$/.exec(id);
|
|
1251
|
+
return m ? m[1] : id;
|
|
1252
|
+
}
|
|
1253
|
+
function constructionRefs(c) {
|
|
1254
|
+
switch (c.kind) {
|
|
1255
|
+
case "perpendicular":
|
|
1256
|
+
case "parallel":
|
|
1257
|
+
return [c.throughPoint, stripBorderSuffix(c.toLine)];
|
|
1258
|
+
case "perpBisector":
|
|
1259
|
+
return [c.p1, c.p2];
|
|
1260
|
+
case "angleBisector":
|
|
1261
|
+
return [c.p1, c.vertex, c.p2];
|
|
1262
|
+
case "angleBisectorLines":
|
|
1263
|
+
return [stripBorderSuffix(c.line1), stripBorderSuffix(c.line2)];
|
|
1264
|
+
case "tangent":
|
|
1265
|
+
return [c.throughPoint, c.toCircle];
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
var def5;
|
|
1269
|
+
var init_line = __esm({
|
|
1270
|
+
"src/core/scene/kinds/line.ts"() {
|
|
1271
|
+
init_registry();
|
|
1272
|
+
init_labelOf();
|
|
1273
|
+
def5 = {
|
|
1274
|
+
type: "line",
|
|
1275
|
+
schemaVersion: 1,
|
|
1276
|
+
migrate: {},
|
|
1277
|
+
validate: (a) => {
|
|
1278
|
+
if (a?.construction) return;
|
|
1279
|
+
if (!a?.p1 || !a?.p2) throw new Error("line: p1 v\xE0 p2 b\u1EAFt bu\u1ED9c (ho\u1EB7c construction)");
|
|
1280
|
+
},
|
|
1281
|
+
dependsOn: (a) => a.construction ? constructionRefs(a.construction) : [a.p1, a.p2],
|
|
1282
|
+
describe: (obj, state) => {
|
|
1283
|
+
const L = (id) => labelOf(id, state);
|
|
1284
|
+
const c = obj.attrs.construction;
|
|
1285
|
+
if (!c) return `\u0110\u01B0\u1EDDng th\u1EB3ng ${L(obj.attrs.p1)}${L(obj.attrs.p2)}`;
|
|
1286
|
+
switch (c.kind) {
|
|
1287
|
+
case "perpendicular":
|
|
1288
|
+
return `${obj.label} \u27C2 ${L(c.toLine)} qua ${L(c.throughPoint)}`;
|
|
1289
|
+
case "parallel":
|
|
1290
|
+
return `${obj.label} \u2225 ${L(c.toLine)} qua ${L(c.throughPoint)}`;
|
|
1291
|
+
case "perpBisector":
|
|
1292
|
+
return `${obj.label}: trung tr\u1EF1c ${L(c.p1)}${L(c.p2)}`;
|
|
1293
|
+
case "angleBisector":
|
|
1294
|
+
return `${obj.label}: ph\xE2n gi\xE1c g\xF3c ${L(c.p1)}${L(c.vertex)}${L(c.p2)}`;
|
|
1295
|
+
case "angleBisectorLines":
|
|
1296
|
+
return `${obj.label}: ph\xE2n gi\xE1c ${L(c.line1)} & ${L(c.line2)} (${c.branch === 0 ? "1" : "2"})`;
|
|
1297
|
+
case "tangent":
|
|
1298
|
+
return `${obj.label}: ti\u1EBFp tuy\u1EBFn ${L(c.toCircle)} qua ${L(c.throughPoint)}`;
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1301
|
+
render: (obj, ctx) => {
|
|
1302
|
+
const board = ctx.jxg;
|
|
1303
|
+
const baseOpts = {
|
|
1304
|
+
name: obj.label,
|
|
1305
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
1306
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1307
|
+
strokeWidth: obj.attrs.width ?? 2,
|
|
1308
|
+
dash: obj.attrs.dash ?? 0,
|
|
1309
|
+
visible: obj.visible,
|
|
1310
|
+
fixed: obj.locked
|
|
1311
|
+
};
|
|
1312
|
+
const c = obj.attrs.construction;
|
|
1313
|
+
if (!c) {
|
|
1314
|
+
const p1 = ctx.resolveRef(obj.attrs.p1);
|
|
1315
|
+
const p2 = ctx.resolveRef(obj.attrs.p2);
|
|
1316
|
+
return board.create("line", [p1, p2], {
|
|
1317
|
+
...baseOpts,
|
|
1318
|
+
straightFirst: true,
|
|
1319
|
+
straightLast: true
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
switch (c.kind) {
|
|
1323
|
+
case "perpendicular": {
|
|
1324
|
+
const through = ctx.resolveRef(c.throughPoint);
|
|
1325
|
+
const toLine = ctx.resolveRef(c.toLine);
|
|
1326
|
+
return board.create("perpendicular", [toLine, through], baseOpts);
|
|
1327
|
+
}
|
|
1328
|
+
case "parallel": {
|
|
1329
|
+
const through = ctx.resolveRef(c.throughPoint);
|
|
1330
|
+
const toLine = ctx.resolveRef(c.toLine);
|
|
1331
|
+
return board.create("parallel", [toLine, through], baseOpts);
|
|
1332
|
+
}
|
|
1333
|
+
case "perpBisector": {
|
|
1334
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1335
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1336
|
+
const mid = board.create("midpoint", [p1, p2], {
|
|
1337
|
+
visible: false,
|
|
1338
|
+
withLabel: false,
|
|
1339
|
+
fixed: true,
|
|
1340
|
+
name: ""
|
|
1341
|
+
});
|
|
1342
|
+
const helperLine = board.create("line", [p1, p2], {
|
|
1343
|
+
visible: false,
|
|
1344
|
+
withLabel: false,
|
|
1345
|
+
fixed: true,
|
|
1346
|
+
name: "",
|
|
1347
|
+
straightFirst: true,
|
|
1348
|
+
straightLast: true
|
|
1349
|
+
});
|
|
1350
|
+
const bisector = board.create("perpendicular", [helperLine, mid], baseOpts);
|
|
1351
|
+
bisector._helpers = [mid, helperLine];
|
|
1352
|
+
return bisector;
|
|
1353
|
+
}
|
|
1354
|
+
case "angleBisector": {
|
|
1355
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1356
|
+
const vertex = ctx.resolveRef(c.vertex);
|
|
1357
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1358
|
+
return board.create("bisector", [p1, vertex, p2], baseOpts);
|
|
1359
|
+
}
|
|
1360
|
+
case "angleBisectorLines": {
|
|
1361
|
+
const line1Jxg = ctx.resolveRef(c.line1);
|
|
1362
|
+
const line2Jxg = ctx.resolveRef(c.line2);
|
|
1363
|
+
const comp = board.create("bisectorlines", [line1Jxg, line2Jxg], {
|
|
1364
|
+
line1: { visible: false, withLabel: false, fixed: true, name: "" },
|
|
1365
|
+
line2: { visible: false, withLabel: false, fixed: true, name: "" }
|
|
1366
|
+
});
|
|
1367
|
+
const selected = c.branch === 0 ? comp.line1 : comp.line2;
|
|
1368
|
+
const other = c.branch === 0 ? comp.line2 : comp.line1;
|
|
1369
|
+
selected.setAttribute({
|
|
1370
|
+
...baseOpts,
|
|
1371
|
+
visible: obj.visible,
|
|
1372
|
+
fixed: obj.locked
|
|
1373
|
+
});
|
|
1374
|
+
selected._helpers = [other];
|
|
1375
|
+
return selected;
|
|
1376
|
+
}
|
|
1377
|
+
case "tangent": {
|
|
1378
|
+
const through = ctx.resolveRef(c.throughPoint);
|
|
1379
|
+
const toCircle = ctx.resolveRef(c.toCircle);
|
|
1380
|
+
const branch = c.branch ?? "on";
|
|
1381
|
+
if (branch === "on") {
|
|
1382
|
+
const glider = board.create("glider", [through.X(), through.Y(), toCircle], {
|
|
1383
|
+
visible: false,
|
|
1384
|
+
withLabel: false,
|
|
1385
|
+
fixed: true,
|
|
1386
|
+
name: ""
|
|
1387
|
+
});
|
|
1388
|
+
const tangent2 = board.create("tangent", [glider], baseOpts);
|
|
1389
|
+
tangent2._helpers = [glider];
|
|
1390
|
+
return tangent2;
|
|
1391
|
+
}
|
|
1392
|
+
const center = toCircle.center;
|
|
1393
|
+
const mid = board.create("midpoint", [center, through], {
|
|
1394
|
+
visible: false,
|
|
1395
|
+
withLabel: false,
|
|
1396
|
+
fixed: true,
|
|
1397
|
+
name: ""
|
|
1398
|
+
});
|
|
1399
|
+
const thales = board.create("circle", [mid, through], {
|
|
1400
|
+
visible: false,
|
|
1401
|
+
withLabel: false,
|
|
1402
|
+
fixed: true,
|
|
1403
|
+
strokeOpacity: 0,
|
|
1404
|
+
fillOpacity: 0
|
|
1405
|
+
});
|
|
1406
|
+
const touch = board.create("intersection", [thales, toCircle, branch], {
|
|
1407
|
+
visible: false,
|
|
1408
|
+
withLabel: false,
|
|
1409
|
+
fixed: true,
|
|
1410
|
+
name: ""
|
|
1411
|
+
});
|
|
1412
|
+
const tangent = board.create("line", [through, touch], {
|
|
1413
|
+
...baseOpts,
|
|
1414
|
+
straightFirst: true,
|
|
1415
|
+
straightLast: true
|
|
1416
|
+
});
|
|
1417
|
+
tangent._helpers = [mid, thales, touch];
|
|
1418
|
+
return tangent;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
registerKind(def5);
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
// src/core/scene/kinds/ray.ts
|
|
1428
|
+
var def6;
|
|
1429
|
+
var init_ray = __esm({
|
|
1430
|
+
"src/core/scene/kinds/ray.ts"() {
|
|
1431
|
+
init_registry();
|
|
1432
|
+
init_labelOf();
|
|
1433
|
+
def6 = {
|
|
1434
|
+
type: "ray",
|
|
1435
|
+
schemaVersion: 1,
|
|
1436
|
+
migrate: {},
|
|
1437
|
+
validate: (a) => {
|
|
1438
|
+
if (!a?.origin || !a?.through) throw new Error("ray: origin v\xE0 through b\u1EAFt bu\u1ED9c");
|
|
1439
|
+
},
|
|
1440
|
+
dependsOn: (a) => [a.origin, a.through],
|
|
1441
|
+
describe: (obj, state) => `Tia ${labelOf(obj.attrs.origin, state)}${labelOf(obj.attrs.through, state)}`,
|
|
1442
|
+
render: (obj, ctx) => {
|
|
1443
|
+
const board = ctx.jxg;
|
|
1444
|
+
const o = ctx.resolveRef(obj.attrs.origin);
|
|
1445
|
+
const t = ctx.resolveRef(obj.attrs.through);
|
|
1446
|
+
return board.create("line", [o, t], {
|
|
1447
|
+
name: obj.label,
|
|
1448
|
+
straightFirst: false,
|
|
1449
|
+
straightLast: true,
|
|
1450
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1451
|
+
strokeWidth: obj.attrs.width ?? 2,
|
|
1452
|
+
dash: obj.attrs.dash ?? 0,
|
|
1453
|
+
visible: obj.visible,
|
|
1454
|
+
fixed: obj.locked
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
registerKind(def6);
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
// src/core/scene/kinds/vector.ts
|
|
1463
|
+
var def7;
|
|
1464
|
+
var init_vector = __esm({
|
|
1465
|
+
"src/core/scene/kinds/vector.ts"() {
|
|
1466
|
+
init_registry();
|
|
1467
|
+
init_labelOf();
|
|
1468
|
+
def7 = {
|
|
1469
|
+
type: "vector",
|
|
1470
|
+
schemaVersion: 1,
|
|
1471
|
+
migrate: {},
|
|
1472
|
+
validate: (a) => {
|
|
1473
|
+
if (!a?.from || !a?.to) throw new Error("vector: from v\xE0 to b\u1EAFt bu\u1ED9c");
|
|
1474
|
+
},
|
|
1475
|
+
dependsOn: (a) => [a.from, a.to],
|
|
1476
|
+
describe: (obj, state) => `Vector ${labelOf(obj.attrs.from, state)}${labelOf(obj.attrs.to, state)}`,
|
|
1477
|
+
render: (obj, ctx) => {
|
|
1478
|
+
const board = ctx.jxg;
|
|
1479
|
+
const f = ctx.resolveRef(obj.attrs.from);
|
|
1480
|
+
const t = ctx.resolveRef(obj.attrs.to);
|
|
1481
|
+
return board.create("arrow", [f, t], {
|
|
1482
|
+
name: obj.label,
|
|
1483
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1484
|
+
strokeWidth: obj.attrs.width ?? 2,
|
|
1485
|
+
visible: obj.visible,
|
|
1486
|
+
fixed: obj.locked
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
registerKind(def7);
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
// src/core/scene/kinds/circle.ts
|
|
1495
|
+
function constructionRefs2(c) {
|
|
1496
|
+
switch (c.kind) {
|
|
1497
|
+
case "circumscribed":
|
|
1498
|
+
return [c.p1, c.p2, c.p3];
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
var def8;
|
|
1502
|
+
var init_circle = __esm({
|
|
1503
|
+
"src/core/scene/kinds/circle.ts"() {
|
|
1504
|
+
init_registry();
|
|
1505
|
+
init_labelOf();
|
|
1506
|
+
def8 = {
|
|
1507
|
+
type: "circle",
|
|
1508
|
+
schemaVersion: 1,
|
|
1509
|
+
migrate: {},
|
|
1510
|
+
validate: (a) => {
|
|
1511
|
+
if (a?.construction) return;
|
|
1512
|
+
if (!a?.center || !a?.surfacePoint) {
|
|
1513
|
+
throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction)");
|
|
1514
|
+
}
|
|
1515
|
+
},
|
|
1516
|
+
dependsOn: (a) => a.construction ? constructionRefs2(a.construction) : [a.center, a.surfacePoint],
|
|
1517
|
+
measure: (obj, state) => {
|
|
1518
|
+
if (obj.attrs.construction) return null;
|
|
1519
|
+
const center = obj.attrs.center ? state.objects[obj.attrs.center] : void 0;
|
|
1520
|
+
const surface = obj.attrs.surfacePoint ? state.objects[obj.attrs.surfacePoint] : void 0;
|
|
1521
|
+
if (!center || !surface) return null;
|
|
1522
|
+
const c1 = center.attrs.constraint;
|
|
1523
|
+
const c2 = surface.attrs.constraint;
|
|
1524
|
+
if (c1?.kind !== "free" || c2?.kind !== "free") return null;
|
|
1525
|
+
const dx = (c2.x ?? 0) - (c1.x ?? 0);
|
|
1526
|
+
const dy = (c2.y ?? 0) - (c1.y ?? 0);
|
|
1527
|
+
return [{ label: "r", value: Math.hypot(dx, dy) }];
|
|
1528
|
+
},
|
|
1529
|
+
describe: (obj, state) => {
|
|
1530
|
+
const L = (id) => labelOf(id, state);
|
|
1531
|
+
const c = obj.attrs.construction;
|
|
1532
|
+
if (c?.kind === "circumscribed") {
|
|
1533
|
+
return `\u0110\u01B0\u1EDDng tr\xF2n \u0111i qua ${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
|
|
1534
|
+
}
|
|
1535
|
+
return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${L(obj.attrs.center)}${L(obj.attrs.surfacePoint)}`;
|
|
1536
|
+
},
|
|
1537
|
+
render: (obj, ctx) => {
|
|
1538
|
+
const board = ctx.jxg;
|
|
1539
|
+
const baseOpts = {
|
|
1540
|
+
name: obj.label,
|
|
1541
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
1542
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1543
|
+
strokeWidth: obj.attrs.width ?? 2,
|
|
1544
|
+
dash: obj.attrs.dash ?? 0,
|
|
1545
|
+
fillColor: "none",
|
|
1546
|
+
visible: obj.visible,
|
|
1547
|
+
fixed: obj.locked
|
|
1548
|
+
};
|
|
1549
|
+
const c = obj.attrs.construction;
|
|
1550
|
+
if (c?.kind === "circumscribed") {
|
|
1551
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1552
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1553
|
+
const p3 = ctx.resolveRef(c.p3);
|
|
1554
|
+
return board.create("circumcircle", [p1, p2, p3], baseOpts);
|
|
1555
|
+
}
|
|
1556
|
+
const center = ctx.resolveRef(obj.attrs.center);
|
|
1557
|
+
const surface = ctx.resolveRef(obj.attrs.surfacePoint);
|
|
1558
|
+
return board.create("circle", [center, surface], baseOpts);
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
registerKind(def8);
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
// src/core/scene/kinds/arc.ts
|
|
1566
|
+
function constructionRefs3(c) {
|
|
1567
|
+
switch (c.kind) {
|
|
1568
|
+
case "semicircle":
|
|
1569
|
+
return [c.p1, c.p2];
|
|
1570
|
+
case "byCenter":
|
|
1571
|
+
return [c.center, c.p1, c.p2];
|
|
1572
|
+
case "by3Points":
|
|
1573
|
+
return [c.p1, c.p2, c.p3];
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
var def9;
|
|
1577
|
+
var init_arc = __esm({
|
|
1578
|
+
"src/core/scene/kinds/arc.ts"() {
|
|
1579
|
+
init_registry();
|
|
1580
|
+
init_labelOf();
|
|
1581
|
+
def9 = {
|
|
1582
|
+
type: "arc",
|
|
1583
|
+
schemaVersion: 1,
|
|
1584
|
+
migrate: {},
|
|
1585
|
+
validate: (a) => {
|
|
1586
|
+
const c = a?.construction;
|
|
1587
|
+
if (!c) throw new Error("arc: construction b\u1EAFt bu\u1ED9c");
|
|
1588
|
+
if (c.kind === "semicircle") {
|
|
1589
|
+
if (!c.p1 || !c.p2) throw new Error("arc.semicircle: p1, p2 b\u1EAFt bu\u1ED9c");
|
|
1590
|
+
} else if (c.kind === "byCenter") {
|
|
1591
|
+
if (!c.center || !c.p1 || !c.p2) throw new Error("arc.byCenter: center, p1, p2 b\u1EAFt bu\u1ED9c");
|
|
1592
|
+
} else if (c.kind === "by3Points") {
|
|
1593
|
+
if (!c.p1 || !c.p2 || !c.p3) throw new Error("arc.by3Points: p1, p2, p3 b\u1EAFt bu\u1ED9c");
|
|
1594
|
+
}
|
|
1595
|
+
},
|
|
1596
|
+
dependsOn: (a) => constructionRefs3(a.construction),
|
|
1597
|
+
describe: (obj, state) => {
|
|
1598
|
+
const L = (id) => labelOf(id, state);
|
|
1599
|
+
const c = obj.attrs.construction;
|
|
1600
|
+
switch (c.kind) {
|
|
1601
|
+
case "semicircle":
|
|
1602
|
+
return `N\u1EEDa \u0111\u01B0\u1EDDng tr\xF2n \u0111\u01B0\u1EDDng k\xEDnh ${L(c.p1)}${L(c.p2)}`;
|
|
1603
|
+
case "byCenter":
|
|
1604
|
+
return `Cung tr\xF2n t\xE2m ${L(c.center)} t\u1EEB ${L(c.p1)} \u0111\u1EBFn ${L(c.p2)}`;
|
|
1605
|
+
case "by3Points":
|
|
1606
|
+
return `Cung tr\xF2n qua ${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
|
|
1607
|
+
}
|
|
1608
|
+
},
|
|
1609
|
+
render: (obj, ctx) => {
|
|
1610
|
+
const board = ctx.jxg;
|
|
1611
|
+
const baseOpts = {
|
|
1612
|
+
name: obj.label,
|
|
1613
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
1614
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1615
|
+
strokeWidth: obj.attrs.width ?? 2,
|
|
1616
|
+
dash: obj.attrs.dash ?? 0,
|
|
1617
|
+
fillColor: "none",
|
|
1618
|
+
visible: obj.visible,
|
|
1619
|
+
fixed: obj.locked
|
|
1620
|
+
};
|
|
1621
|
+
const c = obj.attrs.construction;
|
|
1622
|
+
if (c.kind === "semicircle") {
|
|
1623
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1624
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1625
|
+
return board.create("semicircle", [p1, p2], baseOpts);
|
|
1626
|
+
}
|
|
1627
|
+
if (c.kind === "byCenter") {
|
|
1628
|
+
const O = ctx.resolveRef(c.center);
|
|
1629
|
+
const A2 = ctx.resolveRef(c.p1);
|
|
1630
|
+
const B2 = ctx.resolveRef(c.p2);
|
|
1631
|
+
return board.create("arc", [O, A2, B2], baseOpts);
|
|
1632
|
+
}
|
|
1633
|
+
const A = ctx.resolveRef(c.p1);
|
|
1634
|
+
const B = ctx.resolveRef(c.p2);
|
|
1635
|
+
const C = ctx.resolveRef(c.p3);
|
|
1636
|
+
return board.create("circumcirclearc", [A, B, C], baseOpts);
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
registerKind(def9);
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
// src/core/scene/kinds/sector.ts
|
|
1644
|
+
var def10;
|
|
1645
|
+
var init_sector = __esm({
|
|
1646
|
+
"src/core/scene/kinds/sector.ts"() {
|
|
1647
|
+
init_registry();
|
|
1648
|
+
init_labelOf();
|
|
1649
|
+
def10 = {
|
|
1650
|
+
type: "sector",
|
|
1651
|
+
schemaVersion: 1,
|
|
1652
|
+
migrate: {},
|
|
1653
|
+
validate: (a) => {
|
|
1654
|
+
const c = a?.construction;
|
|
1655
|
+
if (!c) throw new Error("sector: construction b\u1EAFt bu\u1ED9c");
|
|
1656
|
+
if (c.kind === "byCenter") {
|
|
1657
|
+
if (!c.center || !c.p1 || !c.p2) {
|
|
1658
|
+
throw new Error("sector.byCenter: center, p1, p2 b\u1EAFt bu\u1ED9c");
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
},
|
|
1662
|
+
dependsOn: (a) => {
|
|
1663
|
+
const c = a.construction;
|
|
1664
|
+
return [c.center, c.p1, c.p2];
|
|
1665
|
+
},
|
|
1666
|
+
describe: (obj, state) => {
|
|
1667
|
+
const L = (id) => labelOf(id, state);
|
|
1668
|
+
const c = obj.attrs.construction;
|
|
1669
|
+
return `H\xECnh qu\u1EA1t t\xE2m ${L(c.center)} t\u1EEB ${L(c.p1)} \u0111\u1EBFn ${L(c.p2)}`;
|
|
1670
|
+
},
|
|
1671
|
+
render: (obj, ctx) => {
|
|
1672
|
+
const board = ctx.jxg;
|
|
1673
|
+
const c = obj.attrs.construction;
|
|
1674
|
+
const O = ctx.resolveRef(c.center);
|
|
1675
|
+
const A = ctx.resolveRef(c.p1);
|
|
1676
|
+
const B = ctx.resolveRef(c.p2);
|
|
1677
|
+
return board.create("sector", [O, A, B], {
|
|
1678
|
+
name: obj.label,
|
|
1679
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
1680
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1681
|
+
strokeWidth: obj.attrs.width ?? 2,
|
|
1682
|
+
fillColor: obj.attrs.fillColor ?? "#f59e0b",
|
|
1683
|
+
fillOpacity: obj.attrs.fillOpacity ?? 0.18,
|
|
1684
|
+
visible: obj.visible,
|
|
1685
|
+
fixed: obj.locked
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
registerKind(def10);
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
// src/core/scene/kinds/polygon.ts
|
|
1694
|
+
function regularPolygonName(n) {
|
|
1695
|
+
if (n === 3) return "Tam gi\xE1c \u0111\u1EC1u";
|
|
1696
|
+
if (n === 4) return "H\xECnh vu\xF4ng";
|
|
1697
|
+
if (n === 5) return "Ng\u0169 gi\xE1c \u0111\u1EC1u";
|
|
1698
|
+
if (n === 6) return "L\u1EE5c gi\xE1c \u0111\u1EC1u";
|
|
1699
|
+
return `${n}-gi\xE1c \u0111\u1EC1u`;
|
|
1700
|
+
}
|
|
1701
|
+
function regularVertexLabels(p1Label, p2Label, n) {
|
|
1702
|
+
const A = "A".charCodeAt(0);
|
|
1703
|
+
const Z = "Z".charCodeAt(0);
|
|
1704
|
+
if (p1Label.length === 1 && p2Label.length === 1) {
|
|
1705
|
+
const c1 = p1Label.charCodeAt(0);
|
|
1706
|
+
const c2 = p2Label.charCodeAt(0);
|
|
1707
|
+
if (c1 >= A && c1 <= Z && c2 === c1 + 1 && c1 + n - 1 <= Z) {
|
|
1708
|
+
let out = "";
|
|
1709
|
+
for (let i = 0; i < n; i++) out += String.fromCharCode(c1 + i);
|
|
1710
|
+
return out;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
return `${p1Label}${p2Label}\u2026`;
|
|
1714
|
+
}
|
|
1715
|
+
var def11;
|
|
1716
|
+
var init_polygon = __esm({
|
|
1717
|
+
"src/core/scene/kinds/polygon.ts"() {
|
|
1718
|
+
init_registry();
|
|
1719
|
+
init_labelOf();
|
|
1720
|
+
def11 = {
|
|
1721
|
+
type: "polygon",
|
|
1722
|
+
schemaVersion: 1,
|
|
1723
|
+
migrate: {},
|
|
1724
|
+
validate: (a) => {
|
|
1725
|
+
if (a?.construction) {
|
|
1726
|
+
if (a.construction.kind === "regular") {
|
|
1727
|
+
if (!a.construction.p1 || !a.construction.p2) {
|
|
1728
|
+
throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
1729
|
+
}
|
|
1730
|
+
if (!Number.isFinite(a.construction.n) || a.construction.n < 3) {
|
|
1731
|
+
throw new Error("polygon (regular): n \u2265 3");
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
if (!Array.isArray(a?.vertices) || a.vertices.length < 3) {
|
|
1737
|
+
throw new Error("polygon: c\u1EA7n \xEDt nh\u1EA5t 3 \u0111\u1EC9nh");
|
|
1738
|
+
}
|
|
1739
|
+
},
|
|
1740
|
+
dependsOn: (a) => {
|
|
1741
|
+
if (a.construction?.kind === "regular") return [a.construction.p1, a.construction.p2];
|
|
1742
|
+
return [...a.vertices ?? []];
|
|
1743
|
+
},
|
|
1744
|
+
describe: (obj, state) => {
|
|
1745
|
+
if (obj.attrs.construction?.kind === "regular") {
|
|
1746
|
+
const c = obj.attrs.construction;
|
|
1747
|
+
const labels = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
|
|
1748
|
+
return `${regularPolygonName(c.n)} ${labels}`;
|
|
1749
|
+
}
|
|
1750
|
+
return `\u0110a gi\xE1c ${(obj.attrs.vertices ?? []).map((id) => labelOf(id, state)).join("")}`;
|
|
1751
|
+
},
|
|
1752
|
+
render: (obj, ctx) => {
|
|
1753
|
+
const board = ctx.jxg;
|
|
1754
|
+
const label = obj.label;
|
|
1755
|
+
const showValue = obj.attrs.showValue ?? false;
|
|
1756
|
+
if (obj.attrs.construction?.kind === "regular") {
|
|
1757
|
+
const c = obj.attrs.construction;
|
|
1758
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1759
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1760
|
+
return board.create("regularpolygon", [p1, p2, c.n], {
|
|
1761
|
+
name: label,
|
|
1762
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
1763
|
+
borders: {
|
|
1764
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1765
|
+
strokeWidth: obj.attrs.width ?? 2
|
|
1766
|
+
},
|
|
1767
|
+
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
1768
|
+
fillOpacity: obj.attrs.fillOpacity ?? 0.15,
|
|
1769
|
+
visible: obj.visible,
|
|
1770
|
+
fixed: obj.locked
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
const verts = (obj.attrs.vertices ?? []).map((id) => ctx.resolveRef(id));
|
|
1774
|
+
const poly = board.create("polygon", verts, {
|
|
1775
|
+
name: showValue ? function() {
|
|
1776
|
+
const a = typeof this.Area === "function" ? this.Area() : 0;
|
|
1777
|
+
const prefix = obj.attrs.showLabel ?? true ? `${label}: ` : "";
|
|
1778
|
+
return `${prefix}S = ${Math.abs(a).toFixed(2)}`;
|
|
1779
|
+
} : label,
|
|
1780
|
+
withLabel: showValue ? true : obj.attrs.showLabel ?? false,
|
|
1781
|
+
borders: {
|
|
1782
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1783
|
+
strokeWidth: obj.attrs.width ?? 2
|
|
1784
|
+
},
|
|
1785
|
+
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
1786
|
+
fillOpacity: obj.attrs.fillOpacity ?? 0.15,
|
|
1787
|
+
visible: obj.visible,
|
|
1788
|
+
fixed: obj.locked
|
|
1789
|
+
});
|
|
1790
|
+
return poly;
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
registerKind(def11);
|
|
292
1794
|
}
|
|
293
1795
|
});
|
|
294
1796
|
|
|
295
|
-
// src/core/scene/
|
|
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
|
-
|
|
1797
|
+
// src/core/scene/kinds/intersection.ts
|
|
1798
|
+
var def12;
|
|
1799
|
+
var init_intersection = __esm({
|
|
1800
|
+
"src/core/scene/kinds/intersection.ts"() {
|
|
1801
|
+
init_registry();
|
|
1802
|
+
def12 = {
|
|
1803
|
+
type: "intersection",
|
|
1804
|
+
schemaVersion: 1,
|
|
1805
|
+
migrate: {},
|
|
1806
|
+
validate: (a) => {
|
|
1807
|
+
if (!a || !("kind" in a)) throw new Error("intersection: kind b\u1EAFt bu\u1ED9c");
|
|
1808
|
+
if (!a.ref1 || !a.ref2) throw new Error("intersection: ref1 v\xE0 ref2 b\u1EAFt bu\u1ED9c");
|
|
1809
|
+
if (a.kind === "lineLine") return;
|
|
1810
|
+
if (a.kind === "lineCircle" || a.kind === "circleCircle") {
|
|
1811
|
+
if (a.branch !== 0 && a.branch !== 1) {
|
|
1812
|
+
throw new Error(`intersection.${a.kind}: branch ph\u1EA3i l\xE0 0 ho\u1EB7c 1`);
|
|
1813
|
+
}
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
throw new Error(`intersection: kind kh\xF4ng h\u1EE3p l\u1EC7 "${a.kind}"`);
|
|
1817
|
+
},
|
|
1818
|
+
dependsOn: (a) => [a.ref1, a.ref2],
|
|
1819
|
+
describe: (obj) => {
|
|
1820
|
+
const a = obj.attrs;
|
|
1821
|
+
return `${obj.label} = giao ${a.ref1} \u2229 ${a.ref2}`;
|
|
1822
|
+
},
|
|
1823
|
+
render: (obj, ctx) => {
|
|
1824
|
+
const board = ctx.jxg;
|
|
1825
|
+
const a = ctx.resolveRef(obj.attrs.ref1);
|
|
1826
|
+
const b = ctx.resolveRef(obj.attrs.ref2);
|
|
1827
|
+
const opts = {
|
|
1828
|
+
name: obj.label,
|
|
1829
|
+
withLabel: true,
|
|
1830
|
+
strokeColor: obj.attrs.color ?? "#dc2626",
|
|
1831
|
+
fillColor: obj.attrs.color ?? "#dc2626",
|
|
1832
|
+
visible: obj.visible,
|
|
1833
|
+
fixed: obj.locked
|
|
1834
|
+
};
|
|
1835
|
+
if (obj.attrs.kind === "lineLine") {
|
|
1836
|
+
return board.create("intersection", [a, b, 0], opts);
|
|
1837
|
+
}
|
|
1838
|
+
const branch = obj.attrs.branch ?? 0;
|
|
1839
|
+
return board.create("intersection", [a, b, branch], opts);
|
|
1840
|
+
}
|
|
321
1841
|
};
|
|
1842
|
+
registerKind(def12);
|
|
322
1843
|
}
|
|
323
1844
|
});
|
|
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
|
-
|
|
1845
|
+
|
|
1846
|
+
// src/core/scene/kinds/angle.ts
|
|
1847
|
+
var def13;
|
|
1848
|
+
var init_angle = __esm({
|
|
1849
|
+
"src/core/scene/kinds/angle.ts"() {
|
|
1850
|
+
init_registry();
|
|
1851
|
+
init_labelOf();
|
|
1852
|
+
def13 = {
|
|
1853
|
+
type: "angle",
|
|
1854
|
+
schemaVersion: 1,
|
|
1855
|
+
migrate: {},
|
|
1856
|
+
validate: (a) => {
|
|
1857
|
+
if (!a?.p1 || !a?.vertex || !a?.p2) {
|
|
1858
|
+
throw new Error("angle: p1, vertex, p2 b\u1EAFt bu\u1ED9c");
|
|
1859
|
+
}
|
|
1860
|
+
},
|
|
1861
|
+
dependsOn: (a) => [a.p1, a.vertex, a.p2],
|
|
1862
|
+
describe: (obj, state) => `G\xF3c ${labelOf(obj.attrs.p1, state)}${labelOf(obj.attrs.vertex, state)}${labelOf(obj.attrs.p2, state)}`,
|
|
1863
|
+
render: (obj, ctx) => {
|
|
1864
|
+
const board = ctx.jxg;
|
|
1865
|
+
const pa = ctx.resolveRef(obj.attrs.p1);
|
|
1866
|
+
const pv = ctx.resolveRef(obj.attrs.vertex);
|
|
1867
|
+
const pc = ctx.resolveRef(obj.attrs.p2);
|
|
1868
|
+
let parents = [pa, pv, pc];
|
|
1869
|
+
try {
|
|
1870
|
+
const ax = pa.X() - pv.X(), ay = pa.Y() - pv.Y();
|
|
1871
|
+
const cx = pc.X() - pv.X(), cy = pc.Y() - pv.Y();
|
|
1872
|
+
if (ax * cy - ay * cx < 0) parents = [pc, pv, pa];
|
|
1873
|
+
} catch {
|
|
1874
|
+
}
|
|
1875
|
+
return board.create("angle", parents, {
|
|
1876
|
+
name: obj.label,
|
|
1877
|
+
withLabel: obj.attrs.showLabel ?? true,
|
|
1878
|
+
radius: obj.attrs.radius ?? 1,
|
|
1879
|
+
fillColor: obj.attrs.color ?? "#22c55e",
|
|
1880
|
+
fillOpacity: obj.attrs.fillOpacity ?? 0.25,
|
|
1881
|
+
strokeColor: obj.attrs.color ?? "#16a34a",
|
|
1882
|
+
strokeWidth: 1.5,
|
|
1883
|
+
visible: obj.visible,
|
|
1884
|
+
fixed: obj.locked
|
|
1885
|
+
});
|
|
359
1886
|
}
|
|
360
1887
|
};
|
|
361
|
-
|
|
362
|
-
return () => window.removeEventListener("keydown", onKey, { capture: true });
|
|
363
|
-
}, [store, bindKeyboardShortcuts]);
|
|
364
|
-
}
|
|
365
|
-
var init_useEditorState = __esm({
|
|
366
|
-
"src/core/scene/hooks/useEditorState.ts"() {
|
|
1888
|
+
registerKind(def13);
|
|
367
1889
|
}
|
|
368
1890
|
});
|
|
369
1891
|
|
|
370
|
-
// src/core/scene/
|
|
371
|
-
var
|
|
372
|
-
|
|
373
|
-
|
|
1892
|
+
// src/core/scene/kinds/distance.ts
|
|
1893
|
+
var def14;
|
|
1894
|
+
var init_distance = __esm({
|
|
1895
|
+
"src/core/scene/kinds/distance.ts"() {
|
|
1896
|
+
init_registry();
|
|
1897
|
+
init_labelOf();
|
|
1898
|
+
def14 = {
|
|
1899
|
+
type: "distance",
|
|
1900
|
+
schemaVersion: 1,
|
|
1901
|
+
migrate: {},
|
|
1902
|
+
validate: (a) => {
|
|
1903
|
+
if (!a?.p1 || !a?.p2) throw new Error("distance: p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
1904
|
+
},
|
|
1905
|
+
dependsOn: (a) => [a.p1, a.p2],
|
|
1906
|
+
describe: (obj, state) => `Kho\u1EA3ng c\xE1ch ${labelOf(obj.attrs.p1, state)}${labelOf(obj.attrs.p2, state)}`,
|
|
1907
|
+
render: (obj, ctx) => {
|
|
1908
|
+
const board = ctx.jxg;
|
|
1909
|
+
const p1 = ctx.resolveRef(obj.attrs.p1);
|
|
1910
|
+
const p2 = ctx.resolveRef(obj.attrs.p2);
|
|
1911
|
+
const prefix = obj.attrs.prefix ?? "d = ";
|
|
1912
|
+
const precision = obj.attrs.precision ?? 2;
|
|
1913
|
+
return board.create("text", [
|
|
1914
|
+
() => (p1.X() + p2.X()) / 2,
|
|
1915
|
+
() => (p1.Y() + p2.Y()) / 2,
|
|
1916
|
+
() => `${prefix}${Math.hypot(p1.X() - p2.X(), p1.Y() - p2.Y()).toFixed(precision)}`
|
|
1917
|
+
], {
|
|
1918
|
+
fontSize: obj.attrs.fontSize ?? 14,
|
|
1919
|
+
strokeColor: obj.attrs.color ?? "#dc2626",
|
|
1920
|
+
anchorX: "middle",
|
|
1921
|
+
anchorY: "middle",
|
|
1922
|
+
visible: obj.visible,
|
|
1923
|
+
fixed: true
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
registerKind(def14);
|
|
374
1928
|
}
|
|
375
1929
|
});
|
|
376
1930
|
|
|
@@ -498,6 +2052,375 @@ var init_parser = __esm({
|
|
|
498
2052
|
}
|
|
499
2053
|
});
|
|
500
2054
|
|
|
2055
|
+
// src/core/scene/kinds/function2d.ts
|
|
2056
|
+
var def15;
|
|
2057
|
+
var init_function2d = __esm({
|
|
2058
|
+
"src/core/scene/kinds/function2d.ts"() {
|
|
2059
|
+
init_registry();
|
|
2060
|
+
init_parser();
|
|
2061
|
+
def15 = {
|
|
2062
|
+
type: "function2d",
|
|
2063
|
+
schemaVersion: 1,
|
|
2064
|
+
migrate: {},
|
|
2065
|
+
validate: (a) => {
|
|
2066
|
+
if (!a) throw new Error("function2d: attrs b\u1EAFt bu\u1ED9c");
|
|
2067
|
+
if (typeof a.expression !== "string" || !a.expression.trim()) {
|
|
2068
|
+
throw new Error("function2d: expression r\u1ED7ng");
|
|
2069
|
+
}
|
|
2070
|
+
const v = validate(a.expression);
|
|
2071
|
+
if (!v.ok) throw new Error(`function2d: expression invalid \u2014 ${v.error}`);
|
|
2072
|
+
if (typeof a.color !== "string") throw new Error("function2d: color b\u1EAFt bu\u1ED9c");
|
|
2073
|
+
if (typeof a.visible !== "boolean") throw new Error("function2d: visible b\u1EAFt bu\u1ED9c");
|
|
2074
|
+
if (a.domain) {
|
|
2075
|
+
if (a.domain.min >= a.domain.max) {
|
|
2076
|
+
throw new Error("function2d: domain min ph\u1EA3i < max");
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
},
|
|
2080
|
+
dependsOn: () => [],
|
|
2081
|
+
describe: (obj) => `${obj.label}(x) = ${obj.attrs.expression}`,
|
|
2082
|
+
render: (obj, ctx) => {
|
|
2083
|
+
const board = ctx.jxg;
|
|
2084
|
+
if (!obj.visible || !obj.attrs.visible) return null;
|
|
2085
|
+
const fn = compile(obj.attrs.expression, ctx.paramMap ?? {});
|
|
2086
|
+
if (typeof fn !== "function") return null;
|
|
2087
|
+
const view = ctx.defaults.view;
|
|
2088
|
+
const xMin = obj.attrs.domain?.min ?? view?.xMin ?? -10;
|
|
2089
|
+
const xMax = obj.attrs.domain?.max ?? view?.xMax ?? 10;
|
|
2090
|
+
return board.create("functiongraph", [fn, xMin, xMax], {
|
|
2091
|
+
strokeColor: obj.attrs.color,
|
|
2092
|
+
strokeWidth: 2,
|
|
2093
|
+
name: obj.label,
|
|
2094
|
+
withLabel: false,
|
|
2095
|
+
highlight: false,
|
|
2096
|
+
fixed: true
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
registerKind(def15);
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
|
|
2104
|
+
// src/core/scene/kinds/parameter.ts
|
|
2105
|
+
var def16;
|
|
2106
|
+
var init_parameter = __esm({
|
|
2107
|
+
"src/core/scene/kinds/parameter.ts"() {
|
|
2108
|
+
init_registry();
|
|
2109
|
+
def16 = {
|
|
2110
|
+
type: "parameter",
|
|
2111
|
+
schemaVersion: 1,
|
|
2112
|
+
migrate: {},
|
|
2113
|
+
validate: (a) => {
|
|
2114
|
+
if (!a) throw new Error("parameter: attrs b\u1EAFt bu\u1ED9c");
|
|
2115
|
+
if (typeof a.value !== "number" || typeof a.min !== "number" || typeof a.max !== "number") {
|
|
2116
|
+
throw new Error("parameter: value/min/max ph\u1EA3i l\xE0 number");
|
|
2117
|
+
}
|
|
2118
|
+
if (a.min >= a.max) throw new Error("parameter: min ph\u1EA3i < max");
|
|
2119
|
+
if (a.value < a.min || a.value > a.max) throw new Error("parameter: value ngo\xE0i [min, max]");
|
|
2120
|
+
if (typeof a.step !== "number" || a.step <= 0) throw new Error("parameter: step ph\u1EA3i > 0");
|
|
2121
|
+
},
|
|
2122
|
+
dependsOn: () => [],
|
|
2123
|
+
describe: (obj) => `${obj.label} = ${obj.attrs.value}`,
|
|
2124
|
+
render: () => null
|
|
2125
|
+
// Không render lên board
|
|
2126
|
+
};
|
|
2127
|
+
registerKind(def16);
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
// src/core/scene/kinds/pointOnCurve.ts
|
|
2132
|
+
var def17;
|
|
2133
|
+
var init_pointOnCurve = __esm({
|
|
2134
|
+
"src/core/scene/kinds/pointOnCurve.ts"() {
|
|
2135
|
+
init_registry();
|
|
2136
|
+
def17 = {
|
|
2137
|
+
type: "pointOnCurve",
|
|
2138
|
+
schemaVersion: 1,
|
|
2139
|
+
migrate: {},
|
|
2140
|
+
validate: (a) => {
|
|
2141
|
+
if (!a || typeof a.functionId !== "string" || !a.functionId) {
|
|
2142
|
+
throw new Error("pointOnCurve: functionId b\u1EAFt bu\u1ED9c");
|
|
2143
|
+
}
|
|
2144
|
+
if (typeof a.x !== "number" || !Number.isFinite(a.x)) {
|
|
2145
|
+
throw new Error("pointOnCurve: x ph\u1EA3i l\xE0 finite number");
|
|
2146
|
+
}
|
|
2147
|
+
},
|
|
2148
|
+
dependsOn: (a) => [a.functionId],
|
|
2149
|
+
describe: (obj) => `${obj.label} tr\xEAn ${obj.attrs.functionId} t\u1EA1i x=${obj.attrs.x.toFixed(3)}`,
|
|
2150
|
+
render: (obj, ctx) => {
|
|
2151
|
+
const board = ctx.jxg;
|
|
2152
|
+
const curve = ctx.resolveRef(obj.attrs.functionId);
|
|
2153
|
+
if (!curve) return null;
|
|
2154
|
+
return board.create("glider", [obj.attrs.x, 0, curve], {
|
|
2155
|
+
name: obj.label,
|
|
2156
|
+
size: 3,
|
|
2157
|
+
withLabel: obj.label !== "",
|
|
2158
|
+
fillColor: "#000",
|
|
2159
|
+
strokeColor: "#000"
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
registerKind(def17);
|
|
2164
|
+
}
|
|
2165
|
+
});
|
|
2166
|
+
|
|
2167
|
+
// src/core/scene/kinds/tangent2d.ts
|
|
2168
|
+
var def18;
|
|
2169
|
+
var init_tangent2d = __esm({
|
|
2170
|
+
"src/core/scene/kinds/tangent2d.ts"() {
|
|
2171
|
+
init_registry();
|
|
2172
|
+
def18 = {
|
|
2173
|
+
type: "tangent2d",
|
|
2174
|
+
schemaVersion: 1,
|
|
2175
|
+
migrate: {},
|
|
2176
|
+
validate: (a) => {
|
|
2177
|
+
if (!a || typeof a.pointId !== "string" || !a.pointId) {
|
|
2178
|
+
throw new Error("tangent2d: pointId b\u1EAFt bu\u1ED9c");
|
|
2179
|
+
}
|
|
2180
|
+
},
|
|
2181
|
+
dependsOn: (a) => [a.pointId],
|
|
2182
|
+
describe: (obj) => `Ti\u1EBFp tuy\u1EBFn t\u1EA1i ${obj.attrs.pointId}`,
|
|
2183
|
+
render: (obj, ctx) => {
|
|
2184
|
+
const board = ctx.jxg;
|
|
2185
|
+
const pt = ctx.resolveRef(obj.attrs.pointId);
|
|
2186
|
+
if (!pt) return null;
|
|
2187
|
+
return board.create("tangent", [pt], {
|
|
2188
|
+
strokeColor: "#65a30d",
|
|
2189
|
+
strokeWidth: 1.5,
|
|
2190
|
+
dash: 2,
|
|
2191
|
+
withLabel: false
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
2195
|
+
registerKind(def18);
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
// src/core/scene/expressions/evaluator.ts
|
|
2200
|
+
function scanRoots(fn, xMin, xMax, samples = DEFAULT_SAMPLES) {
|
|
2201
|
+
if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || xMin >= xMax) return [];
|
|
2202
|
+
const out = [];
|
|
2203
|
+
const step = (xMax - xMin) / samples;
|
|
2204
|
+
let prev = fn(xMin);
|
|
2205
|
+
for (let i = 1; i <= samples; i++) {
|
|
2206
|
+
const x = xMin + i * step;
|
|
2207
|
+
const curr = fn(x);
|
|
2208
|
+
if (!Number.isFinite(prev) || !Number.isFinite(curr)) {
|
|
2209
|
+
prev = curr;
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
if (prev * curr < 0) {
|
|
2213
|
+
const root = bisect(fn, x - step, x);
|
|
2214
|
+
if (Number.isFinite(root)) out.push(root);
|
|
2215
|
+
} else if (prev !== 0 && curr === 0) {
|
|
2216
|
+
out.push(x);
|
|
2217
|
+
}
|
|
2218
|
+
prev = curr;
|
|
2219
|
+
}
|
|
2220
|
+
return out;
|
|
2221
|
+
}
|
|
2222
|
+
function bisect(fn, lo, hi) {
|
|
2223
|
+
for (let i = 0; i < 50; i++) {
|
|
2224
|
+
const mid = (lo + hi) / 2;
|
|
2225
|
+
const fmid = fn(mid);
|
|
2226
|
+
if (!Number.isFinite(fmid)) break;
|
|
2227
|
+
if (Math.abs(fmid) < 1e-10) return mid;
|
|
2228
|
+
const flo = fn(lo);
|
|
2229
|
+
if (!Number.isFinite(flo)) break;
|
|
2230
|
+
if (flo * fmid <= 0) hi = mid;
|
|
2231
|
+
else lo = mid;
|
|
2232
|
+
}
|
|
2233
|
+
return (lo + hi) / 2;
|
|
2234
|
+
}
|
|
2235
|
+
function scanExtrema(fn, xMin, xMax, samples = DEFAULT_SAMPLES) {
|
|
2236
|
+
if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || xMin >= xMax) return [];
|
|
2237
|
+
const out = [];
|
|
2238
|
+
const step = (xMax - xMin) / samples;
|
|
2239
|
+
const xs = [];
|
|
2240
|
+
const ys = [];
|
|
2241
|
+
for (let i = 0; i <= samples + 1; i++) {
|
|
2242
|
+
const x = xMin + (i - 1) * step;
|
|
2243
|
+
xs.push(x);
|
|
2244
|
+
ys.push(fn(x));
|
|
2245
|
+
}
|
|
2246
|
+
let prevSign = 0;
|
|
2247
|
+
let prevSignIdx = 1;
|
|
2248
|
+
for (let i = 1; i <= samples; i++) {
|
|
2249
|
+
const d = ys[i + 1] - ys[i - 1];
|
|
2250
|
+
if (!Number.isFinite(d)) continue;
|
|
2251
|
+
const sign = d > 0 ? 1 : d < 0 ? -1 : 0;
|
|
2252
|
+
if (sign === 0) continue;
|
|
2253
|
+
if (prevSign !== 0 && sign !== prevSign) {
|
|
2254
|
+
const type = prevSign > 0 ? "max" : "min";
|
|
2255
|
+
const midI = Math.round((prevSignIdx + i) / 2);
|
|
2256
|
+
out.push({ x: xs[midI], y: ys[midI], type });
|
|
2257
|
+
}
|
|
2258
|
+
prevSign = sign;
|
|
2259
|
+
prevSignIdx = i;
|
|
2260
|
+
}
|
|
2261
|
+
return out;
|
|
2262
|
+
}
|
|
2263
|
+
var DEFAULT_SAMPLES;
|
|
2264
|
+
var init_evaluator = __esm({
|
|
2265
|
+
"src/core/scene/expressions/evaluator.ts"() {
|
|
2266
|
+
DEFAULT_SAMPLES = 1e3;
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
|
|
2270
|
+
// src/core/scene/kinds/extremum2d.ts
|
|
2271
|
+
var def19;
|
|
2272
|
+
var init_extremum2d = __esm({
|
|
2273
|
+
"src/core/scene/kinds/extremum2d.ts"() {
|
|
2274
|
+
init_registry();
|
|
2275
|
+
init_evaluator();
|
|
2276
|
+
init_parser();
|
|
2277
|
+
def19 = {
|
|
2278
|
+
type: "extremum2d",
|
|
2279
|
+
schemaVersion: 1,
|
|
2280
|
+
migrate: {},
|
|
2281
|
+
validate: (a) => {
|
|
2282
|
+
if (!a || typeof a.functionId !== "string" || !a.functionId) {
|
|
2283
|
+
throw new Error("extremum2d: functionId b\u1EAFt bu\u1ED9c");
|
|
2284
|
+
}
|
|
2285
|
+
if (!a.interval || a.interval.min >= a.interval.max) {
|
|
2286
|
+
throw new Error("extremum2d: interval min ph\u1EA3i < max");
|
|
2287
|
+
}
|
|
2288
|
+
if (a.mode !== "max" && a.mode !== "min") {
|
|
2289
|
+
throw new Error('extremum2d: mode ph\u1EA3i l\xE0 "max" ho\u1EB7c "min"');
|
|
2290
|
+
}
|
|
2291
|
+
},
|
|
2292
|
+
dependsOn: (a) => [a.functionId],
|
|
2293
|
+
describe: (obj) => `${obj.attrs.mode === "max" ? "C\u1EF1c \u0111\u1EA1i" : "C\u1EF1c ti\u1EC3u"} c\u1EE7a ${obj.attrs.functionId} trong [${obj.attrs.interval.min}, ${obj.attrs.interval.max}]`,
|
|
2294
|
+
render: (obj, ctx) => {
|
|
2295
|
+
const board = ctx.jxg;
|
|
2296
|
+
const expr = ctx.defaults._functionExpr?.[obj.attrs.functionId];
|
|
2297
|
+
if (!expr) return null;
|
|
2298
|
+
const fn = compile(expr, ctx.paramMap ?? {});
|
|
2299
|
+
if (typeof fn !== "function") return null;
|
|
2300
|
+
const extrema = scanExtrema(fn, obj.attrs.interval.min, obj.attrs.interval.max).filter((e) => e.type === obj.attrs.mode);
|
|
2301
|
+
return extrema.map((e) => board.create("point", [e.x, e.y], {
|
|
2302
|
+
name: obj.label,
|
|
2303
|
+
size: 3,
|
|
2304
|
+
fillColor: "#dc2626",
|
|
2305
|
+
strokeColor: "#dc2626",
|
|
2306
|
+
withLabel: obj.label !== ""
|
|
2307
|
+
}));
|
|
2308
|
+
}
|
|
2309
|
+
};
|
|
2310
|
+
registerKind(def19);
|
|
2311
|
+
}
|
|
2312
|
+
});
|
|
2313
|
+
|
|
2314
|
+
// src/core/scene/kinds/root2d.ts
|
|
2315
|
+
var def20;
|
|
2316
|
+
var init_root2d = __esm({
|
|
2317
|
+
"src/core/scene/kinds/root2d.ts"() {
|
|
2318
|
+
init_registry();
|
|
2319
|
+
init_evaluator();
|
|
2320
|
+
init_parser();
|
|
2321
|
+
def20 = {
|
|
2322
|
+
type: "root2d",
|
|
2323
|
+
schemaVersion: 1,
|
|
2324
|
+
migrate: {},
|
|
2325
|
+
validate: (a) => {
|
|
2326
|
+
if (!a || typeof a.functionId !== "string" || !a.functionId) {
|
|
2327
|
+
throw new Error("root2d: functionId b\u1EAFt bu\u1ED9c");
|
|
2328
|
+
}
|
|
2329
|
+
if (!a.interval || a.interval.min >= a.interval.max) {
|
|
2330
|
+
throw new Error("root2d: interval min ph\u1EA3i < max");
|
|
2331
|
+
}
|
|
2332
|
+
},
|
|
2333
|
+
dependsOn: (a) => [a.functionId],
|
|
2334
|
+
describe: (obj) => `Nghi\u1EC7m c\u1EE7a ${obj.attrs.functionId} trong [${obj.attrs.interval.min}, ${obj.attrs.interval.max}]`,
|
|
2335
|
+
render: (obj, ctx) => {
|
|
2336
|
+
const board = ctx.jxg;
|
|
2337
|
+
const expr = ctx.defaults._functionExpr?.[obj.attrs.functionId];
|
|
2338
|
+
if (!expr) return null;
|
|
2339
|
+
const fn = compile(expr, ctx.paramMap ?? {});
|
|
2340
|
+
if (typeof fn !== "function") return null;
|
|
2341
|
+
const roots = scanRoots(fn, obj.attrs.interval.min, obj.attrs.interval.max);
|
|
2342
|
+
return roots.map((x) => board.create("point", [x, 0], {
|
|
2343
|
+
name: obj.label,
|
|
2344
|
+
size: 3,
|
|
2345
|
+
fillColor: "#dc2626",
|
|
2346
|
+
strokeColor: "#dc2626",
|
|
2347
|
+
withLabel: obj.label !== ""
|
|
2348
|
+
}));
|
|
2349
|
+
}
|
|
2350
|
+
};
|
|
2351
|
+
registerKind(def20);
|
|
2352
|
+
}
|
|
2353
|
+
});
|
|
2354
|
+
|
|
2355
|
+
// src/core/scene/kinds/slope2d.ts
|
|
2356
|
+
var def21;
|
|
2357
|
+
var init_slope2d = __esm({
|
|
2358
|
+
"src/core/scene/kinds/slope2d.ts"() {
|
|
2359
|
+
init_registry();
|
|
2360
|
+
def21 = {
|
|
2361
|
+
type: "slope2d",
|
|
2362
|
+
schemaVersion: 1,
|
|
2363
|
+
migrate: {},
|
|
2364
|
+
validate: (a) => {
|
|
2365
|
+
if (!a || typeof a.pointId !== "string" || !a.pointId) {
|
|
2366
|
+
throw new Error("slope2d: pointId b\u1EAFt bu\u1ED9c");
|
|
2367
|
+
}
|
|
2368
|
+
},
|
|
2369
|
+
dependsOn: (a) => [a.pointId],
|
|
2370
|
+
describe: (obj) => `Slope t\u1EA1i ${obj.attrs.pointId}`,
|
|
2371
|
+
render: (obj, ctx) => {
|
|
2372
|
+
const board = ctx.jxg;
|
|
2373
|
+
const pt = ctx.resolveRef(obj.attrs.pointId);
|
|
2374
|
+
if (!pt) return null;
|
|
2375
|
+
return board.create("slopetriangle", [pt], {
|
|
2376
|
+
name: obj.label,
|
|
2377
|
+
withLabel: true,
|
|
2378
|
+
fillColor: "#9333ea",
|
|
2379
|
+
strokeColor: "#9333ea",
|
|
2380
|
+
fillOpacity: 0.2
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2384
|
+
registerKind(def21);
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
|
|
2388
|
+
// src/core/scene/kinds/index.ts
|
|
2389
|
+
var init_kinds = __esm({
|
|
2390
|
+
"src/core/scene/kinds/index.ts"() {
|
|
2391
|
+
init_point3d();
|
|
2392
|
+
init_segment3d();
|
|
2393
|
+
init_line3d();
|
|
2394
|
+
init_ray3d();
|
|
2395
|
+
init_vector3d();
|
|
2396
|
+
init_plane3d();
|
|
2397
|
+
init_polygon3d();
|
|
2398
|
+
init_sphere3d();
|
|
2399
|
+
init_polyhedron3d();
|
|
2400
|
+
init_cylinder3d();
|
|
2401
|
+
init_cone3d();
|
|
2402
|
+
init_point();
|
|
2403
|
+
init_segment();
|
|
2404
|
+
init_line();
|
|
2405
|
+
init_ray();
|
|
2406
|
+
init_vector();
|
|
2407
|
+
init_circle();
|
|
2408
|
+
init_arc();
|
|
2409
|
+
init_sector();
|
|
2410
|
+
init_polygon();
|
|
2411
|
+
init_intersection();
|
|
2412
|
+
init_angle();
|
|
2413
|
+
init_distance();
|
|
2414
|
+
init_function2d();
|
|
2415
|
+
init_parameter();
|
|
2416
|
+
init_pointOnCurve();
|
|
2417
|
+
init_tangent2d();
|
|
2418
|
+
init_extremum2d();
|
|
2419
|
+
init_root2d();
|
|
2420
|
+
init_slope2d();
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
|
|
501
2424
|
// src/core/scene/index.ts
|
|
502
2425
|
var init_scene = __esm({
|
|
503
2426
|
"src/core/scene/index.ts"() {
|
|
@@ -505,6 +2428,7 @@ var init_scene = __esm({
|
|
|
505
2428
|
init_store();
|
|
506
2429
|
init_selectors();
|
|
507
2430
|
init_hooks();
|
|
2431
|
+
init_kinds();
|
|
508
2432
|
}
|
|
509
2433
|
});
|
|
510
2434
|
|
|
@@ -662,8 +2586,8 @@ var init_JxgRenderer = __esm({
|
|
|
662
2586
|
}
|
|
663
2587
|
create(obj) {
|
|
664
2588
|
try {
|
|
665
|
-
const
|
|
666
|
-
const el =
|
|
2589
|
+
const def22 = getKind(obj.kind);
|
|
2590
|
+
const el = def22.render(obj, this.ctx());
|
|
667
2591
|
this.elements.set(obj.id, el);
|
|
668
2592
|
this.attachFreePointDragSync(obj, el);
|
|
669
2593
|
} catch (err) {
|
|
@@ -741,11 +2665,11 @@ var init_JxgRenderer = __esm({
|
|
|
741
2665
|
continue;
|
|
742
2666
|
}
|
|
743
2667
|
if (Object.is(old, cur)) continue;
|
|
744
|
-
const
|
|
2668
|
+
const def22 = getKind(cur.kind);
|
|
745
2669
|
const existing = this.elements.get(id);
|
|
746
|
-
if (
|
|
2670
|
+
if (def22.update && existing) {
|
|
747
2671
|
try {
|
|
748
|
-
|
|
2672
|
+
def22.update(cur, old, this.ctx(), existing);
|
|
749
2673
|
continue;
|
|
750
2674
|
} catch (err) {
|
|
751
2675
|
console.warn(`[scene/render/2d] update fail, recreate:`, err);
|
|
@@ -770,7 +2694,7 @@ var init_JxgRenderer = __esm({
|
|
|
770
2694
|
if (obj.kind !== "function2d") continue;
|
|
771
2695
|
const expr = obj.attrs.expression;
|
|
772
2696
|
const refs = collectFreeVars(expr);
|
|
773
|
-
if (refs.some((
|
|
2697
|
+
if (refs.some((r) => changedParams.has(r))) {
|
|
774
2698
|
this.remove(id);
|
|
775
2699
|
this.create(obj);
|
|
776
2700
|
}
|
|
@@ -1589,8 +3513,8 @@ function useToolHoverTooltip() {
|
|
|
1589
3513
|
const showHover = React18.useCallback((el, t) => {
|
|
1590
3514
|
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
|
|
1591
3515
|
hoverTimerRef.current = setTimeout(() => {
|
|
1592
|
-
const
|
|
1593
|
-
setHover({ label: t.label, hint: t.hint, x:
|
|
3516
|
+
const r = el.getBoundingClientRect();
|
|
3517
|
+
setHover({ label: t.label, hint: t.hint, x: r.right, y: r.top + r.height / 2 });
|
|
1594
3518
|
}, TOOLTIP_DELAY_MS);
|
|
1595
3519
|
}, []);
|
|
1596
3520
|
const hideHover = React18.useCallback(() => {
|
|
@@ -2152,47 +4076,20 @@ var init_StampLeftPanel = __esm({
|
|
|
2152
4076
|
}
|
|
2153
4077
|
});
|
|
2154
4078
|
function UndoIcon3() {
|
|
2155
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "
|
|
2156
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
2157
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3
|
|
4079
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
4080
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "3 7 3 13 9 13" }),
|
|
4081
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 13a9 9 0 1 0 2.13-9.36L3 7" })
|
|
2158
4082
|
] });
|
|
2159
4083
|
}
|
|
2160
4084
|
function RedoIcon3() {
|
|
2161
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "
|
|
2162
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
2163
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "
|
|
4085
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
4086
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "21 7 21 13 15 13" }),
|
|
4087
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20.49 13a9 9 0 1 1-2.12-9.36L21 7" })
|
|
2164
4088
|
] });
|
|
2165
4089
|
}
|
|
2166
|
-
var GeometryIconHeader;
|
|
4090
|
+
var C_POINT, C_CONSTRUCT, C_FILL, C_ARC, Icon, GeometryIconHeader;
|
|
2167
4091
|
var init_icons = __esm({
|
|
2168
4092
|
"src/stamps/geometry-2d/editor/icons.tsx"() {
|
|
2169
|
-
GeometryIconHeader = /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
2170
|
-
/* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "4,20 20,20 12,5" }),
|
|
2171
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4", cy: "20", r: "1.5", fill: "currentColor", stroke: "none" }),
|
|
2172
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "20", cy: "20", r: "1.5", fill: "currentColor", stroke: "none" }),
|
|
2173
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "5", r: "1.5", fill: "currentColor", stroke: "none" })
|
|
2174
|
-
] });
|
|
2175
|
-
}
|
|
2176
|
-
});
|
|
2177
|
-
function letterForGroup(g) {
|
|
2178
|
-
const idx = GROUP_ORDER.indexOf(g);
|
|
2179
|
-
return idx >= 0 ? String.fromCharCode(A_CODE + idx) : "";
|
|
2180
|
-
}
|
|
2181
|
-
function objKind(obj) {
|
|
2182
|
-
if (!obj) return "other";
|
|
2183
|
-
const ec = typeof obj.elementClass === "number" ? obj.elementClass : null;
|
|
2184
|
-
if (ec === 1) return "point";
|
|
2185
|
-
if (ec === 2) return "line";
|
|
2186
|
-
if (ec === 3) return "circle";
|
|
2187
|
-
const e = (obj.elType || obj.type || "").toString().toLowerCase();
|
|
2188
|
-
if (e === "point" || e === "glider" || e === "midpoint" || e === "intersection" || e === "otherintersection" || e === "reflection" || e === "mirrorpoint" || e === "mirrorelement" || e === "orthogonalprojection" || e === "parallelpoint") return "point";
|
|
2189
|
-
if (e === "line" || e === "segment" || e === "arrow" || e === "axis" || e === "normal" || e === "parallel" || e === "perpendicular" || e === "tangent" || e === "bisector" || e === "perpendicularsegment") return "line";
|
|
2190
|
-
if (e === "circle" || e === "circumcircle") return "circle";
|
|
2191
|
-
return "other";
|
|
2192
|
-
}
|
|
2193
|
-
var C_POINT, C_CONSTRUCT, C_FILL, C_ARC, Icon, TOOLS, GROUP_LABELS, GROUP_ORDER, A_CODE;
|
|
2194
|
-
var init_tools = __esm({
|
|
2195
|
-
"src/stamps/geometry-2d/editor/tools.tsx"() {
|
|
2196
4093
|
C_POINT = "#2563eb";
|
|
2197
4094
|
C_CONSTRUCT = "#dc2626";
|
|
2198
4095
|
C_FILL = "#f59e0b";
|
|
@@ -2460,6 +4357,36 @@ var init_tools = __esm({
|
|
|
2460
4357
|
/* @__PURE__ */ jsxRuntime.jsx("text", { x: "10.5", y: "10.5", fontSize: "8", fontFamily: "serif", fontStyle: "italic", fontWeight: "700", fill: "currentColor", children: "k" })
|
|
2461
4358
|
] })
|
|
2462
4359
|
};
|
|
4360
|
+
GeometryIconHeader = /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
4361
|
+
/* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "4,20 20,20 12,5" }),
|
|
4362
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4", cy: "20", r: "1.5", fill: "currentColor", stroke: "none" }),
|
|
4363
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "20", cy: "20", r: "1.5", fill: "currentColor", stroke: "none" }),
|
|
4364
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "5", r: "1.5", fill: "currentColor", stroke: "none" })
|
|
4365
|
+
] });
|
|
4366
|
+
}
|
|
4367
|
+
});
|
|
4368
|
+
|
|
4369
|
+
// src/stamps/geometry-2d/editor/tools.tsx
|
|
4370
|
+
function letterForGroup(g) {
|
|
4371
|
+
const idx = GROUP_ORDER.indexOf(g);
|
|
4372
|
+
return idx >= 0 ? String.fromCharCode(A_CODE + idx) : "";
|
|
4373
|
+
}
|
|
4374
|
+
function objKind(obj) {
|
|
4375
|
+
if (!obj) return "other";
|
|
4376
|
+
const ec = typeof obj.elementClass === "number" ? obj.elementClass : null;
|
|
4377
|
+
if (ec === 1) return "point";
|
|
4378
|
+
if (ec === 2) return "line";
|
|
4379
|
+
if (ec === 3) return "circle";
|
|
4380
|
+
const e = (obj.elType || obj.type || "").toString().toLowerCase();
|
|
4381
|
+
if (e === "point" || e === "glider" || e === "midpoint" || e === "intersection" || e === "otherintersection" || e === "reflection" || e === "mirrorpoint" || e === "mirrorelement" || e === "orthogonalprojection" || e === "parallelpoint") return "point";
|
|
4382
|
+
if (e === "line" || e === "segment" || e === "arrow" || e === "axis" || e === "normal" || e === "parallel" || e === "perpendicular" || e === "tangent" || e === "bisector" || e === "perpendicularsegment") return "line";
|
|
4383
|
+
if (e === "circle" || e === "circumcircle") return "circle";
|
|
4384
|
+
return "other";
|
|
4385
|
+
}
|
|
4386
|
+
var TOOLS, GROUP_LABELS, GROUP_ORDER, A_CODE;
|
|
4387
|
+
var init_tools = __esm({
|
|
4388
|
+
"src/stamps/geometry-2d/editor/tools.tsx"() {
|
|
4389
|
+
init_icons();
|
|
2463
4390
|
TOOLS = [
|
|
2464
4391
|
{ key: "move", label: "Di chuy\u1EC3n", hint: "K\xE9o \u0111i\u1EC3m ho\u1EB7c xoay n\u1EC1n", icon: Icon.cursor, group: "move", needs: 0 },
|
|
2465
4392
|
{ key: "select", label: "Ch\u1ECDn", hint: "Click \u0111\u1EC3 ch\u1ECDn 1 / Shift+click \u0111\u1EC3 b\u1ECF th\xEAm / K\xE9o n\u1EC1n \u0111\u1EC3 khoanh v\xF9ng / DEL \u0111\u1EC3 xo\xE1", icon: Icon.select, group: "move", needs: 0 },
|
|
@@ -2687,7 +4614,7 @@ function handlePointTool(ctx, _e, x, y, hits) {
|
|
|
2687
4614
|
}
|
|
2688
4615
|
dispatchAddFreePoint(ctx, x, y);
|
|
2689
4616
|
}
|
|
2690
|
-
var
|
|
4617
|
+
var init_point2 = __esm({
|
|
2691
4618
|
"src/stamps/geometry-2d/editor/handlers/pointerDown/point.ts"() {
|
|
2692
4619
|
init_tools();
|
|
2693
4620
|
init_safeJsx();
|
|
@@ -2783,7 +4710,7 @@ function handlePolygonTool(ctx, t, toolDef, e, x, y, bestHit) {
|
|
|
2783
4710
|
ctx.setPendingCount(ctx.pendingIdsRef.current.length);
|
|
2784
4711
|
return true;
|
|
2785
4712
|
}
|
|
2786
|
-
var
|
|
4713
|
+
var init_polygon2 = __esm({
|
|
2787
4714
|
"src/stamps/geometry-2d/editor/handlers/pointerDown/polygon.ts"() {
|
|
2788
4715
|
init_tools();
|
|
2789
4716
|
init_safeJsx();
|
|
@@ -2797,11 +4724,11 @@ function classifyPointVsCircle(point, circle) {
|
|
|
2797
4724
|
const dx = point.X() - circle.center.X();
|
|
2798
4725
|
const dy = point.Y() - circle.center.Y();
|
|
2799
4726
|
const d = Math.hypot(dx, dy);
|
|
2800
|
-
const
|
|
2801
|
-
if (!Number.isFinite(d) || !Number.isFinite(
|
|
2802
|
-
const eps = Math.max(1e-9, 1e-6 *
|
|
2803
|
-
if (Math.abs(d -
|
|
2804
|
-
return d <
|
|
4727
|
+
const r = typeof circle.Radius === "function" ? circle.Radius() : Number(circle.radius);
|
|
4728
|
+
if (!Number.isFinite(d) || !Number.isFinite(r)) return "inside";
|
|
4729
|
+
const eps = Math.max(1e-9, 1e-6 * r);
|
|
4730
|
+
if (Math.abs(d - r) <= eps) return "on";
|
|
4731
|
+
return d < r ? "inside" : "outside";
|
|
2805
4732
|
}
|
|
2806
4733
|
var init_classifyPointVsCircle = __esm({
|
|
2807
4734
|
"src/stamps/geometry-2d/editor/handlers/classifyPointVsCircle.ts"() {
|
|
@@ -3477,9 +5404,9 @@ var init_pointerDown = __esm({
|
|
|
3477
5404
|
init_tools();
|
|
3478
5405
|
init_move();
|
|
3479
5406
|
init_select();
|
|
3480
|
-
|
|
5407
|
+
init_point2();
|
|
3481
5408
|
init_singleTarget();
|
|
3482
|
-
|
|
5409
|
+
init_polygon2();
|
|
3483
5410
|
init_multiClick();
|
|
3484
5411
|
}
|
|
3485
5412
|
});
|
|
@@ -4214,9 +6141,9 @@ function useJxgSceneIdMap({ store, rendererRef }) {
|
|
|
4214
6141
|
const jxgIdToSceneRef = React18.useRef(/* @__PURE__ */ new Map());
|
|
4215
6142
|
React18.useEffect(() => {
|
|
4216
6143
|
const rebuild = () => {
|
|
4217
|
-
const
|
|
4218
|
-
if (!
|
|
4219
|
-
const elements =
|
|
6144
|
+
const r = rendererRef.current;
|
|
6145
|
+
if (!r) return;
|
|
6146
|
+
const elements = r.elements;
|
|
4220
6147
|
const next = /* @__PURE__ */ new Map();
|
|
4221
6148
|
if (elements) {
|
|
4222
6149
|
for (const [sid, jxg] of elements) {
|
|
@@ -4557,7 +6484,6 @@ var init_MiniBoard = __esm({
|
|
|
4557
6484
|
deleteSelection,
|
|
4558
6485
|
emitTransform
|
|
4559
6486
|
}),
|
|
4560
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4561
6487
|
[store, clearSelection, deleteSelection, clearPending, emitTransform]
|
|
4562
6488
|
);
|
|
4563
6489
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -5965,9 +7891,9 @@ var init_host = __esm({
|
|
|
5965
7891
|
const [canUndo, setCanUndo] = React18.useState(false);
|
|
5966
7892
|
const [canRedo, setCanRedo] = React18.useState(false);
|
|
5967
7893
|
const [selectedObjectId, setSelectedObjectId] = React18.useState(void 0);
|
|
5968
|
-
const handleHistoryChange = React18.useCallback((u,
|
|
7894
|
+
const handleHistoryChange = React18.useCallback((u, r) => {
|
|
5969
7895
|
setCanUndo(u);
|
|
5970
|
-
setCanRedo(
|
|
7896
|
+
setCanRedo(r);
|
|
5971
7897
|
}, []);
|
|
5972
7898
|
const handleUndo = React18.useCallback(() => sceneStore.undo(), [sceneStore]);
|
|
5973
7899
|
const handleRedo = React18.useCallback(() => sceneStore.redo(), [sceneStore]);
|
|
@@ -6685,8 +8611,8 @@ var init_JxgRenderer3D = __esm({
|
|
|
6685
8611
|
}
|
|
6686
8612
|
create(obj) {
|
|
6687
8613
|
try {
|
|
6688
|
-
const
|
|
6689
|
-
const el =
|
|
8614
|
+
const def22 = getKind(obj.kind);
|
|
8615
|
+
const el = def22.render(obj, this.ctx());
|
|
6690
8616
|
this.elements.set(obj.id, el);
|
|
6691
8617
|
} catch (err) {
|
|
6692
8618
|
console.warn(`[scene/render] kh\xF4ng render \u0111\u01B0\u1EE3c ${obj.kind} id="${obj.id}":`, err);
|
|
@@ -6742,16 +8668,16 @@ var init_JxgRenderer3D = __esm({
|
|
|
6742
8668
|
if (Object.is(old, cur)) {
|
|
6743
8669
|
continue;
|
|
6744
8670
|
}
|
|
6745
|
-
let
|
|
8671
|
+
let def22;
|
|
6746
8672
|
try {
|
|
6747
|
-
|
|
8673
|
+
def22 = getKind(cur.kind);
|
|
6748
8674
|
} catch {
|
|
6749
8675
|
continue;
|
|
6750
8676
|
}
|
|
6751
8677
|
const existing = this.elements.get(id);
|
|
6752
|
-
if (
|
|
8678
|
+
if (def22.update && existing !== void 0) {
|
|
6753
8679
|
try {
|
|
6754
|
-
|
|
8680
|
+
def22.update(cur, old, this.ctx(), existing);
|
|
6755
8681
|
continue;
|
|
6756
8682
|
} catch (err) {
|
|
6757
8683
|
console.warn(`[scene/render] update fail, recreate id="${id}":`, err);
|
|
@@ -7093,7 +9019,7 @@ function buildPoint(args, store) {
|
|
|
7093
9019
|
return addPoint(store, c);
|
|
7094
9020
|
}
|
|
7095
9021
|
var buildPointOnObject;
|
|
7096
|
-
var
|
|
9022
|
+
var init_point3 = __esm({
|
|
7097
9023
|
"src/stamps/geometry-3d/editor/tools/handlers/point.ts"() {
|
|
7098
9024
|
init_ensurePoint();
|
|
7099
9025
|
buildPointOnObject = buildPoint;
|
|
@@ -7148,7 +9074,7 @@ function buildVector(args, store) {
|
|
|
7148
9074
|
if (!from || !to || from === to) return null;
|
|
7149
9075
|
return addDerived(store, "vector3d", "v", { from, to });
|
|
7150
9076
|
}
|
|
7151
|
-
var
|
|
9077
|
+
var init_segment2 = __esm({
|
|
7152
9078
|
"src/stamps/geometry-3d/editor/tools/handlers/segment.ts"() {
|
|
7153
9079
|
init_scene();
|
|
7154
9080
|
init_ensurePoint();
|
|
@@ -7175,7 +9101,7 @@ function buildPolygon(args, store) {
|
|
|
7175
9101
|
store.dispatch({ type: "ADD", payload: { obj } });
|
|
7176
9102
|
return id;
|
|
7177
9103
|
}
|
|
7178
|
-
var
|
|
9104
|
+
var init_polygon3 = __esm({
|
|
7179
9105
|
"src/stamps/geometry-3d/editor/tools/handlers/polygon.ts"() {
|
|
7180
9106
|
init_scene();
|
|
7181
9107
|
init_ensurePoint();
|
|
@@ -7288,8 +9214,8 @@ function constraintToWorld(c, state) {
|
|
|
7288
9214
|
const radius = norm(sub(surface, center));
|
|
7289
9215
|
const x = center[0] + radius * Math.sin(c.phi) * Math.cos(c.theta);
|
|
7290
9216
|
const y = center[1] + radius * Math.sin(c.phi) * Math.sin(c.theta);
|
|
7291
|
-
const
|
|
7292
|
-
return [x, y,
|
|
9217
|
+
const z = center[2] + radius * Math.cos(c.phi);
|
|
9218
|
+
return [x, y, z];
|
|
7293
9219
|
}
|
|
7294
9220
|
}
|
|
7295
9221
|
}
|
|
@@ -7691,9 +9617,9 @@ var init_cone = __esm({
|
|
|
7691
9617
|
var stubBuild, ALL_SURFACES, OBJECT_ONLY, NO_SURFACE, TOOLS2;
|
|
7692
9618
|
var init_spec = __esm({
|
|
7693
9619
|
"src/stamps/geometry-3d/editor/tools/spec.ts"() {
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
9620
|
+
init_point3();
|
|
9621
|
+
init_segment2();
|
|
9622
|
+
init_polygon3();
|
|
7697
9623
|
init_plane();
|
|
7698
9624
|
init_pyramid();
|
|
7699
9625
|
init_prism();
|
|
@@ -8211,8 +10137,8 @@ function hitTest(screen, view, state) {
|
|
|
8211
10137
|
const relX = bestSphere.world[0] - center[0];
|
|
8212
10138
|
const relY = bestSphere.world[1] - center[1];
|
|
8213
10139
|
const relZ = bestSphere.world[2] - center[2];
|
|
8214
|
-
const
|
|
8215
|
-
const phi =
|
|
10140
|
+
const r = Math.hypot(relX, relY, relZ);
|
|
10141
|
+
const phi = r === 0 ? 0 : Math.acos(relZ / r);
|
|
8216
10142
|
const theta = Math.atan2(relY, relX);
|
|
8217
10143
|
return { kind: "onSphere", sphereId: bestSphere.id, theta, phi, world: bestSphere.world };
|
|
8218
10144
|
}
|
|
@@ -9297,7 +11223,7 @@ var init_icons2 = __esm({
|
|
|
9297
11223
|
children
|
|
9298
11224
|
}
|
|
9299
11225
|
);
|
|
9300
|
-
dot4 = (cx, cy,
|
|
11226
|
+
dot4 = (cx, cy, r = 1.4) => /* @__PURE__ */ jsxRuntime.jsx("circle", { cx, cy, r, fill: "currentColor", stroke: "none" });
|
|
9301
11227
|
ToolIcons = {
|
|
9302
11228
|
move: wrap(
|
|
9303
11229
|
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 4 L5 14 L8 11 L10 16 L13 15 L11 10 L15 10 Z" }) })
|
|
@@ -9495,9 +11421,9 @@ var init_host3 = __esm({
|
|
|
9495
11421
|
const [canUndo, setCanUndo] = React18.useState(false);
|
|
9496
11422
|
const [canRedo, setCanRedo] = React18.useState(false);
|
|
9497
11423
|
const [selectedObjectId, setSelectedObjectId] = React18.useState(void 0);
|
|
9498
|
-
const handleHistoryChange = React18.useCallback((u,
|
|
11424
|
+
const handleHistoryChange = React18.useCallback((u, r) => {
|
|
9499
11425
|
setCanUndo(u);
|
|
9500
|
-
setCanRedo(
|
|
11426
|
+
setCanRedo(r);
|
|
9501
11427
|
}, []);
|
|
9502
11428
|
const handleObjectSelect = React18.useCallback((id) => {
|
|
9503
11429
|
setSelectedObjectId(id ?? void 0);
|
|
@@ -10759,9 +12685,9 @@ var init_host4 = __esm({
|
|
|
10759
12685
|
const [showGrid, setShowGridState] = React18.useState(initialView?.showGrid ?? true);
|
|
10760
12686
|
const [canUndo, setCanUndo] = React18.useState(false);
|
|
10761
12687
|
const [canRedo, setCanRedo] = React18.useState(false);
|
|
10762
|
-
const handleHistoryChange = React18.useCallback((u,
|
|
12688
|
+
const handleHistoryChange = React18.useCallback((u, r) => {
|
|
10763
12689
|
setCanUndo(u);
|
|
10764
|
-
setCanRedo(
|
|
12690
|
+
setCanRedo(r);
|
|
10765
12691
|
}, []);
|
|
10766
12692
|
const handleUndo = React18.useCallback(() => sceneStore.undo(), [sceneStore]);
|
|
10767
12693
|
const handleRedo = React18.useCallback(() => sceneStore.redo(), [sceneStore]);
|
|
@@ -12119,7 +14045,6 @@ function useStampDoubleClick({ enabled, stamps, onOpen }) {
|
|
|
12119
14045
|
elementId: null
|
|
12120
14046
|
});
|
|
12121
14047
|
return React18.useCallback(
|
|
12122
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12123
14048
|
(_activeTool, pointerDownState) => {
|
|
12124
14049
|
if (!enabled) return;
|
|
12125
14050
|
const hitElement = pointerDownState?.hit?.element;
|
|
@@ -12972,11 +14897,8 @@ function useScenePersist(opts) {
|
|
|
12972
14897
|
api.addFiles(
|
|
12973
14898
|
entries.map(([id, f]) => ({
|
|
12974
14899
|
id,
|
|
12975
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12976
14900
|
dataURL: f.dataURL,
|
|
12977
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12978
14901
|
mimeType: f.mimeType,
|
|
12979
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12980
14902
|
created: f.created ?? Date.now()
|
|
12981
14903
|
}))
|
|
12982
14904
|
);
|
|
@@ -12998,11 +14920,8 @@ function useScenePersist(opts) {
|
|
|
12998
14920
|
api.addFiles(
|
|
12999
14921
|
entries.map(([id, f]) => ({
|
|
13000
14922
|
id,
|
|
13001
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13002
14923
|
dataURL: f.dataURL,
|
|
13003
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13004
14924
|
mimeType: f.mimeType,
|
|
13005
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13006
14925
|
created: f.created ?? Date.now()
|
|
13007
14926
|
}))
|
|
13008
14927
|
);
|
|
@@ -13115,7 +15034,6 @@ function Whiteboard({
|
|
|
13115
15034
|
const handledCropIdRef = React18.useRef(null);
|
|
13116
15035
|
const prevExcalidrawToolRef = React18.useRef("selection");
|
|
13117
15036
|
const handleChange = React18.useCallback(
|
|
13118
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13119
15037
|
(elements, appState, files) => {
|
|
13120
15038
|
syncThemeFromAppState(appState);
|
|
13121
15039
|
if (readOnly) return;
|
|
@@ -13261,999 +15179,6 @@ function Whiteboard({
|
|
|
13261
15179
|
)
|
|
13262
15180
|
] });
|
|
13263
15181
|
}
|
|
13264
|
-
var NameZ = zod.z.string().regex(/^[A-Za-z][A-Za-z0-9_'₀-₉]{0,11}$/);
|
|
13265
|
-
var DslPoint = zod.z.discriminatedUnion("kind", [
|
|
13266
|
-
zod.z.object({
|
|
13267
|
-
name: NameZ,
|
|
13268
|
-
kind: zod.z.literal("free"),
|
|
13269
|
-
x: zod.z.number().finite(),
|
|
13270
|
-
y: zod.z.number().finite()
|
|
13271
|
-
}),
|
|
13272
|
-
zod.z.object({
|
|
13273
|
-
name: NameZ,
|
|
13274
|
-
kind: zod.z.literal("midpoint"),
|
|
13275
|
-
p1: NameZ,
|
|
13276
|
-
p2: NameZ
|
|
13277
|
-
}),
|
|
13278
|
-
zod.z.object({
|
|
13279
|
-
name: NameZ,
|
|
13280
|
-
kind: zod.z.literal("onSegment"),
|
|
13281
|
-
segmentId: NameZ,
|
|
13282
|
-
t: zod.z.number().min(0).max(1)
|
|
13283
|
-
}),
|
|
13284
|
-
zod.z.object({
|
|
13285
|
-
name: NameZ,
|
|
13286
|
-
kind: zod.z.literal("onLine"),
|
|
13287
|
-
lineId: NameZ,
|
|
13288
|
-
t: zod.z.number().finite()
|
|
13289
|
-
}),
|
|
13290
|
-
zod.z.object({
|
|
13291
|
-
name: NameZ,
|
|
13292
|
-
kind: zod.z.literal("onCircle"),
|
|
13293
|
-
circleId: NameZ,
|
|
13294
|
-
theta: zod.z.number().finite()
|
|
13295
|
-
}),
|
|
13296
|
-
zod.z.object({
|
|
13297
|
-
name: NameZ,
|
|
13298
|
-
kind: zod.z.literal("perpFoot"),
|
|
13299
|
-
from: NameZ,
|
|
13300
|
-
onLine: NameZ
|
|
13301
|
-
}),
|
|
13302
|
-
zod.z.object({
|
|
13303
|
-
name: NameZ,
|
|
13304
|
-
kind: zod.z.literal("circumcenter"),
|
|
13305
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
13306
|
-
}),
|
|
13307
|
-
zod.z.object({
|
|
13308
|
-
name: NameZ,
|
|
13309
|
-
kind: zod.z.literal("incenter"),
|
|
13310
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
13311
|
-
}),
|
|
13312
|
-
zod.z.object({
|
|
13313
|
-
name: NameZ,
|
|
13314
|
-
kind: zod.z.literal("centroid"),
|
|
13315
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
13316
|
-
}),
|
|
13317
|
-
zod.z.object({
|
|
13318
|
-
name: NameZ,
|
|
13319
|
-
kind: zod.z.literal("orthocenter"),
|
|
13320
|
-
vertices: zod.z.tuple([NameZ, NameZ, NameZ])
|
|
13321
|
-
}),
|
|
13322
|
-
zod.z.object({
|
|
13323
|
-
name: NameZ,
|
|
13324
|
-
kind: zod.z.literal("intersection"),
|
|
13325
|
-
ref1: NameZ,
|
|
13326
|
-
ref2: NameZ,
|
|
13327
|
-
branch: zod.z.union([zod.z.literal(0), zod.z.literal(1)]).optional()
|
|
13328
|
-
})
|
|
13329
|
-
]);
|
|
13330
|
-
var DslShape = zod.z.discriminatedUnion("kind", [
|
|
13331
|
-
zod.z.object({
|
|
13332
|
-
name: NameZ,
|
|
13333
|
-
kind: zod.z.literal("segment"),
|
|
13334
|
-
p1: NameZ,
|
|
13335
|
-
p2: NameZ
|
|
13336
|
-
}),
|
|
13337
|
-
zod.z.object({
|
|
13338
|
-
name: NameZ,
|
|
13339
|
-
kind: zod.z.literal("line"),
|
|
13340
|
-
p1: NameZ,
|
|
13341
|
-
p2: NameZ
|
|
13342
|
-
}),
|
|
13343
|
-
zod.z.object({
|
|
13344
|
-
name: NameZ,
|
|
13345
|
-
kind: zod.z.literal("ray"),
|
|
13346
|
-
origin: NameZ,
|
|
13347
|
-
through: NameZ
|
|
13348
|
-
}),
|
|
13349
|
-
zod.z.object({
|
|
13350
|
-
name: NameZ,
|
|
13351
|
-
kind: zod.z.literal("polygon"),
|
|
13352
|
-
vertices: zod.z.array(NameZ).min(3)
|
|
13353
|
-
}),
|
|
13354
|
-
// Line constructions
|
|
13355
|
-
zod.z.object({
|
|
13356
|
-
name: NameZ,
|
|
13357
|
-
kind: zod.z.literal("perpendicular"),
|
|
13358
|
-
throughPoint: NameZ,
|
|
13359
|
-
toLine: NameZ
|
|
13360
|
-
}),
|
|
13361
|
-
zod.z.object({
|
|
13362
|
-
name: NameZ,
|
|
13363
|
-
kind: zod.z.literal("parallel"),
|
|
13364
|
-
throughPoint: NameZ,
|
|
13365
|
-
toLine: NameZ
|
|
13366
|
-
}),
|
|
13367
|
-
zod.z.object({
|
|
13368
|
-
name: NameZ,
|
|
13369
|
-
kind: zod.z.literal("perpBisector"),
|
|
13370
|
-
p1: NameZ,
|
|
13371
|
-
p2: NameZ
|
|
13372
|
-
}),
|
|
13373
|
-
zod.z.object({
|
|
13374
|
-
name: NameZ,
|
|
13375
|
-
kind: zod.z.literal("angleBisector"),
|
|
13376
|
-
p1: NameZ,
|
|
13377
|
-
vertex: NameZ,
|
|
13378
|
-
p2: NameZ
|
|
13379
|
-
}),
|
|
13380
|
-
zod.z.object({
|
|
13381
|
-
name: NameZ,
|
|
13382
|
-
kind: zod.z.literal("tangent"),
|
|
13383
|
-
throughPoint: NameZ,
|
|
13384
|
-
toCircle: NameZ,
|
|
13385
|
-
branch: zod.z.union([zod.z.literal(0), zod.z.literal(1), zod.z.literal("on")]).optional()
|
|
13386
|
-
}),
|
|
13387
|
-
// Circle constructions
|
|
13388
|
-
zod.z.object({
|
|
13389
|
-
name: NameZ,
|
|
13390
|
-
kind: zod.z.literal("circleCP"),
|
|
13391
|
-
center: NameZ,
|
|
13392
|
-
surfacePoint: NameZ
|
|
13393
|
-
}),
|
|
13394
|
-
zod.z.object({
|
|
13395
|
-
name: NameZ,
|
|
13396
|
-
kind: zod.z.literal("circle3"),
|
|
13397
|
-
p1: NameZ,
|
|
13398
|
-
p2: NameZ,
|
|
13399
|
-
p3: NameZ
|
|
13400
|
-
})
|
|
13401
|
-
]);
|
|
13402
|
-
var DslInput = zod.z.object({
|
|
13403
|
-
version: zod.z.literal(1),
|
|
13404
|
-
points: zod.z.array(DslPoint),
|
|
13405
|
-
shapes: zod.z.array(DslShape).default([])
|
|
13406
|
-
});
|
|
13407
|
-
|
|
13408
|
-
// src/stamps/geometry-2d/dsl/transpile.ts
|
|
13409
|
-
init_types();
|
|
13410
|
-
|
|
13411
|
-
// src/stamps/geometry-2d/dsl/transpile/errors.ts
|
|
13412
|
-
function mkError(code, message, opts) {
|
|
13413
|
-
return { code, message, path: opts?.path, hint: opts?.hint };
|
|
13414
|
-
}
|
|
13415
|
-
|
|
13416
|
-
// src/stamps/geometry-2d/dsl/transpile/symbols.ts
|
|
13417
|
-
function buildSymbols(dsl) {
|
|
13418
|
-
const symbols = /* @__PURE__ */ new Map();
|
|
13419
|
-
const errors = [];
|
|
13420
|
-
let counter = 0;
|
|
13421
|
-
for (const p of dsl.points) {
|
|
13422
|
-
if (symbols.has(p.name)) {
|
|
13423
|
-
errors.push(mkError("DUPLICATE_NAME", `T\xEAn tr\xF9ng: "${p.name}"`, { path: [p.name] }));
|
|
13424
|
-
continue;
|
|
13425
|
-
}
|
|
13426
|
-
symbols.set(p.name, { name: p.name, role: "point", entity: p, index: counter++ });
|
|
13427
|
-
}
|
|
13428
|
-
for (const s of dsl.shapes) {
|
|
13429
|
-
if (symbols.has(s.name)) {
|
|
13430
|
-
errors.push(mkError("DUPLICATE_NAME", `T\xEAn tr\xF9ng: "${s.name}"`, { path: [s.name] }));
|
|
13431
|
-
continue;
|
|
13432
|
-
}
|
|
13433
|
-
symbols.set(s.name, { name: s.name, role: "shape", entity: s, index: counter++ });
|
|
13434
|
-
}
|
|
13435
|
-
return { symbols, errors };
|
|
13436
|
-
}
|
|
13437
|
-
|
|
13438
|
-
// src/stamps/geometry-2d/dsl/transpile/refs.ts
|
|
13439
|
-
function isPointLike(sym) {
|
|
13440
|
-
return !!sym && sym.role === "point";
|
|
13441
|
-
}
|
|
13442
|
-
var LINE_LIKE_SHAPE_KINDS = /* @__PURE__ */ new Set([
|
|
13443
|
-
"line",
|
|
13444
|
-
"segment",
|
|
13445
|
-
"ray",
|
|
13446
|
-
"perpendicular",
|
|
13447
|
-
"parallel",
|
|
13448
|
-
"perpBisector",
|
|
13449
|
-
"angleBisector",
|
|
13450
|
-
"tangent"
|
|
13451
|
-
]);
|
|
13452
|
-
var CIRCLE_KINDS = /* @__PURE__ */ new Set(["circleCP", "circle3"]);
|
|
13453
|
-
function isLineLike(sym) {
|
|
13454
|
-
if (!sym || sym.role !== "shape") return false;
|
|
13455
|
-
return LINE_LIKE_SHAPE_KINDS.has(sym.entity.kind);
|
|
13456
|
-
}
|
|
13457
|
-
function isCircleLike(sym) {
|
|
13458
|
-
if (!sym || sym.role !== "shape") return false;
|
|
13459
|
-
return CIRCLE_KINDS.has(sym.entity.kind);
|
|
13460
|
-
}
|
|
13461
|
-
function isSegmentExact(sym) {
|
|
13462
|
-
return !!sym && sym.role === "shape" && sym.entity.kind === "segment";
|
|
13463
|
-
}
|
|
13464
|
-
function validateRefs(dsl, symbols) {
|
|
13465
|
-
const errors = [];
|
|
13466
|
-
const check = (owner, field, refName, predicate, expected) => {
|
|
13467
|
-
const sym = symbols.get(refName);
|
|
13468
|
-
if (!sym) {
|
|
13469
|
-
errors.push(mkError(
|
|
13470
|
-
"UNKNOWN_REF",
|
|
13471
|
-
`${owner}.${field} tham chi\u1EBFu "${refName}" kh\xF4ng t\u1ED3n t\u1EA1i`,
|
|
13472
|
-
{ path: [owner, field] }
|
|
13473
|
-
));
|
|
13474
|
-
return;
|
|
13475
|
-
}
|
|
13476
|
-
if (!predicate(sym)) {
|
|
13477
|
-
errors.push(mkError(
|
|
13478
|
-
"KIND_MISMATCH",
|
|
13479
|
-
`${owner}.${field}="${refName}" sai ki\u1EC3u (c\u1EA7n ${expected}, g\u1EB7p ${sym.role === "point" ? "point" : sym.entity.kind})`,
|
|
13480
|
-
{ path: [owner, field] }
|
|
13481
|
-
));
|
|
13482
|
-
}
|
|
13483
|
-
};
|
|
13484
|
-
for (const p of dsl.points) {
|
|
13485
|
-
switch (p.kind) {
|
|
13486
|
-
case "free":
|
|
13487
|
-
break;
|
|
13488
|
-
case "midpoint":
|
|
13489
|
-
check(p.name, "p1", p.p1, isPointLike, "point");
|
|
13490
|
-
check(p.name, "p2", p.p2, isPointLike, "point");
|
|
13491
|
-
break;
|
|
13492
|
-
case "onSegment":
|
|
13493
|
-
check(p.name, "segmentId", p.segmentId, isSegmentExact, "segment");
|
|
13494
|
-
break;
|
|
13495
|
-
case "onLine":
|
|
13496
|
-
check(p.name, "lineId", p.lineId, isLineLike, "line-like");
|
|
13497
|
-
break;
|
|
13498
|
-
case "onCircle":
|
|
13499
|
-
check(p.name, "circleId", p.circleId, isCircleLike, "circle");
|
|
13500
|
-
break;
|
|
13501
|
-
case "perpFoot":
|
|
13502
|
-
check(p.name, "from", p.from, isPointLike, "point");
|
|
13503
|
-
check(p.name, "onLine", p.onLine, isLineLike, "line-like");
|
|
13504
|
-
break;
|
|
13505
|
-
case "circumcenter":
|
|
13506
|
-
case "incenter":
|
|
13507
|
-
case "centroid":
|
|
13508
|
-
case "orthocenter":
|
|
13509
|
-
for (let i = 0; i < 3; i++) {
|
|
13510
|
-
check(p.name, `vertices[${i}]`, p.vertices[i], isPointLike, "point");
|
|
13511
|
-
}
|
|
13512
|
-
break;
|
|
13513
|
-
case "intersection": {
|
|
13514
|
-
const refPredicate = (s) => isLineLike(s) || isCircleLike(s);
|
|
13515
|
-
check(p.name, "ref1", p.ref1, refPredicate, "line-like ho\u1EB7c circle");
|
|
13516
|
-
check(p.name, "ref2", p.ref2, refPredicate, "line-like ho\u1EB7c circle");
|
|
13517
|
-
break;
|
|
13518
|
-
}
|
|
13519
|
-
}
|
|
13520
|
-
}
|
|
13521
|
-
for (const s of dsl.shapes) {
|
|
13522
|
-
switch (s.kind) {
|
|
13523
|
-
case "segment":
|
|
13524
|
-
case "line":
|
|
13525
|
-
check(s.name, "p1", s.p1, isPointLike, "point");
|
|
13526
|
-
check(s.name, "p2", s.p2, isPointLike, "point");
|
|
13527
|
-
break;
|
|
13528
|
-
case "ray":
|
|
13529
|
-
check(s.name, "origin", s.origin, isPointLike, "point");
|
|
13530
|
-
check(s.name, "through", s.through, isPointLike, "point");
|
|
13531
|
-
break;
|
|
13532
|
-
case "polygon":
|
|
13533
|
-
s.vertices.forEach((v, i) => check(s.name, `vertices[${i}]`, v, isPointLike, "point"));
|
|
13534
|
-
break;
|
|
13535
|
-
case "perpendicular":
|
|
13536
|
-
case "parallel":
|
|
13537
|
-
check(s.name, "throughPoint", s.throughPoint, isPointLike, "point");
|
|
13538
|
-
check(s.name, "toLine", s.toLine, isLineLike, "line-like");
|
|
13539
|
-
break;
|
|
13540
|
-
case "perpBisector":
|
|
13541
|
-
check(s.name, "p1", s.p1, isPointLike, "point");
|
|
13542
|
-
check(s.name, "p2", s.p2, isPointLike, "point");
|
|
13543
|
-
break;
|
|
13544
|
-
case "angleBisector":
|
|
13545
|
-
check(s.name, "p1", s.p1, isPointLike, "point");
|
|
13546
|
-
check(s.name, "vertex", s.vertex, isPointLike, "point");
|
|
13547
|
-
check(s.name, "p2", s.p2, isPointLike, "point");
|
|
13548
|
-
break;
|
|
13549
|
-
case "tangent":
|
|
13550
|
-
check(s.name, "throughPoint", s.throughPoint, isPointLike, "point");
|
|
13551
|
-
check(s.name, "toCircle", s.toCircle, isCircleLike, "circle");
|
|
13552
|
-
break;
|
|
13553
|
-
case "circleCP":
|
|
13554
|
-
check(s.name, "center", s.center, isPointLike, "point");
|
|
13555
|
-
check(s.name, "surfacePoint", s.surfacePoint, isPointLike, "point");
|
|
13556
|
-
break;
|
|
13557
|
-
case "circle3":
|
|
13558
|
-
check(s.name, "p1", s.p1, isPointLike, "point");
|
|
13559
|
-
check(s.name, "p2", s.p2, isPointLike, "point");
|
|
13560
|
-
check(s.name, "p3", s.p3, isPointLike, "point");
|
|
13561
|
-
break;
|
|
13562
|
-
}
|
|
13563
|
-
}
|
|
13564
|
-
return { errors };
|
|
13565
|
-
}
|
|
13566
|
-
function collectRefs(entity) {
|
|
13567
|
-
if ("kind" in entity) {
|
|
13568
|
-
switch (entity.kind) {
|
|
13569
|
-
case "free":
|
|
13570
|
-
return [];
|
|
13571
|
-
case "midpoint":
|
|
13572
|
-
return [entity.p1, entity.p2];
|
|
13573
|
-
case "onSegment":
|
|
13574
|
-
return [entity.segmentId];
|
|
13575
|
-
case "onLine":
|
|
13576
|
-
return [entity.lineId];
|
|
13577
|
-
case "onCircle":
|
|
13578
|
-
return [entity.circleId];
|
|
13579
|
-
case "perpFoot":
|
|
13580
|
-
return [entity.from, entity.onLine];
|
|
13581
|
-
case "circumcenter":
|
|
13582
|
-
case "incenter":
|
|
13583
|
-
case "centroid":
|
|
13584
|
-
case "orthocenter":
|
|
13585
|
-
return [...entity.vertices];
|
|
13586
|
-
case "intersection":
|
|
13587
|
-
return [entity.ref1, entity.ref2];
|
|
13588
|
-
case "segment":
|
|
13589
|
-
case "line":
|
|
13590
|
-
return [entity.p1, entity.p2];
|
|
13591
|
-
case "ray":
|
|
13592
|
-
return [entity.origin, entity.through];
|
|
13593
|
-
case "polygon":
|
|
13594
|
-
return [...entity.vertices];
|
|
13595
|
-
case "perpendicular":
|
|
13596
|
-
case "parallel":
|
|
13597
|
-
return [entity.throughPoint, entity.toLine];
|
|
13598
|
-
case "perpBisector":
|
|
13599
|
-
return [entity.p1, entity.p2];
|
|
13600
|
-
case "angleBisector":
|
|
13601
|
-
return [entity.p1, entity.vertex, entity.p2];
|
|
13602
|
-
case "tangent":
|
|
13603
|
-
return [entity.throughPoint, entity.toCircle];
|
|
13604
|
-
case "circleCP":
|
|
13605
|
-
return [entity.center, entity.surfacePoint];
|
|
13606
|
-
case "circle3":
|
|
13607
|
-
return [entity.p1, entity.p2, entity.p3];
|
|
13608
|
-
}
|
|
13609
|
-
}
|
|
13610
|
-
return [];
|
|
13611
|
-
}
|
|
13612
|
-
|
|
13613
|
-
// src/stamps/geometry-2d/dsl/transpile/cycles.ts
|
|
13614
|
-
function detectCycles(symbols) {
|
|
13615
|
-
const color = /* @__PURE__ */ new Map();
|
|
13616
|
-
const parent = /* @__PURE__ */ new Map();
|
|
13617
|
-
const errors = [];
|
|
13618
|
-
const reportedCycles = /* @__PURE__ */ new Set();
|
|
13619
|
-
for (const name of symbols.keys()) color.set(name, "white");
|
|
13620
|
-
function reportCycle(start, hit) {
|
|
13621
|
-
const chain = [start];
|
|
13622
|
-
let cur = parent.get(start);
|
|
13623
|
-
while (cur && cur !== hit && chain.length < symbols.size + 2) {
|
|
13624
|
-
chain.push(cur);
|
|
13625
|
-
cur = parent.get(cur);
|
|
13626
|
-
}
|
|
13627
|
-
chain.push(hit);
|
|
13628
|
-
const minIdx = chain.indexOf(chain.reduce((a, b) => a < b ? a : b));
|
|
13629
|
-
const rotated = [...chain.slice(minIdx), ...chain.slice(0, minIdx)];
|
|
13630
|
-
const key = rotated.join("\u2192");
|
|
13631
|
-
if (reportedCycles.has(key)) return;
|
|
13632
|
-
reportedCycles.add(key);
|
|
13633
|
-
errors.push(mkError(
|
|
13634
|
-
"CYCLE",
|
|
13635
|
-
`Ph\u1EE5 thu\u1ED9c v\xF2ng: ${chain.reverse().join(" \u2192 ")}`,
|
|
13636
|
-
{ path: [...chain], hint: "Ki\u1EC3m tra l\u1EA1i quan h\u1EC7 midpoint/perpFoot/intersection." }
|
|
13637
|
-
));
|
|
13638
|
-
}
|
|
13639
|
-
function dfs(name) {
|
|
13640
|
-
color.set(name, "gray");
|
|
13641
|
-
const sym = symbols.get(name);
|
|
13642
|
-
if (sym) {
|
|
13643
|
-
for (const ref of collectRefs(sym.entity)) {
|
|
13644
|
-
if (!symbols.has(ref)) continue;
|
|
13645
|
-
const c = color.get(ref);
|
|
13646
|
-
if (c === "gray") {
|
|
13647
|
-
reportCycle(name, ref);
|
|
13648
|
-
continue;
|
|
13649
|
-
}
|
|
13650
|
-
if (c === "white") {
|
|
13651
|
-
parent.set(ref, name);
|
|
13652
|
-
dfs(ref);
|
|
13653
|
-
}
|
|
13654
|
-
}
|
|
13655
|
-
}
|
|
13656
|
-
color.set(name, "black");
|
|
13657
|
-
}
|
|
13658
|
-
for (const name of symbols.keys()) {
|
|
13659
|
-
if (color.get(name) === "white") {
|
|
13660
|
-
parent.set(name, null);
|
|
13661
|
-
dfs(name);
|
|
13662
|
-
}
|
|
13663
|
-
}
|
|
13664
|
-
return { errors };
|
|
13665
|
-
}
|
|
13666
|
-
|
|
13667
|
-
// src/stamps/geometry-2d/dsl/transpile/ids.ts
|
|
13668
|
-
function prefixFor(sym) {
|
|
13669
|
-
if (sym.role === "point") {
|
|
13670
|
-
const p = sym.entity;
|
|
13671
|
-
return p.kind === "intersection" ? "i" : "p";
|
|
13672
|
-
}
|
|
13673
|
-
const s = sym.entity;
|
|
13674
|
-
switch (s.kind) {
|
|
13675
|
-
case "segment":
|
|
13676
|
-
return "s";
|
|
13677
|
-
case "ray":
|
|
13678
|
-
return "r";
|
|
13679
|
-
case "polygon":
|
|
13680
|
-
return "poly";
|
|
13681
|
-
case "circleCP":
|
|
13682
|
-
case "circle3":
|
|
13683
|
-
return "c";
|
|
13684
|
-
// line + 5 line-constructions all share 'l'
|
|
13685
|
-
case "line":
|
|
13686
|
-
case "perpendicular":
|
|
13687
|
-
case "parallel":
|
|
13688
|
-
case "perpBisector":
|
|
13689
|
-
case "angleBisector":
|
|
13690
|
-
case "tangent":
|
|
13691
|
-
return "l";
|
|
13692
|
-
}
|
|
13693
|
-
}
|
|
13694
|
-
function assignIds(symbols) {
|
|
13695
|
-
const counters = { p: 0, i: 0, s: 0, l: 0, r: 0, poly: 0, c: 0 };
|
|
13696
|
-
const ids = /* @__PURE__ */ new Map();
|
|
13697
|
-
for (const [name, sym] of symbols.entries()) {
|
|
13698
|
-
const prefix = prefixFor(sym);
|
|
13699
|
-
counters[prefix] += 1;
|
|
13700
|
-
ids.set(name, `${prefix}${counters[prefix]}`);
|
|
13701
|
-
}
|
|
13702
|
-
return ids;
|
|
13703
|
-
}
|
|
13704
|
-
|
|
13705
|
-
// src/stamps/geometry-2d/dsl/transpile/emitPoint.ts
|
|
13706
|
-
function resolveId(ids, name) {
|
|
13707
|
-
const id = ids.get(name);
|
|
13708
|
-
if (!id) throw new Error(`emitPoint: id not assigned for "${name}"`);
|
|
13709
|
-
return id;
|
|
13710
|
-
}
|
|
13711
|
-
function emitPoint(p, ids, kindHints) {
|
|
13712
|
-
const baseId = resolveId(ids, p.name);
|
|
13713
|
-
const baseFields = {
|
|
13714
|
-
label: p.name,
|
|
13715
|
-
visible: true,
|
|
13716
|
-
locked: false,
|
|
13717
|
-
layer: "default",
|
|
13718
|
-
schemaVersion: 1
|
|
13719
|
-
};
|
|
13720
|
-
if (p.kind === "intersection") {
|
|
13721
|
-
const r1Hint = kindHints.get(p.ref1);
|
|
13722
|
-
const r2Hint = kindHints.get(p.ref2);
|
|
13723
|
-
const r1IsCircle = r1Hint === "circle";
|
|
13724
|
-
const r2IsCircle = r2Hint === "circle";
|
|
13725
|
-
let intersectKind;
|
|
13726
|
-
if (r1IsCircle && r2IsCircle) intersectKind = "circleCircle";
|
|
13727
|
-
else if (r1IsCircle || r2IsCircle) intersectKind = "lineCircle";
|
|
13728
|
-
else intersectKind = "lineLine";
|
|
13729
|
-
const attrs = {
|
|
13730
|
-
kind: intersectKind,
|
|
13731
|
-
ref1: resolveId(ids, p.ref1),
|
|
13732
|
-
ref2: resolveId(ids, p.ref2)
|
|
13733
|
-
};
|
|
13734
|
-
if (intersectKind !== "lineLine") {
|
|
13735
|
-
attrs.branch = p.branch ?? 0;
|
|
13736
|
-
}
|
|
13737
|
-
return {
|
|
13738
|
-
id: baseId,
|
|
13739
|
-
kind: "intersection",
|
|
13740
|
-
...baseFields,
|
|
13741
|
-
attrs
|
|
13742
|
-
};
|
|
13743
|
-
}
|
|
13744
|
-
let constraint;
|
|
13745
|
-
switch (p.kind) {
|
|
13746
|
-
case "free":
|
|
13747
|
-
constraint = { kind: "free", x: p.x, y: p.y };
|
|
13748
|
-
break;
|
|
13749
|
-
case "midpoint":
|
|
13750
|
-
constraint = { kind: "midpoint", p1: resolveId(ids, p.p1), p2: resolveId(ids, p.p2) };
|
|
13751
|
-
break;
|
|
13752
|
-
case "onSegment":
|
|
13753
|
-
constraint = { kind: "onSegment", segmentId: resolveId(ids, p.segmentId), t: p.t };
|
|
13754
|
-
break;
|
|
13755
|
-
case "onLine":
|
|
13756
|
-
constraint = { kind: "onLine", lineId: resolveId(ids, p.lineId), t: p.t };
|
|
13757
|
-
break;
|
|
13758
|
-
case "onCircle":
|
|
13759
|
-
constraint = { kind: "onCircle", circleId: resolveId(ids, p.circleId), theta: p.theta };
|
|
13760
|
-
break;
|
|
13761
|
-
case "perpFoot":
|
|
13762
|
-
constraint = { kind: "perpFoot", from: resolveId(ids, p.from), onLine: resolveId(ids, p.onLine) };
|
|
13763
|
-
break;
|
|
13764
|
-
case "circumcenter":
|
|
13765
|
-
case "incenter":
|
|
13766
|
-
case "centroid":
|
|
13767
|
-
case "orthocenter":
|
|
13768
|
-
constraint = {
|
|
13769
|
-
kind: p.kind,
|
|
13770
|
-
vertices: [resolveId(ids, p.vertices[0]), resolveId(ids, p.vertices[1]), resolveId(ids, p.vertices[2])]
|
|
13771
|
-
};
|
|
13772
|
-
break;
|
|
13773
|
-
}
|
|
13774
|
-
return {
|
|
13775
|
-
id: baseId,
|
|
13776
|
-
kind: "point",
|
|
13777
|
-
...baseFields,
|
|
13778
|
-
attrs: { constraint }
|
|
13779
|
-
};
|
|
13780
|
-
}
|
|
13781
|
-
|
|
13782
|
-
// src/stamps/geometry-2d/dsl/transpile/emitShape.ts
|
|
13783
|
-
function r(ids, name) {
|
|
13784
|
-
const id = ids.get(name);
|
|
13785
|
-
if (!id) throw new Error(`emitShape: id not assigned for "${name}"`);
|
|
13786
|
-
return id;
|
|
13787
|
-
}
|
|
13788
|
-
function emitShape(s, ids) {
|
|
13789
|
-
const id = r(ids, s.name);
|
|
13790
|
-
const base = {
|
|
13791
|
-
label: s.name,
|
|
13792
|
-
visible: true,
|
|
13793
|
-
locked: false,
|
|
13794
|
-
layer: "default",
|
|
13795
|
-
schemaVersion: 1
|
|
13796
|
-
};
|
|
13797
|
-
switch (s.kind) {
|
|
13798
|
-
case "segment":
|
|
13799
|
-
return { id, kind: "segment", ...base, attrs: { p1: r(ids, s.p1), p2: r(ids, s.p2) } };
|
|
13800
|
-
case "line":
|
|
13801
|
-
return { id, kind: "line", ...base, attrs: { p1: r(ids, s.p1), p2: r(ids, s.p2) } };
|
|
13802
|
-
case "ray":
|
|
13803
|
-
return { id, kind: "ray", ...base, attrs: { origin: r(ids, s.origin), through: r(ids, s.through) } };
|
|
13804
|
-
case "polygon":
|
|
13805
|
-
return { id, kind: "polygon", ...base, attrs: { vertices: s.vertices.map((v) => r(ids, v)) } };
|
|
13806
|
-
case "perpendicular":
|
|
13807
|
-
case "parallel":
|
|
13808
|
-
return {
|
|
13809
|
-
id,
|
|
13810
|
-
kind: "line",
|
|
13811
|
-
...base,
|
|
13812
|
-
attrs: { construction: { kind: s.kind, throughPoint: r(ids, s.throughPoint), toLine: r(ids, s.toLine) } }
|
|
13813
|
-
};
|
|
13814
|
-
case "perpBisector":
|
|
13815
|
-
return {
|
|
13816
|
-
id,
|
|
13817
|
-
kind: "line",
|
|
13818
|
-
...base,
|
|
13819
|
-
attrs: { construction: { kind: "perpBisector", p1: r(ids, s.p1), p2: r(ids, s.p2) } }
|
|
13820
|
-
};
|
|
13821
|
-
case "angleBisector":
|
|
13822
|
-
return {
|
|
13823
|
-
id,
|
|
13824
|
-
kind: "line",
|
|
13825
|
-
...base,
|
|
13826
|
-
attrs: { construction: { kind: "angleBisector", p1: r(ids, s.p1), vertex: r(ids, s.vertex), p2: r(ids, s.p2) } }
|
|
13827
|
-
};
|
|
13828
|
-
case "tangent": {
|
|
13829
|
-
const construction = {
|
|
13830
|
-
kind: "tangent",
|
|
13831
|
-
throughPoint: r(ids, s.throughPoint),
|
|
13832
|
-
toCircle: r(ids, s.toCircle)
|
|
13833
|
-
};
|
|
13834
|
-
if (s.branch !== void 0) construction.branch = s.branch;
|
|
13835
|
-
return { id, kind: "line", ...base, attrs: { construction } };
|
|
13836
|
-
}
|
|
13837
|
-
case "circleCP":
|
|
13838
|
-
return {
|
|
13839
|
-
id,
|
|
13840
|
-
kind: "circle",
|
|
13841
|
-
...base,
|
|
13842
|
-
attrs: { center: r(ids, s.center), surfacePoint: r(ids, s.surfacePoint) }
|
|
13843
|
-
};
|
|
13844
|
-
case "circle3":
|
|
13845
|
-
return {
|
|
13846
|
-
id,
|
|
13847
|
-
kind: "circle",
|
|
13848
|
-
...base,
|
|
13849
|
-
attrs: { construction: { kind: "circumscribed", p1: r(ids, s.p1), p2: r(ids, s.p2), p3: r(ids, s.p3) } }
|
|
13850
|
-
};
|
|
13851
|
-
}
|
|
13852
|
-
}
|
|
13853
|
-
|
|
13854
|
-
// src/stamps/geometry-2d/dsl/transpile.ts
|
|
13855
|
-
function hintOf(entity) {
|
|
13856
|
-
if ("kind" in entity) {
|
|
13857
|
-
switch (entity.kind) {
|
|
13858
|
-
case "free":
|
|
13859
|
-
case "midpoint":
|
|
13860
|
-
case "onSegment":
|
|
13861
|
-
case "onLine":
|
|
13862
|
-
case "onCircle":
|
|
13863
|
-
case "perpFoot":
|
|
13864
|
-
case "circumcenter":
|
|
13865
|
-
case "incenter":
|
|
13866
|
-
case "centroid":
|
|
13867
|
-
case "orthocenter":
|
|
13868
|
-
case "intersection":
|
|
13869
|
-
return "point";
|
|
13870
|
-
case "segment":
|
|
13871
|
-
return "segment";
|
|
13872
|
-
case "line":
|
|
13873
|
-
return "line";
|
|
13874
|
-
case "ray":
|
|
13875
|
-
return "ray";
|
|
13876
|
-
case "polygon":
|
|
13877
|
-
return "point";
|
|
13878
|
-
// not used as ref target in MVP
|
|
13879
|
-
case "perpendicular":
|
|
13880
|
-
case "parallel":
|
|
13881
|
-
case "perpBisector":
|
|
13882
|
-
case "angleBisector":
|
|
13883
|
-
case "tangent":
|
|
13884
|
-
return "lineConstruction";
|
|
13885
|
-
case "circleCP":
|
|
13886
|
-
case "circle3":
|
|
13887
|
-
return "circle";
|
|
13888
|
-
}
|
|
13889
|
-
}
|
|
13890
|
-
return "point";
|
|
13891
|
-
}
|
|
13892
|
-
function transpile(dslRaw) {
|
|
13893
|
-
const parsed = DslInput.safeParse(dslRaw);
|
|
13894
|
-
if (!parsed.success) {
|
|
13895
|
-
const errors = parsed.error.issues.map(
|
|
13896
|
-
(iss) => mkError("SCHEMA", iss.message, { path: iss.path.map(String) })
|
|
13897
|
-
);
|
|
13898
|
-
return { ok: false, errors };
|
|
13899
|
-
}
|
|
13900
|
-
const dsl = parsed.data;
|
|
13901
|
-
const { symbols, errors: dupErrors } = buildSymbols(dsl);
|
|
13902
|
-
const { errors: refErrors } = validateRefs(dsl, symbols);
|
|
13903
|
-
const { errors: cycleErrors } = detectCycles(symbols);
|
|
13904
|
-
const allErrors = [...dupErrors, ...refErrors, ...cycleErrors];
|
|
13905
|
-
if (allErrors.length > 0) return { ok: false, errors: allErrors };
|
|
13906
|
-
const ids = assignIds(symbols);
|
|
13907
|
-
const kindHints = /* @__PURE__ */ new Map();
|
|
13908
|
-
for (const [name, sym] of symbols.entries()) {
|
|
13909
|
-
kindHints.set(name, hintOf(sym.entity));
|
|
13910
|
-
}
|
|
13911
|
-
const objects = {};
|
|
13912
|
-
const order = [];
|
|
13913
|
-
for (const p of dsl.points) {
|
|
13914
|
-
const obj = emitPoint(p, ids, kindHints);
|
|
13915
|
-
objects[obj.id] = obj;
|
|
13916
|
-
order.push(obj.id);
|
|
13917
|
-
}
|
|
13918
|
-
for (const s of dsl.shapes) {
|
|
13919
|
-
const obj = emitShape(s, ids);
|
|
13920
|
-
objects[obj.id] = obj;
|
|
13921
|
-
order.push(obj.id);
|
|
13922
|
-
}
|
|
13923
|
-
const empty = createEmptyState("2d");
|
|
13924
|
-
const state = {
|
|
13925
|
-
objects,
|
|
13926
|
-
order,
|
|
13927
|
-
counter: order.length,
|
|
13928
|
-
meta: empty.meta
|
|
13929
|
-
};
|
|
13930
|
-
return { ok: true, state };
|
|
13931
|
-
}
|
|
13932
|
-
async function callProvider(args) {
|
|
13933
|
-
const client = new Anthropic__default.default({ apiKey: args.apiKey });
|
|
13934
|
-
const resp = await client.messages.create(
|
|
13935
|
-
{
|
|
13936
|
-
model: args.model,
|
|
13937
|
-
max_tokens: args.maxTokens,
|
|
13938
|
-
system: args.system,
|
|
13939
|
-
tools: args.tools,
|
|
13940
|
-
tool_choice: args.toolChoice,
|
|
13941
|
-
messages: args.messages
|
|
13942
|
-
},
|
|
13943
|
-
args.signal ? { signal: args.signal } : void 0
|
|
13944
|
-
);
|
|
13945
|
-
return resp;
|
|
13946
|
-
}
|
|
13947
|
-
|
|
13948
|
-
// src/stamps/geometry-2d/dsl/fixtures/triangle-equilateral.ts
|
|
13949
|
-
var fixture = {
|
|
13950
|
-
problem: "Cho tam gi\xE1c \u0111\u1EC1u ABC c\u1EA1nh 4.",
|
|
13951
|
-
dsl: {
|
|
13952
|
-
version: 1,
|
|
13953
|
-
points: [
|
|
13954
|
-
{ name: "A", kind: "free", x: 0, y: 0 },
|
|
13955
|
-
{ name: "B", kind: "free", x: 4, y: 0 },
|
|
13956
|
-
{ name: "C", kind: "free", x: 2, y: 3.464 }
|
|
13957
|
-
],
|
|
13958
|
-
shapes: [
|
|
13959
|
-
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] }
|
|
13960
|
-
]
|
|
13961
|
-
}
|
|
13962
|
-
};
|
|
13963
|
-
|
|
13964
|
-
// src/stamps/geometry-2d/dsl/fixtures/triangle-median.ts
|
|
13965
|
-
var fixture2 = {
|
|
13966
|
-
problem: "Tam gi\xE1c ABC, M l\xE0 trung \u0111i\u1EC3m BC. V\u1EBD AM.",
|
|
13967
|
-
dsl: {
|
|
13968
|
-
version: 1,
|
|
13969
|
-
points: [
|
|
13970
|
-
{ name: "A", kind: "free", x: 0, y: 3 },
|
|
13971
|
-
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
13972
|
-
{ name: "C", kind: "free", x: 3, y: 0 },
|
|
13973
|
-
{ name: "M", kind: "midpoint", p1: "B", p2: "C" }
|
|
13974
|
-
],
|
|
13975
|
-
shapes: [
|
|
13976
|
-
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] },
|
|
13977
|
-
{ name: "AM", kind: "segment", p1: "A", p2: "M" }
|
|
13978
|
-
]
|
|
13979
|
-
}
|
|
13980
|
-
};
|
|
13981
|
-
|
|
13982
|
-
// src/stamps/geometry-2d/dsl/fixtures/triangle-altitude.ts
|
|
13983
|
-
var fixture3 = {
|
|
13984
|
-
problem: "Tam gi\xE1c ABC, AH l\xE0 \u0111\u01B0\u1EDDng cao xu\u1ED1ng BC.",
|
|
13985
|
-
dsl: {
|
|
13986
|
-
version: 1,
|
|
13987
|
-
points: [
|
|
13988
|
-
{ name: "A", kind: "free", x: 1, y: 3 },
|
|
13989
|
-
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
13990
|
-
{ name: "C", kind: "free", x: 3, y: 0 },
|
|
13991
|
-
{ name: "H", kind: "perpFoot", from: "A", onLine: "BC" }
|
|
13992
|
-
],
|
|
13993
|
-
shapes: [
|
|
13994
|
-
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] },
|
|
13995
|
-
{ name: "BC", kind: "segment", p1: "B", p2: "C" },
|
|
13996
|
-
{ name: "AH", kind: "segment", p1: "A", p2: "H" }
|
|
13997
|
-
]
|
|
13998
|
-
}
|
|
13999
|
-
};
|
|
14000
|
-
|
|
14001
|
-
// src/stamps/geometry-2d/dsl/fixtures/triangle-centroid.ts
|
|
14002
|
-
var fixture4 = {
|
|
14003
|
-
problem: "Tam gi\xE1c ABC, G l\xE0 tr\u1ECDng t\xE2m.",
|
|
14004
|
-
dsl: {
|
|
14005
|
-
version: 1,
|
|
14006
|
-
points: [
|
|
14007
|
-
{ name: "A", kind: "free", x: 0, y: 3 },
|
|
14008
|
-
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
14009
|
-
{ name: "C", kind: "free", x: 3, y: 0 },
|
|
14010
|
-
{ name: "G", kind: "centroid", vertices: ["A", "B", "C"] }
|
|
14011
|
-
],
|
|
14012
|
-
shapes: [
|
|
14013
|
-
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] }
|
|
14014
|
-
]
|
|
14015
|
-
}
|
|
14016
|
-
};
|
|
14017
|
-
|
|
14018
|
-
// src/stamps/geometry-2d/dsl/fixtures/triangle-orthocenter.ts
|
|
14019
|
-
var fixture5 = {
|
|
14020
|
-
problem: "Tam gi\xE1c ABC, H l\xE0 tr\u1EF1c t\xE2m.",
|
|
14021
|
-
dsl: {
|
|
14022
|
-
version: 1,
|
|
14023
|
-
points: [
|
|
14024
|
-
{ name: "A", kind: "free", x: 0, y: 3 },
|
|
14025
|
-
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
14026
|
-
{ name: "C", kind: "free", x: 3, y: 0 },
|
|
14027
|
-
{ name: "H", kind: "orthocenter", vertices: ["A", "B", "C"] }
|
|
14028
|
-
],
|
|
14029
|
-
shapes: [
|
|
14030
|
-
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] }
|
|
14031
|
-
]
|
|
14032
|
-
}
|
|
14033
|
-
};
|
|
14034
|
-
|
|
14035
|
-
// src/stamps/geometry-2d/dsl/fixtures/triangle-circumcircle.ts
|
|
14036
|
-
var fixture6 = {
|
|
14037
|
-
problem: "Tam gi\xE1c ABC n\u1ED9i ti\u1EBFp \u0111\u01B0\u1EDDng tr\xF2n t\xE2m O.",
|
|
14038
|
-
dsl: {
|
|
14039
|
-
version: 1,
|
|
14040
|
-
points: [
|
|
14041
|
-
{ name: "A", kind: "free", x: 0, y: 3 },
|
|
14042
|
-
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
14043
|
-
{ name: "C", kind: "free", x: 3, y: 0 },
|
|
14044
|
-
{ name: "O", kind: "circumcenter", vertices: ["A", "B", "C"] }
|
|
14045
|
-
],
|
|
14046
|
-
shapes: [
|
|
14047
|
-
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] },
|
|
14048
|
-
{ name: "k", kind: "circle3", p1: "A", p2: "B", p3: "C" }
|
|
14049
|
-
]
|
|
14050
|
-
}
|
|
14051
|
-
};
|
|
14052
|
-
|
|
14053
|
-
// src/stamps/geometry-2d/dsl/fixtures/triangle-incircle.ts
|
|
14054
|
-
var fixture7 = {
|
|
14055
|
-
problem: "Tam gi\xE1c ABC, I l\xE0 t\xE2m n\u1ED9i ti\u1EBFp, \u0111\u01B0\u1EDDng tr\xF2n (I) ti\u1EBFp x\xFAc BC t\u1EA1i D.",
|
|
14056
|
-
dsl: {
|
|
14057
|
-
version: 1,
|
|
14058
|
-
points: [
|
|
14059
|
-
{ name: "A", kind: "free", x: 0, y: 3 },
|
|
14060
|
-
{ name: "B", kind: "free", x: -2, y: 0 },
|
|
14061
|
-
{ name: "C", kind: "free", x: 3, y: 0 },
|
|
14062
|
-
{ name: "I", kind: "incenter", vertices: ["A", "B", "C"] },
|
|
14063
|
-
{ name: "D", kind: "perpFoot", from: "I", onLine: "BC" }
|
|
14064
|
-
],
|
|
14065
|
-
shapes: [
|
|
14066
|
-
{ name: "ABC", kind: "polygon", vertices: ["A", "B", "C"] },
|
|
14067
|
-
{ name: "BC", kind: "segment", p1: "B", p2: "C" },
|
|
14068
|
-
{ name: "incircle", kind: "circleCP", center: "I", surfacePoint: "D" }
|
|
14069
|
-
]
|
|
14070
|
-
}
|
|
14071
|
-
};
|
|
14072
|
-
|
|
14073
|
-
// src/stamps/geometry-2d/dsl/fixtures/parallelogram.ts
|
|
14074
|
-
var fixture8 = {
|
|
14075
|
-
problem: "H\xECnh b\xECnh h\xE0nh ABCD, hai \u0111\u01B0\u1EDDng ch\xE9o AC, BD c\u1EAFt nhau t\u1EA1i O.",
|
|
14076
|
-
dsl: {
|
|
14077
|
-
version: 1,
|
|
14078
|
-
points: [
|
|
14079
|
-
{ name: "A", kind: "free", x: 0, y: 0 },
|
|
14080
|
-
{ name: "B", kind: "free", x: 4, y: 0 },
|
|
14081
|
-
{ name: "C", kind: "free", x: 5, y: 2 },
|
|
14082
|
-
{ name: "D", kind: "free", x: 1, y: 2 },
|
|
14083
|
-
{ name: "O", kind: "intersection", ref1: "AC", ref2: "BD" }
|
|
14084
|
-
],
|
|
14085
|
-
shapes: [
|
|
14086
|
-
{ name: "ABCD", kind: "polygon", vertices: ["A", "B", "C", "D"] },
|
|
14087
|
-
{ name: "AC", kind: "segment", p1: "A", p2: "C" },
|
|
14088
|
-
{ name: "BD", kind: "segment", p1: "B", p2: "D" }
|
|
14089
|
-
]
|
|
14090
|
-
}
|
|
14091
|
-
};
|
|
14092
|
-
|
|
14093
|
-
// src/stamps/geometry-2d/dsl/fixtures/two-circles-intersect.ts
|
|
14094
|
-
var fixture9 = {
|
|
14095
|
-
problem: "Hai \u0111\u01B0\u1EDDng tr\xF2n (O\u2081), (O\u2082) c\u1EAFt nhau t\u1EA1i P, Q.",
|
|
14096
|
-
dsl: {
|
|
14097
|
-
version: 1,
|
|
14098
|
-
points: [
|
|
14099
|
-
{ name: "O1", kind: "free", x: 0, y: 0 },
|
|
14100
|
-
{ name: "A1", kind: "free", x: 2, y: 0 },
|
|
14101
|
-
{ name: "O2", kind: "free", x: 3, y: 0 },
|
|
14102
|
-
{ name: "A2", kind: "free", x: 5, y: 0 },
|
|
14103
|
-
{ name: "P", kind: "intersection", ref1: "k1", ref2: "k2", branch: 0 },
|
|
14104
|
-
{ name: "Q", kind: "intersection", ref1: "k1", ref2: "k2", branch: 1 }
|
|
14105
|
-
],
|
|
14106
|
-
shapes: [
|
|
14107
|
-
{ name: "k1", kind: "circleCP", center: "O1", surfacePoint: "A1" },
|
|
14108
|
-
{ name: "k2", kind: "circleCP", center: "O2", surfacePoint: "A2" }
|
|
14109
|
-
]
|
|
14110
|
-
}
|
|
14111
|
-
};
|
|
14112
|
-
|
|
14113
|
-
// src/stamps/geometry-2d/ai/prompt.ts
|
|
14114
|
-
var FIXTURES = [fixture, fixture2, fixture3, fixture4, fixture5, fixture6, fixture7, fixture8, fixture9];
|
|
14115
|
-
function buildSystemPrompt() {
|
|
14116
|
-
const examples = FIXTURES.map(
|
|
14117
|
-
(f, i) => `### V\xED d\u1EE5 ${i + 1}
|
|
14118
|
-
**\u0110\u1EC1:** ${f.problem}
|
|
14119
|
-
**DSL:**
|
|
14120
|
-
\`\`\`json
|
|
14121
|
-
${JSON.stringify(f.dsl, null, 2)}
|
|
14122
|
-
\`\`\``
|
|
14123
|
-
).join("\n\n");
|
|
14124
|
-
return `B\u1EA1n l\xE0 tr\u1EE3 l\xFD v\u1EBD h\xECnh h\u1ECDc 2D cho h\u1ECDc sinh THCS v\xE0 l\u1EDBp 10 Vi\u1EC7t Nam.
|
|
14125
|
-
|
|
14126
|
-
## Nhi\u1EC7m v\u1EE5
|
|
14127
|
-
\u0110\u1ECDc \u0111\u1EC1 b\xE0i ti\u1EBFng Vi\u1EC7t \u2192 emit DSL JSON m\xF4 t\u1EA3 h\xECnh. H\u1EC7 th\u1ED1ng s\u1EBD render h\xECnh t\u1EEB DSL.
|
|
14128
|
-
|
|
14129
|
-
## Quy t\u1EAFc
|
|
14130
|
-
1. D\xF9ng tool \`build_figure\` khi v\u1EBD \u0111\u01B0\u1EE3c. D\xF9ng tool \`refuse\` khi kh\xF4ng v\u1EBD \u0111\u01B0\u1EE3c ho\u1EB7c \u0111\u1EC1 ngo\xE0i ph\u1EA1m vi (3D, l\u01B0\u1EE3ng gi\xE1c, ph\xE9p bi\u1EBFn h\xECnh l\u1EDBp 11+, \u0111\u1EA1i s\u1ED1).
|
|
14131
|
-
2. \u01AFu ti\xEAn derived points (midpoint, perpFoot, circumcenter, ...) thay v\xEC t\u1EF1 compute to\u1EA1 \u0111\u1ED9.
|
|
14132
|
-
3. Anchor (free) ch\u1EC9 d\xF9ng cho \u0111i\u1EC3m g\u1ED1c (th\u01B0\u1EDDng A, B, C c\u1EE7a tam gi\xE1c). \u0110\u1EB7t coord h\u1EE3p l\xFD quanh g\u1ED1c (-5..5).
|
|
14133
|
-
4. M\u1ECDi \u0111i\u1EC3m + h\xECnh ph\u1EA3i c\xF3 \`name\` (label "A", "M", "O\u2081", ...). Tham chi\u1EBFu b\u1EB1ng name, kh\xF4ng ph\u1EA3i id.
|
|
14134
|
-
5. Tam gi\xE1c: emit c\u1EA3 \`polygon\` (v\u1EBD vi\u1EC1n) + segment/\u0111\u01B0\u1EDDng ph\u1EE5 ri\xEAng n\u1EBFu \u0111\u1EC1 y\xEAu c\u1EA7u (\u0111\u01B0\u1EDDng cao, trung tuy\u1EBFn).
|
|
14135
|
-
6. \u0110\u01B0\u1EDDng tr\xF2n (O; R) cho tr\u01B0\u1EDBc b\xE1n k\xEDnh s\u1ED1: emit anchor helper tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n r\u1ED3i d\xF9ng \`circleCP\` (DSL kh\xF4ng h\u1ED7 tr\u1EE3 radius numeric tr\u1EF1c ti\u1EBFp).
|
|
14136
|
-
7. N\u1EBFu \u0111\u1EC1 m\u01A1 h\u1ED3: ch\u1ECDn case ph\u1ED5 bi\u1EBFn nh\u1EA5t, kh\xF4ng h\u1ECFi l\u1EA1i.
|
|
14137
|
-
|
|
14138
|
-
## Primitives s\u1EB5n c\xF3
|
|
14139
|
-
**Points:** free, midpoint, onSegment, onLine, onCircle, perpFoot, circumcenter, incenter, centroid, orthocenter, intersection
|
|
14140
|
-
**Shapes:** segment, line, ray, polygon, perpendicular, parallel, perpBisector, angleBisector, tangent, circleCP, circle3
|
|
14141
|
-
|
|
14142
|
-
## 9 v\xED d\u1EE5
|
|
14143
|
-
${examples}
|
|
14144
|
-
|
|
14145
|
-
## Khi kh\xF4ng v\u1EBD \u0111\u01B0\u1EE3c
|
|
14146
|
-
G\u1ECDi \`refuse\` v\u1EDBi \`reason\` ti\u1EBFng Vi\u1EC7t gi\u1EA3i th\xEDch c\u1EE5 th\u1EC3 (vd: "\u0110\u1EC1 thu\u1ED9c l\u1EDBp 11, ngo\xE0i ph\u1EA1m vi MVP" ho\u1EB7c "\u0110\u1EC1 kh\xF4ng r\xF5 v\u1ECB tr\xED \u0111i\u1EC3m M").`;
|
|
14147
|
-
}
|
|
14148
|
-
var BUILD_FIGURE_TOOL = {
|
|
14149
|
-
name: "build_figure",
|
|
14150
|
-
description: "V\u1EBD h\xECnh h\u1ECDc 2D theo \u0111\u1EC1 b\xE0i. Emit DSL JSON m\xF4 t\u1EA3 c\xE1c \u0111i\u1EC3m v\xE0 h\xECnh.",
|
|
14151
|
-
input_schema: zodToJsonSchema.zodToJsonSchema(DslInput, {
|
|
14152
|
-
target: "jsonSchema7",
|
|
14153
|
-
$refStrategy: "none"
|
|
14154
|
-
})
|
|
14155
|
-
};
|
|
14156
|
-
var RefuseInputZ = zod.z.object({
|
|
14157
|
-
reason: zod.z.string().min(1).describe("L\xFD do kh\xF4ng v\u1EBD \u0111\u01B0\u1EE3c (ti\u1EBFng Vi\u1EC7t)")
|
|
14158
|
-
});
|
|
14159
|
-
var REFUSE_TOOL = {
|
|
14160
|
-
name: "refuse",
|
|
14161
|
-
description: "T\u1EEB ch\u1ED1i khi kh\xF4ng v\u1EBD \u0111\u01B0\u1EE3c ho\u1EB7c \u0111\u1EC1 ngo\xE0i ph\u1EA1m vi (3D, l\u01B0\u1EE3ng gi\xE1c, l\u1EDBp 11+).",
|
|
14162
|
-
input_schema: zodToJsonSchema.zodToJsonSchema(RefuseInputZ, { target: "jsonSchema7" })
|
|
14163
|
-
};
|
|
14164
|
-
var TOOLS4 = [BUILD_FIGURE_TOOL, REFUSE_TOOL];
|
|
14165
|
-
|
|
14166
|
-
// src/stamps/geometry-2d/ai/buildFigure.ts
|
|
14167
|
-
var DEFAULT_MODEL = "claude-opus-4-7";
|
|
14168
|
-
var DEFAULT_MAX_TOKENS = 8192;
|
|
14169
|
-
function toUsage(u) {
|
|
14170
|
-
return {
|
|
14171
|
-
inputTokens: u.input_tokens,
|
|
14172
|
-
outputTokens: u.output_tokens,
|
|
14173
|
-
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
14174
|
-
cacheCreationTokens: u.cache_creation_input_tokens ?? 0
|
|
14175
|
-
};
|
|
14176
|
-
}
|
|
14177
|
-
async function generateFigure(problem, opts) {
|
|
14178
|
-
if (!opts.apiKey) {
|
|
14179
|
-
return { ok: false, reason: "api_error", message: "apiKey b\u1EAFt bu\u1ED9c" };
|
|
14180
|
-
}
|
|
14181
|
-
if (!problem || !problem.trim()) {
|
|
14182
|
-
return { ok: false, reason: "api_error", message: "\u0110\u1EC1 b\xE0i r\u1ED7ng" };
|
|
14183
|
-
}
|
|
14184
|
-
const systemText = buildSystemPrompt();
|
|
14185
|
-
const enableCaching = opts.enableCaching !== false;
|
|
14186
|
-
const systemBlock = enableCaching ? { type: "text", text: systemText, cache_control: { type: "ephemeral" } } : { type: "text", text: systemText };
|
|
14187
|
-
let response;
|
|
14188
|
-
try {
|
|
14189
|
-
response = await callProvider({
|
|
14190
|
-
apiKey: opts.apiKey,
|
|
14191
|
-
model: opts.model ?? DEFAULT_MODEL,
|
|
14192
|
-
maxTokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
14193
|
-
system: [systemBlock],
|
|
14194
|
-
tools: TOOLS4,
|
|
14195
|
-
toolChoice: { type: "any" },
|
|
14196
|
-
messages: [{ role: "user", content: problem }],
|
|
14197
|
-
signal: opts.signal
|
|
14198
|
-
});
|
|
14199
|
-
} catch (e) {
|
|
14200
|
-
const err = e;
|
|
14201
|
-
return {
|
|
14202
|
-
ok: false,
|
|
14203
|
-
reason: "api_error",
|
|
14204
|
-
message: err.message ?? "L\u1ED7i g\u1ECDi Claude API",
|
|
14205
|
-
...err.status !== void 0 ? { status: err.status } : {}
|
|
14206
|
-
};
|
|
14207
|
-
}
|
|
14208
|
-
const usage = toUsage(response.usage);
|
|
14209
|
-
const toolUse = response.content.find((c) => c.type === "tool_use");
|
|
14210
|
-
if (!toolUse || toolUse.type !== "tool_use") {
|
|
14211
|
-
const text = response.content.find((c) => c.type === "text");
|
|
14212
|
-
const textStr = text?.type === "text" ? text.text : "(empty)";
|
|
14213
|
-
return {
|
|
14214
|
-
ok: false,
|
|
14215
|
-
reason: "parse_error",
|
|
14216
|
-
message: "AI kh\xF4ng g\u1ECDi tool n\xE0o. Response: " + textStr,
|
|
14217
|
-
raw: response.content,
|
|
14218
|
-
usage
|
|
14219
|
-
};
|
|
14220
|
-
}
|
|
14221
|
-
if (toolUse.name === "refuse") {
|
|
14222
|
-
const input = toolUse.input;
|
|
14223
|
-
return {
|
|
14224
|
-
ok: false,
|
|
14225
|
-
reason: "refused",
|
|
14226
|
-
message: input.reason ?? "AI t\u1EEB ch\u1ED1i kh\xF4ng n\xEAu l\xFD do",
|
|
14227
|
-
usage
|
|
14228
|
-
};
|
|
14229
|
-
}
|
|
14230
|
-
if (toolUse.name !== "build_figure") {
|
|
14231
|
-
return {
|
|
14232
|
-
ok: false,
|
|
14233
|
-
reason: "parse_error",
|
|
14234
|
-
message: `Tool kh\xF4ng x\xE1c \u0111\u1ECBnh: "${toolUse.name}"`,
|
|
14235
|
-
raw: toolUse,
|
|
14236
|
-
usage
|
|
14237
|
-
};
|
|
14238
|
-
}
|
|
14239
|
-
const tResult = transpile(toolUse.input);
|
|
14240
|
-
if (!tResult.ok) {
|
|
14241
|
-
return {
|
|
14242
|
-
ok: false,
|
|
14243
|
-
reason: "transpile_error",
|
|
14244
|
-
message: "DSL t\u1EEB AI kh\xF4ng h\u1EE3p l\u1EC7",
|
|
14245
|
-
errors: tResult.errors,
|
|
14246
|
-
dsl: toolUse.input,
|
|
14247
|
-
usage
|
|
14248
|
-
};
|
|
14249
|
-
}
|
|
14250
|
-
return {
|
|
14251
|
-
ok: true,
|
|
14252
|
-
state: tResult.state,
|
|
14253
|
-
dsl: toolUse.input,
|
|
14254
|
-
usage
|
|
14255
|
-
};
|
|
14256
|
-
}
|
|
14257
15182
|
|
|
14258
15183
|
exports.ALL_STAMPS = ALL_STAMPS;
|
|
14259
15184
|
exports.DEFAULT_STAMPS = DEFAULT_STAMPS;
|
|
@@ -14265,7 +15190,6 @@ exports.closePdfDocument = closePdfDocument;
|
|
|
14265
15190
|
exports.configurePdfWorker = configurePdfWorker;
|
|
14266
15191
|
exports.findCatalogEntry = findCatalogEntry;
|
|
14267
15192
|
exports.findStampForCustomData = findStampForCustomData;
|
|
14268
|
-
exports.generateFigure = generateFigure;
|
|
14269
15193
|
exports.geometry3dStamp = geometry3dStamp;
|
|
14270
15194
|
exports.geometryStamp = geometryStamp;
|
|
14271
15195
|
exports.graph2dStamp = graph2dStamp;
|