@adatechnology/game-floorplan-structure 2.0.2 → 2.1.1

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/index.js CHANGED
@@ -334,12 +334,15 @@ function createContext(source, construction, extras, tank, options) {
334
334
  builder,
335
335
  porchRoofOnPostsOnly: options.porchRoofOnPostsOnly,
336
336
  rafterRoof: options.rafterRoof,
337
+ wallPillarSpacing: options.wallPillarSpacing,
338
+ visorSections: options.visorSections,
337
339
  floorCount,
338
340
  isUnit,
339
341
  elevation,
340
342
  isTimber: system.timber,
341
343
  walls: [],
342
344
  wallParts: /* @__PURE__ */ new Map(),
345
+ groundBeams: /* @__PURE__ */ new Map(),
343
346
  carriers: /* @__PURE__ */ new Map(),
344
347
  plates: /* @__PURE__ */ new Map(),
345
348
  pillars: /* @__PURE__ */ new Map(),
@@ -412,7 +415,8 @@ function addBalconies(context) {
412
415
  builder.rest(deck, base, system.joints.slab);
413
416
  continue;
414
417
  }
415
- const pillar = builder.add({ id: `balcony_column_${room.id}`, role: "column", material: construction.frame.pillars, aabb: [box2[0] - 0.08, box2[1] - 0.08, box2[0] + 0.08, box2[1] + 0.08], y0: 0, y1: context.level(floor) - system.slabThickness }, { floor: 0 });
418
+ const half = context.visorSections ? 0.08 : 0.1;
419
+ const pillar = builder.add({ id: `balcony_column_${room.id}`, role: "column", material: construction.frame.pillars, aabb: [box2[0] - half, box2[1] - half, box2[0] + half, box2[1] + half], y0: 0, y1: context.level(floor) - system.slabThickness }, { floor: 0 });
416
420
  builder.rest(pillar, GROUND, "rebar");
417
421
  builder.rest(deck, pillar, "rebar");
418
422
  }
