@chanmeng666/archlang 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -169
- package/dist/{chunk-GUNWYUR2.js → chunk-CPK5CI5Y.js} +559 -165
- package/dist/chunk-CPK5CI5Y.js.map +1 -0
- package/dist/cli.js +51 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +310 -1
- package/dist/index.js +11 -3
- package/package.json +8 -2
- package/dist/chunk-GUNWYUR2.js.map +0 -1
|
@@ -352,6 +352,15 @@ function mergeTheme(...layers) {
|
|
|
352
352
|
}
|
|
353
353
|
return out;
|
|
354
354
|
}
|
|
355
|
+
function sanitizeTheme(theme) {
|
|
356
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
357
|
+
const out = { ...theme };
|
|
358
|
+
for (const k of Object.keys(out)) {
|
|
359
|
+
const v = out[k];
|
|
360
|
+
if (typeof v === "string") out[k] = esc(v);
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
355
364
|
|
|
356
365
|
// src/geometry.ts
|
|
357
366
|
var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
@@ -387,6 +396,13 @@ function distPointToSegment(p, a, b) {
|
|
|
387
396
|
const cy = a.y + t * aby;
|
|
388
397
|
return Math.hypot(p.x - cx, p.y - cy);
|
|
389
398
|
}
|
|
399
|
+
function minorArcDegrees(center, start, end) {
|
|
400
|
+
const deg = (p) => Math.atan2(-(p.y - center.y), p.x - center.x) * 180 / Math.PI;
|
|
401
|
+
const a1 = deg(start);
|
|
402
|
+
const a2 = deg(end);
|
|
403
|
+
const ccw = ((a2 - a1) % 360 + 360) % 360;
|
|
404
|
+
return ccw <= 180 ? [a1, a2] : [a2, a1];
|
|
405
|
+
}
|
|
390
406
|
function rectCorners(x, y, w, h) {
|
|
391
407
|
return [
|
|
392
408
|
{ x, y },
|
|
@@ -418,30 +434,23 @@ function segmentsOfWall(w) {
|
|
|
418
434
|
}
|
|
419
435
|
return segs;
|
|
420
436
|
}
|
|
421
|
-
function
|
|
437
|
+
function hostInfoForWalls(walls, at, ref) {
|
|
422
438
|
const candidates = ref ? walls.filter((w) => w.id === ref || w.category === ref) : walls;
|
|
423
|
-
let
|
|
439
|
+
let host = null;
|
|
424
440
|
let bestDist = Infinity;
|
|
441
|
+
let onWall = false;
|
|
425
442
|
for (const w of candidates) {
|
|
443
|
+
const tol = w.thickness / 2 + Math.max(w.thickness, 1);
|
|
426
444
|
for (const s of segmentsOfWall(w)) {
|
|
427
445
|
const dist = distPointToSegment(at, s.a, s.b);
|
|
428
446
|
if (dist < bestDist) {
|
|
429
447
|
bestDist = dist;
|
|
430
|
-
|
|
448
|
+
host = s;
|
|
431
449
|
}
|
|
450
|
+
if (!onWall && dist <= tol) onWall = true;
|
|
432
451
|
}
|
|
433
452
|
}
|
|
434
|
-
return
|
|
435
|
-
}
|
|
436
|
-
function isOnSomeWall(walls, at, ref) {
|
|
437
|
-
const candidates = ref ? walls.filter((w) => w.id === ref || w.category === ref) : walls;
|
|
438
|
-
for (const w of candidates) {
|
|
439
|
-
const tol = w.thickness / 2 + Math.max(w.thickness, 1);
|
|
440
|
-
for (const s of segmentsOfWall(w)) {
|
|
441
|
-
if (distPointToSegment(at, s.a, s.b) <= tol) return true;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return false;
|
|
453
|
+
return { host, onWall };
|
|
445
454
|
}
|
|
446
455
|
|
|
447
456
|
// src/hatches.ts
|
|
@@ -545,33 +554,29 @@ var wall = {
|
|
|
545
554
|
const w = resolved;
|
|
546
555
|
return segmentsOfWall(w).flatMap((s) => segmentRectangle(s.a, s.b, s.thickness));
|
|
547
556
|
},
|
|
557
|
+
/**
|
|
558
|
+
* Per-segment wall fill (poché) + two crisp face lines. This is the angled-wall
|
|
559
|
+
* path; orthogonal walls are unioned into clean loops in `scene-build.ts`. The
|
|
560
|
+
* fill always references the default poché pattern, matching v0.1.
|
|
561
|
+
*/
|
|
548
562
|
render(resolved, ctx) {
|
|
549
563
|
const w = resolved;
|
|
550
|
-
const {
|
|
564
|
+
const { theme, sizes } = ctx;
|
|
551
565
|
const segs = segmentsOfWall(w);
|
|
552
|
-
const
|
|
566
|
+
const nodes = [];
|
|
553
567
|
for (const s of segs) {
|
|
554
568
|
const poly = segmentRectangle(s.a, s.b, s.thickness);
|
|
555
|
-
|
|
569
|
+
nodes.push({ layer: "wallFill", prim: { t: "polygon", pts: poly }, paint: { fill: "url(#poche)" } });
|
|
556
570
|
}
|
|
557
571
|
for (const s of segs) {
|
|
558
572
|
const d = unit(sub(s.b, s.a));
|
|
559
573
|
const n = normal(d);
|
|
560
574
|
const h = s.thickness / 2;
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const fb2 = add(s.b, mul(n, -h));
|
|
565
|
-
ops.push({
|
|
566
|
-
pass: "wallFace",
|
|
567
|
-
svg: `<line x1="${fmt2(fa1.x)}" y1="${fmt2(fa1.y)}" x2="${fmt2(fb1.x)}" y2="${fmt2(fb1.y)}" stroke="${theme.wallStroke}" stroke-width="${fmt2(sizes.wallStroke)}" stroke-linecap="square"/>`
|
|
568
|
-
});
|
|
569
|
-
ops.push({
|
|
570
|
-
pass: "wallFace",
|
|
571
|
-
svg: `<line x1="${fmt2(fa2.x)}" y1="${fmt2(fa2.y)}" x2="${fmt2(fb2.x)}" y2="${fmt2(fb2.y)}" stroke="${theme.wallStroke}" stroke-width="${fmt2(sizes.wallStroke)}" stroke-linecap="square"/>`
|
|
572
|
-
});
|
|
575
|
+
const face = { stroke: theme.wallStroke, width: sizes.wallStroke, linecap: "square" };
|
|
576
|
+
nodes.push({ layer: "wallFace", prim: { t: "line", a: add(s.a, mul(n, h)), b: add(s.b, mul(n, h)) }, paint: face });
|
|
577
|
+
nodes.push({ layer: "wallFace", prim: { t: "line", a: add(s.a, mul(n, -h)), b: add(s.b, mul(n, -h)) }, paint: face });
|
|
573
578
|
}
|
|
574
|
-
return
|
|
579
|
+
return nodes;
|
|
575
580
|
}
|
|
576
581
|
};
|
|
577
582
|
function describe2(ctx) {
|
|
@@ -616,24 +621,26 @@ var room = {
|
|
|
616
621
|
},
|
|
617
622
|
render(resolved, ctx) {
|
|
618
623
|
const r = resolved;
|
|
619
|
-
const {
|
|
620
|
-
const
|
|
624
|
+
const { theme, sizes } = ctx;
|
|
625
|
+
const nodes = [];
|
|
621
626
|
const c = rectCorners(r.at.x, r.at.y, r.size.w, r.size.h);
|
|
622
|
-
|
|
627
|
+
nodes.push({ layer: "floor", prim: { t: "polygon", pts: c }, paint: { fill: theme.roomFill } });
|
|
623
628
|
const cx = r.at.x + r.size.w / 2;
|
|
624
629
|
const cy = r.at.y + r.size.h / 2;
|
|
625
630
|
const areaM2 = (r.size.w / 1e3 * (r.size.h / 1e3)).toFixed(1);
|
|
626
631
|
if (r.label) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
632
|
+
nodes.push({
|
|
633
|
+
layer: "labels",
|
|
634
|
+
prim: { t: "text", at: { x: cx, y: cy - sizes.roomFont * 0.2 }, value: r.label, size: sizes.roomFont, anchor: "middle", baseline: "central", weight: 600 },
|
|
635
|
+
paint: { fill: theme.roomLabel }
|
|
630
636
|
});
|
|
631
637
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
638
|
+
nodes.push({
|
|
639
|
+
layer: "labels",
|
|
640
|
+
prim: { t: "text", at: { x: cx, y: cy + (r.label ? sizes.roomFont * 0.9 : 0) }, value: `${areaM2} m\xB2`, size: sizes.areaFont, anchor: "middle", baseline: "central" },
|
|
641
|
+
paint: { fill: theme.areaLabel }
|
|
635
642
|
});
|
|
636
|
-
return
|
|
643
|
+
return nodes;
|
|
637
644
|
}
|
|
638
645
|
};
|
|
639
646
|
|
|
@@ -683,11 +690,17 @@ var door = {
|
|
|
683
690
|
return { kind: "door", id, at, width, hinge: n.hinge, swing: n.swing, host: ctx.hostSegment(at, n.wall), span: n.span };
|
|
684
691
|
},
|
|
685
692
|
bounds: () => [],
|
|
693
|
+
/**
|
|
694
|
+
* Opening cover + leaf line + swing arc. The swing geometry (hinge, leaf,
|
|
695
|
+
* far jamb, minor-arc orientation) is computed **here, once** — every backend
|
|
696
|
+
* (SVG, DXF, PDF) now serializes the same `arc` primitive rather than
|
|
697
|
+
* re-deriving it.
|
|
698
|
+
*/
|
|
686
699
|
render(resolved, ctx) {
|
|
687
700
|
const dr = resolved;
|
|
688
701
|
const seg = dr.host;
|
|
689
702
|
if (!seg) return [];
|
|
690
|
-
const {
|
|
703
|
+
const { theme, sizes } = ctx;
|
|
691
704
|
const d = unit(sub(seg.b, seg.a));
|
|
692
705
|
const n = normal(d);
|
|
693
706
|
const h = seg.thickness / 2 + sizes.wallStroke;
|
|
@@ -698,23 +711,25 @@ var door = {
|
|
|
698
711
|
add(add(dr.at, mul(d, hw)), mul(n, -h)),
|
|
699
712
|
add(add(dr.at, mul(d, -hw)), mul(n, -h))
|
|
700
713
|
];
|
|
701
|
-
const
|
|
702
|
-
|
|
714
|
+
const nodes = [];
|
|
715
|
+
nodes.push({ layer: "doors", prim: { t: "polygon", pts: cover }, paint: { fill: theme.opening } });
|
|
703
716
|
const hinge = dr.hinge === "left" ? add(dr.at, mul(d, -hw)) : add(dr.at, mul(d, hw));
|
|
704
717
|
const farJamb = dr.hinge === "left" ? add(dr.at, mul(d, hw)) : add(dr.at, mul(d, -hw));
|
|
705
718
|
const leafDir = dr.swing === "in" ? n : mul(n, -1);
|
|
706
719
|
const leafEnd = add(hinge, mul(leafDir, dr.width));
|
|
707
720
|
const cross = (leafEnd.x - hinge.x) * (farJamb.y - hinge.y) - (leafEnd.y - hinge.y) * (farJamb.x - hinge.x);
|
|
708
721
|
const sweep = cross < 0 ? 1 : 0;
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
722
|
+
nodes.push({
|
|
723
|
+
layer: "doors",
|
|
724
|
+
prim: { t: "line", a: hinge, b: leafEnd },
|
|
725
|
+
paint: { stroke: theme.doorLeaf, width: sizes.thin * 1.3 }
|
|
712
726
|
});
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
727
|
+
nodes.push({
|
|
728
|
+
layer: "doors",
|
|
729
|
+
prim: { t: "arc", center: hinge, r: dr.width, start: leafEnd, end: farJamb, sweep },
|
|
730
|
+
paint: { fill: "none", stroke: theme.doorLeaf, width: sizes.thin, dash: [sizes.thin * 4, sizes.thin * 3] }
|
|
716
731
|
});
|
|
717
|
-
return
|
|
732
|
+
return nodes;
|
|
718
733
|
}
|
|
719
734
|
};
|
|
720
735
|
|
|
@@ -756,7 +771,7 @@ var windowEl = {
|
|
|
756
771
|
const wn = resolved;
|
|
757
772
|
const seg = wn.host;
|
|
758
773
|
if (!seg) return [];
|
|
759
|
-
const {
|
|
774
|
+
const { theme, sizes } = ctx;
|
|
760
775
|
const d = unit(sub(seg.b, seg.a));
|
|
761
776
|
const n = normal(d);
|
|
762
777
|
const h = seg.thickness / 2;
|
|
@@ -768,23 +783,23 @@ var windowEl = {
|
|
|
768
783
|
add(add(wn.at, mul(d, hw)), mul(n, -he)),
|
|
769
784
|
add(add(wn.at, mul(d, -hw)), mul(n, -he))
|
|
770
785
|
];
|
|
771
|
-
const
|
|
772
|
-
|
|
786
|
+
const nodes = [];
|
|
787
|
+
nodes.push({ layer: "windows", prim: { t: "polygon", pts: cover }, paint: { fill: theme.opening } });
|
|
773
788
|
const jA = add(wn.at, mul(d, -hw));
|
|
774
789
|
const jB = add(wn.at, mul(d, hw));
|
|
775
790
|
for (const off of [h, -h]) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
svg: `<line x1="${fmt2(a.x)}" y1="${fmt2(a.y)}" x2="${fmt2(bb.x)}" y2="${fmt2(bb.y)}" stroke="${theme.wallStroke}" stroke-width="${fmt2(sizes.thin)}"/>`
|
|
791
|
+
nodes.push({
|
|
792
|
+
layer: "windows",
|
|
793
|
+
prim: { t: "line", a: add(jA, mul(n, off)), b: add(jB, mul(n, off)) },
|
|
794
|
+
paint: { stroke: theme.wallStroke, width: sizes.thin }
|
|
781
795
|
});
|
|
782
796
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
797
|
+
nodes.push({
|
|
798
|
+
layer: "windows",
|
|
799
|
+
prim: { t: "line", a: jA, b: jB },
|
|
800
|
+
paint: { stroke: theme.windowPane, width: sizes.thin }
|
|
786
801
|
});
|
|
787
|
-
return
|
|
802
|
+
return nodes;
|
|
788
803
|
}
|
|
789
804
|
};
|
|
790
805
|
|
|
@@ -824,22 +839,24 @@ var furniture = {
|
|
|
824
839
|
},
|
|
825
840
|
render(resolved, ctx) {
|
|
826
841
|
const f = resolved;
|
|
827
|
-
const {
|
|
828
|
-
const
|
|
842
|
+
const { theme, sizes } = ctx;
|
|
843
|
+
const nodes = [];
|
|
829
844
|
const c = rectCorners(f.at.x, f.at.y, f.size.w, f.size.h);
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
845
|
+
nodes.push({
|
|
846
|
+
layer: "furniture",
|
|
847
|
+
prim: { t: "polygon", pts: c },
|
|
848
|
+
paint: { fill: theme.furnitureFill, stroke: theme.furnitureStroke, width: sizes.thin }
|
|
833
849
|
});
|
|
834
850
|
if (f.label) {
|
|
835
851
|
const cx = f.at.x + f.size.w / 2;
|
|
836
852
|
const cy = f.at.y + f.size.h / 2;
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
853
|
+
nodes.push({
|
|
854
|
+
layer: "furniture",
|
|
855
|
+
prim: { t: "text", at: { x: cx, y: cy }, value: f.label, size: sizes.furnFont, anchor: "middle", baseline: "central" },
|
|
856
|
+
paint: { fill: theme.furnitureLabel }
|
|
840
857
|
});
|
|
841
858
|
}
|
|
842
|
-
return
|
|
859
|
+
return nodes;
|
|
843
860
|
}
|
|
844
861
|
};
|
|
845
862
|
|
|
@@ -882,33 +899,22 @@ var dim = {
|
|
|
882
899
|
},
|
|
883
900
|
render(resolved, ctx) {
|
|
884
901
|
const dm = resolved;
|
|
885
|
-
const {
|
|
902
|
+
const { theme, sizes } = ctx;
|
|
886
903
|
const dir = unit(sub(dm.to, dm.from));
|
|
887
904
|
const n = normal(dir);
|
|
888
905
|
const off = mul(n, dm.offset);
|
|
889
906
|
const p1 = add(dm.from, off);
|
|
890
907
|
const p2 = add(dm.to, off);
|
|
891
908
|
const tick = sizes.refDim * 0.012;
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
});
|
|
897
|
-
ops.push({
|
|
898
|
-
pass: "dims",
|
|
899
|
-
svg: `<line x1="${fmt2(dm.to.x)}" y1="${fmt2(dm.to.y)}" x2="${fmt2(p2.x)}" y2="${fmt2(p2.y)}" stroke="${theme.dim}" stroke-width="${fmt2(sizes.thin * 0.7)}"/>`
|
|
900
|
-
});
|
|
901
|
-
ops.push({
|
|
902
|
-
pass: "dims",
|
|
903
|
-
svg: `<line x1="${fmt2(p1.x)}" y1="${fmt2(p1.y)}" x2="${fmt2(p2.x)}" y2="${fmt2(p2.y)}" stroke="${theme.dim}" stroke-width="${fmt2(sizes.thin)}"/>`
|
|
904
|
-
});
|
|
909
|
+
const thinPaint = { stroke: theme.dim, width: sizes.thin };
|
|
910
|
+
const nodes = [];
|
|
911
|
+
nodes.push({ layer: "dims", prim: { t: "line", a: dm.from, b: p1 }, paint: { stroke: theme.dim, width: sizes.thin * 0.7 } });
|
|
912
|
+
nodes.push({ layer: "dims", prim: { t: "line", a: dm.to, b: p2 }, paint: { stroke: theme.dim, width: sizes.thin * 0.7 } });
|
|
913
|
+
nodes.push({ layer: "dims", prim: { t: "line", a: p1, b: p2 }, paint: thinPaint });
|
|
905
914
|
for (const p of [p1, p2]) {
|
|
906
915
|
const t1 = add(p, mul(unit({ x: dir.x + n.x, y: dir.y + n.y }), tick));
|
|
907
916
|
const t2 = add(p, mul(unit({ x: dir.x + n.x, y: dir.y + n.y }), -tick));
|
|
908
|
-
|
|
909
|
-
pass: "dims",
|
|
910
|
-
svg: `<line x1="${fmt2(t1.x)}" y1="${fmt2(t1.y)}" x2="${fmt2(t2.x)}" y2="${fmt2(t2.y)}" stroke="${theme.dim}" stroke-width="${fmt2(sizes.thin)}"/>`
|
|
911
|
-
});
|
|
917
|
+
nodes.push({ layer: "dims", prim: { t: "line", a: t1, b: t2 }, paint: thinPaint });
|
|
912
918
|
}
|
|
913
919
|
const mid = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
|
|
914
920
|
const tp = add(mid, mul(n, sizes.dimFont * 0.7));
|
|
@@ -916,11 +922,12 @@ var dim = {
|
|
|
916
922
|
if (angle > 90) angle -= 180;
|
|
917
923
|
if (angle < -90) angle += 180;
|
|
918
924
|
const label = dm.text ?? String(Math.round(length(sub(dm.to, dm.from))));
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
925
|
+
nodes.push({
|
|
926
|
+
layer: "dims",
|
|
927
|
+
prim: { t: "text", at: tp, value: label, size: sizes.dimFont, anchor: "middle", baseline: "central", rotate: angle },
|
|
928
|
+
paint: { fill: theme.dim }
|
|
922
929
|
});
|
|
923
|
-
return
|
|
930
|
+
return nodes;
|
|
924
931
|
}
|
|
925
932
|
};
|
|
926
933
|
|
|
@@ -954,12 +961,13 @@ var column = {
|
|
|
954
961
|
},
|
|
955
962
|
render(resolved, ctx) {
|
|
956
963
|
const c = resolved;
|
|
957
|
-
const {
|
|
964
|
+
const { theme, sizes } = ctx;
|
|
958
965
|
const pts = rectCorners(c.at.x, c.at.y, c.size.w, c.size.h);
|
|
959
966
|
return [
|
|
960
967
|
{
|
|
961
|
-
|
|
962
|
-
|
|
968
|
+
layer: "furniture",
|
|
969
|
+
prim: { t: "polygon", pts },
|
|
970
|
+
paint: { fill: theme.column, stroke: theme.wallStroke, width: sizes.thin }
|
|
963
971
|
}
|
|
964
972
|
];
|
|
965
973
|
}
|
|
@@ -1427,6 +1435,15 @@ function resolve(ast) {
|
|
|
1427
1435
|
let activeEnv = /* @__PURE__ */ new Map();
|
|
1428
1436
|
const evalNum = (e) => evalExpr(e, activeEnv, (d) => diagnostics.push(d));
|
|
1429
1437
|
const evalPt = (p) => ({ x: evalNum(p.x), y: evalNum(p.y) });
|
|
1438
|
+
let hiKey = "";
|
|
1439
|
+
let hiVal = null;
|
|
1440
|
+
const hostInfo = (at, ref) => {
|
|
1441
|
+
const key = `${at.x},${at.y},${ref ?? ""}`;
|
|
1442
|
+
if (key === hiKey && hiVal) return hiVal;
|
|
1443
|
+
hiKey = key;
|
|
1444
|
+
hiVal = hostInfoForWalls(walls, at, ref);
|
|
1445
|
+
return hiVal;
|
|
1446
|
+
};
|
|
1430
1447
|
const ctx = {
|
|
1431
1448
|
grid: g,
|
|
1432
1449
|
snap,
|
|
@@ -1435,8 +1452,8 @@ function resolve(ast) {
|
|
|
1435
1452
|
evalPt,
|
|
1436
1453
|
id: "",
|
|
1437
1454
|
walls,
|
|
1438
|
-
hostSegment: (at, ref) =>
|
|
1439
|
-
isOnWall: (at, ref) =>
|
|
1455
|
+
hostSegment: (at, ref) => hostInfo(at, ref).host,
|
|
1456
|
+
isOnWall: (at, ref) => hostInfo(at, ref).onWall,
|
|
1440
1457
|
diag: (d) => diagnostics.push(d)
|
|
1441
1458
|
};
|
|
1442
1459
|
for (const def of registryOrder) {
|
|
@@ -1491,19 +1508,6 @@ function resolve(ast) {
|
|
|
1491
1508
|
return { ir, diagnostics };
|
|
1492
1509
|
}
|
|
1493
1510
|
|
|
1494
|
-
// src/registry.ts
|
|
1495
|
-
var RENDER_PASSES = [
|
|
1496
|
-
"floor",
|
|
1497
|
-
"furniture",
|
|
1498
|
-
"wallFill",
|
|
1499
|
-
"wallFace",
|
|
1500
|
-
"doors",
|
|
1501
|
-
"windows",
|
|
1502
|
-
"labels",
|
|
1503
|
-
"dims",
|
|
1504
|
-
"annotations"
|
|
1505
|
-
];
|
|
1506
|
-
|
|
1507
1511
|
// src/geometry/union.ts
|
|
1508
1512
|
function uniqSorted(values) {
|
|
1509
1513
|
const out = [...new Set(values)].sort((a, b) => a - b);
|
|
@@ -1601,21 +1605,7 @@ function mergeCollinear(loop) {
|
|
|
1601
1605
|
return out.length >= 3 ? out : loop;
|
|
1602
1606
|
}
|
|
1603
1607
|
|
|
1604
|
-
// src/
|
|
1605
|
-
function fmt(v) {
|
|
1606
|
-
const r = Math.round(v * 100) / 100;
|
|
1607
|
-
return Object.is(r, -0) ? "0" : String(r);
|
|
1608
|
-
}
|
|
1609
|
-
var pt = (p) => `${fmt(p.x)},${fmt(p.y)}`;
|
|
1610
|
-
function xml(s) {
|
|
1611
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1612
|
-
}
|
|
1613
|
-
var NICE_LENGTHS = [500, 1e3, 2e3, 5e3, 1e4, 2e4, 5e4, 1e5];
|
|
1614
|
-
function niceBarLength(target) {
|
|
1615
|
-
let best = NICE_LENGTHS[0];
|
|
1616
|
-
for (const v of NICE_LENGTHS) if (v <= target) best = v;
|
|
1617
|
-
return best;
|
|
1618
|
-
}
|
|
1608
|
+
// src/scene-build.ts
|
|
1619
1609
|
function planBounds(ir) {
|
|
1620
1610
|
const b = emptyBounds();
|
|
1621
1611
|
for (const el of ir.elements) {
|
|
@@ -1631,20 +1621,17 @@ function planBounds(ir) {
|
|
|
1631
1621
|
function allOrthogonal(walls) {
|
|
1632
1622
|
return walls.every((w) => segmentsOfWall(w).every((s) => s.a.x === s.b.x || s.a.y === s.b.y));
|
|
1633
1623
|
}
|
|
1634
|
-
function loopsToPath(loops) {
|
|
1635
|
-
return loops.map((loop) => "M " + loop.map(pt).join(" L ") + " Z").join(" ");
|
|
1636
|
-
}
|
|
1637
1624
|
function materialsUsed(walls) {
|
|
1638
1625
|
return [...new Set(walls.map((w) => w.material))].sort();
|
|
1639
1626
|
}
|
|
1640
|
-
function
|
|
1627
|
+
function lowerWalls(walls, ctx) {
|
|
1641
1628
|
if (walls.length === 0) return [];
|
|
1642
|
-
const
|
|
1629
|
+
const nodes = [];
|
|
1643
1630
|
for (const mat of materialsUsed(walls)) {
|
|
1644
1631
|
const group = walls.filter((w) => w.material === mat);
|
|
1645
1632
|
if (!allOrthogonal(group)) {
|
|
1646
1633
|
const def = registry.get("wall");
|
|
1647
|
-
|
|
1634
|
+
nodes.push(...group.flatMap((w) => def.render(w, ctx)));
|
|
1648
1635
|
continue;
|
|
1649
1636
|
}
|
|
1650
1637
|
const rects = [];
|
|
@@ -1658,18 +1645,18 @@ function renderWalls(walls, ctx) {
|
|
|
1658
1645
|
}
|
|
1659
1646
|
const loops = rectUnionOutline(rects);
|
|
1660
1647
|
if (loops.length === 0) continue;
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1648
|
+
nodes.push({ layer: "wallFill", prim: { t: "region", loops }, paint: { fill: `url(#${patternId(mat)})`, fillRule: "nonzero" } });
|
|
1649
|
+
nodes.push({
|
|
1650
|
+
layer: "wallFace",
|
|
1651
|
+
prim: { t: "region", loops },
|
|
1652
|
+
paint: { fill: "none", stroke: ctx.theme.wallStroke, width: ctx.sizes.wallStroke, linejoin: "miter" }
|
|
1666
1653
|
});
|
|
1667
1654
|
}
|
|
1668
|
-
return
|
|
1655
|
+
return nodes;
|
|
1669
1656
|
}
|
|
1670
|
-
function
|
|
1671
|
-
const
|
|
1672
|
-
const lw =
|
|
1657
|
+
function toScene(ir, opts = {}) {
|
|
1658
|
+
const theme = sanitizeTheme(mergeTheme(DEFAULT_THEME, ir.theme, opts.theme));
|
|
1659
|
+
const lw = theme.lineWeight;
|
|
1673
1660
|
const b = planBounds(ir);
|
|
1674
1661
|
const drawW = b.maxX - b.minX;
|
|
1675
1662
|
const drawH = b.maxY - b.minY;
|
|
@@ -1685,7 +1672,100 @@ function render(ir, opts = {}) {
|
|
|
1685
1672
|
margin: refDim * 0.17,
|
|
1686
1673
|
hatchGap: refDim * 0.013
|
|
1687
1674
|
};
|
|
1675
|
+
const ctx = { theme, sizes, bounds: b };
|
|
1676
|
+
const nodes = [];
|
|
1677
|
+
for (const el of ir.elements) {
|
|
1678
|
+
if (el.kind === "wall") continue;
|
|
1679
|
+
const def = registry.get(el.kind);
|
|
1680
|
+
if (def) nodes.push(...def.render(el, ctx));
|
|
1681
|
+
}
|
|
1682
|
+
nodes.push(...lowerWalls(ir.walls, ctx));
|
|
1683
|
+
return {
|
|
1684
|
+
width: drawW + sizes.margin * 2,
|
|
1685
|
+
height: drawH + sizes.margin * 2,
|
|
1686
|
+
bounds: b,
|
|
1687
|
+
nodes,
|
|
1688
|
+
theme,
|
|
1689
|
+
sizes,
|
|
1690
|
+
north: ir.north,
|
|
1691
|
+
scale: ir.scale,
|
|
1692
|
+
title: ir.title,
|
|
1693
|
+
name: ir.name,
|
|
1694
|
+
materials: materialsUsed(ir.walls)
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// src/scene.ts
|
|
1699
|
+
var RENDER_PASSES = [
|
|
1700
|
+
"floor",
|
|
1701
|
+
"furniture",
|
|
1702
|
+
"wallFill",
|
|
1703
|
+
"wallFace",
|
|
1704
|
+
"doors",
|
|
1705
|
+
"windows",
|
|
1706
|
+
"labels",
|
|
1707
|
+
"dims",
|
|
1708
|
+
"annotations"
|
|
1709
|
+
];
|
|
1710
|
+
|
|
1711
|
+
// src/backends/svg.ts
|
|
1712
|
+
function fmt(v) {
|
|
1713
|
+
const r = Math.round(v * 100) / 100;
|
|
1714
|
+
return Object.is(r, -0) ? "0" : String(r);
|
|
1715
|
+
}
|
|
1716
|
+
var pt = (p) => `${fmt(p.x)},${fmt(p.y)}`;
|
|
1717
|
+
function xml(s) {
|
|
1718
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1719
|
+
}
|
|
1720
|
+
var NICE_LENGTHS = [500, 1e3, 2e3, 5e3, 1e4, 2e4, 5e4, 1e5];
|
|
1721
|
+
function niceBarLength(target) {
|
|
1722
|
+
let best = NICE_LENGTHS[0];
|
|
1723
|
+
for (const v of NICE_LENGTHS) if (v <= target) best = v;
|
|
1724
|
+
return best;
|
|
1725
|
+
}
|
|
1726
|
+
function strokeAttrs(paint) {
|
|
1727
|
+
if (!paint.stroke) return "";
|
|
1728
|
+
let s = ` stroke="${paint.stroke}" stroke-width="${fmt(paint.width ?? 0)}"`;
|
|
1729
|
+
if (paint.linecap) s += ` stroke-linecap="${paint.linecap}"`;
|
|
1730
|
+
return s;
|
|
1731
|
+
}
|
|
1732
|
+
function pathPaint(paint) {
|
|
1733
|
+
let s = ` fill="${paint.fill ?? "none"}"`;
|
|
1734
|
+
if (paint.fillRule) s += ` fill-rule="${paint.fillRule}"`;
|
|
1735
|
+
if (paint.stroke) s += ` stroke="${paint.stroke}" stroke-width="${fmt(paint.width ?? 0)}"`;
|
|
1736
|
+
if (paint.linejoin) s += ` stroke-linejoin="${paint.linejoin}"`;
|
|
1737
|
+
if (paint.dash) s += ` stroke-dasharray="${fmt(paint.dash[0])} ${fmt(paint.dash[1])}"`;
|
|
1738
|
+
return s;
|
|
1739
|
+
}
|
|
1740
|
+
function regionPath(loops) {
|
|
1741
|
+
return loops.map((loop) => "M " + loop.map(pt).join(" L ") + " Z").join(" ");
|
|
1742
|
+
}
|
|
1743
|
+
function serialize(node) {
|
|
1744
|
+
const { prim, paint } = node;
|
|
1745
|
+
switch (prim.t) {
|
|
1746
|
+
case "polygon":
|
|
1747
|
+
return `<polygon points="${prim.pts.map(pt).join(" ")}" fill="${paint.fill ?? "none"}"${strokeAttrs(paint)}/>`;
|
|
1748
|
+
case "line":
|
|
1749
|
+
return `<line x1="${fmt(prim.a.x)}" y1="${fmt(prim.a.y)}" x2="${fmt(prim.b.x)}" y2="${fmt(prim.b.y)}" stroke="${paint.stroke ?? "none"}" stroke-width="${fmt(paint.width ?? 0)}"${paint.linecap ? ` stroke-linecap="${paint.linecap}"` : ""}/>`;
|
|
1750
|
+
case "region":
|
|
1751
|
+
return `<path d="${regionPath(prim.loops)}"${pathPaint(paint)}/>`;
|
|
1752
|
+
case "arc":
|
|
1753
|
+
return `<path d="M ${pt(prim.start)} A ${fmt(prim.r)} ${fmt(prim.r)} 0 0 ${prim.sweep} ${pt(prim.end)}"${pathPaint(paint)}/>`;
|
|
1754
|
+
case "text": {
|
|
1755
|
+
const weight = prim.weight !== void 0 ? ` font-weight="${prim.weight}"` : "";
|
|
1756
|
+
const transform = prim.rotate !== void 0 ? ` transform="rotate(${fmt(prim.rotate)} ${fmt(prim.at.x)} ${fmt(prim.at.y)})"` : "";
|
|
1757
|
+
return `<text x="${fmt(prim.at.x)}" y="${fmt(prim.at.y)}" font-size="${fmt(prim.size)}" fill="${paint.fill ?? "none"}" text-anchor="${prim.anchor}" dominant-baseline="${prim.baseline}"${weight}${transform}>${xml(prim.value)}</text>`;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
function renderSvg(scene, opts = {}) {
|
|
1762
|
+
const THEME = scene.theme;
|
|
1763
|
+
const sizes = scene.sizes;
|
|
1764
|
+
const b = scene.bounds;
|
|
1765
|
+
const refDim = sizes.refDim;
|
|
1688
1766
|
const { thin, margin, hatchGap } = sizes;
|
|
1767
|
+
const drawW = b.maxX - b.minX;
|
|
1768
|
+
const drawH = b.maxY - b.minY;
|
|
1689
1769
|
const vbX = b.minX - margin;
|
|
1690
1770
|
const vbY = b.minY - margin;
|
|
1691
1771
|
const vbW = drawW + margin * 2;
|
|
@@ -1693,35 +1773,28 @@ function render(ir, opts = {}) {
|
|
|
1693
1773
|
const out = [];
|
|
1694
1774
|
const svgAttrs = opts.width ? `width="${fmt(opts.width)}" height="${fmt(opts.width * vbH / vbW)}"` : "";
|
|
1695
1775
|
out.push(
|
|
1696
|
-
`<svg xmlns="http://www.w3.org/2000/svg" ${svgAttrs} viewBox="${fmt(vbX)} ${fmt(vbY)} ${fmt(vbW)} ${fmt(vbH)}" font-family="${
|
|
1776
|
+
`<svg xmlns="http://www.w3.org/2000/svg" ${svgAttrs} viewBox="${fmt(vbX)} ${fmt(vbY)} ${fmt(vbW)} ${fmt(vbH)}" font-family="${THEME.font}">`
|
|
1697
1777
|
);
|
|
1698
1778
|
const hatchCtx = { fmt, gap: hatchGap, thin, base: THEME.pocheBase, line: THEME.pocheHatch };
|
|
1699
|
-
const patterns =
|
|
1779
|
+
const patterns = scene.materials.map((m) => hatchPattern(m, hatchCtx)).join("");
|
|
1700
1780
|
out.push(`<defs>${patterns}</defs>`);
|
|
1701
1781
|
out.push(`<rect x="${fmt(vbX)}" y="${fmt(vbY)}" width="${fmt(vbW)}" height="${fmt(vbH)}" fill="${THEME.bg}"/>`);
|
|
1702
|
-
const ctx = { fmt, pt, xml, theme: THEME, sizes, bounds: b };
|
|
1703
|
-
const ops = ir.elements.flatMap((el) => {
|
|
1704
|
-
if (el.kind === "wall") return [];
|
|
1705
|
-
const def = registry.get(el.kind);
|
|
1706
|
-
return def ? def.render(el, ctx) : [];
|
|
1707
|
-
});
|
|
1708
|
-
ops.push(...renderWalls(ir.walls, ctx));
|
|
1709
1782
|
for (const pass of RENDER_PASSES) {
|
|
1710
|
-
for (const
|
|
1783
|
+
for (const node of scene.nodes) if (node.layer === pass) out.push(serialize(node));
|
|
1711
1784
|
}
|
|
1712
|
-
out.push(northArrow(
|
|
1785
|
+
out.push(northArrow(scene.north, b, margin, refDim, THEME));
|
|
1713
1786
|
out.push(scaleBar(b, margin, refDim, thin, THEME));
|
|
1714
|
-
const tb = titleBlock(
|
|
1787
|
+
const tb = titleBlock(scene.title, scene.scale, b, margin, refDim, thin, THEME);
|
|
1715
1788
|
if (tb) out.push(tb);
|
|
1716
1789
|
out.push("</svg>");
|
|
1717
1790
|
return out.join("\n");
|
|
1718
1791
|
}
|
|
1719
|
-
function northArrow(
|
|
1792
|
+
function northArrow(north, b, margin, refDim, THEME) {
|
|
1720
1793
|
const r = refDim * 0.045;
|
|
1721
1794
|
const cx = b.maxX - r;
|
|
1722
1795
|
const cy = b.minY - margin * 0.55;
|
|
1723
1796
|
let deg;
|
|
1724
|
-
switch (
|
|
1797
|
+
switch (north) {
|
|
1725
1798
|
case "up":
|
|
1726
1799
|
deg = 0;
|
|
1727
1800
|
break;
|
|
@@ -1735,7 +1808,7 @@ function northArrow(ir, b, margin, refDim, THEME) {
|
|
|
1735
1808
|
deg = 90;
|
|
1736
1809
|
break;
|
|
1737
1810
|
default:
|
|
1738
|
-
deg = typeof
|
|
1811
|
+
deg = typeof north === "object" ? north.deg : 0;
|
|
1739
1812
|
}
|
|
1740
1813
|
const fs = refDim * 0.026;
|
|
1741
1814
|
const tri = `${fmt(cx)},${fmt(cy - r)} ${fmt(cx - r * 0.5)},${fmt(cy + r * 0.6)} ${fmt(cx)},${fmt(cy + r * 0.25)} ${fmt(cx + r * 0.5)},${fmt(cy + r * 0.6)}`;
|
|
@@ -1766,9 +1839,8 @@ function scaleBar(b, margin, refDim, thin, THEME) {
|
|
|
1766
1839
|
);
|
|
1767
1840
|
return `<g>${parts.join("")}</g>`;
|
|
1768
1841
|
}
|
|
1769
|
-
function titleBlock(
|
|
1770
|
-
|
|
1771
|
-
if (!t && !ir.scale) return null;
|
|
1842
|
+
function titleBlock(t, scale, b, margin, refDim, thin, THEME) {
|
|
1843
|
+
if (!t && !scale) return null;
|
|
1772
1844
|
const boxW = refDim * 0.34;
|
|
1773
1845
|
const boxH = margin * 0.82;
|
|
1774
1846
|
const x0 = b.maxX - boxW;
|
|
@@ -1779,7 +1851,7 @@ function titleBlock(ir, b, margin, refDim, thin, THEME) {
|
|
|
1779
1851
|
if (t?.project) lines.push({ k: "PROJECT", v: t.project });
|
|
1780
1852
|
if (t?.drawnBy) lines.push({ k: "DRAWN BY", v: t.drawnBy });
|
|
1781
1853
|
if (t?.date) lines.push({ k: "DATE", v: t.date });
|
|
1782
|
-
if (
|
|
1854
|
+
if (scale) lines.push({ k: "SCALE", v: scale });
|
|
1783
1855
|
const parts = [];
|
|
1784
1856
|
parts.push(
|
|
1785
1857
|
`<rect x="${fmt(x0)}" y="${fmt(y0)}" width="${fmt(boxW)}" height="${fmt(boxH)}" fill="none" stroke="${THEME.annotation}" stroke-width="${fmt(thin)}"/>`
|
|
@@ -1828,8 +1900,8 @@ function lineEnd(source, offset) {
|
|
|
1828
1900
|
}
|
|
1829
1901
|
function formatDiagnostic(source, d) {
|
|
1830
1902
|
const codeTag = d.code ? `[${d.code}]` : "";
|
|
1831
|
-
const
|
|
1832
|
-
const lines = [
|
|
1903
|
+
const header2 = `${d.severity}${codeTag}: ${d.message}`;
|
|
1904
|
+
const lines = [header2];
|
|
1833
1905
|
if (d.span) {
|
|
1834
1906
|
const { line, col } = offsetToLineCol(source, d.span.start);
|
|
1835
1907
|
const ls = lineStart(source, d.span.start);
|
|
@@ -1855,6 +1927,319 @@ function formatDiagnostic(source, d) {
|
|
|
1855
1927
|
return lines.join("\n");
|
|
1856
1928
|
}
|
|
1857
1929
|
|
|
1930
|
+
// src/export/dxf.ts
|
|
1931
|
+
function num(v) {
|
|
1932
|
+
const r = Math.round(v * 1e4) / 1e4;
|
|
1933
|
+
return Object.is(r, -0) ? "0" : String(r);
|
|
1934
|
+
}
|
|
1935
|
+
var DxfBuilder = class {
|
|
1936
|
+
out = [];
|
|
1937
|
+
/** group-code / value pair. */
|
|
1938
|
+
pair(code, value) {
|
|
1939
|
+
this.out.push(String(code), String(value));
|
|
1940
|
+
}
|
|
1941
|
+
line(layer, a, b) {
|
|
1942
|
+
this.pair(0, "LINE");
|
|
1943
|
+
this.pair(8, layer);
|
|
1944
|
+
this.pair(10, num(a.x));
|
|
1945
|
+
this.pair(20, num(-a.y));
|
|
1946
|
+
this.pair(11, num(b.x));
|
|
1947
|
+
this.pair(21, num(-b.y));
|
|
1948
|
+
}
|
|
1949
|
+
arc(layer, center, radius, startDeg, endDeg) {
|
|
1950
|
+
this.pair(0, "ARC");
|
|
1951
|
+
this.pair(8, layer);
|
|
1952
|
+
this.pair(10, num(center.x));
|
|
1953
|
+
this.pair(20, num(-center.y));
|
|
1954
|
+
this.pair(40, num(radius));
|
|
1955
|
+
this.pair(50, num(startDeg));
|
|
1956
|
+
this.pair(51, num(endDeg));
|
|
1957
|
+
}
|
|
1958
|
+
text(layer, at, height, value) {
|
|
1959
|
+
this.pair(0, "TEXT");
|
|
1960
|
+
this.pair(8, layer);
|
|
1961
|
+
this.pair(10, num(at.x));
|
|
1962
|
+
this.pair(20, num(-at.y));
|
|
1963
|
+
this.pair(40, num(height));
|
|
1964
|
+
this.pair(1, value.replace(/\n/g, " "));
|
|
1965
|
+
}
|
|
1966
|
+
/** Closed loop of points as a chain of LINEs (R12-safe; no LWPOLYLINE). */
|
|
1967
|
+
loop(layer, pts) {
|
|
1968
|
+
for (let i = 0; i < pts.length; i++) {
|
|
1969
|
+
this.line(layer, pts[i], pts[(i + 1) % pts.length]);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
toString() {
|
|
1973
|
+
return this.out.join("\n") + "\n";
|
|
1974
|
+
}
|
|
1975
|
+
};
|
|
1976
|
+
var LAYERS = ["WALLS", "ROOMS", "DOORS", "WINDOWS", "FURNITURE", "COLUMNS", "DIMS", "LABELS"];
|
|
1977
|
+
function dxfLayer(layer) {
|
|
1978
|
+
switch (layer) {
|
|
1979
|
+
case "wallFill":
|
|
1980
|
+
case "wallFace":
|
|
1981
|
+
return "WALLS";
|
|
1982
|
+
case "floor":
|
|
1983
|
+
return "ROOMS";
|
|
1984
|
+
case "doors":
|
|
1985
|
+
return "DOORS";
|
|
1986
|
+
case "windows":
|
|
1987
|
+
return "WINDOWS";
|
|
1988
|
+
case "furniture":
|
|
1989
|
+
return "FURNITURE";
|
|
1990
|
+
case "labels":
|
|
1991
|
+
return "LABELS";
|
|
1992
|
+
case "dims":
|
|
1993
|
+
return "DIMS";
|
|
1994
|
+
default:
|
|
1995
|
+
return "0";
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
function header() {
|
|
1999
|
+
const h = [];
|
|
2000
|
+
const p = (c, v) => h.push(String(c), String(v));
|
|
2001
|
+
p(0, "SECTION");
|
|
2002
|
+
p(2, "HEADER");
|
|
2003
|
+
p(9, "$ACADVER");
|
|
2004
|
+
p(1, "AC1009");
|
|
2005
|
+
p(0, "ENDSEC");
|
|
2006
|
+
p(0, "SECTION");
|
|
2007
|
+
p(2, "TABLES");
|
|
2008
|
+
p(0, "TABLE");
|
|
2009
|
+
p(2, "LAYER");
|
|
2010
|
+
p(70, LAYERS.length);
|
|
2011
|
+
for (const name of LAYERS) {
|
|
2012
|
+
p(0, "LAYER");
|
|
2013
|
+
p(2, name);
|
|
2014
|
+
p(70, 0);
|
|
2015
|
+
p(62, 7);
|
|
2016
|
+
p(6, "CONTINUOUS");
|
|
2017
|
+
}
|
|
2018
|
+
p(0, "ENDTAB");
|
|
2019
|
+
p(0, "ENDSEC");
|
|
2020
|
+
return h.join("\n") + "\n";
|
|
2021
|
+
}
|
|
2022
|
+
function emit(b, node) {
|
|
2023
|
+
const layer = dxfLayer(node.layer);
|
|
2024
|
+
const prim = node.prim;
|
|
2025
|
+
switch (prim.t) {
|
|
2026
|
+
case "polygon":
|
|
2027
|
+
b.loop(layer, prim.pts);
|
|
2028
|
+
break;
|
|
2029
|
+
case "line":
|
|
2030
|
+
b.line(layer, prim.a, prim.b);
|
|
2031
|
+
break;
|
|
2032
|
+
case "region":
|
|
2033
|
+
for (const lp of prim.loops) b.loop(layer, lp);
|
|
2034
|
+
break;
|
|
2035
|
+
case "arc": {
|
|
2036
|
+
const [a0, a1] = minorArcDegrees(prim.center, prim.start, prim.end);
|
|
2037
|
+
b.arc(layer, prim.center, prim.r, a0, a1);
|
|
2038
|
+
break;
|
|
2039
|
+
}
|
|
2040
|
+
case "text":
|
|
2041
|
+
b.text(layer, prim.at, prim.size, prim.value);
|
|
2042
|
+
break;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
function toDxf(scene) {
|
|
2046
|
+
const b = new DxfBuilder();
|
|
2047
|
+
b.pair(0, "SECTION");
|
|
2048
|
+
b.pair(2, "ENTITIES");
|
|
2049
|
+
for (const node of scene.nodes) emit(b, node);
|
|
2050
|
+
b.pair(0, "ENDSEC");
|
|
2051
|
+
return header() + b.toString() + "0\nEOF\n";
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// src/export/pdf.ts
|
|
2055
|
+
function fillColor(paint, theme) {
|
|
2056
|
+
const f = paint.fill;
|
|
2057
|
+
if (!f || f === "none") return null;
|
|
2058
|
+
if (f.startsWith("url(")) return theme.pocheBase;
|
|
2059
|
+
return f;
|
|
2060
|
+
}
|
|
2061
|
+
function regionPath2(loops) {
|
|
2062
|
+
return loops.map((loop) => "M " + loop.map((p) => `${p.x} ${p.y}`).join(" L ") + " Z").join(" ");
|
|
2063
|
+
}
|
|
2064
|
+
function applyPaint(doc, paint, theme) {
|
|
2065
|
+
const fill = fillColor(paint, theme);
|
|
2066
|
+
const stroke = paint.stroke && paint.stroke !== "none" ? paint.stroke : null;
|
|
2067
|
+
if (paint.width !== void 0) doc.lineWidth(paint.width);
|
|
2068
|
+
doc.lineJoin(paint.linejoin ?? "miter");
|
|
2069
|
+
doc.lineCap(paint.linecap === "square" ? "square" : "butt");
|
|
2070
|
+
if (paint.dash) doc.dash(paint.dash[0], { space: paint.dash[1] });
|
|
2071
|
+
else doc.undash();
|
|
2072
|
+
if (fill && stroke) doc.fillAndStroke(fill, stroke);
|
|
2073
|
+
else if (fill) doc.fill(fill);
|
|
2074
|
+
else if (stroke) doc.stroke(stroke);
|
|
2075
|
+
else doc.stroke();
|
|
2076
|
+
}
|
|
2077
|
+
function drawText(doc, at, value, size, anchor, rotate, color) {
|
|
2078
|
+
doc.undash();
|
|
2079
|
+
doc.fontSize(size).fillColor(color);
|
|
2080
|
+
const w = doc.widthOfString(value);
|
|
2081
|
+
let x = at.x;
|
|
2082
|
+
if (anchor === "middle") x -= w / 2;
|
|
2083
|
+
else if (anchor === "end") x -= w;
|
|
2084
|
+
const y = at.y - size * 0.5;
|
|
2085
|
+
if (rotate !== void 0) {
|
|
2086
|
+
doc.save();
|
|
2087
|
+
doc.rotate(rotate, { origin: [at.x, at.y] });
|
|
2088
|
+
doc.text(value, x, y, { lineBreak: false });
|
|
2089
|
+
doc.restore();
|
|
2090
|
+
} else {
|
|
2091
|
+
doc.text(value, x, y, { lineBreak: false });
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
function drawNode(doc, node, theme) {
|
|
2095
|
+
const { prim, paint } = node;
|
|
2096
|
+
switch (prim.t) {
|
|
2097
|
+
case "polygon":
|
|
2098
|
+
doc.polygon(...prim.pts.map((p) => [p.x, p.y]));
|
|
2099
|
+
applyPaint(doc, paint, theme);
|
|
2100
|
+
break;
|
|
2101
|
+
case "line":
|
|
2102
|
+
doc.moveTo(prim.a.x, prim.a.y).lineTo(prim.b.x, prim.b.y);
|
|
2103
|
+
applyPaint(doc, paint, theme);
|
|
2104
|
+
break;
|
|
2105
|
+
case "region":
|
|
2106
|
+
doc.path(regionPath2(prim.loops));
|
|
2107
|
+
applyPaint(doc, paint, theme);
|
|
2108
|
+
break;
|
|
2109
|
+
case "arc":
|
|
2110
|
+
doc.path(`M ${prim.start.x} ${prim.start.y} A ${prim.r} ${prim.r} 0 0 ${prim.sweep} ${prim.end.x} ${prim.end.y}`);
|
|
2111
|
+
applyPaint(doc, paint, theme);
|
|
2112
|
+
break;
|
|
2113
|
+
case "text":
|
|
2114
|
+
drawText(doc, prim.at, prim.value, prim.size, prim.anchor, prim.rotate, fillColor(paint, theme) ?? "#000000");
|
|
2115
|
+
break;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
async function toPdf(scene) {
|
|
2119
|
+
let PDFDocument;
|
|
2120
|
+
try {
|
|
2121
|
+
PDFDocument = (await import("pdfkit")).default;
|
|
2122
|
+
} catch {
|
|
2123
|
+
throw new Error(
|
|
2124
|
+
"PDF export needs the optional dependency 'pdfkit'. Install it: npm install pdfkit"
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
const { theme, sizes, bounds: b } = scene;
|
|
2128
|
+
const margin = sizes.margin;
|
|
2129
|
+
const vbX = b.minX - margin;
|
|
2130
|
+
const vbY = b.minY - margin;
|
|
2131
|
+
const W = scene.width;
|
|
2132
|
+
const H = scene.height;
|
|
2133
|
+
const doc = new PDFDocument({ size: [W, H], margin: 0 });
|
|
2134
|
+
const chunks = [];
|
|
2135
|
+
const done = new Promise((resolve2, reject) => {
|
|
2136
|
+
doc.on("data", (c) => chunks.push(c));
|
|
2137
|
+
doc.on("end", () => resolve2());
|
|
2138
|
+
doc.on("error", (e) => reject(e));
|
|
2139
|
+
});
|
|
2140
|
+
doc.save();
|
|
2141
|
+
doc.translate(-vbX, -vbY);
|
|
2142
|
+
doc.rect(vbX, vbY, W, H).fill(theme.bg);
|
|
2143
|
+
for (const pass of RENDER_PASSES) {
|
|
2144
|
+
for (const node of scene.nodes) if (node.layer === pass) drawNode(doc, node, theme);
|
|
2145
|
+
}
|
|
2146
|
+
drawChrome(doc, scene);
|
|
2147
|
+
doc.restore();
|
|
2148
|
+
doc.end();
|
|
2149
|
+
await done;
|
|
2150
|
+
return concat(chunks);
|
|
2151
|
+
}
|
|
2152
|
+
function drawChrome(doc, scene) {
|
|
2153
|
+
const { theme, sizes, bounds: b } = scene;
|
|
2154
|
+
const refDim = sizes.refDim;
|
|
2155
|
+
const margin = sizes.margin;
|
|
2156
|
+
const thin = sizes.thin;
|
|
2157
|
+
{
|
|
2158
|
+
const r = refDim * 0.045;
|
|
2159
|
+
const cx = b.maxX - r;
|
|
2160
|
+
const cy = b.minY - margin * 0.55;
|
|
2161
|
+
const deg = northDegrees(scene.north);
|
|
2162
|
+
const fs = refDim * 0.026;
|
|
2163
|
+
doc.save();
|
|
2164
|
+
doc.rotate(deg, { origin: [cx, cy] });
|
|
2165
|
+
doc.polygon([cx, cy - r], [cx - r * 0.5, cy + r * 0.6], [cx, cy + r * 0.25], [cx + r * 0.5, cy + r * 0.6]).fill(theme.annotation);
|
|
2166
|
+
doc.restore();
|
|
2167
|
+
const rad = deg * Math.PI / 180;
|
|
2168
|
+
const lx = cx + Math.sin(rad) * (r + fs * 0.8);
|
|
2169
|
+
const ly = cy - Math.cos(rad) * (r + fs * 0.8);
|
|
2170
|
+
drawText(doc, { x: lx, y: ly }, "N", fs, "middle", void 0, theme.annotation);
|
|
2171
|
+
}
|
|
2172
|
+
{
|
|
2173
|
+
const barLen = niceBarLength2(refDim * 0.3);
|
|
2174
|
+
const x0 = b.minX;
|
|
2175
|
+
const y0 = b.maxY + margin * 0.55;
|
|
2176
|
+
const hgt = refDim * 0.014;
|
|
2177
|
+
const fs = refDim * 0.02;
|
|
2178
|
+
const half = barLen / 2;
|
|
2179
|
+
doc.rect(x0, y0, half, hgt).fill(theme.annotation);
|
|
2180
|
+
doc.lineWidth(thin).undash();
|
|
2181
|
+
doc.rect(x0 + half, y0, half, hgt).stroke(theme.annotation);
|
|
2182
|
+
drawText(doc, { x: x0, y: y0 + hgt + fs }, "0", fs, "start", void 0, theme.annotation);
|
|
2183
|
+
drawText(doc, { x: x0 + barLen, y: y0 + hgt + fs }, `${barLen / 1e3} m`, fs, "middle", void 0, theme.annotation);
|
|
2184
|
+
}
|
|
2185
|
+
const t = scene.title;
|
|
2186
|
+
if (t || scene.scale) {
|
|
2187
|
+
const boxW = refDim * 0.34;
|
|
2188
|
+
const boxH = margin * 0.82;
|
|
2189
|
+
const x0 = b.maxX - boxW;
|
|
2190
|
+
const y0 = b.maxY + margin * 0.15;
|
|
2191
|
+
const fs = refDim * 0.019;
|
|
2192
|
+
const pad = boxW * 0.05;
|
|
2193
|
+
const lines = [];
|
|
2194
|
+
if (t?.project) lines.push({ k: "PROJECT", v: t.project });
|
|
2195
|
+
if (t?.drawnBy) lines.push({ k: "DRAWN BY", v: t.drawnBy });
|
|
2196
|
+
if (t?.date) lines.push({ k: "DATE", v: t.date });
|
|
2197
|
+
if (scene.scale) lines.push({ k: "SCALE", v: scene.scale });
|
|
2198
|
+
doc.lineWidth(thin).undash();
|
|
2199
|
+
doc.rect(x0, y0, boxW, boxH).stroke(theme.annotation);
|
|
2200
|
+
const rowH = boxH / Math.max(lines.length, 1);
|
|
2201
|
+
lines.forEach((ln, i) => {
|
|
2202
|
+
const ly = y0 + rowH * (i + 0.5);
|
|
2203
|
+
drawText(doc, { x: x0 + pad, y: ly }, ln.k, fs * 0.8, "start", void 0, theme.annotationMuted);
|
|
2204
|
+
drawText(doc, { x: x0 + boxW - pad, y: ly }, ln.v, fs, "end", void 0, theme.annotation);
|
|
2205
|
+
if (i > 0) {
|
|
2206
|
+
doc.lineWidth(thin * 0.5).moveTo(x0, y0 + rowH * i).lineTo(x0 + boxW, y0 + rowH * i).stroke(theme.annotation);
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
function northDegrees(north) {
|
|
2212
|
+
switch (north) {
|
|
2213
|
+
case "up":
|
|
2214
|
+
return 0;
|
|
2215
|
+
case "down":
|
|
2216
|
+
return 180;
|
|
2217
|
+
case "left":
|
|
2218
|
+
return 270;
|
|
2219
|
+
case "right":
|
|
2220
|
+
return 90;
|
|
2221
|
+
default:
|
|
2222
|
+
return typeof north === "object" ? north.deg : 0;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
var NICE_LENGTHS2 = [500, 1e3, 2e3, 5e3, 1e4, 2e4, 5e4, 1e5];
|
|
2226
|
+
function niceBarLength2(target) {
|
|
2227
|
+
let best = NICE_LENGTHS2[0];
|
|
2228
|
+
for (const v of NICE_LENGTHS2) if (v <= target) best = v;
|
|
2229
|
+
return best;
|
|
2230
|
+
}
|
|
2231
|
+
function concat(chunks) {
|
|
2232
|
+
let total = 0;
|
|
2233
|
+
for (const c of chunks) total += c.length;
|
|
2234
|
+
const out = new Uint8Array(total);
|
|
2235
|
+
let offset = 0;
|
|
2236
|
+
for (const c of chunks) {
|
|
2237
|
+
out.set(c, offset);
|
|
2238
|
+
offset += c.length;
|
|
2239
|
+
}
|
|
2240
|
+
return out;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
1858
2243
|
// src/index.ts
|
|
1859
2244
|
var cache = /* @__PURE__ */ new Map();
|
|
1860
2245
|
var CACHE_MAX = 64;
|
|
@@ -1886,17 +2271,26 @@ function compileUncached(source, opts) {
|
|
|
1886
2271
|
const errs = diagnostics.filter((d) => d.severity === "error");
|
|
1887
2272
|
const errors = errs.map((d) => toLegacy(source, d));
|
|
1888
2273
|
const warnings = diagnostics.filter((d) => d.severity === "warning").map((d) => toLegacy(source, d));
|
|
1889
|
-
|
|
1890
|
-
|
|
2274
|
+
let svg = "";
|
|
2275
|
+
let scene;
|
|
2276
|
+
if (resolved && errs.length === 0) {
|
|
2277
|
+
scene = toScene(resolved.ir, opts);
|
|
2278
|
+
svg = renderSvg(scene, opts);
|
|
2279
|
+
}
|
|
2280
|
+
return { svg, errors, warnings, diagnostics, ast: plan, scene };
|
|
1891
2281
|
}
|
|
1892
2282
|
function clearCache() {
|
|
1893
2283
|
cache.clear();
|
|
1894
2284
|
}
|
|
1895
2285
|
|
|
1896
2286
|
export {
|
|
2287
|
+
resolve,
|
|
2288
|
+
toScene,
|
|
1897
2289
|
offsetToLineCol,
|
|
1898
2290
|
formatDiagnostic,
|
|
2291
|
+
toDxf,
|
|
2292
|
+
toPdf,
|
|
1899
2293
|
compile,
|
|
1900
2294
|
clearCache
|
|
1901
2295
|
};
|
|
1902
|
-
//# sourceMappingURL=chunk-
|
|
2296
|
+
//# sourceMappingURL=chunk-CPK5CI5Y.js.map
|