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) => ({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'"
})[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};