Skip to content

Commit 0b37bd1

Browse files
authored
Fix edge-crossing PCB holes in board mesh rendering (#158)
* Fix edge-crossing PCB holes in board mesh rendering * patch * patch
1 parent 9d534a6 commit 0b37bd1

7 files changed

Lines changed: 1741 additions & 5 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as geom3 from "@jscad/modeling/src/geometries/geom3"
2+
import measureBoundingBox from "@jscad/modeling/src/measurements/measureBoundingBox"
3+
import { subtract } from "@jscad/modeling/src/operations/booleans"
4+
import { rotateX } from "@jscad/modeling/src/operations/transforms"
5+
import type { PcbBoard, PcbHole, PcbPanel, PcbPlatedHole } from "circuit-json"
6+
import type { STLMesh } from "../types"
7+
import { batchedUnion } from "./batched-union"
8+
import type { BoardCutout } from "./pcb-board-cutouts"
9+
import { createCutoutGeoms } from "./pcb-board-cutouts"
10+
import {
11+
createBoardOutlineGeom,
12+
createBoundingBox,
13+
createHoleGeoms,
14+
geom3ToTriangles,
15+
} from "./pcb-board-geometry"
16+
17+
export const cutBoardMeshOutsideBoardBoundary = ({
18+
board,
19+
center,
20+
thickness,
21+
holes,
22+
platedHoles,
23+
cutouts,
24+
segments,
25+
}: {
26+
board: PcbPanel | PcbBoard
27+
center: { x: number; y: number }
28+
thickness: number
29+
holes: PcbHole[]
30+
platedHoles: PcbPlatedHole[]
31+
cutouts: BoardCutout[]
32+
segments: number
33+
}): STLMesh => {
34+
let boardGeom = createBoardOutlineGeom(board, center, thickness)
35+
const subtractGeoms = [
36+
...createHoleGeoms(center, thickness, holes, platedHoles, segments),
37+
...createCutoutGeoms(center, thickness, cutouts, segments),
38+
]
39+
40+
if (subtractGeoms.length > 0) {
41+
boardGeom = subtract(boardGeom, batchedUnion(subtractGeoms))
42+
}
43+
44+
boardGeom = rotateX(-Math.PI / 2, boardGeom)
45+
46+
const polygons = geom3.toPolygons(boardGeom)
47+
return {
48+
triangles: geom3ToTriangles(boardGeom, polygons),
49+
boundingBox: createBoundingBox(measureBoundingBox(boardGeom)),
50+
}
51+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { Vec2Point } from "./geometry-loops"
2+
3+
const LOOP_CONTAINMENT_EPSILON = 1e-8
4+
5+
export const hasBoundaryCrossingLoops = (
6+
outerLoop: Vec2Point[],
7+
innerLoops: Vec2Point[][],
8+
): boolean => {
9+
return innerLoops.some((loop) =>
10+
loop.some((point) => !isPointStrictlyInsideLoop(point, outerLoop)),
11+
)
12+
}
13+
14+
const isPointStrictlyInsideLoop = (
15+
point: Vec2Point,
16+
loop: Vec2Point[],
17+
): boolean => {
18+
let inside = false
19+
20+
for (let i = 0, j = loop.length - 1; i < loop.length; j = i++) {
21+
const a = loop[i]!
22+
const b = loop[j]!
23+
24+
if (isPointOnSegment(point, a, b)) {
25+
return false
26+
}
27+
28+
const intersects =
29+
a.y > point.y !== b.y > point.y &&
30+
point.x < ((b.x - a.x) * (point.y - a.y)) / (b.y - a.y) + a.x
31+
32+
if (intersects) {
33+
inside = !inside
34+
}
35+
}
36+
37+
return inside
38+
}
39+
40+
const isPointOnSegment = (
41+
point: Vec2Point,
42+
a: Vec2Point,
43+
b: Vec2Point,
44+
): boolean => {
45+
const cross = (point.y - a.y) * (b.x - a.x) - (point.x - a.x) * (b.y - a.y)
46+
if (Math.abs(cross) > LOOP_CONTAINMENT_EPSILON) return false
47+
48+
const dot = (point.x - a.x) * (b.x - a.x) + (point.y - a.y) * (b.y - a.y)
49+
if (dot < -LOOP_CONTAINMENT_EPSILON) return false
50+
51+
const squaredLength = (b.x - a.x) ** 2 + (b.y - a.y) ** 2
52+
return dot <= squaredLength + LOOP_CONTAINMENT_EPSILON
53+
}

lib/utils/pcb-board-geometry.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ import * as geom3 from "@jscad/modeling/src/geometries/geom3"
22
import type { Geom3 } from "@jscad/modeling/src/geometries/types"
33
import type { Vec2 } from "@jscad/modeling/src/maths/types"
44
import { extrudeLinear } from "@jscad/modeling/src/operations/extrusions"
5-
import {
6-
rotateX,
7-
rotateZ,
8-
translate,
9-
} from "@jscad/modeling/src/operations/transforms"
5+
import { rotateZ, translate } from "@jscad/modeling/src/operations/transforms"
106
import {
117
polygon,
128
rectangle,
@@ -20,6 +16,8 @@ import type {
2016
Point,
2117
} from "circuit-json"
2218
import type { BoundingBox, STLMesh, Triangle } from "../types"
19+
import { cutBoardMeshOutsideBoardBoundary } from "./cut-board-mesh-outside-board-boundary"
20+
import { hasBoundaryCrossingLoops } from "./has-boundary-crossing-loops"
2321
import { normalizeLoop, signedArea, type Vec2Point } from "./geometry-loops"
2422
import type { BoardCutout } from "./pcb-board-cutouts"
2523
import {
@@ -329,6 +327,18 @@ export const createBoardMesh = (
329327
}),
330328
]
331329

330+
if (hasBoundaryCrossingLoops(outerLoop, holeLoops)) {
331+
return cutBoardMeshOutsideBoardBoundary({
332+
board,
333+
center,
334+
thickness,
335+
holes,
336+
platedHoles,
337+
cutouts,
338+
segments,
339+
})
340+
}
341+
332342
return buildBoardMeshFromLoops({ outerLoop, holeLoops, thickness })
333343
}
334344

0 commit comments

Comments
 (0)