@@ -483,8 +487,8 @@ function addJoists(context, options) {
483
487
  const top = context.level(floor) - thickness * 0.15;
484
488
  const out = [];
485
489
  for (let k = 0; k < count; k++) {
486
- const centre = (alongU ? box2[0] : box2[1]) + 0.05 + k * (span - 0.1) / (count - 1);
487
- const aabb = alongU ? [centre - 0.025, box2[1], centre + 0.025, box2[3]] : [box2[0], centre - 0.025, box2[2], centre + 0.025];
490
+ const centre2 = (alongU ? box2[0] : box2[1]) + 0.05 + k * (span - 0.1) / (count - 1);
491
+ const aabb = alongU ? [centre2 - 0.025, box2[1], centre2 + 0.025, box2[3]] : [box2[0], centre2 - 0.025, box2[2], centre2 + 0.025];
488
492
  const joist = context.builder.add({ id: `joist_${floor}${suffix}_${k}`, role: "beam", material, aabb, y0: context.level(floor) - thickness, y1: top }, { floor, fill: 1, spacing: JOIST_PITCH });
489
493
  out.push(joist);
490
494
  const below = carriers.filter((carrier) => carrier.y0 <= top + 0.05);
@@ -574,8 +578,8 @@ function addPiers(context) {
574
578
  const at2 = alongU ? box2[1] + line * (box2[3] - box2[1]) / (rows - 1) : box2[0] + line * (box2[2] - box2[0]) / (columns - 1);
575
579
  const aabb = alongU ? [box2[0], at2 - 0.07, box2[2], at2 + 0.07] : [at2 - 0.07, box2[1], at2 + 0.07, box2[3]];
576
580
  const beam = builder.add({ id: `base_beam_${line}`, role: "beam", material: construction.frame.timber, aabb, y0: height, y1: elevation - context.system.slabThickness }, { floor: 0 });
577
- const centre = (q) => alongU ? (q.aabb[1] + q.aabb[3]) / 2 : (q.aabb[0] + q.aabb[2]) / 2;
578
- for (const pier of piers.filter((q) => Math.abs(centre(q) - at2) < 0.05)) builder.rest(beam, pier, "bolt");
581
+ const centre2 = (q) => alongU ? (q.aabb[1] + q.aabb[3]) / 2 : (q.aabb[0] + q.aabb[2]) / 2;
582
+ for (const pier of piers.filter((q) => Math.abs(centre2(q) - at2) < 0.05)) builder.rest(beam, pier, "bolt");
579
583
  beams.push(beam);
580
584
  }
581
585
  return beams;
@@ -596,18 +600,115 @@ function addFoundation(context) {
596
600
  else if (context.isTimber && type !== "ground") context.base0 = raft(context, Math.max(0.12, context.elevation), "concrete");
597
601
  }
598
602
 
603
+ // src/structure/ground-beams.ts
604
+ var GROUND_BEAM_DEPTH = 0.3;
605
+ function addGroundBeams(context) {
606
+ const { builder, construction } = context;
607
+ if (construction.system !== "masonry" || context.isUnit || construction.foundation.type !== "strip_footing") return;
608
+ const top = context.level(0);
609
+ for (const wall of context.walls) {
610
+ if (wall.floor !== 0 || wall.type !== "wall") continue;
611
+ const beam = builder.add({ id: `ground_beam_${context.groundBeams.size}`, role: "beam", material: construction.frame.beams, aabb: wall.box, y0: top - GROUND_BEAM_DEPTH, y1: top }, { floor: 0, beamKind: "ground_beam" });
612
+ builder.rest(beam, GROUND, "rebar");
613
+ context.groundBeams.set(wall.id, beam);
614
+ }
615
+ }
616
+
617
+ // src/structure/wall-pillars.ts
618
+ var WALL_PILLAR_SPACING = 4;
619
+ var WALL_PILLAR_SIDE = 0.2;
620
+ var MERGE_DISTANCE = 0.45;
621
+ var HALF = WALL_PILLAR_SIDE / 2;
622
+ var TOLERANCE = 0.03;
623
+ var alongRange = (wall) => wall.axis === "u" ? [Math.min(wall.u0, wall.u1), Math.max(wall.u0, wall.u1)] : [Math.min(wall.v0, wall.v1), Math.max(wall.v0, wall.v1)];
624
+ var acrossOf = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
625
+ function stretchesOf(context, wall) {
626
+ const [low, high] = alongRange(wall);
627
+ const windows = (context.source.windows ?? []).filter((w) => w.floor === wall.floor && w.axis === wall.axis && Math.abs(w.pos - acrossOf(wall)) <= TOLERANCE && w.at + w.width / 2 > low && w.at - w.width / 2 < high).map((w) => [w.at - w.width / 2, w.at + w.width / 2]).sort((a, b) => a[0] - b[0]);
628
+ const out = [];
629
+ let cursor = low;
630
+ for (const [from, to] of windows) {
631
+ if (from - cursor > TOLERANCE) out.push({ wall, from: cursor, to: from });
632
+ cursor = Math.max(cursor, to);
633
+ }
634
+ if (high - cursor > TOLERANCE) out.push({ wall, from: cursor, to: high });
635
+ return out;
636
+ }
637
+ function junctionsOf(context, wall) {
638
+ const [low, high] = alongRange(wall);
639
+ const line = acrossOf(wall);
640
+ const out = [];
641
+ for (const other of context.walls) {
642
+ if (other === wall || other.floor !== wall.floor || other.axis === wall.axis || other.type !== "wall") continue;
643
+ const at2 = acrossOf(other);
644
+ const [a, b] = alongRange(other);
645
+ const meets = Math.abs(a - line) <= (wall.thickness ?? 0.15) / 2 + TOLERANCE || Math.abs(b - line) <= (wall.thickness ?? 0.15) / 2 + TOLERANCE;
646
+ if (meets && at2 > low + HALF && at2 < high - HALF) out.push(at2);
647
+ }
648
+ return out;
649
+ }
650
+ function pillarStations(from, to, junctions, spacing) {
651
+ const length = to - from;
652
+ if (length < WALL_PILLAR_SIDE - 1e-6) return [];
653
+ if (length < 2 * WALL_PILLAR_SIDE) return [(from + to) / 2];
654
+ if (length < WALL_PILLAR_SIDE + MERGE_DISTANCE) return [from + HALF, to - HALF];
655
+ const fixed = [from + HALF, ...junctions.filter((at2) => at2 > from + HALF + MERGE_DISTANCE && at2 < to - HALF - MERGE_DISTANCE), to - HALF].sort((a, b) => a - b);
656
+ const out = [];
657
+ fixed.forEach((at2, k) => {
658
+ out.push(at2);
659
+ const next = fixed[k + 1];
660
+ if (next === void 0) return;
661
+ const spans = Math.ceil((next - at2) / spacing - 1e-6);
662
+ for (let s = 1; s < spans; s++) out.push(at2 + (next - at2) * s / spans);
663
+ });
664
+ return out;
665
+ }
666
+ function addWallPillars(context, walls, floor, plates, taken, spacing) {
667
+ const { builder } = context;
668
+ const pillars = [];
669
+ const placed = taken.map((p) => [(p.aabb[0] + p.aabb[2]) / 2, (p.aabb[1] + p.aabb[3]) / 2]);
670
+ const base = context.baseOf(floor);
671
+ for (const wall of walls) {
672
+ if (!wall.exterior || wall.type !== "wall") continue;
673
+ const top = context.wallParts.get(wall.id).y1;
674
+ const across = acrossOf(wall);
675
+ const junctions = junctionsOf(context, wall);
676
+ for (const stretch of stretchesOf(context, wall)) {
677
+ const standing = (end) => placed.map(([pu, pv]) => wall.axis === "u" ? [pu, pv] : [pv, pu]).find(([a, c]) => Math.abs(c - across) <= TOLERANCE && Math.abs(a - end) < MERGE_DISTANCE + HALF)?.[0];
678
+ const first = standing(stretch.from);
679
+ const last = standing(stretch.to);
680
+ const from = first === void 0 ? stretch.from : Math.min(stretch.from, first - HALF);
681
+ const to = last === void 0 ? stretch.to : Math.max(stretch.to, last + HALF);
682
+ for (const along of pillarStations(from, to, junctions, spacing)) {
683
+ const [u, v] = wall.axis === "u" ? [along, across] : [across, along];
684
+ const end = Math.abs(along - stretch.from) < Math.abs(along - stretch.to) ? stretch.from : stretch.to;
685
+ const endPoint = wall.axis === "u" ? [end, across] : [across, end];
686
+ const isEnd = Math.abs(along - end) <= HALF + TOLERANCE;
687
+ if (placed.some(([pu, pv]) => Math.hypot(pu - u, pv - v) < MERGE_DISTANCE && (!isEnd || Math.hypot(pu - endPoint[0], pv - endPoint[1]) < MERGE_DISTANCE))) continue;
688
+ placed.push([u, v]);
689
+ const box2 = [u - HALF, v - HALF, u + HALF, v + HALF];
690
+ const pillar = builder.add({ id: `wall_column_${floor}_${pillars.length}`, role: "column", material: context.construction.frame.pillars, aabb: box2, y0: context.level(floor), y1: top }, { floor });
691
+ builder.rest(pillar, base === GROUND || !base ? GROUND : base, "rebar");
692
+ for (const plate of plates.filter((p) => touches(p.aabb, box2, 0.01))) builder.rest(plate, pillar, "rebar");
693
+ pillars.push(pillar);
694
+ }
695
+ }
696
+ }
697
+ return pillars;
698
+ }
699
+
599
700
  // src/structure/plates.ts
600
701
  var MAX_GAP_WITHIN_RUN = 3.2;
601
702
  var POST_SPACING = 2.4;
602
703
  var POST_SIDE = 0.05;
603
704
  var alongOf = (wall, pick) => wall.axis === "u" ? pick(wall.u0, wall.u1) : pick(wall.v0, wall.v1);
604
- var acrossOf = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
705
+ var acrossOf2 = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
605
706
  function runsOf(context, walls, floor) {
606
707
  const lines = /* @__PURE__ */ new Map();
607
708
  for (const wall of walls) {
608
709
  if (!wall.exterior || wall.type !== "wall") continue;
609
710
  const top = round2(context.wallParts.get(wall.id).y1);
610
- const key = `${floor}|${wall.axis}|${acrossOf(wall).toFixed(2)}|${top.toFixed(2)}`;
711
+ const key = `${floor}|${wall.axis}|${acrossOf2(wall).toFixed(2)}|${top.toFixed(2)}`;
611
712
  lines.set(key, [...lines.get(key) ?? [], wall]);
612
713
  }
613
714
  return [...lines.values()].map((line) => {
@@ -627,7 +728,7 @@ function runsOf(context, walls, floor) {
627
728
  }
628
729
  function addShackPosts(context, run, plate, floor, index) {
629
730
  const first = run.walls[0];
630
- const across = acrossOf(first);
731
+ const across = acrossOf2(first);
631
732
  const top = context.wallParts.get(first.id).y1;
632
733
  const count = Math.max(1, Math.ceil((run.to - run.from) / POST_SPACING));
633
734
  const posts = [];
@@ -653,7 +754,7 @@ function addPlates(context, walls, floor) {
653
754
  for (const run of runs) {
654
755
  const first = run.walls[0];
655
756
  const half = (first.thickness ?? 0.15) / 2;
656
- const across = acrossOf(first);
757
+ const across = acrossOf2(first);
657
758
  const top = context.wallParts.get(first.id).y1;
658
759
  const aabb = first.axis === "u" ? [run.from - half, across - half, run.to + half, across + half] : [across - half, run.from - half, across + half, run.to + half];
659
760
  const material = context.isTimber ? construction.frame.timber : construction.frame.beams;
@@ -692,7 +793,11 @@ function addCornerPillars(context, walls, floor, plates) {
692
793
  function addPlatesAndPillars(context, floor) {
693
794
  const walls = context.walls.filter((wall) => wall.floor === floor);
694
795
  const { plates, pillars } = addPlates(context, walls, floor);
695
- if (context.construction.system === "masonry") pillars.push(...addCornerPillars(context, walls, floor, plates));
796
+ if (context.construction.system === "masonry") {
797
+ const corners = addCornerPillars(context, walls, floor, plates);
798
+ pillars.push(...corners);
799
+ if (context.wallPillarSpacing) pillars.push(...addWallPillars(context, walls, floor, plates, corners, context.wallPillarSpacing));
800
+ }
696
801
  context.pillars.set(floor, pillars);
697
802
  context.plates.set(floor, plates);
698
803
  const innerBearing = walls.filter((wall) => wall.role === "wall" && !wall.exterior).map((wall) => context.wallParts.get(wall.id));
@@ -977,21 +1082,21 @@ function addStairs(context) {
977
1082
  }
978
1083
 
979
1084
  // src/structure/openings.ts
980
- var TOLERANCE = 0.02;
1085
+ var TOLERANCE2 = 0.02;
981
1086
  var DOOR_HEIGHT = 2.1;
982
1087
  var LINTEL_DEPTH = 0.2;
983
1088
  var LINTEL_BEARING = 0.2;
984
1089
  var crossOf = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
985
- var alongRange = (wall) => wall.axis === "u" ? [Math.min(wall.u0, wall.u1), Math.max(wall.u0, wall.u1)] : [Math.min(wall.v0, wall.v1), Math.max(wall.v0, wall.v1)];
1090
+ var alongRange2 = (wall) => wall.axis === "u" ? [Math.min(wall.u0, wall.u1), Math.max(wall.u0, wall.u1)] : [Math.min(wall.v0, wall.v1), Math.max(wall.v0, wall.v1)];
986
1091
  function findOpenings(context) {
987
1092
  const openings = [];
988
1093
  for (const window of context.source.windows ?? []) {
989
1094
  const from = window.at - window.width / 2;
990
1095
  const to = window.at + window.width / 2;
991
1096
  const wall = context.walls.find((w) => {
992
- if (w.floor !== window.floor || w.axis !== window.axis || w.type !== "wall" || Math.abs(crossOf(w) - window.pos) > TOLERANCE) return false;
993
- const [low, high] = alongRange(w);
994
- return from >= low - TOLERANCE && to <= high + TOLERANCE;
1097
+ if (w.floor !== window.floor || w.axis !== window.axis || w.type !== "wall" || Math.abs(crossOf(w) - window.pos) > TOLERANCE2) return false;
1098
+ const [low, high] = alongRange2(w);
1099
+ return from >= low - TOLERANCE2 && to <= high + TOLERANCE2;
995
1100
  });
996
1101
  if (!wall) continue;
997
1102
  const base = context.level(window.floor);
@@ -1006,18 +1111,39 @@ function addDoorLintels(context) {
1006
1111
  const cross = door.axis === "u" ? door.v : door.u;
1007
1112
  const from = along - door.width / 2;
1008
1113
  const to = along + door.width / 2;
1009
- const beside = context.walls.filter((w) => w.floor === door.floor && w.axis === door.axis && w.type === "wall" && Math.abs(crossOf(w) - cross) <= TOLERANCE && (Math.abs(alongRange(w)[1] - from) <= 0.05 || Math.abs(alongRange(w)[0] - to) <= 0.05));
1114
+ const beside = context.walls.filter((w) => w.floor === door.floor && w.axis === door.axis && w.type === "wall" && Math.abs(crossOf(w) - cross) <= TOLERANCE2 && (Math.abs(alongRange2(w)[1] - from) <= 0.05 || Math.abs(alongRange2(w)[0] - to) <= 0.05));
1010
1115
  if (!beside.length) continue;
1011
1116
  const half = (beside[0].thickness ?? 0.15) / 2;
1012
1117
  const lo = from - LINTEL_BEARING;
1013
1118
  const hi = to + LINTEL_BEARING;
1014
1119
  const box2 = door.axis === "u" ? [lo, cross - half, hi, cross + half] : [cross - half, lo, cross + half, hi];
1015
1120
  const y0 = context.level(door.floor) + DOOR_HEIGHT;
1016
- const lintel = builder.add({ id: `lintel_${door.id}`, role: "beam", material: system.timber ? construction.frame.timber : "concrete", aabb: box2, y0, y1: y0 + LINTEL_DEPTH }, { floor: door.floor, ...system.timber ? {} : { assembly: "reinforced_concrete" } });
1121
+ const depth = Math.max(LINTEL_DEPTH, (door.width + LINTEL_BEARING) / 10);
1122
+ const lintel = builder.add({ id: `lintel_${door.id}`, role: "beam", material: system.timber ? construction.frame.timber : "concrete", aabb: box2, y0, y1: y0 + depth }, { floor: door.floor, ...system.timber ? {} : { assembly: "reinforced_concrete" } });
1017
1123
  for (const wall of beside) builder.rest(lintel, context.wallParts.get(wall.id), system.joints.beam);
1124
+ const jambs = (context.pillars.get(door.floor) ?? []).filter((pillar) => pillar.material === "concrete" && overlapsInPlan(pillar.aabb, box2) && pillar.y1 >= y0);
1125
+ if (construction.system === "masonry" && hi - lo > WIDE_DOOR) jambs.push(...addJambPillars(context, { door, beside, from, to, cross }, jambs));
1126
+ for (const pillar of jambs) builder.rest(lintel, pillar, "rebar");
1018
1127
  for (const plate of context.plates.get(door.floor) ?? []) if (overlapsInPlan(plate.aabb, box2)) builder.rest(plate, lintel, system.joints.beam);
1019
1128
  }
1020
1129
  }
1130
+ var WIDE_DOOR = 3;
1131
+ var HALF_PILLAR = WALL_PILLAR_SIDE / 2;
1132
+ function addJambPillars(context, gap, standing) {
1133
+ const { builder, construction } = context;
1134
+ const top = Math.max(...gap.beside.map((w) => context.wallParts.get(w.id).y1));
1135
+ const base = context.baseOf(gap.door.floor);
1136
+ const out = [];
1137
+ for (const [k, along] of [[0, gap.from - HALF_PILLAR], [1, gap.to + HALF_PILLAR]]) {
1138
+ const [u, v] = gap.door.axis === "u" ? [along, gap.cross] : [gap.cross, along];
1139
+ const box2 = [u - HALF_PILLAR, v - HALF_PILLAR, u + HALF_PILLAR, v + HALF_PILLAR];
1140
+ if (standing.some((p) => overlapsInPlan(p.aabb, box2))) continue;
1141
+ const pillar = builder.add({ id: `jamb_column_${gap.door.id}_${k}`, role: "column", material: construction.frame.pillars, aabb: box2, y0: context.level(gap.door.floor), y1: top }, { floor: gap.door.floor });
1142
+ builder.rest(pillar, base === GROUND || !base ? GROUND : base, "rebar");
1143
+ out.push(pillar);
1144
+ }
1145
+ return out;
1146
+ }
1021
1147
  var overlapsInPlan = (a, b) => a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
1022
1148
 
1023
1149
  // src/structure/steps.ts
@@ -1197,10 +1323,10 @@ function addWallPart(context, wall, floor) {
1197
1323
  const framed = system.studs !== void 0 && !isRailing;
1198
1324
  const part = builder.add(
1199
1325
  { id: wall.id, role: wall.role, material: wall.material, aabb: wall.box, y0, y1: Math.max(y1, y0 + 0.2) },
1200
- { floor, fill: context.isTimber && wall.role === "wall" ? system.wallFill : 1, ...framed ? { studs: system.studs } : {}, ...isRailing ? {} : { assembly: assemblyOf(context) } }
1326
+ { floor, fill: context.isTimber && wall.role === "wall" ? system.wallFill : 1, ...framed ? { studs: system.studs } : {}, ...isRailing ? {} : { assembly: assemblyOf(context) }, ...wall.exterior && !isRailing ? { exterior: true } : {} }
1201
1327
  );
1202
1328
  context.wallParts.set(wall.id, part);
1203
- const base = isRailing ? railingBase(context, wall, floor) : context.baseOf(floor);
1329
+ const base = isRailing ? railingBase(context, wall, floor) : context.groundBeams.get(wall.id) ?? context.baseOf(floor);
1204
1330
  const joint = isRailing ? "nail" : system.joints.wall;
1205
1331
  if (base === GROUND || floor === 0 && !base) builder.rest(part, GROUND, joint);
1206
1332
  else if (base) builder.rest(part, base, joint);
@@ -1218,6 +1344,11 @@ function buildFloors(context) {
1218
1344
  addUpperFloor(context, floor);
1219
1345
  }
1220
1346
  }
1347
+ var wallPillarSpacingOf = (options) => {
1348
+ const pillars = options.wallPillars ?? true;
1349
+ if (pillars === false) return 0;
1350
+ return pillars === true ? WALL_PILLAR_SPACING : pillars.spacing ?? WALL_PILLAR_SPACING;
1351
+ };
1221
1352
  function buildStructure(source, options = {}) {
1222
1353
  const refined = options.wallDetail !== "coarse";
1223
1354
  const built = options.construction ? { construction: options.construction, extras: options.extras ?? {} } : resolveConstruction(source);
@@ -1225,8 +1356,9 @@ function buildStructure(source, options = {}) {
1225
1356
  const extras = options.extras ?? built.extras ?? {};
1226
1357
  if (construction.system === "canvas") return buildCanvasStructure(construction, extras);
1227
1358
  const tank = options.tank !== void 0 ? options.tank : source.waterTank ?? null;
1228
- const context = createContext(source, construction, extras, tank, { porchRoofOnPostsOnly: options.porchRoofOnPostsOnly ?? refined, rafterRoof: options.rafterRoof ?? refined });
1359
+ const context = createContext(source, construction, extras, tank, { porchRoofOnPostsOnly: options.porchRoofOnPostsOnly ?? refined, rafterRoof: options.rafterRoof ?? refined, wallPillarSpacing: wallPillarSpacingOf(options), visorSections: options.visorSections === true });
1229
1360
  addFoundation(context);
1361
+ if (options.groundBeams !== false) addGroundBeams(context);
1230
1362
  buildFloors(context);
1231
1363
  addBalconies(context);
1232
1364
  addPorchRoofs(context);
@@ -1675,13 +1807,13 @@ function placeMeter(plan) {
1675
1807
  const segments = groundExteriorWalls(plan).sort((a, b) => (b.sides.includes("outside") ? 1 : 0) - (a.sides.includes("outside") ? 1 : 0) || (a.axis === "u" && a.v0 === 0 ? -1 : 1));
1676
1808
  const wall = segments.find((w) => w.axis === "u" && w.v0 === 0) || segments[0];
1677
1809
  if (!wall) return null;
1678
- const centre = wall.axis === "u" ? (wall.u0 + wall.u1) / 2 : (wall.v0 + wall.v1) / 2;
1810
+ const centre2 = wall.axis === "u" ? (wall.u0 + wall.u1) / 2 : (wall.v0 + wall.v1) / 2;
1679
1811
  const vector = outwardOf(wall);
1680
1812
  const offset = HALF_WALL + 0.1;
1681
1813
  return {
1682
1814
  id: "gas.meter",
1683
- u: round22(wall.axis === "u" ? centre : wall.u0 + vector[0] * offset),
1684
- v: round22(wall.axis === "u" ? wall.v0 + vector[1] * offset : centre),
1815
+ u: round22(wall.axis === "u" ? centre2 : wall.u0 + vector[0] * offset),
1816
+ v: round22(wall.axis === "u" ? wall.v0 + vector[1] * offset : centre2),
1685
1817
  y: 1.2,
1686
1818
  side: sideNameOf(vector),
1687
1819
  w: 0.3,
@@ -1872,9 +2004,9 @@ function placeInverter(plan) {
1872
2004
  const wall = groundExteriorWalls(plan).filter((q) => q.sides.includes("outside")).sort((a, b) => (a.axis === "v" ? 0 : 1) - (b.axis === "v" ? 0 : 1))[0];
1873
2005
  if (!wall) return null;
1874
2006
  const vector = outwardOf(wall);
1875
- const centre = wall.axis === "u" ? (wall.u0 + wall.u1) / 2 : (wall.v0 + wall.v1) / 2;
2007
+ const centre2 = wall.axis === "u" ? (wall.u0 + wall.u1) / 2 : (wall.v0 + wall.v1) / 2;
1876
2008
  const offset = HALF_WALL + 0.08;
1877
- return { id: "inverter", u: round22(wall.axis === "u" ? centre : wall.u0 + vector[0] * offset), v: round22(wall.axis === "u" ? wall.v0 + vector[1] * offset : centre), y: 1.5, side: sideNameOf(vector), w: 0.4, d: 0.15, h: 0.5, outdoor: true, link: "panel" };
2009
+ return { id: "inverter", u: round22(wall.axis === "u" ? centre2 : wall.u0 + vector[0] * offset), v: round22(wall.axis === "u" ? wall.v0 + vector[1] * offset : centre2), y: 1.5, side: sideNameOf(vector), w: 0.4, d: 0.15, h: 0.5, outdoor: true, link: "panel" };
1878
2010
  }
1879
2011
  function planSolar(context) {
1880
2012
  const { plan, rng, energy } = context;
@@ -2082,6 +2214,8 @@ function addOutbuildingPaths(lot, outbuildings, items) {
2082
2214
 
2083
2215
  // src/water-tank.ts
2084
2216
  var TANK_SIDE = 1.1;
2217
+ var TANK_PILLAR_SIDE = 0.2;
2218
+ var VISOR_TANK_PILLAR_SIDE = 0.12;
2085
2219
  var NOT_UNDER_TANK = ["void", "elevator", "stairs", "balcony", "porch"];
2086
2220
  var WET_ROOMS = ["bathroom", "kitchen", "laundry"];
2087
2221
  var ATTIC_CHANCE = 0.12;
@@ -2091,26 +2225,27 @@ function capacityOf(source, capacities) {
2091
2225
  return capacities[source.style ?? ""] ?? capacities[source.template ?? ""] ?? 2e3;
2092
2226
  }
2093
2227
  function bestSpot(source, floor, reference) {
2094
- const centre = reference ? [reference.u, reference.v] : [source.w / 2, source.d / 2];
2228
+ const centre2 = reference ? [reference.u, reference.v] : [source.w / 2, source.d / 2];
2095
2229
  let best;
2096
2230
  for (const room of source.rooms.filter((r) => r.floor === floor && !NOT_UNDER_TANK.includes(r.type))) {
2097
2231
  for (const part of room.parts) {
2098
2232
  const rect = [part[0] + 0.4, part[1] + 0.4, part[2] - 0.4, part[3] - 0.4];
2099
2233
  if (rect[2] - rect[0] < TANK_SIDE || rect[3] - rect[1] < TANK_SIDE) continue;
2100
2234
  const base = WET_ROOMS.includes(room.type) ? 0 : room.type === "terrace" ? 500 : 100;
2101
- const weight = base + Math.hypot((rect[0] + rect[2]) / 2 - centre[0], (rect[1] + rect[3]) / 2 - centre[1]);
2235
+ const weight = base + Math.hypot((rect[0] + rect[2]) / 2 - centre2[0], (rect[1] + rect[3]) / 2 - centre2[1]);
2102
2236
  if (!best || weight < best.weight) best = { roomType: room.type, roomId: room.id, rect, weight };
2103
2237
  }
2104
2238
  }
2105
2239
  return best;
2106
2240
  }
2107
- function raisedPlatform(source, u, v, baseY) {
2241
+ function raisedPlatform(source, spot, side) {
2242
+ const { u, v, baseY } = spot;
2108
2243
  const margin = 0.12;
2109
2244
  const edge = Math.max(0, Math.min(u, source.w - u, v, source.d - v));
2110
2245
  const rise = round2(clamp2(edge * 0.35 + 0.7, 1.1, 2.4));
2111
2246
  const thickness = 0.12;
2112
- const reach = TANK_SIDE / 2 + margin - 0.06;
2113
- const pillars = [[-1, -1], [1, -1], [-1, 1], [1, 1]].map(([a, b]) => ({ u: round2(u + a * reach), v: round2(v + b * reach), side: 0.12 }));
2247
+ const reach = TANK_SIDE / 2 + margin - side / 2;
2248
+ const pillars = [[-1, -1], [1, -1], [-1, 1], [1, 1]].map(([a, b]) => ({ u: round2(u + a * reach), v: round2(v + b * reach), side }));
2114
2249
  const half = TANK_SIDE / 2 + margin;
2115
2250
  return { aabb: [u - half, v - half, u + half, v + half].map(round2), baseY, height: rise, thickness, topY: round2(baseY + rise + thickness), pillars, ladder: true };
2116
2251
  }
@@ -2147,7 +2282,7 @@ function planWaterTank(source, rng, options = {}) {
2147
2282
  aabb: [u - TANK_SIDE / 2, v - TANK_SIDE / 2, u + TANK_SIDE / 2, v + TANK_SIDE / 2].map(round2)
2148
2283
  };
2149
2284
  if (where === "raised_slab") {
2150
- tank.platform = raisedPlatform(source, u, v, slabY);
2285
+ tank.platform = raisedPlatform(source, { u, v, baseY: slabY }, options.pillarSide ?? TANK_PILLAR_SIDE);
2151
2286
  tank.y = tank.platform.topY;
2152
2287
  }
2153
2288
  return tank;
@@ -2252,7 +2387,7 @@ function constructionExtension(context) {
2252
2387
  }
2253
2388
  function structureExtension(context) {
2254
2389
  const cache = cacheOf(context.result);
2255
- const options = { construction: cache.build.construction, extras: cache.build.extras ?? {}, tank: tankOf(context), wallDetail: context.options.wallDetail ?? "panels" };
2390
+ const options = { construction: cache.build.construction, extras: cache.build.extras ?? {}, tank: tankOf(context), wallDetail: context.options.wallDetail ?? "panels", ...context.options.wallPillars !== void 0 ? { wallPillars: context.options.wallPillars } : {}, ...context.options.groundBeams !== void 0 ? { groundBeams: context.options.groundBeams === true } : {}, ...context.options.visorSections === true ? { visorSections: true } : {} };
2256
2391
  const { roof, ...structure } = buildStructure(context.result, options);
2257
2392
  cache.roof = roof;
2258
2393
  for (const part of structure.parts) {
@@ -2276,7 +2411,7 @@ function register(engine, options = {}) {
2276
2411
  engine.registerResultExtension(
2277
2412
  "waterTank",
2278
2413
  (context) => {
2279
- const tank = { capacities: options.tankCapacities, custom: context.plan.tpl.tank, location: context.plan.tpl.tankLocation };
2414
+ const tank = { capacities: options.tankCapacities, custom: context.plan.tpl.tank, location: context.plan.tpl.tankLocation, ...context.options.visorSections === true ? { pillarSide: VISOR_TANK_PILLAR_SIDE } : {} };
2280
2415
  return planWaterTank(context.result, context.rng, tank);
2281
2416
  },
2282
2417
  { after: ["construction"], seedKey: "caixa" }
@@ -2971,6 +3106,8 @@ function splitIntoCourses(wall, openings, spec) {
2971
3106
  // src/wall-detail/panels.ts
2972
3107
  var LINTEL_DEPTH2 = 0.2;
2973
3108
  var LINTEL_BEARING2 = 0.2;
3109
+ var SILL_BEAM_DEPTH = 0.1;
3110
+ var LINTEL_SPAN_RATIO = 10;
2974
3111
  var MIN_PIECE = 0.05;
2975
3112
  var frameOf = (wall) => {
2976
3113
  const alongU = wall.aabb[2] - wall.aabb[0] >= wall.aabb[3] - wall.aabb[1];
@@ -2984,6 +3121,7 @@ function splitIntoPanels(wall, openings, style) {
2984
3121
  const drafts = [];
2985
3122
  const piers = [];
2986
3123
  const lintels = [];
3124
+ const sillBeams = [];
2987
3125
  const spandrels = [];
2988
3126
  const height = wall.y1 - wall.y0;
2989
3127
  let cursor = frame.from;
@@ -2994,23 +3132,26 @@ function splitIntoPanels(wall, openings, style) {
2994
3132
  };
2995
3133
  own.forEach((opening2, k) => {
2996
3134
  addPier(Math.max(cursor, opening2.from));
2997
- const lintelTop = Math.min(wall.y1, opening2.y1 + Math.max(LINTEL_DEPTH2, style.lintelDepth?.(opening2.to - opening2.from) ?? 0));
3135
+ const span = opening2.to - opening2.from;
3136
+ const lintelTop = Math.min(wall.y1, opening2.y1 + Math.max(LINTEL_DEPTH2, style.lintelDepth?.(span) ?? 0, (span + LINTEL_BEARING2) / LINTEL_SPAN_RATIO));
2998
3137
  const lintelFrom = Math.max(frame.from, opening2.from - LINTEL_BEARING2);
2999
3138
  const lintelTo = Math.min(frame.to, opening2.to + LINTEL_BEARING2);
3000
3139
  const lintelHeight = lintelTop - opening2.y1;
3001
- if (opening2.y0 - wall.y0 > MIN_PIECE) drafts.push({ id: `${wall.id}_sill_${k}`, role: "partition", material: wall.material, from: opening2.from, to: opening2.to, y0: wall.y0, y1: opening2.y0, volume: (opening2.to - opening2.from) * (opening2.y0 - wall.y0) });
3140
+ const sillTop = style.sillBeam && opening2.y0 - wall.y0 > SILL_BEAM_DEPTH + MIN_PIECE ? opening2.y0 - SILL_BEAM_DEPTH : opening2.y0;
3141
+ if (sillTop - wall.y0 > MIN_PIECE) drafts.push({ id: `${wall.id}_sill_${k}`, role: "partition", material: wall.material, from: opening2.from, to: opening2.to, y0: wall.y0, y1: sillTop, volume: (opening2.to - opening2.from) * (sillTop - wall.y0) });
3142
+ if (sillTop < opening2.y0) sillBeams.push({ id: `${wall.id}_sillbeam_${k}`, role: "beam", material: style.lintelMaterial, from: lintelFrom, to: lintelTo, y0: sillTop, y1: opening2.y0, volume: (lintelTo - lintelFrom) * SILL_BEAM_DEPTH });
3002
3143
  lintels.push({ id: `${wall.id}_lintel_${k}`, role: "beam", material: style.lintelMaterial, from: lintelFrom, to: lintelTo, y0: opening2.y1, y1: lintelTop, volume: (lintelTo - lintelFrom) * lintelHeight });
3003
3144
  spandrels.push(wall.y1 - lintelTop > MIN_PIECE ? { id: `${wall.id}_spandrel_${k}`, role: roleOf2, material: wall.material, from: opening2.from, to: opening2.to, y0: lintelTop, y1: wall.y1, volume: (opening2.to - opening2.from) * (wall.y1 - lintelTop) } : void 0);
3004
3145
  cursor = opening2.to;
3005
3146
  });
3006
3147
  addPier(frame.to);
3007
3148
  for (const pier of piers) {
3008
- for (const lintel of lintels) {
3149
+ for (const lintel of [...lintels, ...sillBeams]) {
3009
3150
  const overlap = Math.max(0, Math.min(pier.to, lintel.to) - Math.max(pier.from, lintel.from));
3010
3151
  pier.volume -= overlap * (lintel.y1 - lintel.y0);
3011
3152
  }
3012
3153
  }
3013
- const all = [...piers, ...drafts, ...lintels, ...spandrels.filter((d) => d !== void 0)];
3154
+ const all = [...piers, ...drafts, ...sillBeams, ...lintels, ...spandrels.filter((d) => d !== void 0)];
3014
3155
  const total = all.reduce((sum, d) => sum + d.volume, 0);
3015
3156
  const pieces = all.map((d) => ({
3016
3157
  id: d.id,
@@ -3025,6 +3166,11 @@ function splitIntoPanels(wall, openings, style) {
3025
3166
  ...d.role === "beam" ? style.lintelAssembly ? { assembly: style.lintelAssembly } : {} : wall.assembly ? { assembly: wall.assembly } : {}
3026
3167
  }));
3027
3168
  const internal = [];
3169
+ for (const beam of sillBeams) {
3170
+ const sill = drafts.find((d) => d.id === beam.id.replace("_sillbeam_", "_sill_"));
3171
+ if (sill) internal.push({ part: beam.id, on: sill.id, joint: style.lintelJoint });
3172
+ for (const pier of piers) if (Math.min(pier.to, beam.to) - Math.max(pier.from, beam.from) > 0) internal.push({ part: beam.id, on: pier.id, joint: style.lintelJoint });
3173
+ }
3028
3174
  lintels.forEach((lintel, k) => {
3029
3175
  for (const pier of piers) if (Math.min(pier.to, lintel.to) - Math.max(pier.from, lintel.from) > 0) internal.push({ part: lintel.id, on: pier.id, joint: style.lintelJoint });
3030
3176
  const spandrel = spandrels[k];
@@ -3056,7 +3202,8 @@ function panelSplits(input, structure, lintelDepth) {
3056
3202
  ...system.timber ? {} : { lintelAssembly: "reinforced_concrete" },
3057
3203
  lintelJoint: system.joints.beam,
3058
3204
  wallJoint: system.joints.wall,
3059
- ...lintelDepth && !system.timber ? { lintelDepth } : {}
3205
+ ...lintelDepth && !system.timber ? { lintelDepth } : {},
3206
+ ...construction.system === "masonry" ? { sillBeam: true } : {}
3060
3207
  });
3061
3208
  if (split.pieces.length > 1) splits.set(part.id, split);
3062
3209
  }
@@ -3064,7 +3211,18 @@ function panelSplits(input, structure, lintelDepth) {
3064
3211
  }
3065
3212
  function applyPanels(input, structure, detail, lintelDepth) {
3066
3213
  if (detail === "coarse") return input;
3067
- return applySplits(input, panelSplits(input, structure, lintelDepth));
3214
+ return onJambPillars(applySplits(input, panelSplits(input, structure, lintelDepth)));
3215
+ }
3216
+ var OPENING_BEAM = /_(lintel|sillbeam)_\d+$/;
3217
+ var overlaps2 = (a, b) => a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
3218
+ function onJambPillars(split) {
3219
+ const pillars = split.parts.filter((part) => part.role === "column" && part.material === "concrete");
3220
+ const extra = [];
3221
+ for (const beam of split.parts) {
3222
+ if (beam.role !== "beam" || !OPENING_BEAM.test(beam.id)) continue;
3223
+ for (const pillar of pillars) if (pillar.floor === beam.floor && overlaps2(pillar.aabb, beam.aabb) && pillar.y0 <= beam.y0 && pillar.y1 >= beam.y1 - 0.01) extra.push({ part: beam.id, on: pillar.id, joint: "rebar" });
3224
+ }
3225
+ return extra.length ? { parts: split.parts, supports: [...split.supports, ...extra] } : split;
3068
3226
  }
3069
3227
  function applyUnits(input, structure) {
3070
3228
  const splits = /* @__PURE__ */ new Map();
@@ -3128,21 +3286,35 @@ var assemblySpec = (part, capacity) => {
3128
3286
  };
3129
3287
  var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
3130
3288
  var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
3289
+ var CAGE_COVER = 0.03;
3290
+ var CAGE_BUCKLING_SHARE = 0.5;
3291
+ var DEFAULT_RESIDUAL = 0.15;
3292
+ function cageResidualCapacity(size, reinforcement, capacity) {
3293
+ const [a = 0, b = 0] = [...size].sort((x, y) => x - y);
3294
+ const perSide = (w) => Math.max(2, Math.floor(Math.max(0, w - 2 * CAGE_COVER) / reinforcement.spacing) + 1);
3295
+ const bars = 2 * perSide(a) + 2 * perSide(b) - 4;
3296
+ const steel = MATERIALS.find((m) => m.id === reinforcement.material)?.compressive ?? 0;
3297
+ const carries2 = bars * Math.PI * (reinforcement.diameter / 2) ** 2 * steel * CAGE_BUCKLING_SHARE;
3298
+ if (!capacity || capacity <= 0 || carries2 <= 0) return DEFAULT_RESIDUAL;
3299
+ return Math.min(1, Math.max(DEFAULT_RESIDUAL, carries2 / capacity));
3300
+ }
3131
3301
  var caged = (spec, part, rebar) => {
3132
3302
  if (!rebar || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
3133
3303
  const size = spec.shape.kind === "box" ? spec.shape.size : null;
3134
3304
  if (!size) return spec;
3135
3305
  const thin = Math.min(...size);
3306
+ const reinforcement = { material: rebar, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter };
3136
3307
  return {
3137
3308
  ...spec,
3138
- reinforcement: { material: rebar, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
3309
+ reinforcement: part.role === "column" ? { ...reinforcement, residualCapacity: cageResidualCapacity(size, reinforcement, spec.structure?.capacity) } : reinforcement,
3139
3310
  units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
3140
3311
  };
3141
3312
  };
3142
3313
  var partSpecOf = (part, capacity, detailed, rebar) => {
3143
3314
  if (detailed && part.assembly) return assemblySpec(part, capacity);
3144
3315
  const spec = boxSpec(part, capacity);
3145
- return detailed ? caged(spec, part, rebar) : spec;
3316
+ const out = detailed ? caged(spec, part, rebar) : spec;
3317
+ return part.beamKind === "ground_beam" ? { ...out, anchored: true, structure: { role: "foundation" } } : out;
3146
3318
  };
3147
3319
  function toDestructibleSpec(structure, options = {}) {
3148
3320
  const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
@@ -3158,7 +3330,9 @@ function toDestructibleSpec(structure, options = {}) {
3158
3330
  const walled = applyPanels(slabbed, structure, detail, slabOptions && structure.construction.frame.rebar ? (span) => beamDepthFor(span, slabOptions) : void 0);
3159
3331
  const panels = slabOptions && detail !== "coarse" ? deepenBondBeams(walled, structure, slabOptions) : walled;
3160
3332
  const sizing = { safety, redundancy, safetyOthers };
3161
- const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
3333
+ const foundation = new Set(panels.parts.filter((part) => part.beamKind === "ground_beam").map((part) => part.id));
3334
+ const onFoundation = foundation.size ? panels.supports.filter((s) => !foundation.has(s.part)).map((s) => foundation.has(s.on) ? { ...s, on: GROUND } : s) : panels.supports;
3335
+ const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts.filter((part) => !foundation.has(part.id)), onFoundation, sizing) };
3162
3336
  const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
3163
3337
  const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
3164
3338
  const margin = options.terrainMargin ?? TERRAIN_MARGIN;
@@ -3224,8 +3398,362 @@ function onSoil(spec, structure, options) {
3224
3398
  return out;
3225
3399
  }
3226
3400
 
3401
+ // src/validate/codes.ts
3402
+ var STRUCTURAL_VIOLATION = {
3403
+ /** A pillar with no load path to the ground, or that starts above what it rests on. */
3404
+ PILLAR_NOT_GROUNDED: "PILLAR_NOT_GROUNDED",
3405
+ /** A pillar of a masonry wall that stops short of the bond beam, or that the bond beam does not rest on. */
3406
+ PILLAR_DOES_NOT_REACH_BOND_BEAM: "PILLAR_DOES_NOT_REACH_BOND_BEAM",
3407
+ /** A masonry wall that runs more than `MAX_PILLAR_SPACING` between two of its pillars. */
3408
+ WALL_SPAN_WITHOUT_PILLAR: "WALL_SPAN_WITHOUT_PILLAR",
3409
+ /** A free end of a masonry wall (a door jamb, the end of a garden wall) with no pillar. */
3410
+ WALL_END_WITHOUT_PILLAR: "WALL_END_WITHOUT_PILLAR",
3411
+ /** Two masonry walls meet at a corner with no pillar there. */
3412
+ CORNER_WITHOUT_PILLAR: "CORNER_WITHOUT_PILLAR",
3413
+ /** Another wall meets a masonry wall inside its length (a T) with no pillar there. */
3414
+ WALL_JUNCTION_WITHOUT_PILLAR: "WALL_JUNCTION_WITHOUT_PILLAR",
3415
+ /** A window or door in a masonry wall with no lintel (verga) over it. */
3416
+ OPENING_WITHOUT_LINTEL: "OPENING_WITHOUT_LINTEL",
3417
+ /** A window in a masonry wall with no sill beam (contraverga) under it. */
3418
+ OPENING_WITHOUT_SILL: "OPENING_WITHOUT_SILL",
3419
+ /** An exterior masonry wall with no bond beam (cinta de amarração) on top. */
3420
+ MISSING_BOND_BEAM: "MISSING_BOND_BEAM",
3421
+ /** A ground-floor masonry wall standing on the soil with no ground beam (viga baldrame) or foundation under it. */
3422
+ MISSING_GROUND_BEAM: "MISSING_GROUND_BEAM",
3423
+ /** A part with no load path down to the ground. */
3424
+ FLOATING_PART: "FLOATING_PART",
3425
+ /** A concrete beam longer than a lintel that rests on masonry alone (no pillar, beam or slab carries it). */
3426
+ BEAM_ON_WEAK_MASONRY: "BEAM_ON_WEAK_MASONRY",
3427
+ /** A concrete pillar or beam below the NBR 6118 minimum section, or a beam between point supports shallower than a tenth of its span. */
3428
+ CONCRETE_BELOW_MIN_SECTION: "CONCRETE_BELOW_MIN_SECTION",
3429
+ /** A concrete pillar or beam with no steel (the construction has no rebar). */
3430
+ UNREINFORCED_CONCRETE: "UNREINFORCED_CONCRETE",
3431
+ /** A brick or block wall laid in stack bond: the vertical joints line up and the wall splits along them. */
3432
+ UNBONDED_MASONRY: "UNBONDED_MASONRY",
3433
+ /** A plastered brick wall without plaster on both faces. */
3434
+ MISSING_PLASTER: "MISSING_PLASTER"
3435
+ };
3436
+ var MAX_PILLAR_SPACING = 4;
3437
+ var MIN_PILLAR_SIDE = 0.19;
3438
+ var MIN_PILLAR_AREA = 0.036;
3439
+ var MIN_BEAM_WIDTH = 0.12;
3440
+ var BEAM_DEPTH_RATIO = 10;
3441
+ var MAX_LINTEL_ON_MASONRY = 3;
3442
+
3443
+ // src/validate/load-path.ts
3444
+ var CONTACT = 0.05;
3445
+ var EPSILON = 5e-3;
3446
+ function groundedParts(parts, supports) {
3447
+ const ids = new Set(parts.map((part) => part.id));
3448
+ const grounded = /* @__PURE__ */ new Set([GROUND]);
3449
+ const bearing = supports.filter((s) => s.bears !== false && ids.has(s.part));
3450
+ let grew = true;
3451
+ while (grew) {
3452
+ grew = false;
3453
+ for (const support of bearing) {
3454
+ if (grounded.has(support.part) || !grounded.has(support.on)) continue;
3455
+ grounded.add(support.part);
3456
+ grew = true;
3457
+ }
3458
+ }
3459
+ return grounded;
3460
+ }
3461
+ function loadPathViolations(structure) {
3462
+ const { parts, supports } = structure;
3463
+ const grounded = groundedParts(parts, supports);
3464
+ const byId = new Map(parts.map((part) => [part.id, part]));
3465
+ const out = [];
3466
+ for (const part of parts) {
3467
+ if (part.role === "none") continue;
3468
+ const pillar = part.role === "column";
3469
+ if (!grounded.has(part.id)) {
3470
+ out.push({ code: pillar ? STRUCTURAL_VIOLATION.PILLAR_NOT_GROUNDED : STRUCTURAL_VIOLATION.FLOATING_PART, part: part.id, message: `${part.id} has no load path to the ground` });
3471
+ continue;
3472
+ }
3473
+ if (!pillar) continue;
3474
+ const bases = supports.filter((s) => s.part === part.id && s.bears !== false);
3475
+ const standsOnBase = bases.some((s) => {
3476
+ if (s.on === GROUND) return part.y0 <= CONTACT;
3477
+ const base = byId.get(s.on);
3478
+ return base !== void 0 && part.y0 >= base.y0 - EPSILON && part.y0 <= base.y1 + CONTACT;
3479
+ });
3480
+ if (!standsOnBase) out.push({ code: STRUCTURAL_VIOLATION.PILLAR_NOT_GROUNDED, part: part.id, message: `${part.id} starts at ${part.y0} m, above what it rests on` });
3481
+ }
3482
+ return out;
3483
+ }
3484
+ var alongOf2 = (part) => {
3485
+ const alongU = part.aabb[2] - part.aabb[0] >= part.aabb[3] - part.aabb[1];
3486
+ return alongU ? { alongU, from: part.aabb[0], to: part.aabb[2], width: part.aabb[3] - part.aabb[1] } : { alongU, from: part.aabb[1], to: part.aabb[3], width: part.aabb[2] - part.aabb[0] };
3487
+ };
3488
+ function bearingsOf(beam, supports, byId) {
3489
+ const frame = alongOf2(beam);
3490
+ return supports.filter((s) => s.part === beam.id && s.bears !== false && s.on !== GROUND).map((s) => byId.get(s.on)).filter((on) => on !== void 0).map((on) => {
3491
+ const [low, high] = frame.alongU ? [on.aabb[0], on.aabb[2]] : [on.aabb[1], on.aabb[3]];
3492
+ return { on, from: Math.max(frame.from, low), to: Math.min(frame.to, high) };
3493
+ }).filter((bearing) => bearing.to - bearing.from > -EPSILON);
3494
+ }
3495
+ var isWallLike = (part) => part.role === "wall" || part.role === "partition";
3496
+ var PEDESTAL_RATIO = 3;
3497
+ var isPedestal = (part) => part.role === "column" && part.y1 - part.y0 <= PEDESTAL_RATIO * Math.min(part.aabb[2] - part.aabb[0], part.aabb[3] - part.aabb[1]);
3498
+ function concreteViolations(structure, isMasonry) {
3499
+ const { parts, supports } = structure;
3500
+ const byId = new Map(parts.map((part) => [part.id, part]));
3501
+ const out = [];
3502
+ for (const part of parts) {
3503
+ if (part.material !== "concrete" || part.role !== "column" && part.role !== "beam") continue;
3504
+ if (!structure.construction.frame.rebar && !isPedestal(part)) out.push({ code: STRUCTURAL_VIOLATION.UNREINFORCED_CONCRETE, part: part.id, message: `${part.id} is concrete without steel` });
3505
+ if (part.role === "column") {
3506
+ const sides = [part.aabb[2] - part.aabb[0], part.aabb[3] - part.aabb[1]];
3507
+ if (Math.min(...sides) < MIN_PILLAR_SIDE - EPSILON || sides[0] * sides[1] < MIN_PILLAR_AREA - EPSILON)
3508
+ out.push({ code: STRUCTURAL_VIOLATION.CONCRETE_BELOW_MIN_SECTION, part: part.id, message: `${part.id} is ${sides.map((s) => s.toFixed(2)).join(" x ")} m: NBR 6118 asks at least ${MIN_PILLAR_SIDE} m a side and ${MIN_PILLAR_AREA} m\xB2` });
3509
+ continue;
3510
+ }
3511
+ const castOn = supports.filter((s) => s.on === part.id && s.bears !== false).map((s) => byId.get(s.part)).filter((top) => top !== void 0 && top.role === "beam" && top.material === "concrete" && Math.abs(top.y0 - part.y1) < 0.01).reduce((most, top) => Math.max(most, top.y1 - top.y0), 0);
3512
+ out.push(...beamViolations(part, bearingsOf(part, supports, byId), { isMasonry, castOn }));
3513
+ }
3514
+ return out;
3515
+ }
3516
+ function effectiveSpan(bearings, depth) {
3517
+ const sorted = [...bearings].sort((a, b) => a.from - b.from);
3518
+ let most = 0;
3519
+ for (let k = 1; k < sorted.length; k++) {
3520
+ const previous = sorted.slice(0, k).reduce((a, b) => b.to > a.to ? b : a);
3521
+ const next = sorted[k];
3522
+ const clear = next.from - previous.to;
3523
+ if (clear <= 0) continue;
3524
+ most = Math.max(most, clear + Math.min((previous.to - previous.from) / 2, 0.3 * depth) + Math.min((next.to - next.from) / 2, 0.3 * depth));
3525
+ }
3526
+ return most;
3527
+ }
3528
+ function beamViolations(beam, bearings, context) {
3529
+ const out = [];
3530
+ const frame = alongOf2(beam);
3531
+ const length = frame.to - frame.from;
3532
+ const depth = beam.y1 - beam.y0 + context.castOn;
3533
+ if (frame.width < MIN_BEAM_WIDTH - EPSILON) out.push({ code: STRUCTURAL_VIOLATION.CONCRETE_BELOW_MIN_SECTION, part: beam.id, message: `${beam.id} is ${frame.width.toFixed(2)} m wide: NBR 6118 asks at least ${MIN_BEAM_WIDTH} m` });
3534
+ if (!bearings.length) return out;
3535
+ const continuous = bearings.some((b) => isWallLike(b.on) && b.to - b.from >= length / 2);
3536
+ const span = continuous ? 0 : effectiveSpan(bearings, depth);
3537
+ if (depth < span / BEAM_DEPTH_RATIO - EPSILON) out.push({ code: STRUCTURAL_VIOLATION.CONCRETE_BELOW_MIN_SECTION, part: beam.id, message: `${beam.id} is ${depth.toFixed(2)} m deep over a ${span.toFixed(2)} m span: it needs at least L/${BEAM_DEPTH_RATIO} = ${(span / BEAM_DEPTH_RATIO).toFixed(2)} m` });
3538
+ if (length > MAX_LINTEL_ON_MASONRY && bearings.every((b) => context.isMasonry(b.on)))
3539
+ out.push({ code: STRUCTURAL_VIOLATION.BEAM_ON_WEAK_MASONRY, part: beam.id, message: `${beam.id} spans ${length.toFixed(2)} m on masonry alone: no pillar carries it` });
3540
+ return out;
3541
+ }
3542
+
3543
+ // src/validate/masonry.ts
3544
+ import { getAssembly as getAssembly2 } from "@adatechnology/game-destruction";
3545
+ var TOLERANCE3 = 0.05;
3546
+ var AT_POINT = 0.45;
3547
+ var EXPOSED_FINISHES = ["exposed_brick", "exposed_block"];
3548
+ var lineOf2 = (part) => {
3549
+ const alongU = part.aabb[2] - part.aabb[0] >= part.aabb[3] - part.aabb[1];
3550
+ const half = (alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0]) / 2;
3551
+ const [from, to] = alongU ? [part.aabb[0], part.aabb[2]] : [part.aabb[1], part.aabb[3]];
3552
+ return { part, alongU, line: alongU ? part.aabb[1] + half : part.aabb[0] + half, from: from + half, to: to - half, half };
3553
+ };
3554
+ function isMasonryWall(part) {
3555
+ if (!part.id.startsWith("wall_") || !part.assembly || part.role !== "wall" && part.role !== "partition") return false;
3556
+ const units = getAssembly2(part.assembly)?.units;
3557
+ return units !== void 0 && units.mortar !== "concrete";
3558
+ }
3559
+ var pointOf2 = (wall, along) => wall.alongU ? [along, wall.line] : [wall.line, along];
3560
+ var alongOfPoint = (wall, point) => wall.alongU ? point[0] : point[1];
3561
+ var acrossOfPoint = (wall, point) => wall.alongU ? point[1] : point[0];
3562
+ var centre = (part) => [(part.aabb[0] + part.aabb[2]) / 2, (part.aabb[1] + part.aabb[3]) / 2];
3563
+ var onLine = (wall, point, slack = 0) => Math.abs(acrossOfPoint(wall, point) - wall.line) <= wall.half + TOLERANCE3 && alongOfPoint(wall, point) >= wall.from - wall.half - slack && alongOfPoint(wall, point) <= wall.to + wall.half + slack;
3564
+ function endKind(wall, along, others) {
3565
+ const point = pointOf2(wall, along);
3566
+ const meeting = others.filter((other) => onLine(other, point));
3567
+ const across = meeting.filter((other) => other.alongU !== wall.alongU);
3568
+ const continued = meeting.length > across.length;
3569
+ if (!across.length) return continued ? "continued" : "free";
3570
+ const atEnd = (other) => Math.min(Math.abs(alongOfPoint(other, point) - other.from), Math.abs(alongOfPoint(other, point) - other.to)) <= other.half + wall.half + TOLERANCE3;
3571
+ return !continued && across.every(atEnd) ? "corner" : "junction";
3572
+ }
3573
+ var masonryBetween = (from, to, windows) => windows.reduce((left, [a, b]) => left - Math.max(0, Math.min(to, b) - Math.max(from, a)), to - from);
3574
+ function pointViolations(context, others) {
3575
+ const { wall, stations } = context;
3576
+ const out = [];
3577
+ const covered = (along) => stations.some((at2) => Math.abs(at2 - along) <= AT_POINT);
3578
+ for (const along of [wall.from, wall.to]) {
3579
+ const kind = endKind(wall, along, others);
3580
+ if (kind === "continued" || covered(along)) continue;
3581
+ const code = kind === "corner" ? STRUCTURAL_VIOLATION.CORNER_WITHOUT_PILLAR : kind === "junction" ? STRUCTURAL_VIOLATION.WALL_JUNCTION_WITHOUT_PILLAR : STRUCTURAL_VIOLATION.WALL_END_WITHOUT_PILLAR;
3582
+ out.push({ code, part: wall.part.id, message: `${wall.part.id} has no pillar at its ${kind} end (${along.toFixed(2)} m along)` });
3583
+ }
3584
+ for (const other of others) {
3585
+ if (other.alongU === wall.alongU) continue;
3586
+ for (const end of [other.from, other.to]) {
3587
+ const point = pointOf2(other, end);
3588
+ const at2 = alongOfPoint(wall, point);
3589
+ if (!onLine(wall, point) || at2 <= wall.from + AT_POINT || at2 >= wall.to - AT_POINT || covered(at2)) continue;
3590
+ out.push({ code: STRUCTURAL_VIOLATION.WALL_JUNCTION_WITHOUT_PILLAR, part: wall.part.id, message: `${other.part.id} meets ${wall.part.id} at ${at2.toFixed(2)} m along with no pillar there` });
3591
+ }
3592
+ }
3593
+ const line = [...stations].sort((a, b) => a - b);
3594
+ for (let k = 1; k < line.length; k++) {
3595
+ const gap = masonryBetween(line[k - 1], line[k], context.windows);
3596
+ if (gap > MAX_PILLAR_SPACING + TOLERANCE3) out.push({ code: STRUCTURAL_VIOLATION.WALL_SPAN_WITHOUT_PILLAR, part: wall.part.id, message: `${wall.part.id} runs ${gap.toFixed(2)} m between pillars (at most ${MAX_PILLAR_SPACING} m)` });
3597
+ }
3598
+ if (!line.length && wall.to - wall.from > MAX_PILLAR_SPACING) out.push({ code: STRUCTURAL_VIOLATION.WALL_SPAN_WITHOUT_PILLAR, part: wall.part.id, message: `${wall.part.id} runs ${(wall.to - wall.from).toFixed(2)} m with no pillar` });
3599
+ return out;
3600
+ }
3601
+ function bondBeamViolations(context, structure) {
3602
+ const { wall, pillars } = context;
3603
+ const resting = new Set(structure.supports.filter((s) => s.on === wall.part.id).map((s) => s.part));
3604
+ const beams = structure.parts.filter((p) => p.beamKind === "bond_beam" && resting.has(p.id));
3605
+ if (!beams.length) return [{ code: STRUCTURAL_VIOLATION.MISSING_BOND_BEAM, part: wall.part.id, message: `${wall.part.id} has no bond beam on top` }];
3606
+ const out = [];
3607
+ for (const pillar of pillars) {
3608
+ const [u, v] = centre(pillar);
3609
+ const over = beams.filter((b) => u >= b.aabb[0] - TOLERANCE3 && u <= b.aabb[2] + TOLERANCE3 && v >= b.aabb[1] - TOLERANCE3 && v <= b.aabb[3] + TOLERANCE3);
3610
+ const carries2 = over.some((b) => pillar.y1 >= b.y0 - TOLERANCE3 && structure.supports.some((s) => s.part === b.id && s.on === pillar.id && s.bears !== false));
3611
+ if (!carries2) out.push({ code: STRUCTURAL_VIOLATION.PILLAR_DOES_NOT_REACH_BOND_BEAM, part: pillar.id, message: `${pillar.id} stops at ${pillar.y1} m and does not carry the bond beam of ${wall.part.id}` });
3612
+ }
3613
+ return out;
3614
+ }
3615
+ function assemblyViolations(part, finish) {
3616
+ const def = getAssembly2(part.assembly ?? "");
3617
+ if (!def) return [];
3618
+ const out = [];
3619
+ if (def.units && def.units.bond !== "running") out.push({ code: STRUCTURAL_VIOLATION.UNBONDED_MASONRY, part: part.id, message: `${part.id} is laid in ${def.units.bond} bond: the joints must be staggered` });
3620
+ const plastered = (def.layers ?? []).some((layer) => layer.material === "plaster" && layer.faces === "both");
3621
+ if (def.core.material === "brick" && !plastered && !EXPOSED_FINISHES.includes(finish ?? "")) out.push({ code: STRUCTURAL_VIOLATION.MISSING_PLASTER, part: part.id, message: `${part.id} is brick without plaster on both faces` });
3622
+ return out;
3623
+ }
3624
+ function masonryViolations(structure) {
3625
+ const walls = structure.parts.filter(isMasonryWall);
3626
+ const columns = structure.parts.filter((p) => p.role === "column" && p.material === "concrete");
3627
+ const out = [];
3628
+ for (const part of walls) {
3629
+ out.push(...assemblyViolations(part, structure.construction.walls.finish));
3630
+ if (part.floor === 0 && structure.supports.some((s) => s.part === part.id && s.on === GROUND))
3631
+ out.push({ code: STRUCTURAL_VIOLATION.MISSING_GROUND_BEAM, part: part.id, message: `${part.id} stands on the soil with no ground beam under it` });
3632
+ if (!part.exterior) continue;
3633
+ const wall = lineOf2(part);
3634
+ const pillars = columns.filter((c) => c.floor === part.floor && onLine(wall, centre(c), AT_POINT) && Math.min(c.y1, part.y1) - Math.max(c.y0, part.y0) > 0.5);
3635
+ const windows = structure.openings.filter((o) => o.wall === part.id).map((o) => [o.from, o.to]);
3636
+ const context = { wall, pillars, stations: pillars.map((p) => alongOfPoint(wall, centre(p))), windows };
3637
+ const others = walls.filter((w) => w !== part && w.floor === part.floor).map(lineOf2);
3638
+ out.push(...pointViolations(context, others), ...bondBeamViolations(context, structure));
3639
+ }
3640
+ return out;
3641
+ }
3642
+
3643
+ // src/validate/openings.ts
3644
+ var TOLERANCE4 = 0.05;
3645
+ var LOW_WINDOW = SILL_BEAM_DEPTH + 0.05;
3646
+ function beamAcross(parts, wall, span, side, y) {
3647
+ return parts.some((part) => {
3648
+ const off = part[side] - y;
3649
+ if (part.role !== "beam" || off < -TOLERANCE4 || off > (side === "y0" ? LINTEL_DEPTH2 : TOLERANCE4)) return false;
3650
+ const [from, to, low, high] = wall.alongU ? [part.aabb[0], part.aabb[2], part.aabb[1], part.aabb[3]] : [part.aabb[1], part.aabb[3], part.aabb[0], part.aabb[2]];
3651
+ return from <= span.from + TOLERANCE4 && to >= span.to - TOLERANCE4 && low <= wall.line + TOLERANCE4 && high >= wall.line - TOLERANCE4;
3652
+ });
3653
+ }
3654
+ function windowViolations(opening2, wall, built) {
3655
+ const line = lineOf2(wall);
3656
+ const out = [];
3657
+ if (!beamAcross(built, line, opening2, "y0", opening2.y1)) out.push({ code: STRUCTURAL_VIOLATION.OPENING_WITHOUT_LINTEL, part: opening2.id, message: `window ${opening2.id} in ${wall.id} has no lintel over it` });
3658
+ if (opening2.y0 - wall.y0 > LOW_WINDOW && !beamAcross(built, line, opening2, "y1", opening2.y0))
3659
+ out.push({ code: STRUCTURAL_VIOLATION.OPENING_WITHOUT_SILL, part: opening2.id, message: `window ${opening2.id} in ${wall.id} has no sill beam under it` });
3660
+ return out;
3661
+ }
3662
+ function wallsBeside(door, walls) {
3663
+ const along = door.axis === "u" ? door.u : door.v;
3664
+ const across = door.axis === "u" ? door.v : door.u;
3665
+ const from = along - door.width / 2;
3666
+ const to = along + door.width / 2;
3667
+ return walls.filter((w) => w.floor === door.floor).map(lineOf2).filter((w) => w.alongU === (door.axis === "u") && Math.abs(w.line - across) <= TOLERANCE4 && (Math.abs(w.to - from) <= TOLERANCE4 || Math.abs(w.from - to) <= TOLERANCE4));
3668
+ }
3669
+ function openingViolations(structure, built, doors) {
3670
+ const walls = structure.parts.filter(isMasonryWall);
3671
+ const hosts = new Map(walls.map((wall) => [wall.id, wall]));
3672
+ const out = [];
3673
+ for (const opening2 of structure.openings) {
3674
+ const wall = hosts.get(opening2.wall);
3675
+ if (wall) out.push(...windowViolations(opening2, wall, built));
3676
+ }
3677
+ for (const door of doors) {
3678
+ const beside = wallsBeside(door, walls);
3679
+ if (!beside.length) continue;
3680
+ const along = door.axis === "u" ? door.u : door.v;
3681
+ const span = { from: along - door.width / 2, to: along + door.width / 2 };
3682
+ const lintel = beamAcross(built, beside[0], span, "y0", (structure.levels[door.floor] ?? 0) + DOOR_HEIGHT);
3683
+ if (!lintel) out.push({ code: STRUCTURAL_VIOLATION.OPENING_WITHOUT_LINTEL, part: door.id, message: `door ${door.id} has no lintel over it` });
3684
+ }
3685
+ return out;
3686
+ }
3687
+
3688
+ // src/validate/spec.ts
3689
+ var EPSILON2 = 5e-3;
3690
+ var carries = (part) => part?.structure !== void 0 && part.structure.role !== "none";
3691
+ function flows(joint, byId) {
3692
+ const bears = joint.bears ?? (carries(byId.get(joint.a)) && carries(byId.get(joint.b)));
3693
+ if (bears === false) return [];
3694
+ if (bears === "a_on_b") return [[joint.a, joint.b]];
3695
+ if (bears === "b_on_a") return [[joint.b, joint.a]];
3696
+ return [[joint.a, joint.b], [joint.b, joint.a]];
3697
+ }
3698
+ function groundedOf(spec, byId) {
3699
+ const grounded = new Set(spec.parts.filter((p) => p.anchored || p.structure?.role === "foundation").map((p) => p.id));
3700
+ const edges = (spec.joints ?? []).flatMap((joint) => flows(joint, byId));
3701
+ let grew = true;
3702
+ while (grew) {
3703
+ grew = false;
3704
+ for (const [part, on] of edges) {
3705
+ if (grounded.has(part) || !grounded.has(on)) continue;
3706
+ grounded.add(part);
3707
+ grew = true;
3708
+ }
3709
+ }
3710
+ return grounded;
3711
+ }
3712
+ function sectionViolation(part) {
3713
+ if (part.material !== "concrete" || part.shape.kind !== "box") return null;
3714
+ const role = part.structure?.role;
3715
+ const [x, y, z] = part.shape.size;
3716
+ if (role === "column" && (Math.min(x, z) < MIN_PILLAR_SIDE - EPSILON2 || x * z < MIN_PILLAR_AREA - EPSILON2))
3717
+ return { code: STRUCTURAL_VIOLATION.CONCRETE_BELOW_MIN_SECTION, part: part.id, message: `${part.id} is ${x.toFixed(2)} x ${z.toFixed(2)} m: NBR 6118 asks at least ${MIN_PILLAR_SIDE} m a side` };
3718
+ if (role === "beam" && Math.min(x, z) < MIN_BEAM_WIDTH - EPSILON2 && y >= Math.min(x, z))
3719
+ return { code: STRUCTURAL_VIOLATION.CONCRETE_BELOW_MIN_SECTION, part: part.id, message: `${part.id} is ${Math.min(x, z).toFixed(2)} m wide: NBR 6118 asks at least ${MIN_BEAM_WIDTH} m` };
3720
+ return null;
3721
+ }
3722
+ function specViolations(spec) {
3723
+ const byId = new Map(spec.parts.map((part) => [part.id, part]));
3724
+ const grounded = groundedOf(spec, byId);
3725
+ const out = [];
3726
+ for (const part of spec.parts) {
3727
+ if (!carries(part)) continue;
3728
+ if (!grounded.has(part.id)) {
3729
+ const pillar = part.structure.role === "column";
3730
+ out.push({ code: pillar ? STRUCTURAL_VIOLATION.PILLAR_NOT_GROUNDED : STRUCTURAL_VIOLATION.FLOATING_PART, part: part.id, message: `${part.id} has no load path to an anchored part` });
3731
+ }
3732
+ const section = sectionViolation(part);
3733
+ if (section) out.push(section);
3734
+ }
3735
+ return out;
3736
+ }
3737
+
3738
+ // src/validate/index.ts
3739
+ function validateStructure(structure, options = {}) {
3740
+ const detail = options.wallDetail ?? "panels";
3741
+ const built = applyPanels({ parts: structure.parts, supports: structure.supports }, structure, detail);
3742
+ const out = [...loadPathViolations(structure), ...concreteViolations({ ...built, construction: structure.construction }, isMasonryWall)];
3743
+ if (structure.construction.system !== "masonry") return out;
3744
+ return [...out, ...masonryViolations(structure), ...openingViolations(structure, built.parts, options.doors ?? [])];
3745
+ }
3746
+ function validateSpec(spec) {
3747
+ return specViolations(spec);
3748
+ }
3749
+ function countViolations(violations) {
3750
+ const out = {};
3751
+ for (const violation of violations) out[violation.code] = (out[violation.code] ?? 0) + 1;
3752
+ return out;
3753
+ }
3754
+
3227
3755
  // src/wall-specimen.ts
3228
- import { assemblyDepth, assemblyPart as assemblyPart2, getAssembly as getAssembly2 } from "@adatechnology/game-destruction";
3756
+ import { assemblyDepth, assemblyPart as assemblyPart2, getAssembly as getAssembly3 } from "@adatechnology/game-destruction";
3229
3757
  var SYSTEM_OF_ASSEMBLY = { log_wall: "log", wood_frame_wall: "wood_frame", sheet_metal_shack: "improvised" };
3230
3758
  var DOOR_WIDTH2 = 0.9;
3231
3759
  var WINDOW_WIDTH = 1;
@@ -3234,7 +3762,7 @@ function openingOf(kind, height) {
3234
3762
  }
3235
3763
  function buildWallSpecimen(options) {
3236
3764
  const { assembly, w, h, opening: opening2 = null, detail = "panels" } = options;
3237
- const def = getAssembly2(assembly);
3765
+ const def = getAssembly3(assembly);
3238
3766
  if (!def) throw new Error(`unknown assembly "${assembly}"`);
3239
3767
  const system = SYSTEM_OF_ASSEMBLY[assembly] ?? "masonry";
3240
3768
  const depth = assemblyDepth(def);
@@ -3414,7 +3942,7 @@ function addPunchedFacade(spec, g, options = {}) {
3414
3942
  parts.push({ id, material: c.material, shape: box(size), position, structure: { role: "partition" }, tags: ["infill"] });
3415
3943
  for (const o of on) rests(joints, id, o, "mortar");
3416
3944
  };
3417
- const bay = (side, k, along, centre, fixed, length, c1, c2) => {
3945
+ const bay = (side, k, along, centre2, fixed, length, c1, c2) => {
3418
3946
  const clear = length - g.columnSize;
3419
3947
  const win = clear * c.window;
3420
3948
  const pier = (clear - win) / 2;
@@ -3425,11 +3953,11 @@ function addPunchedFacade(spec, g, options = {}) {
3425
3953
  wall(`infill_${f}_${side}_${k}_${name}`, size(u, h), [p[0], y, p[2]], on);
3426
3954
  };
3427
3955
  const id = (name) => `infill_${f}_${side}_${k}_${name}`;
3428
- put("sill", clear, sill, yb + sill / 2, centre, [c1, c2]);
3429
- put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre - clear / 2 + pier / 2, [c1, id("sill")]);
3430
- put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre + clear / 2 - pier / 2, [c2, id("sill")]);
3431
- put("head", clear, room - head, yb + head + (room - head) / 2, centre, [c1, c2]);
3432
- const gp = at2(centre);
3956
+ put("sill", clear, sill, yb + sill / 2, centre2, [c1, c2]);
3957
+ put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre2 - clear / 2 + pier / 2, [c1, id("sill")]);
3958
+ put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre2 + clear / 2 - pier / 2, [c2, id("sill")]);
3959
+ put("head", clear, room - head, yb + head + (room - head) / 2, centre2, [c1, c2]);
3960
+ const gp = at2(centre2);
3433
3961
  const gid = `glass_${f}_${side}_${k}`;
3434
3962
  parts.push({
3435
3963
  id: gid,
@@ -3950,6 +4478,7 @@ var towerLocales = { en: { labels: en_default.labels }, "pt-BR": { labels: pt_BR
3950
4478
  // src/index.ts
3951
4479
  var version = "0.1.0";
3952
4480
  export {
4481
+ BEAM_DEPTH_RATIO,
3953
4482
  CONSTRUCTIONS,
3954
4483
  CONSTRUCTION_SYSTEMS,
3955
4484
  CORE_DEFAULTS,
@@ -3958,8 +4487,14 @@ export {
3958
4487
  DENSITY,
3959
4488
  FLAT_ROOF_DEFAULTS,
3960
4489
  GROUND,
4490
+ GROUND_BEAM_DEPTH,
4491
+ MAX_LINTEL_ON_MASONRY,
4492
+ MAX_PILLAR_SPACING,
3961
4493
  MECHANICAL_DEFAULTS,
3962
4494
  MEMBER_CAGE,
4495
+ MIN_BEAM_WIDTH,
4496
+ MIN_PILLAR_AREA,
4497
+ MIN_PILLAR_SIDE,
3963
4498
  PILE_AREA,
3964
4499
  PILE_DEFAULTS,
3965
4500
  PLATE_DEFAULTS,
@@ -3967,10 +4502,15 @@ export {
3967
4502
  RAFT_THICKNESS,
3968
4503
  REBAR_RATIOS,
3969
4504
  ROOF_COVERINGS,
4505
+ SILL_BEAM_DEPTH,
3970
4506
  SKYSCRAPER_DEFAULTS,
3971
4507
  SKYSCRAPER_MAX_STOREYS,
3972
4508
  SLAB_PANEL_DEFAULTS,
4509
+ STRUCTURAL_VIOLATION,
4510
+ TANK_PILLAR_SIDE,
3973
4511
  TOWER_DEFAULTS,
4512
+ WALL_PILLAR_SIDE,
4513
+ WALL_PILLAR_SPACING,
3974
4514
  addCore,
3975
4515
  addCurtainWall,
3976
4516
  addFlatRoof2 as addFlatRoof,
@@ -3984,16 +4524,19 @@ export {
3984
4524
  buildStructure,
3985
4525
  buildTower,
3986
4526
  buildWallSpecimen,
4527
+ cageResidualCapacity,
3987
4528
  campConstruction,
3988
4529
  chaletConstruction,
3989
4530
  constructionOfSystem,
3990
4531
  coreSize,
4532
+ countViolations,
3991
4533
  damperMass,
3992
4534
  exteriorWindows,
3993
4535
  logCabinConstruction,
3994
4536
  masonryConstruction,
3995
4537
  mechanicalStoreys,
3996
4538
  pilesFor,
4539
+ pillarStations,
3997
4540
  planToSpace,
3998
4541
  planWaterTank,
3999
4542
  register,
@@ -4014,6 +4557,8 @@ export {
4014
4557
  towerLocales,
4015
4558
  towerPlate,
4016
4559
  towerWater,
4560
+ validateSpec,
4561
+ validateStructure,
4017
4562
  version,
4018
4563
  windExposureOf,
4019
4564
  woodFrameConstruction