@chanmeng666/archlang 0.6.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/dist/{chunk-PABYLU6Z.js → chunk-CPK5CI5Y.js} +421 -241
- package/dist/chunk-CPK5CI5Y.js.map +1 -0
- package/dist/cli.js +5 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +211 -25
- package/dist/index.js +5 -3
- package/package.json +2 -3
- package/dist/chunk-PABYLU6Z.js.map +0 -1
|
@@ -396,6 +396,13 @@ function distPointToSegment(p, a, b) {
|
|
|
396
396
|
const cy = a.y + t * aby;
|
|
397
397
|
return Math.hypot(p.x - cx, p.y - cy);
|
|
398
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
|
+
}
|
|
399
406
|
function rectCorners(x, y, w, h) {
|
|
400
407
|
return [
|
|
401
408
|
{ x, y },
|
|
@@ -547,33 +554,29 @@ var wall = {
|
|
|
547
554
|
const w = resolved;
|
|
548
555
|
return segmentsOfWall(w).flatMap((s) => segmentRectangle(s.a, s.b, s.thickness));
|
|
549
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
|
+
*/
|
|
550
562
|
render(resolved, ctx) {
|
|
551
563
|
const w = resolved;
|
|
552
|
-
const {
|
|
564
|
+
const { theme, sizes } = ctx;
|
|
553
565
|
const segs = segmentsOfWall(w);
|
|
554
|
-
const
|
|
566
|
+
const nodes = [];
|
|
555
567
|
for (const s of segs) {
|
|
556
568
|
const poly = segmentRectangle(s.a, s.b, s.thickness);
|
|
557
|
-
|
|
569
|
+
nodes.push({ layer: "wallFill", prim: { t: "polygon", pts: poly }, paint: { fill: "url(#poche)" } });
|
|
558
570
|
}
|
|
559
571
|
for (const s of segs) {
|
|
560
572
|
const d = unit(sub(s.b, s.a));
|
|
561
573
|
const n = normal(d);
|
|
562
574
|
const h = s.thickness / 2;
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const fb2 = add(s.b, mul(n, -h));
|
|
567
|
-
ops.push({
|
|
568
|
-
pass: "wallFace",
|
|
569
|
-
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"/>`
|
|
570
|
-
});
|
|
571
|
-
ops.push({
|
|
572
|
-
pass: "wallFace",
|
|
573
|
-
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"/>`
|
|
574
|
-
});
|
|
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 });
|
|
575
578
|
}
|
|
576
|
-
return
|
|
579
|
+
return nodes;
|
|
577
580
|
}
|
|
578
581
|
};
|
|
579
582
|
function describe2(ctx) {
|
|
@@ -618,24 +621,26 @@ var room = {
|
|
|
618
621
|
},
|
|
619
622
|
render(resolved, ctx) {
|
|
620
623
|
const r = resolved;
|
|
621
|
-
const {
|
|
622
|
-
const
|
|
624
|
+
const { theme, sizes } = ctx;
|
|
625
|
+
const nodes = [];
|
|
623
626
|
const c = rectCorners(r.at.x, r.at.y, r.size.w, r.size.h);
|
|
624
|
-
|
|
627
|
+
nodes.push({ layer: "floor", prim: { t: "polygon", pts: c }, paint: { fill: theme.roomFill } });
|
|
625
628
|
const cx = r.at.x + r.size.w / 2;
|
|
626
629
|
const cy = r.at.y + r.size.h / 2;
|
|
627
630
|
const areaM2 = (r.size.w / 1e3 * (r.size.h / 1e3)).toFixed(1);
|
|
628
631
|
if (r.label) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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 }
|
|
632
636
|
});
|
|
633
637
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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 }
|
|
637
642
|
});
|
|
638
|
-
return
|
|
643
|
+
return nodes;
|
|
639
644
|
}
|
|
640
645
|
};
|
|
641
646
|
|
|
@@ -685,11 +690,17 @@ var door = {
|
|
|
685
690
|
return { kind: "door", id, at, width, hinge: n.hinge, swing: n.swing, host: ctx.hostSegment(at, n.wall), span: n.span };
|
|
686
691
|
},
|
|
687
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
|
+
*/
|
|
688
699
|
render(resolved, ctx) {
|
|
689
700
|
const dr = resolved;
|
|
690
701
|
const seg = dr.host;
|
|
691
702
|
if (!seg) return [];
|
|
692
|
-
const {
|
|
703
|
+
const { theme, sizes } = ctx;
|
|
693
704
|
const d = unit(sub(seg.b, seg.a));
|
|
694
705
|
const n = normal(d);
|
|
695
706
|
const h = seg.thickness / 2 + sizes.wallStroke;
|
|
@@ -700,23 +711,25 @@ var door = {
|
|
|
700
711
|
add(add(dr.at, mul(d, hw)), mul(n, -h)),
|
|
701
712
|
add(add(dr.at, mul(d, -hw)), mul(n, -h))
|
|
702
713
|
];
|
|
703
|
-
const
|
|
704
|
-
|
|
714
|
+
const nodes = [];
|
|
715
|
+
nodes.push({ layer: "doors", prim: { t: "polygon", pts: cover }, paint: { fill: theme.opening } });
|
|
705
716
|
const hinge = dr.hinge === "left" ? add(dr.at, mul(d, -hw)) : add(dr.at, mul(d, hw));
|
|
706
717
|
const farJamb = dr.hinge === "left" ? add(dr.at, mul(d, hw)) : add(dr.at, mul(d, -hw));
|
|
707
718
|
const leafDir = dr.swing === "in" ? n : mul(n, -1);
|
|
708
719
|
const leafEnd = add(hinge, mul(leafDir, dr.width));
|
|
709
720
|
const cross = (leafEnd.x - hinge.x) * (farJamb.y - hinge.y) - (leafEnd.y - hinge.y) * (farJamb.x - hinge.x);
|
|
710
721
|
const sweep = cross < 0 ? 1 : 0;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
722
|
+
nodes.push({
|
|
723
|
+
layer: "doors",
|
|
724
|
+
prim: { t: "line", a: hinge, b: leafEnd },
|
|
725
|
+
paint: { stroke: theme.doorLeaf, width: sizes.thin * 1.3 }
|
|
714
726
|
});
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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] }
|
|
718
731
|
});
|
|
719
|
-
return
|
|
732
|
+
return nodes;
|
|
720
733
|
}
|
|
721
734
|
};
|
|
722
735
|
|
|
@@ -758,7 +771,7 @@ var windowEl = {
|
|
|
758
771
|
const wn = resolved;
|
|
759
772
|
const seg = wn.host;
|
|
760
773
|
if (!seg) return [];
|
|
761
|
-
const {
|
|
774
|
+
const { theme, sizes } = ctx;
|
|
762
775
|
const d = unit(sub(seg.b, seg.a));
|
|
763
776
|
const n = normal(d);
|
|
764
777
|
const h = seg.thickness / 2;
|
|
@@ -770,23 +783,23 @@ var windowEl = {
|
|
|
770
783
|
add(add(wn.at, mul(d, hw)), mul(n, -he)),
|
|
771
784
|
add(add(wn.at, mul(d, -hw)), mul(n, -he))
|
|
772
785
|
];
|
|
773
|
-
const
|
|
774
|
-
|
|
786
|
+
const nodes = [];
|
|
787
|
+
nodes.push({ layer: "windows", prim: { t: "polygon", pts: cover }, paint: { fill: theme.opening } });
|
|
775
788
|
const jA = add(wn.at, mul(d, -hw));
|
|
776
789
|
const jB = add(wn.at, mul(d, hw));
|
|
777
790
|
for (const off of [h, -h]) {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
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 }
|
|
783
795
|
});
|
|
784
796
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
797
|
+
nodes.push({
|
|
798
|
+
layer: "windows",
|
|
799
|
+
prim: { t: "line", a: jA, b: jB },
|
|
800
|
+
paint: { stroke: theme.windowPane, width: sizes.thin }
|
|
788
801
|
});
|
|
789
|
-
return
|
|
802
|
+
return nodes;
|
|
790
803
|
}
|
|
791
804
|
};
|
|
792
805
|
|
|
@@ -826,22 +839,24 @@ var furniture = {
|
|
|
826
839
|
},
|
|
827
840
|
render(resolved, ctx) {
|
|
828
841
|
const f = resolved;
|
|
829
|
-
const {
|
|
830
|
-
const
|
|
842
|
+
const { theme, sizes } = ctx;
|
|
843
|
+
const nodes = [];
|
|
831
844
|
const c = rectCorners(f.at.x, f.at.y, f.size.w, f.size.h);
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
845
|
+
nodes.push({
|
|
846
|
+
layer: "furniture",
|
|
847
|
+
prim: { t: "polygon", pts: c },
|
|
848
|
+
paint: { fill: theme.furnitureFill, stroke: theme.furnitureStroke, width: sizes.thin }
|
|
835
849
|
});
|
|
836
850
|
if (f.label) {
|
|
837
851
|
const cx = f.at.x + f.size.w / 2;
|
|
838
852
|
const cy = f.at.y + f.size.h / 2;
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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 }
|
|
842
857
|
});
|
|
843
858
|
}
|
|
844
|
-
return
|
|
859
|
+
return nodes;
|
|
845
860
|
}
|
|
846
861
|
};
|
|
847
862
|
|
|
@@ -884,33 +899,22 @@ var dim = {
|
|
|
884
899
|
},
|
|
885
900
|
render(resolved, ctx) {
|
|
886
901
|
const dm = resolved;
|
|
887
|
-
const {
|
|
902
|
+
const { theme, sizes } = ctx;
|
|
888
903
|
const dir = unit(sub(dm.to, dm.from));
|
|
889
904
|
const n = normal(dir);
|
|
890
905
|
const off = mul(n, dm.offset);
|
|
891
906
|
const p1 = add(dm.from, off);
|
|
892
907
|
const p2 = add(dm.to, off);
|
|
893
908
|
const tick = sizes.refDim * 0.012;
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
});
|
|
899
|
-
ops.push({
|
|
900
|
-
pass: "dims",
|
|
901
|
-
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)}"/>`
|
|
902
|
-
});
|
|
903
|
-
ops.push({
|
|
904
|
-
pass: "dims",
|
|
905
|
-
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)}"/>`
|
|
906
|
-
});
|
|
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 });
|
|
907
914
|
for (const p of [p1, p2]) {
|
|
908
915
|
const t1 = add(p, mul(unit({ x: dir.x + n.x, y: dir.y + n.y }), tick));
|
|
909
916
|
const t2 = add(p, mul(unit({ x: dir.x + n.x, y: dir.y + n.y }), -tick));
|
|
910
|
-
|
|
911
|
-
pass: "dims",
|
|
912
|
-
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)}"/>`
|
|
913
|
-
});
|
|
917
|
+
nodes.push({ layer: "dims", prim: { t: "line", a: t1, b: t2 }, paint: thinPaint });
|
|
914
918
|
}
|
|
915
919
|
const mid = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
|
|
916
920
|
const tp = add(mid, mul(n, sizes.dimFont * 0.7));
|
|
@@ -918,11 +922,12 @@ var dim = {
|
|
|
918
922
|
if (angle > 90) angle -= 180;
|
|
919
923
|
if (angle < -90) angle += 180;
|
|
920
924
|
const label = dm.text ?? String(Math.round(length(sub(dm.to, dm.from))));
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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 }
|
|
924
929
|
});
|
|
925
|
-
return
|
|
930
|
+
return nodes;
|
|
926
931
|
}
|
|
927
932
|
};
|
|
928
933
|
|
|
@@ -956,12 +961,13 @@ var column = {
|
|
|
956
961
|
},
|
|
957
962
|
render(resolved, ctx) {
|
|
958
963
|
const c = resolved;
|
|
959
|
-
const {
|
|
964
|
+
const { theme, sizes } = ctx;
|
|
960
965
|
const pts = rectCorners(c.at.x, c.at.y, c.size.w, c.size.h);
|
|
961
966
|
return [
|
|
962
967
|
{
|
|
963
|
-
|
|
964
|
-
|
|
968
|
+
layer: "furniture",
|
|
969
|
+
prim: { t: "polygon", pts },
|
|
970
|
+
paint: { fill: theme.column, stroke: theme.wallStroke, width: sizes.thin }
|
|
965
971
|
}
|
|
966
972
|
];
|
|
967
973
|
}
|
|
@@ -1502,19 +1508,6 @@ function resolve(ast) {
|
|
|
1502
1508
|
return { ir, diagnostics };
|
|
1503
1509
|
}
|
|
1504
1510
|
|
|
1505
|
-
// src/registry.ts
|
|
1506
|
-
var RENDER_PASSES = [
|
|
1507
|
-
"floor",
|
|
1508
|
-
"furniture",
|
|
1509
|
-
"wallFill",
|
|
1510
|
-
"wallFace",
|
|
1511
|
-
"doors",
|
|
1512
|
-
"windows",
|
|
1513
|
-
"labels",
|
|
1514
|
-
"dims",
|
|
1515
|
-
"annotations"
|
|
1516
|
-
];
|
|
1517
|
-
|
|
1518
1511
|
// src/geometry/union.ts
|
|
1519
1512
|
function uniqSorted(values) {
|
|
1520
1513
|
const out = [...new Set(values)].sort((a, b) => a - b);
|
|
@@ -1612,21 +1605,7 @@ function mergeCollinear(loop) {
|
|
|
1612
1605
|
return out.length >= 3 ? out : loop;
|
|
1613
1606
|
}
|
|
1614
1607
|
|
|
1615
|
-
// src/
|
|
1616
|
-
function fmt(v) {
|
|
1617
|
-
const r = Math.round(v * 100) / 100;
|
|
1618
|
-
return Object.is(r, -0) ? "0" : String(r);
|
|
1619
|
-
}
|
|
1620
|
-
var pt = (p) => `${fmt(p.x)},${fmt(p.y)}`;
|
|
1621
|
-
function xml(s) {
|
|
1622
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1623
|
-
}
|
|
1624
|
-
var NICE_LENGTHS = [500, 1e3, 2e3, 5e3, 1e4, 2e4, 5e4, 1e5];
|
|
1625
|
-
function niceBarLength(target) {
|
|
1626
|
-
let best = NICE_LENGTHS[0];
|
|
1627
|
-
for (const v of NICE_LENGTHS) if (v <= target) best = v;
|
|
1628
|
-
return best;
|
|
1629
|
-
}
|
|
1608
|
+
// src/scene-build.ts
|
|
1630
1609
|
function planBounds(ir) {
|
|
1631
1610
|
const b = emptyBounds();
|
|
1632
1611
|
for (const el of ir.elements) {
|
|
@@ -1642,20 +1621,17 @@ function planBounds(ir) {
|
|
|
1642
1621
|
function allOrthogonal(walls) {
|
|
1643
1622
|
return walls.every((w) => segmentsOfWall(w).every((s) => s.a.x === s.b.x || s.a.y === s.b.y));
|
|
1644
1623
|
}
|
|
1645
|
-
function loopsToPath(loops) {
|
|
1646
|
-
return loops.map((loop) => "M " + loop.map(pt).join(" L ") + " Z").join(" ");
|
|
1647
|
-
}
|
|
1648
1624
|
function materialsUsed(walls) {
|
|
1649
1625
|
return [...new Set(walls.map((w) => w.material))].sort();
|
|
1650
1626
|
}
|
|
1651
|
-
function
|
|
1627
|
+
function lowerWalls(walls, ctx) {
|
|
1652
1628
|
if (walls.length === 0) return [];
|
|
1653
|
-
const
|
|
1629
|
+
const nodes = [];
|
|
1654
1630
|
for (const mat of materialsUsed(walls)) {
|
|
1655
1631
|
const group = walls.filter((w) => w.material === mat);
|
|
1656
1632
|
if (!allOrthogonal(group)) {
|
|
1657
1633
|
const def = registry.get("wall");
|
|
1658
|
-
|
|
1634
|
+
nodes.push(...group.flatMap((w) => def.render(w, ctx)));
|
|
1659
1635
|
continue;
|
|
1660
1636
|
}
|
|
1661
1637
|
const rects = [];
|
|
@@ -1669,18 +1645,18 @@ function renderWalls(walls, ctx) {
|
|
|
1669
1645
|
}
|
|
1670
1646
|
const loops = rectUnionOutline(rects);
|
|
1671
1647
|
if (loops.length === 0) continue;
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
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" }
|
|
1677
1653
|
});
|
|
1678
1654
|
}
|
|
1679
|
-
return
|
|
1655
|
+
return nodes;
|
|
1680
1656
|
}
|
|
1681
|
-
function
|
|
1682
|
-
const
|
|
1683
|
-
const lw =
|
|
1657
|
+
function toScene(ir, opts = {}) {
|
|
1658
|
+
const theme = sanitizeTheme(mergeTheme(DEFAULT_THEME, ir.theme, opts.theme));
|
|
1659
|
+
const lw = theme.lineWeight;
|
|
1684
1660
|
const b = planBounds(ir);
|
|
1685
1661
|
const drawW = b.maxX - b.minX;
|
|
1686
1662
|
const drawH = b.maxY - b.minY;
|
|
@@ -1696,7 +1672,100 @@ function render(ir, opts = {}) {
|
|
|
1696
1672
|
margin: refDim * 0.17,
|
|
1697
1673
|
hatchGap: refDim * 0.013
|
|
1698
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;
|
|
1699
1766
|
const { thin, margin, hatchGap } = sizes;
|
|
1767
|
+
const drawW = b.maxX - b.minX;
|
|
1768
|
+
const drawH = b.maxY - b.minY;
|
|
1700
1769
|
const vbX = b.minX - margin;
|
|
1701
1770
|
const vbY = b.minY - margin;
|
|
1702
1771
|
const vbW = drawW + margin * 2;
|
|
@@ -1707,32 +1776,25 @@ function render(ir, opts = {}) {
|
|
|
1707
1776
|
`<svg xmlns="http://www.w3.org/2000/svg" ${svgAttrs} viewBox="${fmt(vbX)} ${fmt(vbY)} ${fmt(vbW)} ${fmt(vbH)}" font-family="${THEME.font}">`
|
|
1708
1777
|
);
|
|
1709
1778
|
const hatchCtx = { fmt, gap: hatchGap, thin, base: THEME.pocheBase, line: THEME.pocheHatch };
|
|
1710
|
-
const patterns =
|
|
1779
|
+
const patterns = scene.materials.map((m) => hatchPattern(m, hatchCtx)).join("");
|
|
1711
1780
|
out.push(`<defs>${patterns}</defs>`);
|
|
1712
1781
|
out.push(`<rect x="${fmt(vbX)}" y="${fmt(vbY)}" width="${fmt(vbW)}" height="${fmt(vbH)}" fill="${THEME.bg}"/>`);
|
|
1713
|
-
const ctx = { fmt, pt, xml, theme: THEME, sizes, bounds: b };
|
|
1714
|
-
const ops = ir.elements.flatMap((el) => {
|
|
1715
|
-
if (el.kind === "wall") return [];
|
|
1716
|
-
const def = registry.get(el.kind);
|
|
1717
|
-
return def ? def.render(el, ctx) : [];
|
|
1718
|
-
});
|
|
1719
|
-
ops.push(...renderWalls(ir.walls, ctx));
|
|
1720
1782
|
for (const pass of RENDER_PASSES) {
|
|
1721
|
-
for (const
|
|
1783
|
+
for (const node of scene.nodes) if (node.layer === pass) out.push(serialize(node));
|
|
1722
1784
|
}
|
|
1723
|
-
out.push(northArrow(
|
|
1785
|
+
out.push(northArrow(scene.north, b, margin, refDim, THEME));
|
|
1724
1786
|
out.push(scaleBar(b, margin, refDim, thin, THEME));
|
|
1725
|
-
const tb = titleBlock(
|
|
1787
|
+
const tb = titleBlock(scene.title, scene.scale, b, margin, refDim, thin, THEME);
|
|
1726
1788
|
if (tb) out.push(tb);
|
|
1727
1789
|
out.push("</svg>");
|
|
1728
1790
|
return out.join("\n");
|
|
1729
1791
|
}
|
|
1730
|
-
function northArrow(
|
|
1792
|
+
function northArrow(north, b, margin, refDim, THEME) {
|
|
1731
1793
|
const r = refDim * 0.045;
|
|
1732
1794
|
const cx = b.maxX - r;
|
|
1733
1795
|
const cy = b.minY - margin * 0.55;
|
|
1734
1796
|
let deg;
|
|
1735
|
-
switch (
|
|
1797
|
+
switch (north) {
|
|
1736
1798
|
case "up":
|
|
1737
1799
|
deg = 0;
|
|
1738
1800
|
break;
|
|
@@ -1746,7 +1808,7 @@ function northArrow(ir, b, margin, refDim, THEME) {
|
|
|
1746
1808
|
deg = 90;
|
|
1747
1809
|
break;
|
|
1748
1810
|
default:
|
|
1749
|
-
deg = typeof
|
|
1811
|
+
deg = typeof north === "object" ? north.deg : 0;
|
|
1750
1812
|
}
|
|
1751
1813
|
const fs = refDim * 0.026;
|
|
1752
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)}`;
|
|
@@ -1777,9 +1839,8 @@ function scaleBar(b, margin, refDim, thin, THEME) {
|
|
|
1777
1839
|
);
|
|
1778
1840
|
return `<g>${parts.join("")}</g>`;
|
|
1779
1841
|
}
|
|
1780
|
-
function titleBlock(
|
|
1781
|
-
|
|
1782
|
-
if (!t && !ir.scale) return null;
|
|
1842
|
+
function titleBlock(t, scale, b, margin, refDim, thin, THEME) {
|
|
1843
|
+
if (!t && !scale) return null;
|
|
1783
1844
|
const boxW = refDim * 0.34;
|
|
1784
1845
|
const boxH = margin * 0.82;
|
|
1785
1846
|
const x0 = b.maxX - boxW;
|
|
@@ -1790,7 +1851,7 @@ function titleBlock(ir, b, margin, refDim, thin, THEME) {
|
|
|
1790
1851
|
if (t?.project) lines.push({ k: "PROJECT", v: t.project });
|
|
1791
1852
|
if (t?.drawnBy) lines.push({ k: "DRAWN BY", v: t.drawnBy });
|
|
1792
1853
|
if (t?.date) lines.push({ k: "DATE", v: t.date });
|
|
1793
|
-
if (
|
|
1854
|
+
if (scale) lines.push({ k: "SCALE", v: scale });
|
|
1794
1855
|
const parts = [];
|
|
1795
1856
|
parts.push(
|
|
1796
1857
|
`<rect x="${fmt(x0)}" y="${fmt(y0)}" width="${fmt(boxW)}" height="${fmt(boxH)}" fill="none" stroke="${THEME.annotation}" stroke-width="${fmt(thin)}"/>`
|
|
@@ -1902,9 +1963,10 @@ var DxfBuilder = class {
|
|
|
1902
1963
|
this.pair(40, num(height));
|
|
1903
1964
|
this.pair(1, value.replace(/\n/g, " "));
|
|
1904
1965
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
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]);
|
|
1908
1970
|
}
|
|
1909
1971
|
}
|
|
1910
1972
|
toString() {
|
|
@@ -1912,6 +1974,27 @@ var DxfBuilder = class {
|
|
|
1912
1974
|
}
|
|
1913
1975
|
};
|
|
1914
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
|
+
}
|
|
1915
1998
|
function header() {
|
|
1916
1999
|
const h = [];
|
|
1917
2000
|
const p = (c, v) => h.push(String(c), String(v));
|
|
@@ -1936,124 +2019,215 @@ function header() {
|
|
|
1936
2019
|
p(0, "ENDSEC");
|
|
1937
2020
|
return h.join("\n") + "\n";
|
|
1938
2021
|
}
|
|
1939
|
-
function
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
const d = unit(sub(seg.b, seg.a));
|
|
1961
|
-
const n = normal(d);
|
|
1962
|
-
const hw = wn.width / 2;
|
|
1963
|
-
const h = seg.thickness / 2;
|
|
1964
|
-
const jA = add(wn.at, mul(d, -hw));
|
|
1965
|
-
const jB = add(wn.at, mul(d, hw));
|
|
1966
|
-
b.line("WINDOWS", add(jA, mul(n, h)), add(jB, mul(n, h)));
|
|
1967
|
-
b.line("WINDOWS", add(jA, mul(n, -h)), add(jB, mul(n, -h)));
|
|
1968
|
-
b.line("WINDOWS", jA, jB);
|
|
1969
|
-
}
|
|
1970
|
-
function emitDim(b, dm) {
|
|
1971
|
-
const dd = unit(sub(dm.to, dm.from));
|
|
1972
|
-
const dn = normal(dd);
|
|
1973
|
-
const p1 = add(dm.from, mul(dn, dm.offset));
|
|
1974
|
-
const p2 = add(dm.to, mul(dn, dm.offset));
|
|
1975
|
-
b.line("DIMS", p1, p2);
|
|
1976
|
-
if (dm.text) {
|
|
1977
|
-
const mid = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
|
|
1978
|
-
b.text("DIMS", mid, 150, dm.text);
|
|
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;
|
|
1979
2043
|
}
|
|
1980
2044
|
}
|
|
1981
|
-
function toDxf(
|
|
2045
|
+
function toDxf(scene) {
|
|
1982
2046
|
const b = new DxfBuilder();
|
|
1983
2047
|
b.pair(0, "SECTION");
|
|
1984
2048
|
b.pair(2, "ENTITIES");
|
|
1985
|
-
const
|
|
1986
|
-
for (const el of ir.elements) {
|
|
1987
|
-
switch (el.kind) {
|
|
1988
|
-
case "wall":
|
|
1989
|
-
for (const s of segmentsOfWall(el)) {
|
|
1990
|
-
const d = unit(sub(s.b, s.a));
|
|
1991
|
-
const n = normal(d);
|
|
1992
|
-
const off = s.thickness / 2;
|
|
1993
|
-
b.line("WALLS", add(s.a, mul(n, off)), add(s.b, mul(n, off)));
|
|
1994
|
-
b.line("WALLS", add(s.a, mul(n, -off)), add(s.b, mul(n, -off)));
|
|
1995
|
-
}
|
|
1996
|
-
break;
|
|
1997
|
-
case "room":
|
|
1998
|
-
b.rect("ROOMS", rectCorners(el.at.x, el.at.y, el.size.w, el.size.h));
|
|
1999
|
-
if (el.label) b.text("LABELS", labelAt(el.at, el.size.w, el.size.h), 200, el.label);
|
|
2000
|
-
break;
|
|
2001
|
-
case "furniture":
|
|
2002
|
-
b.rect("FURNITURE", rectCorners(el.at.x, el.at.y, el.size.w, el.size.h));
|
|
2003
|
-
if (el.label) b.text("LABELS", labelAt(el.at, el.size.w, el.size.h), 150, el.label);
|
|
2004
|
-
break;
|
|
2005
|
-
case "column":
|
|
2006
|
-
b.rect("COLUMNS", rectCorners(el.at.x, el.at.y, el.size.w, el.size.h));
|
|
2007
|
-
break;
|
|
2008
|
-
case "door":
|
|
2009
|
-
emitDoor(b, el);
|
|
2010
|
-
break;
|
|
2011
|
-
case "window":
|
|
2012
|
-
emitWindow(b, el);
|
|
2013
|
-
break;
|
|
2014
|
-
case "dim":
|
|
2015
|
-
emitDim(b, el);
|
|
2016
|
-
break;
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2049
|
+
for (const node of scene.nodes) emit(b, node);
|
|
2019
2050
|
b.pair(0, "ENDSEC");
|
|
2020
|
-
|
|
2021
|
-
return header() + entities + "0\nEOF\n";
|
|
2051
|
+
return header() + b.toString() + "0\nEOF\n";
|
|
2022
2052
|
}
|
|
2023
2053
|
|
|
2024
2054
|
// src/export/pdf.ts
|
|
2025
|
-
function
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
if (
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
}
|
|
2033
|
-
|
|
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) {
|
|
2034
2119
|
let PDFDocument;
|
|
2035
|
-
let SVGtoPDF;
|
|
2036
2120
|
try {
|
|
2037
2121
|
PDFDocument = (await import("pdfkit")).default;
|
|
2038
|
-
SVGtoPDF = (await import("svg-to-pdfkit")).default;
|
|
2039
2122
|
} catch {
|
|
2040
2123
|
throw new Error(
|
|
2041
|
-
"PDF export needs the optional
|
|
2124
|
+
"PDF export needs the optional dependency 'pdfkit'. Install it: npm install pdfkit"
|
|
2042
2125
|
);
|
|
2043
2126
|
}
|
|
2044
|
-
const {
|
|
2045
|
-
const
|
|
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 });
|
|
2046
2134
|
const chunks = [];
|
|
2047
2135
|
const done = new Promise((resolve2, reject) => {
|
|
2048
2136
|
doc.on("data", (c) => chunks.push(c));
|
|
2049
2137
|
doc.on("end", () => resolve2());
|
|
2050
2138
|
doc.on("error", (e) => reject(e));
|
|
2051
2139
|
});
|
|
2052
|
-
|
|
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();
|
|
2053
2148
|
doc.end();
|
|
2054
2149
|
await done;
|
|
2055
2150
|
return concat(chunks);
|
|
2056
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
|
+
}
|
|
2057
2231
|
function concat(chunks) {
|
|
2058
2232
|
let total = 0;
|
|
2059
2233
|
for (const c of chunks) total += c.length;
|
|
@@ -2097,8 +2271,13 @@ function compileUncached(source, opts) {
|
|
|
2097
2271
|
const errs = diagnostics.filter((d) => d.severity === "error");
|
|
2098
2272
|
const errors = errs.map((d) => toLegacy(source, d));
|
|
2099
2273
|
const warnings = diagnostics.filter((d) => d.severity === "warning").map((d) => toLegacy(source, d));
|
|
2100
|
-
|
|
2101
|
-
|
|
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 };
|
|
2102
2281
|
}
|
|
2103
2282
|
function clearCache() {
|
|
2104
2283
|
cache.clear();
|
|
@@ -2106,6 +2285,7 @@ function clearCache() {
|
|
|
2106
2285
|
|
|
2107
2286
|
export {
|
|
2108
2287
|
resolve,
|
|
2288
|
+
toScene,
|
|
2109
2289
|
offsetToLineCol,
|
|
2110
2290
|
formatDiagnostic,
|
|
2111
2291
|
toDxf,
|
|
@@ -2113,4 +2293,4 @@ export {
|
|
|
2113
2293
|
compile,
|
|
2114
2294
|
clearCache
|
|
2115
2295
|
};
|
|
2116
|
-
//# sourceMappingURL=chunk-
|
|
2296
|
+
//# sourceMappingURL=chunk-CPK5CI5Y.js.map
|