abse/smartwatch-flex-top-only

Three-panel flex smartwatch with two parallel R3.9 bends, all components flat-top, low-profile JST connector harnesses, split heartbeat optical windows and screw-retained PCB mounting. Modeled 39 × 50.5 × 14.5mm prototype; 100% routing, zero native DRC and shorts. Hardware qualification pending.

Version
0.2.22
License
unset
Stars
0

tools/pcb-style/analyzer.mjs

// Exact bundled style analyzer from CLI 0.1.2300, unmodified.
const __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/segments.ts
function endpoint(p, outgoing) {
  if (p.route_type === "through_pad")
    return outgoing ? p.end : p.start;
  return { x: p.x, y: p.y };
}
function layer(p, outgoing) {
  if (p.route_type === "wire")
    return p.layer;
  if (p.route_type === "via")
    return outgoing ? p.to_layer : p.from_layer;
  return outgoing ? p.end_layer : p.start_layer;
}
function buildAnalysisContext(circuitJson) {
  const segments = [];
  for (const [circuitJsonIndex, item] of circuitJson.entries()) {
    if (item.type !== "pcb_trace")
      continue;
    for (let i = 0;i < item.route.length - 1; i++) {
      const a = item.route[i];
      const b = item.route[i + 1];
      const supported = (p) => ["wire", "via", "through_pad"].includes(p.route_type);
      if (!supported(a) || !supported(b))
        throw new Error(item.pcb_trace_id + ": unsupported route point at " + i);
      if (a.route_type !== "wire" && b.route_type !== "wire")
        continue;
      if (layer(a, true) !== layer(b, false))
        continue;
      const start = endpoint(a, true);
      const end = endpoint(b, false);
      if (![start.x, start.y, end.x, end.y].every(Number.isFinite))
        throw new Error(item.pcb_trace_id + ": nonfinite route coordinates at " + i);
      const lengthMm = Math.hypot(end.x - start.x, end.y - start.y);
      if (lengthMm <= 0.000000001)
        continue;
      const wire = a.route_type === "wire" ? a : b;
      const width = wire.route_type === "wire" ? Math.max(wire.width, wire.end_width ?? wire.width) : 0;
      if (!Number.isFinite(width) || width <= 0)
        throw new Error(item.pcb_trace_id + ": invalid wire width at " + i);
      segments.push({
        pcbTraceId: item.pcb_trace_id,
        sourceTraceId: item.source_trace_id,
        pcbGroupId: item.pcb_group_id,
        subcircuitId: item.subcircuit_id,
        circuitJsonIndex,
        startRouteIndex: i,
        endRouteIndex: i + 1,
        layer: layer(a, true),
        start: { ...start },
        end: { ...end },
        width,
        lengthMm
      });
    }
  }
  return { circuitJson, segments };
}

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/issue-highlights.ts
function getIssueHighlights(issues) {
  return issues.map((issue) => ({
    issue,
    color: "#ff5555",
    message: issue.message
  }));
}

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/visualize.ts
function visualizeIssues(ctx, issues) {
  const highlights = getIssueHighlights(issues);
  return {
    coordinateSystem: "cartesian",
    lines: [
      ...ctx.segments.map((s) => ({
        points: [s.start, s.end],
        strokeColor: s.layer === "bottom" ? "#60a5fa" : "#94a3b8",
        strokeWidth: s.width
      })),
      ...highlights.flatMap(({ issue, color, message }) => (issue.constituentSegments ?? [issue]).map((segment) => ({
        points: [segment.start, segment.end],
        strokeColor: color,
        strokeWidth: Math.max(segment.width * 2, 0.25),
        label: message
      })))
    ],
    points: highlights.map(({ issue, color, message }, i) => ({
      ...issue.location,
      color,
      label: i + 1 + ": " + message
    }))
  };
}
var init_visualize = () => {};

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/trace-runs.ts
function distanceToSegment(p, a, b) {
  const dx = b.x - a.x;
  const dy = b.y - a.y;
  const lengthSquared = dx * dx + dy * dy;
  const t = lengthSquared ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSquared)) : 0;
  return Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy);
}
function buildTraceRuns(ctx) {
  const runs = [];
  const simplify = (segments) => {
    const pending = [[0, segments.length - 1]];
    while (pending.length) {
      const [lo, hi] = pending.pop();
      if (lo === hi)
        continue;
      const start = segments[lo].start;
      const end = segments[hi].end;
      let width = Infinity;
      let maxDistance = -1;
      let split = lo;
      for (let i = lo;i <= hi; i++) {
        width = Math.min(width, segments[i].width);
        if (i === hi)
          continue;
        const distance = distanceToSegment(segments[i].end, start, end);
        if (distance > maxDistance) {
          maxDistance = distance;
          split = i;
        }
      }
      if (maxDistance > width / 2 + 0.000000001) {
        pending.push([split + 1, hi], [lo, split]);
        continue;
      }
      const constituentSegments = segments.slice(lo, hi + 1);
      runs.push({
        ...segments[lo],
        endRouteIndex: segments[hi].endRouteIndex,
        end,
        width: constituentSegments.reduce((max, s) => Math.max(max, s.width), 0),
        lengthMm: Math.hypot(end.x - start.x, end.y - start.y),
        constituentSegments,
        maxCenterlineDeviationMm: maxDistance <= 0.000000001 ? 0 : maxDistance
      });
    }
  };
  for (const connected of getConnectedTraceSegments(ctx))
    simplify(connected);
  return [...ctx.segments, ...runs];
}
function getConnectedTraceSegments(ctx) {
  const chains = [];
  let connected = [];
  for (const segment of ctx.segments) {
    const previous = connected.at(-1);
    if (previous) {
      const trace = ctx.circuitJson[segment.circuitJsonIndex];
      const sameTrace = previous.circuitJsonIndex === segment.circuitJsonIndex;
      const wireJoin = sameTrace && trace.type === "pcb_trace" && trace.route.slice(previous.endRouteIndex, segment.startRouteIndex + 1).every((p) => p.route_type === "wire" && p.layer === segment.layer);
      if (!wireJoin || previous.layer !== segment.layer || Math.hypot(previous.end.x - segment.start.x, previous.end.y - segment.start.y) > 0.000000001) {
        chains.push(connected);
        connected = [];
      }
    }
    connected.push(segment);
  }
  if (connected.length)
    chains.push(connected);
  return chains;
}

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/solvers/LongTraceSegmentSolver.ts
import { BaseSolver } from "@tscircuit/solver-utils";
var LongTraceSegmentSolver;
var init_LongTraceSegmentSolver = __esm(() => {
  init_visualize();
  LongTraceSegmentSolver = class LongTraceSegmentSolver extends BaseSolver {
    params;
    segments = [];
    nextSegment = 0;
    candidates;
    constructor(params2) {
      super();
      this.params = params2;
      if (!Number.isFinite(params2.maxSegmentLengthMm) || params2.maxSegmentLengthMm <= 0)
        throw new Error("maxSegmentLengthMm must be finite and positive");
      this.candidates = buildTraceRuns(params2.ctx);
      this.MAX_ITERATIONS = this.candidates.length + 2;
    }
    _step() {
      const segment = this.candidates[this.nextSegment++];
      if (!segment) {
        this.solved = true;
        this.progress = 1;
        return;
      }
      if (segment.lengthMm > this.params.maxSegmentLengthMm)
        this.segments.push(segment);
      this.progress = this.nextSegment / this.candidates.length;
      this.stats = {
        segmentsChecked: this.nextSegment,
        candidateCount: this.segments.length
      };
    }
    getOutput() {
      return this.segments;
    }
    visualize() {
      return visualizeIssues(this.params.ctx, []);
    }
    getConstructorParams() {
      return [this.params];
    }
  };
});

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/solvers/SegmentIssueSolver.ts
import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
function locateIssue(segment, rule) {
  const { start, end } = segment;
  const parts = segment.constituentSegments ?? [segment];
  const bounds = parts.reduce((bounds, s) => ({
    minX: Math.min(bounds.minX, s.start.x - s.width / 2, s.end.x - s.width / 2),
    minY: Math.min(bounds.minY, s.start.y - s.width / 2, s.end.y - s.width / 2),
    maxX: Math.max(bounds.maxX, s.start.x + s.width / 2, s.end.x + s.width / 2),
    maxY: Math.max(bounds.maxY, s.start.y + s.width / 2, s.end.y + s.width / 2)
  }), { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });
  return {
    ...segment,
    issueId: rule + ":" + segment.circuitJsonIndex + ":" + segment.startRouteIndex,
    severity: "error",
    message: "",
    location: { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 },
    bounds
  };
}
var SegmentIssueSolver;
var init_SegmentIssueSolver = __esm(() => {
  init_visualize();
  SegmentIssueSolver = class SegmentIssueSolver extends BaseSolver2 {
    ctx;
    issues = [];
    nextSegment = 0;
    constructor(ctx) {
      super();
      this.ctx = ctx;
      this.MAX_ITERATIONS = ctx.segments.length + 2;
    }
    _step() {
      const segment = this.ctx.segments[this.nextSegment++];
      if (!segment) {
        this.solved = true;
        this.progress = 1;
        return;
      }
      const issue = this.checkSegment(segment);
      if (issue)
        this.issues.push(issue);
      this.progress = this.nextSegment / this.ctx.segments.length;
      this.stats = {
        segmentsChecked: this.nextSegment,
        issueCount: this.issues.length
      };
    }
    getOutput() {
      return this.issues;
    }
    visualize() {
      return visualizeIssues(this.ctx, this.issues);
    }
  };
});

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/solvers/OddAngleTraceSegmentSolver.ts
var OddAngleTraceSegmentSolver;
var init_OddAngleTraceSegmentSolver = __esm(() => {
  init_visualize();
  init_SegmentIssueSolver();
  OddAngleTraceSegmentSolver = class OddAngleTraceSegmentSolver extends SegmentIssueSolver {
    params;
    constructor(params2) {
      super({
        ...params2.ctx,
        segments: params2.candidateSegments ?? buildTraceRuns(params2.ctx)
      });
      this.params = params2;
      if (!Number.isFinite(params2.maxSegmentLengthMm) || params2.maxSegmentLengthMm <= 0)
        throw new Error("maxSegmentLengthMm must be finite and positive");
      if (!Number.isFinite(params2.angleToleranceDegrees) || params2.angleToleranceDegrees < 0 || params2.angleToleranceDegrees >= 22.5)
        throw new Error("angleToleranceDegrees must be in [0, 22.5)");
      const covered = new Set;
      for (const segment of this.ctx.segments) {
        if (!segment.constituentSegments || !this.checkSegment(segment))
          continue;
        for (const part of segment.constituentSegments)
          covered.add(part.circuitJsonIndex + ":" + part.startRouteIndex);
      }
      this.ctx = {
        ...this.ctx,
        segments: this.ctx.segments.filter((s) => s.constituentSegments || !covered.has(s.circuitJsonIndex + ":" + s.startRouteIndex))
      };
    }
    checkSegment(s) {
      if (s.lengthMm <= this.params.maxSegmentLengthMm)
        return;
      const angleDegrees = (Math.atan2(s.end.y - s.start.y, s.end.x - s.start.x) * 180 / Math.PI + 360) % 360;
      const nearest = Math.round(angleDegrees / 45) * 45;
      const deviationDegrees = Math.abs(angleDegrees - nearest);
      const uncertaintyDegrees = Math.atan2(2 * (s.maxCenterlineDeviationMm ?? 0), s.lengthMm) * 180 / Math.PI;
      if (deviationDegrees <= this.params.angleToleranceDegrees + uncertaintyDegrees + 0.0000000001)
        return;
      return {
        ...locateIssue(s, "odd-angle"),
        lineItemType: "PcbTraceSegmentOddAngle",
        maxSegmentLengthMm: this.params.maxSegmentLengthMm,
        angleDegrees,
        nearestAllowedAngleDegrees: nearest % 360,
        deviationDegrees,
        angleToleranceDegrees: this.params.angleToleranceDegrees,
        ...s.constituentSegments ? { angleUncertaintyDegrees: uncertaintyDegrees } : {},
        message: s.pcbTraceId + (s.constituentSegments ? " run " + s.startRouteIndex + " → " + s.endRouteIndex : " segment " + s.startRouteIndex) + " on " + s.layer + " is " + s.lengthMm.toFixed(3) + " mm long (threshold " + this.params.maxSegmentLengthMm + " mm) and at " + angleDegrees.toFixed(3) + "°; nearest allowed direction " + nearest % 360 + "° (deviation " + deviationDegrees.toFixed(3) + "°, tolerance " + this.params.angleToleranceDegrees + "°" + (uncertaintyDegrees ? ", approximation allowance " + uncertaintyDegrees.toFixed(3) + "°" : "") + ")"
      };
    }
    visualize() {
      return visualizeIssues(this.params.ctx, this.issues);
    }
    getConstructorParams() {
      return [this.params];
    }
  };
});

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/solvers/StaircaseTraceSolver.ts
function direction(s) {
  const angle = (Math.atan2(s.end.y - s.start.y, s.end.x - s.start.x) * 180 / Math.PI + 360) % 360;
  return angle;
}
function angleDistance(a, b) {
  return Math.abs((a - b + 540) % 360 - 180);
}
var StaircaseTraceSolver;
var init_StaircaseTraceSolver = __esm(() => {
  init_visualize();
  init_SegmentIssueSolver();
  StaircaseTraceSolver = class StaircaseTraceSolver extends SegmentIssueSolver {
    params;
    bendCounts = new Map;
    constructor(params2) {
      super({ ...params2.ctx, segments: [] });
      this.params = params2;
      const minBends = params2.minStaircaseBends ?? 6;
      const minLength = params2.minStaircaseLengthMm ?? 2;
      const maxStep = params2.maxStairStepLengthMm ?? 1;
      if (!Number.isInteger(minBends) || minBends < 2)
        throw new Error("minStaircaseBends must be an integer of at least 2");
      if (!Number.isFinite(minLength) || minLength <= 0)
        throw new Error("minStaircaseLengthMm must be finite and positive");
      if (!Number.isFinite(maxStep) || maxStep <= 0)
        throw new Error("maxStairStepLengthMm must be finite and positive");
      const candidates = [];
      for (const chain of getConnectedTraceSegments(params2.ctx)) {
        const steps = [];
        for (const s of chain) {
          const heading = direction(s);
          const last = steps.at(-1);
          if (last && angleDistance(last.direction, heading) <= 4 + 0.0000000001) {
            last.parts.push(s);
            last.lengthMm += s.lengthMm;
          } else
            steps.push({ direction: heading, parts: [s], lengthMm: s.lengthMm });
        }
        for (let i = 0;i < steps.length - 1; ) {
          const a = steps[i], b = steps[i + 1];
          const difference = angleDistance(a.direction, b.direction);
          if (difference < 15 - 0.0000000001 || difference > 90 + 0.0000000001 || a.lengthMm > maxStep + 0.000000001 || b.lengthMm > maxStep + 0.000000001) {
            i++;
            continue;
          }
          let end = i + 2;
          while (end < steps.length && angleDistance(steps[end].direction, steps[i + (end - i) % 2].direction) <= 4 + 0.0000000001 && steps[end].lengthMm <= maxStep + 0.000000001)
            end++;
          const parts = steps.slice(i, end).flatMap((s) => s.parts);
          const lengthMm = parts.reduce((n, s) => n + s.lengthMm, 0);
          if (end - i - 1 >= minBends && lengthMm + 0.000000001 >= minLength) {
            const candidate = {
              ...parts[0],
              end: parts.at(-1).end,
              endRouteIndex: parts.at(-1).endRouteIndex,
              width: parts.reduce((width, s) => Math.max(width, s.width), 0),
              lengthMm,
              constituentSegments: parts
            };
            candidates.push(candidate);
            this.bendCounts.set(candidate, end - i - 1);
            i = end;
          } else
            i = end - 1;
        }
      }
      this.ctx = { ...params2.ctx, segments: candidates };
      this.MAX_ITERATIONS = candidates.length + 2;
    }
    checkSegment(s) {
      const bendCount = this.bendCounts.get(s);
      return {
        ...locateIssue(s, "staircase"),
        lineItemType: "PcbTraceStaircase",
        bendCount,
        minStaircaseBends: this.params.minStaircaseBends ?? 6,
        minStaircaseLengthMm: this.params.minStaircaseLengthMm ?? 2,
        maxStairStepLengthMm: this.params.maxStairStepLengthMm ?? 1,
        message: `${s.pcbTraceId} route ${s.startRouteIndex} → ${s.endRouteIndex} on ${s.layer} has ${bendCount} alternating staircase bends over ${s.lengthMm.toFixed(3)} mm`
      };
    }
    visualize() {
      return visualizeIssues(this.params.ctx, this.issues);
    }
    getConstructorParams() {
      return [this.params];
    }
  };
});

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/solvers/PcbStyleAnalysisPipeline.ts
import {
  BasePipelineSolver,
  definePipelineStep
} from "@tscircuit/solver-utils";
var PcbStyleAnalysisPipeline;
var init_PcbStyleAnalysisPipeline = __esm(() => {
  init_visualize();
  init_LongTraceSegmentSolver();
  init_OddAngleTraceSegmentSolver();
  init_StaircaseTraceSolver();
  PcbStyleAnalysisPipeline = class PcbStyleAnalysisPipeline extends BasePipelineSolver {
    options;
    ctx;
    pipelineDef = [
      definePipelineStep("LongTraceSegmentSolver", LongTraceSegmentSolver, (p) => [
        { ctx: p.ctx, maxSegmentLengthMm: p.options.maxSegmentLengthMm ?? 5 }
      ]),
      definePipelineStep("OddAngleTraceSegmentSolver", OddAngleTraceSegmentSolver, (p) => [
        {
          ctx: p.ctx,
          candidateSegments: p.getSolver("LongTraceSegmentSolver").getOutput(),
          maxSegmentLengthMm: p.options.maxSegmentLengthMm ?? 5,
          angleToleranceDegrees: p.options.angleToleranceDegrees ?? 4
        }
      ]),
      definePipelineStep("StaircaseTraceSolver", StaircaseTraceSolver, (p) => [
        {
          ctx: p.ctx,
          minStaircaseBends: p.options.minStaircaseBends,
          minStaircaseLengthMm: p.options.minStaircaseLengthMm,
          maxStairStepLengthMm: p.options.maxStairStepLengthMm
        }
      ])
    ];
    constructor(circuitJson, options = {}) {
      super(circuitJson);
      this.options = options;
      if (options.issueTypes) {
        const oddAngles = options.issueTypes.includes("PcbTraceSegmentOddAngle");
        const staircases = options.issueTypes.includes("PcbTraceStaircase");
        this.pipelineDef = this.pipelineDef.filter((step) => step.solverName === "StaircaseTraceSolver" ? staircases : oddAngles);
      }
      this.MAX_ITERATIONS = circuitJson.reduce((n, e) => n + (e.type === "pcb_trace" ? e.route.length * 5 : 0), 10);
    }
    _setup() {
      this.ctx = buildAnalysisContext(this.inputProblem);
    }
    getOutput() {
      return {
        issues: [
          ...this.getSolver("OddAngleTraceSegmentSolver")?.getOutput() ?? [],
          ...this.getSolver("StaircaseTraceSolver")?.getOutput() ?? []
        ]
      };
    }
    getConstructorParams() {
      return [this.inputProblem, this.options];
    }
    initialVisualize() {
      this.setup();
      return visualizeIssues(this.ctx, []);
    }
    finalVisualize() {
      this.setup();
      return visualizeIssues(this.ctx, this.getOutput().issues);
    }
  };
});

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/analyze-pcb-style.ts
function analyzePcbStyle(circuitJson, options = {}) {
  const solver = new PcbStyleAnalysisPipeline(circuitJson, options);
  solver.solve();
  if (solver.failed)
    throw new Error(solver.error ?? "PCB style analysis failed");
  return solver.getOutput();
}
var init_analyze_pcb_style = __esm(() => {
  init_PcbStyleAnalysisPipeline();
});

