diff --git a/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts b/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts index 5aae38063..ce54dcc47 100644 --- a/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +++ b/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts @@ -75,7 +75,11 @@ export class MspConnectionPairSolver extends BaseSolver { } } - this.queuedDcNetIds = Object.keys(netConnMap.netMap) + const directNetIds = new Set(Object.keys(this.dcConnMap.netMap)) + const netLabelOnlyNetIds = Object.keys(netConnMap.netMap).filter( + (id) => !directNetIds.has(id), + ) + this.queuedDcNetIds = [...directNetIds, ...netLabelOnlyNetIds] } override getConstructorParams(): ConstructorParameters< @@ -94,7 +98,11 @@ export class MspConnectionPairSolver extends BaseSolver { const dcNetId = this.queuedDcNetIds.shift()! - const allIds = this.globalConnMap.getIdsConnectedToNet(dcNetId) as string[] + const dcIds = this.dcConnMap.getIdsConnectedToNet(dcNetId) as string[] + const allIds = + dcIds.length > 0 + ? dcIds + : (this.globalConnMap.getIdsConnectedToNet(dcNetId) as string[]) const directlyConnectedPins = allIds.filter((id) => !!this.pinMap[id]) if (directlyConnectedPins.length <= 1) { diff --git a/lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts b/lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts index d3a098c33..5c38ca046 100644 --- a/lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts +++ b/lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts @@ -14,7 +14,11 @@ export const getConnectivityMapsFromInputProblem = ( ]) } - const netConnMap = new ConnectivityMap(directConnMap.netMap) + const netConnMap = new ConnectivityMap( + Object.fromEntries( + Object.entries(directConnMap.netMap).map(([k, v]) => [k, [...v]]), + ), + ) for (const netConn of inputProblem.netConnections) { netConnMap.addConnections([[netConn.netId, ...netConn.pinIds]]) diff --git a/lib/solvers/SameNetTraceCombiningSolver/SameNetTraceCombiningSolver.ts b/lib/solvers/SameNetTraceCombiningSolver/SameNetTraceCombiningSolver.ts new file mode 100644 index 000000000..f8336e3fe --- /dev/null +++ b/lib/solvers/SameNetTraceCombiningSolver/SameNetTraceCombiningSolver.ts @@ -0,0 +1,288 @@ +import type { Point } from "@tscircuit/math-utils" +import type { GraphicsObject, Line } from "graphics-debug" +import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver" +import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" +import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem" +import type { InputProblem } from "lib/types/InputProblem" + +/** + * Tolerance for considering two segments as collinear (on the same axis line). + * Segments whose perpendicular-axis coordinates differ by at most this value + * are treated as being on the same line. + */ +const COLLINEAR_TOLERANCE = 0.05 + +/** + * Maximum gap between two collinear segments for them to be merged. + * If the gap between the closest endpoints is larger than this, the + * segments are left untouched. + */ +const GAP_TOLERANCE = 0.15 + +type Segment = { + /** Index of the trace in the traces array */ + traceIndex: number + /** Starting point index in the trace path */ + startIdx: number + /** Ending point index in the trace path */ + endIdx: number + /** Whether the segment is horizontal or vertical */ + orientation: "horizontal" | "vertical" + /** + * The coordinate on the constant axis: + * - For horizontal segments: the y coordinate + * - For vertical segments: the x coordinate + */ + axisCoord: number + /** Lower bound along the varying axis */ + minCoord: number + /** Upper bound along the varying axis */ + maxCoord: number +} + +interface SameNetTraceCombiningSolverInput { + inputProblem: InputProblem + allTraces: SolvedTracePath[] +} + +/** + * SameNetTraceCombiningSolver merges collinear trace segments belonging to the + * same net when they are close together (gap <= GAP_TOLERANCE) or overlapping. + * + * This reduces visual clutter in schematics by combining redundant parallel + * traces into single cleaner lines. It operates specifically on axis-aligned + * (horizontal or vertical) trace segments. + * + * Pipeline position: after TraceCleanupSolver, before NetLabelPlacementSolver. + */ +export class SameNetTraceCombiningSolver extends BaseSolver { + private input: SameNetTraceCombiningSolverInput + private outputTraces: SolvedTracePath[] + + constructor(input: SameNetTraceCombiningSolverInput) { + super() + this.input = input + // Deep-copy trace paths so we never mutate the originals + this.outputTraces = input.allTraces.map((t) => ({ + ...t, + tracePath: [...t.tracePath.map((p) => ({ x: p.x, y: p.y }))], + })) + } + + override _step() { + // Group traces by globalConnNetId + const tracesByNet = new Map() + for (let i = 0; i < this.outputTraces.length; i++) { + const netId = this.outputTraces[i].globalConnNetId + if (!netId) continue + if (!tracesByNet.has(netId)) { + tracesByNet.set(netId, []) + } + tracesByNet.get(netId)!.push(i) + } + + for (const [, traceIndices] of tracesByNet) { + if (traceIndices.length < 2) continue + + // Collect every axis-aligned segment from every trace in this net + const allSegments: Segment[] = [] + for (const traceIdx of traceIndices) { + const trace = this.outputTraces[traceIdx] + const segments = this.extractSegments(traceIdx, trace.tracePath) + allSegments.push(...segments) + } + + // Merge eligible segments iteratively + this.mergeCollinearSegments(allSegments) + } + + // Final pass: simplify every trace by removing zero-length segments + // and consecutive duplicate points + for (const trace of this.outputTraces) { + trace.tracePath = simplifyTracePath(trace.tracePath) + } + + this.solved = true + } + + /** + * Extract all axis-aligned segments from a single trace polyline. + * A segment is a pair of consecutive points in the polyline. + */ + private extractSegments(traceIndex: number, path: Point[]): Segment[] { + const segments: Segment[] = [] + for (let i = 0; i < path.length - 1; i++) { + const p1 = path[i] + const p2 = path[i + 1] + + const dx = Math.abs(p2.x - p1.x) + const dy = Math.abs(p2.y - p1.y) + + // Skip near-zero-length segments + if (dx < 1e-9 && dy < 1e-9) continue + + if (dx >= dy) { + // Horizontal segment — constant Y + segments.push({ + traceIndex, + startIdx: i, + endIdx: i + 1, + orientation: "horizontal", + axisCoord: (p1.y + p2.y) / 2, + minCoord: Math.min(p1.x, p2.x), + maxCoord: Math.max(p1.x, p2.x), + }) + } else { + // Vertical segment — constant X + segments.push({ + traceIndex, + startIdx: i, + endIdx: i + 1, + orientation: "vertical", + axisCoord: (p1.x + p2.x) / 2, + minCoord: Math.min(p1.y, p2.y), + maxCoord: Math.max(p1.y, p2.y), + }) + } + } + return segments + } + + /** + * Scan all segments and merge every eligible collinear pair. + * + * Merging strategy: + * - Extend segment A to cover the combined span of A+B + * - Collapse segment B so its start equals its end (a zero-length point) + * The post-processing `simplifyTracePath` step later removes those + * collapsed points. + */ + private mergeCollinearSegments(segments: Segment[]) { + const merged = new Set() + + for (let i = 0; i < segments.length; i++) { + if (merged.has(i)) continue + for (let j = i + 1; j < segments.length; j++) { + if (merged.has(j)) continue + + const segA = segments[i] + const segB = segments[j] + + // Only merge segments from different traces + if (segA.traceIndex === segB.traceIndex) continue + + // Must have the same orientation + if (segA.orientation !== segB.orientation) continue + + // Must be near-collinear (on approximately the same axis) + if (Math.abs(segA.axisCoord - segB.axisCoord) > COLLINEAR_TOLERANCE) + continue + + // Compute the gap between the two segments along the primary axis + const gap = Math.max( + 0, + Math.max(segA.minCoord, segB.minCoord) - + Math.min(segA.maxCoord, segB.maxCoord), + ) + if (gap > GAP_TOLERANCE) continue + + // Perform merge + this.performMerge(segA, segB) + merged.add(j) + } + } + + // Mark any collapsed segments on already-processed traces as merged + // so they don't get extended again in a future iteration + for (const idx of merged) { + const seg = segments[idx] + const trace = this.outputTraces[seg.traceIndex] + // Mark the segment endpoints as equal (already done in performMerge for B) + } + } + + /** + * Extend segment A to the full union of A and B, then collapse segment B + * into a zero-length point at the midpoint of its original span. + */ + private performMerge(segA: Segment, segB: Segment) { + const traceA = this.outputTraces[segA.traceIndex] + const traceB = this.outputTraces[segB.traceIndex] + + const mergedMin = Math.min(segA.minCoord, segB.minCoord) + const mergedMax = Math.max(segA.maxCoord, segB.maxCoord) + + // Snap both segments to a shared axis coordinate + const avgAxis = (segA.axisCoord + segB.axisCoord) / 2 + + if (segA.orientation === "horizontal") { + // Extend segment A to cover the entire horizontal span + const pathA = traceA.tracePath + pathA[segA.startIdx] = { x: mergedMin, y: avgAxis } + pathA[segA.endIdx] = { x: mergedMax, y: avgAxis } + + // Collapse segment B into a single point at its midpoint + const mid = (segB.minCoord + segB.maxCoord) / 2 + const pathB = traceB.tracePath + pathB[segB.startIdx] = { x: mid, y: avgAxis } + pathB[segB.endIdx] = { x: mid, y: avgAxis } + } else { + // Extend segment A to cover the entire vertical span + const pathA = traceA.tracePath + pathA[segA.startIdx] = { x: avgAxis, y: mergedMin } + pathA[segA.endIdx] = { x: avgAxis, y: mergedMax } + + // Collapse segment B + const mid = (segB.minCoord + segB.maxCoord) / 2 + const pathB = traceB.tracePath + pathB[segB.startIdx] = { x: avgAxis, y: mid } + pathB[segB.endIdx] = { x: avgAxis, y: mid } + } + } + + getOutput() { + return { + traces: this.outputTraces, + } + } + + override visualize(): GraphicsObject { + const graphics = visualizeInputProblem(this.input.inputProblem, { + chipAlpha: 0.1, + connectionAlpha: 0.1, + }) + + if (!graphics.lines) graphics.lines = [] + + for (const trace of this.outputTraces) { + const line: Line = { + points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })), + strokeColor: "blue", + } + graphics.lines.push(line) + } + return graphics + } +} + +/** + * Remove zero-length segments and consecutive duplicate points from a + * trace path. This is called after merging to clean up collapsed segments. + */ +export function simplifyTracePath(path: Point[]): Point[] { + if (path.length < 2) return path + + const result: Point[] = [path[0]] + + for (let i = 1; i < path.length; i++) { + const prev = result[result.length - 1] + const curr = path[i] + // Skip if the current point is identical to the previous + if (Math.abs(curr.x - prev.x) < 1e-9 && Math.abs(curr.y - prev.y) < 1e-9) { + continue + } + result.push(curr) + } + + return result +} diff --git a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts index 59821f0c1..eafa9abb0 100644 --- a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +++ b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts @@ -25,6 +25,7 @@ import { Example28Solver } from "../Example28Solver/Example28Solver" import { AvailableNetOrientationSolver } from "../AvailableNetOrientationSolver/AvailableNetOrientationSolver" import { VccNetLabelCornerPlacementSolver } from "../VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver" import { TraceAnchoredNetLabelOverlapSolver } from "../TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver" +import { SameNetTraceCombiningSolver } from "../SameNetTraceCombiningSolver/SameNetTraceCombiningSolver" import { NetLabelTraceCollisionSolver } from "../NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver" type PipelineStep BaseSolver> = { @@ -79,6 +80,7 @@ export class SchematicTracePipelineSolver extends BaseSolver { availableNetOrientationSolver?: AvailableNetOrientationSolver vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver + sameNetTraceCombiningSolver?: SameNetTraceCombiningSolver netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver startTimeOfPhase: Record @@ -217,11 +219,28 @@ export class SchematicTracePipelineSolver extends BaseSolver { }, ] }), + definePipelineStep( + "sameNetTraceCombiningSolver", + SameNetTraceCombiningSolver, + (instance) => { + const traces = + instance.traceCleanupSolver?.getOutput().traces ?? + instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces + + return [ + { + inputProblem: instance.inputProblem, + allTraces: traces, + }, + ] + }, + ), definePipelineStep( "netLabelPlacementSolver", NetLabelPlacementSolver, (instance) => { const traces = + instance.sameNetTraceCombiningSolver?.getOutput().traces ?? instance.traceCleanupSolver?.getOutput().traces ?? instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces @@ -237,6 +256,7 @@ export class SchematicTracePipelineSolver extends BaseSolver { ), definePipelineStep("example28Solver", Example28Solver, (instance) => { const traces = + instance.sameNetTraceCombiningSolver?.getOutput().traces ?? instance.traceCleanupSolver?.getOutput().traces ?? instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces diff --git a/tests/examples/__snapshots__/example01.snap.svg b/tests/examples/__snapshots__/example01.snap.svg index 2614ba80f..fdb3a5758 100644 --- a/tests/examples/__snapshots__/example01.snap.svg +++ b/tests/examples/__snapshots__/example01.snap.svg @@ -50,7 +50,7 @@ orientation: x-" data-x="-1.275" data-y="0" cx="369.90099009900996" cy="300.5940 +orientation: y-" data-x="-4" data-y="-0.7000000000000001" cx="67.72277227722776" cy="378.21782178217825" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> @@ -74,10 +74,10 @@ orientation: y-" data-x="-1.4" data-y="-0.7" cx="356.0396039603961" cy="378.2178 - + - + @@ -101,7 +101,7 @@ available orientations: x+, x-" data-x="-1.5" data-y="0" x="320" y="289.50495049 +available orientations: y-" data-x="-4" data-y="-0.925" x="56.63366336633669" y="378.21782178217825" width="22.17821782178214" height="49.900990099009846" fill="#00000066" stroke="#000000" stroke-width="0.009017857142857143" /> +orientation: y+" data-x="-4.173189849999999" data-y="1.3024186000000004" cx="62.36981027183117" cy="222.4230062437483" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> - + - + - + - + - + - + - + @@ -197,7 +197,7 @@ orientation: y+" data-x="1.4571549750000001" data-y="0.29999999999999966" cx="53 +available orientations: y+" data-x="-4.173189849999999" data-y="1.5274186000000005" x="53.90633386020494" y="184.33736239143013" width="16.926952823252464" height="38.08564385231816" fill="#ef444466" stroke="#ef4444" stroke-width="0.011815475714285715" /> +orientation: y+" data-x="-2.3148566499999994" data-y="0.7512093000000004" cx="70.92192940766586" cy="322.9220591216218" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> - - - - + @@ -125,6 +122,9 @@ orientation: x+" data-x="1.757519574999999" data-y="0.85" cx="547.3539750075635" + + + @@ -137,7 +137,7 @@ orientation: x+" data-x="1.757519574999999" data-y="0.85" cx="547.3539750075635" +available orientations: y+" data-x="-2.3148566499999994" data-y="0.9762093000000004" x="59.22281274267999" y="270.2760341291854" width="23.398233329971788" height="52.64602499243642" fill="#ef444466" stroke="#ef4444" stroke-width="0.00854765388392857" /> +x+" data-x="1.15" data-y="0.75" cx="361.285140562249" cy="303.93574297188763" r="3" fill="hsl(159, 100%, 50%, 0.8)" /> +x+" data-x="1.15" data-y="0" cx="361.285140562249" cy="352.12851405622496" r="3" fill="hsl(158, 100%, 50%, 0.8)" /> +x+" data-x="1.15" data-y="-0.75" cx="361.285140562249" cy="400.3212851405623" r="3" fill="hsl(157, 100%, 50%, 0.8)" /> +x-" data-x="-1.15" data-y="-1.125" cx="213.49397590361446" cy="424.417670682731" r="3" fill="hsl(156, 100%, 50%, 0.8)" /> +x-" data-x="-1.15" data-y="-0.375" cx="213.49397590361446" cy="376.2248995983936" r="3" fill="hsl(155, 100%, 50%, 0.8)" /> +x-" data-x="-1.15" data-y="0.375" cx="213.49397590361446" cy="328.0321285140563" r="3" fill="hsl(154, 100%, 50%, 0.8)" /> +x-" data-x="-1.15" data-y="1.125" cx="213.49397590361446" cy="279.8393574297189" r="3" fill="hsl(153, 100%, 50%, 0.8)" /> +x-" data-x="-3.55" data-y="0" cx="59.27710843373495" cy="352.12851405622496" r="3" fill="hsl(351, 100%, 50%, 0.8)" /> +x+" data-x="-2.45" data-y="0" cx="129.9598393574297" cy="352.12851405622496" r="3" fill="hsl(352, 100%, 50%, 0.8)" /> +x-" data-x="-3.55" data-y="-1.9999999999999998" cx="59.27710843373495" cy="480.64257028112456" r="3" fill="hsl(232, 100%, 50%, 0.8)" /> +x+" data-x="-2.45" data-y="-1.9999999999999998" cx="129.9598393574297" cy="480.64257028112456" r="3" fill="hsl(233, 100%, 50%, 0.8)" /> +x-" data-x="-3.55" data-y="2" cx="59.27710843373495" cy="223.61445783132535" r="3" fill="hsl(113, 100%, 50%, 0.8)" /> +x+" data-x="-2.45" data-y="2" cx="129.9598393574297" cy="223.61445783132535" r="3" fill="hsl(114, 100%, 50%, 0.8)" /> +x-" data-x="2.45" data-y="-1.5" cx="444.81927710843377" cy="448.51405622489966" r="3" fill="hsl(246, 100%, 50%, 0.8)" /> +x+" data-x="3.55" data-y="-1.5" cx="515.5020080321285" cy="448.51405622489966" r="3" fill="hsl(247, 100%, 50%, 0.8)" /> +x-" data-x="2.45" data-y="0" cx="444.81927710843377" cy="352.12851405622496" r="3" fill="hsl(127, 100%, 50%, 0.8)" /> +x+" data-x="3.55" data-y="0" cx="515.5020080321285" cy="352.12851405622496" r="3" fill="hsl(128, 100%, 50%, 0.8)" /> +x-" data-x="3.435" data-y="3" cx="508.11244979919684" cy="159.35742971887555" r="3" fill="hsl(57, 100%, 50%, 0.8)" /> +x+" data-x="4.5649999999999995" data-y="3" cx="580.722891566265" cy="159.35742971887555" r="3" fill="hsl(58, 100%, 50%, 0.8)" /> +orientation: y-" data-x="4.765" data-y="3" cx="593.574297188755" cy="159.35742971887555" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y-" data-x="-2.125" data-y="-1.9999999999999998" cx="150.8433734939759" cy="480.64257028112456" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y+" data-x="1.475" data-y="0" cx="382.1686746987952" cy="352.12851405622496" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y+" data-x="-1.301" data-y="-1.074" cx="203.79116465863456" cy="421.14056224899605" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y+" data-x="-2.125" data-y="0" cx="150.8433734939759" cy="352.12851405622496" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y+" data-x="-3.75" data-y="1.125" cx="46.425702811244975" cy="279.8393574297189" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y+" data-x="-3.651" data-y="2.201" cx="52.78714859437753" cy="210.69879518072293" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y+" data-x="2.349" data-y="0.45099999999999996" cx="438.32931726907634" cy="323.1485943775101" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> - + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + +available orientations: y-" data-x="4.765" data-y="2.775" x="587.1485943775101" y="159.35742971887555" width="12.851405622489892" height="28.915662650602428" fill="#00000066" stroke="#000000" stroke-width="0.0155625" /> +available orientations: y-" data-x="-2.125" data-y="-2.2249999999999996" x="144.41767068273091" y="480.64257028112456" width="12.851405622489978" height="28.91566265060237" fill="#00000066" stroke="#000000" stroke-width="0.0155625" /> +available orientations: y+" data-x="1.475" data-y="0.225" x="375.7429718875502" y="323.21285140562253" width="12.851405622490006" height="28.915662650602428" fill="#ef444466" stroke="#ef4444" stroke-width="0.0155625" /> +available orientations: y+" data-x="-1.301" data-y="-0.8490000000000001" x="197.36546184738955" y="392.2248995983936" width="12.851405622489978" height="28.915662650602428" fill="#ef444466" stroke="#ef4444" stroke-width="0.0155625" /> +available orientations: any" data-x="-2.125" data-y="0.225" x="144.41767068273091" y="323.21285140562253" width="12.851405622489978" height="28.915662650602428" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.0155625" /> + + + +available orientations: y+" data-x="-3.651" data-y="2.426" x="46.361445783132524" y="181.78313253012053" width="12.851405622489978" height="28.9156626506024" fill="#ef444466" stroke="#ef4444" stroke-width="0.0155625" /> +available orientations: y+" data-x="2.349" data-y="0.6759999999999999" x="431.90361445783134" y="294.2329317269077" width="12.851405622489949" height="28.915662650602428" fill="#ef444466" stroke="#ef4444" stroke-width="0.0155625" /> +available orientations: any" data-x="1.96375" data-y="3.225" x="407.14859437751005" y="130.44176706827312" width="12.851405622489949" height="28.915662650602428" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.0155625" /> - + - + - + - + @@ -168,7 +168,7 @@ orientation: y+" data-x="1.4755000000000003" data-y="-1.2944553500000002" cx="44 - + diff --git a/tests/examples/__snapshots__/example15.snap.svg b/tests/examples/__snapshots__/example15.snap.svg index 5d1b2f9d6..bf9227a18 100644 --- a/tests/examples/__snapshots__/example15.snap.svg +++ b/tests/examples/__snapshots__/example15.snap.svg @@ -322,7 +322,27 @@ orientation: y+" data-x="-1.3099999999999998" data-y="1.2000000000000015" cx="35 +orientation: y+" data-x="-1.5675" data-y="6.705000000000002" cx="341.08322324966974" cy="62.19286657859976" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> + + + + + + + + + + + + + + + +orientation: y+" data-x="-4.6850000000000005" data-y="3.9683333333333355" cx="187.3359753412594" cy="197.15837369734325" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> + + + +orientation: y-" data-x="-0.6375" data-y="5.205000000000002" cx="386.94848084544253" cy="136.16908850726549" r="3" fill="hsl(80, 100%, 50%, 0.9)" /> +orientation: y-" data-x="-6.415" data-y="2.4683333333333346" cx="102.01673271686491" cy="271.13459562600906" r="3" fill="hsl(80, 100%, 50%, 0.9)" /> - - - - - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -888,7 +900,32 @@ available orientations: y+" data-x="-1.3099999999999998" data-y="1.4250000000000 +available orientations: y+" data-x="-1.5675" data-y="6.9300000000000015" x="336.15147512109206" y="40.00000000000006" width="9.863496257155418" height="22.192866578599705" fill="#ef444466" stroke="#ef4444" stroke-width="0.020276785714285723" /> + + + + + + + + + + + + + + + +available orientations: y+" data-x="-4.6850000000000005" data-y="4.193333333333335" x="182.4042272126817" y="174.96550711874357" width="9.863496257155418" height="22.192866578599705" fill="#ef444466" stroke="#ef4444" stroke-width="0.020276785714285723" /> + + + +available orientations: y-" data-x="-0.6375" data-y="4.980000000000002" x="382.0167327168648" y="136.16908850726549" width="9.863496257155418" height="22.192866578599705" fill="#00000066" stroke="#000000" stroke-width="0.020276785714285723" /> +available orientations: y-" data-x="-6.415" data-y="2.2433333333333345" x="97.08498458828723" y="271.13459562600906" width="9.863496257155418" height="22.192866578599705" fill="#00000066" stroke="#000000" stroke-width="0.020276785714285723" /> +x-" data-x="-1.4" data-y="0.42500000000000004" cx="207.72778152529193" cy="255.3187797395407" r="3" fill="hsl(88, 100%, 50%, 0.8)" /> +x-" data-x="-1.4" data-y="-0.42500000000000004" cx="207.72778152529193" cy="333.9164026436013" r="3" fill="hsl(84, 100%, 50%, 0.8)" /> +x+" data-x="1.4" data-y="0.5" cx="466.63759815043284" cy="248.383695365653" r="3" fill="hsl(81, 100%, 50%, 0.8)" /> +x+" data-x="1.4" data-y="0.30000000000000004" cx="466.63759815043284" cy="266.8772536960202" r="3" fill="hsl(86, 100%, 50%, 0.8)" /> +x+" data-x="1.4" data-y="0.10000000000000009" cx="466.63759815043284" cy="285.3708120263874" r="3" fill="hsl(85, 100%, 50%, 0.8)" /> +x+" data-x="1.4" data-y="-0.09999999999999998" cx="466.63759815043284" cy="303.86437035675465" r="3" fill="hsl(82, 100%, 50%, 0.8)" /> +x+" data-x="1.4" data-y="-0.3" cx="466.63759815043284" cy="322.35792868712184" r="3" fill="hsl(83, 100%, 50%, 0.8)" /> +x+" data-x="1.4" data-y="-0.5" cx="466.63759815043284" cy="340.85148701748903" r="3" fill="hsl(87, 100%, 50%, 0.8)" /> +y+" data-x="-2.3148566499999994" data-y="0.5512093000000002" cx="123.13300742179533" cy="243.64848448261662" r="3" fill="hsl(140, 100%, 50%, 0.8)" /> +y-" data-x="-2.31430995" data-y="-0.5512093000000002" cx="123.18355956349131" cy="345.58669790052545" r="3" fill="hsl(141, 100%, 50%, 0.8)" /> +y+" data-x="1.7580660749999977" data-y="2.3025814000000002" cx="499.74717737112326" cy="81.7029740349781" r="3" fill="hsl(125, 100%, 50%, 0.8)" /> +y-" data-x="1.757519574999999" data-y="1.2" cx="499.69664372298564" cy="183.6562412093678" r="3" fill="hsl(126, 100%, 50%, 0.8)" /> +y-" data-x="-1.7580660749999977" data-y="-3.3025814000000002" cx="174.61820230460145" cy="600" r="3" fill="hsl(6, 100%, 50%, 0.8)" /> +y+" data-x="-1.757519574999999" data-y="-2.2" cx="174.66873595273907" cy="498.0467328256103" r="3" fill="hsl(7, 100%, 50%, 0.8)" /> +y-" data-x="1.7580660749999977" data-y="-3.3025814000000002" cx="499.74717737112326" cy="600" r="3" fill="hsl(247, 100%, 50%, 0.8)" /> +y+" data-x="1.757519574999999" data-y="-2.2" cx="499.69664372298564" cy="498.0467328256103" r="3" fill="hsl(248, 100%, 50%, 0.8)" /> +orientation: y+" data-x="-2.3148566499999994" data-y="0.7512093000000004" cx="123.13300742179533" cy="225.1549261522494" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y+" data-x="1.5999999999999999" data-y="-0.3" cx="485.1311564808" cy="322.35792868712184" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y+" data-x="1.7580660749999977" data-y="2.3025814000000002" cx="499.74717737112326" cy="81.7029740349781" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: y-" data-x="-2.31430995" data-y="-0.7512093000000004" cx="123.18355956349131" cy="364.08025623089264" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> +orientation: x+" data-x="1.757519574999999" data-y="0.85" cx="499.69664372298564" cy="216.0199682875104" r="3" fill="hsl(40, 100%, 50%, 0.9)" /> - + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + +available orientations: y+" data-x="1.5999999999999999" data-y="-0.07499999999999998" x="475.88437731561635" y="280.74742244379564" width="18.49355833036725" height="41.6105062433262" fill="#ef444466" stroke="#ef4444" stroke-width="0.010814576428571431" /> +available orientations: y+" data-x="1.7580660749999977" data-y="2.5285814" x="490.50039820593963" y="40.00000000000006" width="18.49355833036725" height="41.61050624332623" fill="#ef444466" stroke="#ef4444" stroke-width="0.010814576428571431" /> +available orientations: y-" data-x="-2.31430995" data-y="-0.9762093000000004" x="113.93678039830769" y="364.08025623089264" width="18.49355833036722" height="41.6105062433262" fill="#00000066" stroke="#000000" stroke-width="0.010814576428571431" /> +available orientations: any" data-x="1.982519574999999" data-y="0.85" x="499.69664372298564" y="206.7731891223268" width="41.61050624332631" height="18.493558330367193" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010814576428571431" /> +available orientations: any" data-x="1.982519574999999" data-y="-2" x="499.69664372298564" y="470.30639533005944" width="41.61050624332631" height="18.49355833036725" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010814576428571431" /> - + - + - + - + @@ -129,19 +129,23 @@ orientation: y+" data-x="3.3884680250000008" data-y="1.2997267500000007" cx="438 +globalConnNetId: connectivity_net3 +available orientations: any" data-x="2.2284928" data-y="-0.46751595000000035" x="300.34928" y="375.9895275000001" width="45" height="19.999999999999943" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.01" /> +globalConnNetId: connectivity_net0 +available orientations: any" data-x="1.6252733499999996" data-y="-0.30120930000000024" x="240.027335" y="359.3588625000001" width="45" height="20" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.01" /> +globalConnNetId: connectivity_net1 +available orientations: any" data-x="0.7999316625000001" data-y="0.42500000000000004" x="169.99316625000006" y="274.23793250000006" width="20" height="45" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.01" /> +globalConnNetId: connectivity_net2 +available orientations: any" data-x="3.3884680250000008" data-y="1.5247267500000008" x="428.8468025000001" y="164.26525749999996" width="20" height="45.00000000000003" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.01" />