Skip to content

Commit 8a6ec43

Browse files
Mark Fully Covered Trace Segments With Copper Pour Metadata (#2050)
* Mark Fully Covered Trace Segments With Copper Pour Metadata * update ss
1 parent 4e2ca5f commit 8a6ec43

File tree

6 files changed

+264
-3
lines changed

6 files changed

+264
-3
lines changed

lib/components/primitive-components/CopperPour/CopperPour.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { createNetsFromProps } from "lib/utils/components/createNetsFromProps"
88
import type { Net } from "../Net"
99
import type { PcbCopperPour, SourceNet } from "circuit-json"
1010
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
11+
import { markTraceSegmentsInsideCopperPour } from "./utils/mark-trace-segments-inside-copper-pour"
1112

1213
export type { CopperPourProps }
1314

@@ -80,14 +81,19 @@ export class CopperPour extends PrimitiveComponent<typeof copperPourProps> {
8081
const coveredWithSolderMask = props.coveredWithSolderMask ?? false
8182

8283
for (const brep_shape of brep_shapes) {
83-
db.pcb_copper_pour.insert({
84+
const insertedPour = db.pcb_copper_pour.insert({
8485
shape: "brep",
8586
layer: props.layer,
8687
brep_shape,
8788
source_net_id: net.source_net_id,
8889
subcircuit_id: subcircuit?.subcircuit_id ?? undefined,
8990
covered_with_solder_mask: coveredWithSolderMask,
9091
} as PcbCopperPour)
92+
93+
markTraceSegmentsInsideCopperPour({
94+
db,
95+
copperPour: insertedPour,
96+
})
9197
}
9298
})
9399
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util"
2+
import type {
3+
PcbCopperPour,
4+
PcbCopperPourBRep,
5+
PcbCopperPourRect,
6+
PcbTrace,
7+
SourceTrace,
8+
} from "circuit-json"
9+
10+
const EPSILON = 1e-9
11+
12+
type Point = { x: number; y: number }
13+
14+
const isWireRoutePoint = (
15+
routePoint: PcbTrace["route"][number],
16+
): routePoint is Extract<PcbTrace["route"][number], { route_type: "wire" }> =>
17+
routePoint.route_type === "wire"
18+
19+
const isPointOnSegment = (p: Point, a: Point, b: Point): boolean => {
20+
const cross = (p.y - a.y) * (b.x - a.x) - (p.x - a.x) * (b.y - a.y)
21+
if (Math.abs(cross) > EPSILON) return false
22+
23+
const dot = (p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)
24+
if (dot < -EPSILON) return false
25+
26+
const squaredLength = (b.x - a.x) ** 2 + (b.y - a.y) ** 2
27+
if (dot - squaredLength > EPSILON) return false
28+
29+
return true
30+
}
31+
32+
const isPointInRing = (point: Point, ring: Point[]): boolean => {
33+
if (ring.length < 3) return false
34+
35+
let inside = false
36+
let previous = ring[ring.length - 1]!
37+
for (const current of ring) {
38+
if (isPointOnSegment(point, previous, current)) return true
39+
40+
const intersects =
41+
current.y > point.y !== previous.y > point.y &&
42+
point.x <
43+
((previous.x - current.x) * (point.y - current.y)) /
44+
(previous.y - current.y) +
45+
current.x
46+
47+
if (intersects) inside = !inside
48+
previous = current
49+
}
50+
return inside
51+
}
52+
53+
const isPointInRectPour = (p: Point, pour: PcbCopperPourRect): boolean => {
54+
const { center, width, height } = pour
55+
const rotationRad = ((pour.rotation ?? 0) * Math.PI) / 180
56+
const cosR = Math.cos(-rotationRad)
57+
const sinR = Math.sin(-rotationRad)
58+
59+
const dx = p.x - center.x
60+
const dy = p.y - center.y
61+
const localX = dx * cosR - dy * sinR
62+
const localY = dx * sinR + dy * cosR
63+
64+
return (
65+
Math.abs(localX) <= width / 2 + EPSILON &&
66+
Math.abs(localY) <= height / 2 + EPSILON
67+
)
68+
}
69+
70+
const isPointInBrepPour = (p: Point, pour: PcbCopperPourBRep): boolean => {
71+
const outerRing = pour.brep_shape.outer_ring.vertices.map((v) => ({
72+
x: v.x,
73+
y: v.y,
74+
}))
75+
if (!isPointInRing(p, outerRing)) return false
76+
77+
for (const innerRing of pour.brep_shape.inner_rings) {
78+
const points = innerRing.vertices.map((v) => ({ x: v.x, y: v.y }))
79+
if (isPointInRing(p, points)) return false
80+
}
81+
82+
return true
83+
}
84+
85+
const isPointInCopperPour = (point: Point, pour: PcbCopperPour): boolean => {
86+
if (pour.shape === "rect") {
87+
return isPointInRectPour(point, pour)
88+
}
89+
if (pour.shape === "brep") {
90+
return isPointInBrepPour(point, pour)
91+
}
92+
return false
93+
}
94+
95+
const isTraceConnectedToSourceNet = (
96+
trace: PcbTrace,
97+
sourceNetId: string,
98+
sourceTraceById: Map<string, SourceTrace>,
99+
): boolean => {
100+
if (trace.source_trace_id === sourceNetId) return true
101+
if (!trace.source_trace_id) return false
102+
103+
const sourceTrace = sourceTraceById.get(trace.source_trace_id)
104+
if (!sourceTrace) return false
105+
106+
return sourceTrace.connected_source_net_ids.includes(sourceNetId)
107+
}
108+
109+
const isSegmentFullyInsideCopperPour = (
110+
start: Point,
111+
end: Point,
112+
pour: PcbCopperPour,
113+
): boolean => {
114+
const dx = end.x - start.x
115+
const dy = end.y - start.y
116+
const length = Math.hypot(dx, dy)
117+
if (length <= EPSILON) return false
118+
119+
const samples = [0, 0.25, 0.5, 0.75, 1]
120+
return samples.every((t) =>
121+
isPointInCopperPour(
122+
{
123+
x: start.x + dx * t,
124+
y: start.y + dy * t,
125+
},
126+
pour,
127+
),
128+
)
129+
}
130+
131+
export const markTraceSegmentsInsideCopperPour = ({
132+
db,
133+
copperPour,
134+
}: {
135+
db: CircuitJsonUtilObjects
136+
copperPour: PcbCopperPour
137+
}): void => {
138+
if (!copperPour.source_net_id) return
139+
140+
const sourceTraceById = new Map(
141+
db.source_trace
142+
.list()
143+
.map((sourceTrace) => [sourceTrace.source_trace_id, sourceTrace]),
144+
)
145+
146+
for (const trace of db.pcb_trace.list()) {
147+
if (
148+
!isTraceConnectedToSourceNet(
149+
trace,
150+
copperPour.source_net_id,
151+
sourceTraceById,
152+
)
153+
) {
154+
continue
155+
}
156+
157+
let routeChanged = false
158+
const nextRoute = trace.route.map((routePoint) => ({ ...routePoint }))
159+
160+
for (let i = 0; i < nextRoute.length - 1; i++) {
161+
const fromRoutePoint = nextRoute[i]
162+
const toRoutePoint = nextRoute[i + 1]
163+
if (!fromRoutePoint || !toRoutePoint) continue
164+
if (!isWireRoutePoint(fromRoutePoint) || !isWireRoutePoint(toRoutePoint))
165+
continue
166+
if (
167+
fromRoutePoint.layer !== copperPour.layer ||
168+
toRoutePoint.layer !== copperPour.layer
169+
)
170+
continue
171+
172+
if (
173+
isSegmentFullyInsideCopperPour(
174+
{ x: fromRoutePoint.x, y: fromRoutePoint.y },
175+
{ x: toRoutePoint.x, y: toRoutePoint.y },
176+
copperPour,
177+
)
178+
) {
179+
fromRoutePoint.is_inside_copper_pour = true
180+
fromRoutePoint.copper_pour_id = copperPour.pcb_copper_pour_id
181+
toRoutePoint.is_inside_copper_pour = true
182+
toRoutePoint.copper_pour_id = copperPour.pcb_copper_pour_id
183+
routeChanged = true
184+
}
185+
}
186+
187+
if (routeChanged) {
188+
db.pcb_trace.update(trace.pcb_trace_id, { route: nextRoute })
189+
}
190+
}
191+
}

tests/components/primitive-components/__snapshots__/copperpour-multiple-outlines-pcb.snap.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)