// node_modules/@tscircuit/circuit-json-pcb-style-analysis/lib/create-pcb-style-issue-artifacts.ts
function escapeXml(value) {
  return value.replace(/[&<>"']/g, (c) => ({
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': "&quot;",
    "'": "&apos;"
  })[c]);
}
function describeIssueXml(issue) {
  return "<" + issue.lineItemType + ' severity="error" pcbTraceId="' + escapeXml(issue.pcbTraceId) + '" startRouteIndex="' + issue.startRouteIndex + '" endRouteIndex="' + issue.endRouteIndex + '" layer="' + escapeXml(issue.layer) + '" x="' + issue.location.x + '" y="' + issue.location.y + '" lengthMm="' + issue.lengthMm + '" message="' + escapeXml(issue.message) + '" />';
}
function renderPcbStyleSvg(circuitJson, issues, options = {}) {
  const ctx = buildAnalysisContext(circuitJson);
  const segments = ctx.segments.filter((s) => !options.layer || s.layer === options.layer);
  const points = segments.flatMap((s) => [s.start, s.end]);
  const bounds = options.bounds ?? {
    minX: Math.min(0, ...points.map((p) => p.x)) - 2,
    minY: Math.min(0, ...points.map((p) => p.y)) - 2,
    maxX: Math.max(0, ...points.map((p) => p.x)) + 2,
    maxY: Math.max(0, ...points.map((p) => p.y)) + 2
  };
  const scale = Math.min(740 / Math.max(1, bounds.maxX - bounds.minX), 350 / Math.max(1, bounds.maxY - bounds.minY));
  const cx = (bounds.minX + bounds.maxX) / 2;
  const cy = (bounds.minY + bounds.maxY) / 2;
  const x = (n) => Number((400 + (n - cx) * scale).toFixed(4));
  const y = (n) => Number((215 - (n - cy) * scale).toFixed(4));
  const line = (s, color, highlight = false) => '<line x1="' + x(s.start.x) + '" y1="' + y(s.start.y) + '" x2="' + x(s.end.x) + '" y2="' + y(s.end.y) + '" stroke="' + color + '" stroke-width="' + Number(Math.max(highlight ? 4 : 1, s.width * scale * (highlight ? 1.8 : 1)).toFixed(4)) + '" stroke-linecap="round"/>';
  const context = segments.filter((s) => Math.max(s.start.x, s.end.x) + s.width >= bounds.minX && Math.min(s.start.x, s.end.x) - s.width <= bounds.maxX && Math.max(s.start.y, s.end.y) + s.width >= bounds.minY && Math.min(s.start.y, s.end.y) - s.width <= bounds.maxY).map((s) => line(s, s.layer === "bottom" ? "#74a9d8" : "#9aa9a2")).join("");
  const individual = options.bounds !== undefined && issues.length === 1;
  const highlights = getIssueHighlights(issues).map(({ issue: s, color }, i) => (s.constituentSegments ?? [s]).map((part) => line(part, color, true)).join("") + (individual ? '<circle cx="' + x(s.location.x) + '" cy="' + y(s.location.y) + '" r="10" fill="#fff" stroke="' + color + '" stroke-width="2"/>' + '<text x="' + x(s.location.x) + '" y="' + (y(s.location.y) + 4) + '" text-anchor="middle" fill="#9b1111" font-size="11">' + (i + 1) + "</text>" : "")).join("");
  const title = options.title ?? "PCB style analysis — " + issues.length + " errors";
  const details = individual ? [
    issues[0].message,
    "Start (" + issues[0].start.x.toFixed(3) + ", " + issues[0].start.y.toFixed(3) + ") → End (" + issues[0].end.x.toFixed(3) + ", " + issues[0].end.y.toFixed(3) + ") mm",
    "Layer: " + issues[0].layer + " | Circuit JSON index: " + issues[0].circuitJsonIndex + " | Route indices: " + issues[0].startRouteIndex + " → " + issues[0].endRouteIndex
  ] : [
    "Red: PCB traces with detected style issues",
    "Coordinates in millimeters; Y points upward. " + issues.length + " located errors."
  ];
  return '<svg xmlns="http://www.w3.org/2000/svg" width="800" height="490" viewBox="0 0 800 490"><title>' + escapeXml(title) + '</title><rect width="800" height="490" fill="#10271c"/><style>text{font-family:monospace}</style><text x="20" y="26" fill="#fff" font-size="16">' + escapeXml(title) + '</text><defs><clipPath id="board"><rect x="15" y="40" width="770" height="350"/></clipPath></defs><g clip-path="url(#board)">' + context + highlights + '</g><rect x="0" y="400" width="800" height="90" fill="#f1f5f9"/>' + details.map((t, i) => '<text x="16" y="' + (423 + i * 22) + '" fill="#152d22" font-size="11">' + escapeXml(t) + "</text>").join("") + `</svg>
`;
}
function createPcbStyleIssueArtifacts(circuitJson, options = {}) {
  const analysis = options.analysis ?? analyzePcbStyle(circuitJson, {
    maxSegmentLengthMm: options.maxSegmentLengthMm,
    angleToleranceDegrees: options.angleToleranceDegrees,
    minStaircaseBends: options.minStaircaseBends,
    minStaircaseLengthMm: options.minStaircaseLengthMm,
    maxStairStepLengthMm: options.maxStairStepLengthMm
  });
  return analysis.issues.flatMap((issue, issueIndex) => {
    if (options.layer && issue.layer !== options.layer)
      return [];
    if (options.issueTypes && !options.issueTypes.includes(issue.lineItemType))
      return [];
    const margin = Math.max(2, (issue.bounds.maxX - issue.bounds.minX) * 0.2, (issue.bounds.maxY - issue.bounds.minY) * 0.2);
    const crop = {
      minX: issue.bounds.minX - margin,
      minY: issue.bounds.minY - margin,
      maxX: issue.bounds.maxX + margin,
      maxY: issue.bounds.maxY + margin
    };
    return [
      {
        issueIndex,
        issue,
        layer: issue.layer,
        bounds: issue.bounds,
        descriptionXml: describeIssueXml(issue),
        fileName: "issue-" + String(issueIndex + 1).padStart(4, "0") + "-" + issue.lineItemType + ".svg",
        contentType: "image/svg+xml",
        content: renderPcbStyleSvg(circuitJson, [issue], {
          bounds: crop,
          layer: issue.layer,
          title: "Issue " + (issueIndex + 1) + " — " + issue.lineItemType
        })
      }
    ];
  });
}
var init_create_pcb_style_issue_artifacts = __esm(() => {
  init_analyze_pcb_style();
});


init_analyze_pcb_style();
export {analyzePcbStyle,renderPcbStyleSvg};