circuit-json-to-step 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +16 -0
- package/CLAUDE.md +10 -0
- package/LICENSE +21 -0
- package/README.md +6 -0
- package/biome.json +100 -0
- package/bunfig.toml +5 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +705 -0
- package/lib/index.ts +545 -0
- package/lib/mesh-generation.ts +333 -0
- package/package.json +29 -0
- package/test/basics/basics01/basics01.json +40 -0
- package/test/basics/basics01/basics01.test.ts +54 -0
- package/test/basics/basics02/basics02.json +19 -0
- package/test/basics/basics02/basics02.test.ts +38 -0
- package/test/basics/basics03/basics03.json +49 -0
- package/test/basics/basics03/basics03.test.ts +38 -0
- package/test/basics/basics04/basics04.json +52 -0
- package/test/basics/basics04/basics04.test.ts +49 -0
- package/test/repros/repro01/repro01.json +2917 -0
- package/test/repros/repro01/repro01.test.ts +48 -0
- package/test/utils/occt/importer.ts +119 -0
- package/tsconfig.json +30 -0
package/lib/index.ts
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import type { CircuitJson } from "circuit-json"
|
|
2
|
+
import {
|
|
3
|
+
Repository,
|
|
4
|
+
ApplicationContext,
|
|
5
|
+
ApplicationProtocolDefinition,
|
|
6
|
+
ProductContext,
|
|
7
|
+
Product,
|
|
8
|
+
ProductDefinitionContext,
|
|
9
|
+
ProductDefinitionFormation,
|
|
10
|
+
ProductDefinition,
|
|
11
|
+
ProductDefinitionShape,
|
|
12
|
+
Unknown,
|
|
13
|
+
CartesianPoint,
|
|
14
|
+
Direction,
|
|
15
|
+
Axis2Placement3D,
|
|
16
|
+
Plane,
|
|
17
|
+
CylindricalSurface,
|
|
18
|
+
VertexPoint,
|
|
19
|
+
EdgeCurve,
|
|
20
|
+
Line,
|
|
21
|
+
Vector,
|
|
22
|
+
EdgeLoop,
|
|
23
|
+
OrientedEdge,
|
|
24
|
+
FaceOuterBound,
|
|
25
|
+
FaceBound,
|
|
26
|
+
AdvancedFace,
|
|
27
|
+
Circle,
|
|
28
|
+
ClosedShell,
|
|
29
|
+
ManifoldSolidBrep,
|
|
30
|
+
ColourRgb,
|
|
31
|
+
FillAreaStyleColour,
|
|
32
|
+
FillAreaStyle,
|
|
33
|
+
SurfaceStyleFillArea,
|
|
34
|
+
SurfaceSideStyle,
|
|
35
|
+
SurfaceStyleUsage,
|
|
36
|
+
PresentationStyleAssignment,
|
|
37
|
+
StyledItem,
|
|
38
|
+
MechanicalDesignGeometricPresentationRepresentation,
|
|
39
|
+
AdvancedBrepShapeRepresentation,
|
|
40
|
+
ShapeDefinitionRepresentation,
|
|
41
|
+
type Ref,
|
|
42
|
+
} from "stepts"
|
|
43
|
+
import { generateComponentMeshes } from "./mesh-generation"
|
|
44
|
+
|
|
45
|
+
export interface CircuitJsonToStepOptions {
|
|
46
|
+
/** Board width in mm (optional if pcb_board is present) */
|
|
47
|
+
boardWidth?: number
|
|
48
|
+
/** Board height in mm (optional if pcb_board is present) */
|
|
49
|
+
boardHeight?: number
|
|
50
|
+
/** Board thickness in mm (default: 1.6mm or from pcb_board) */
|
|
51
|
+
boardThickness?: number
|
|
52
|
+
/** Product name (default: "PCB") */
|
|
53
|
+
productName?: string
|
|
54
|
+
/** Include component meshes (default: false) */
|
|
55
|
+
includeComponents?: boolean
|
|
56
|
+
/** Include external model meshes from model_*_url fields (default: false). Only applicable when includeComponents is true. */
|
|
57
|
+
includeExternalMeshes?: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Converts circuit JSON to STEP format, creating holes in a PCB board
|
|
62
|
+
*/
|
|
63
|
+
export async function circuitJsonToStep(
|
|
64
|
+
circuitJson: CircuitJson,
|
|
65
|
+
options: CircuitJsonToStepOptions = {},
|
|
66
|
+
): Promise<string> {
|
|
67
|
+
const repo = new Repository()
|
|
68
|
+
|
|
69
|
+
// Extract pcb_board and holes from circuit JSON
|
|
70
|
+
const pcbBoard = circuitJson.find((item) => item.type === "pcb_board")
|
|
71
|
+
const holes: any[] = circuitJson.filter(
|
|
72
|
+
(item) => item.type === "pcb_hole" || item.type === "pcb_plated_hole",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
// Get dimensions from pcb_board or options
|
|
76
|
+
const boardWidth = options.boardWidth ?? pcbBoard?.width
|
|
77
|
+
const boardHeight = options.boardHeight ?? pcbBoard?.height
|
|
78
|
+
const boardThickness = options.boardThickness ?? pcbBoard?.thickness ?? 1.6
|
|
79
|
+
const productName = options.productName ?? "PCB"
|
|
80
|
+
|
|
81
|
+
// Get board center position (defaults to 0, 0 if not specified)
|
|
82
|
+
const boardCenterX = pcbBoard?.center?.x ?? 0
|
|
83
|
+
const boardCenterY = pcbBoard?.center?.y ?? 0
|
|
84
|
+
|
|
85
|
+
if (!boardWidth || !boardHeight) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Board dimensions not found. Either provide boardWidth and boardHeight in options, or include a pcb_board in the circuit JSON with width and height properties.",
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Product structure (required for STEP validation)
|
|
92
|
+
const appContext = repo.add(
|
|
93
|
+
new ApplicationContext(
|
|
94
|
+
"core data for automotive mechanical design processes",
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
repo.add(
|
|
98
|
+
new ApplicationProtocolDefinition(
|
|
99
|
+
"international standard",
|
|
100
|
+
"automotive_design",
|
|
101
|
+
2010,
|
|
102
|
+
appContext,
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
const productContext = repo.add(
|
|
106
|
+
new ProductContext("", appContext, "mechanical"),
|
|
107
|
+
)
|
|
108
|
+
const product = repo.add(
|
|
109
|
+
new Product(productName, productName, "", [productContext]),
|
|
110
|
+
)
|
|
111
|
+
const productDefContext = repo.add(
|
|
112
|
+
new ProductDefinitionContext("part definition", appContext, "design"),
|
|
113
|
+
)
|
|
114
|
+
const productDefFormation = repo.add(
|
|
115
|
+
new ProductDefinitionFormation("", "", product),
|
|
116
|
+
)
|
|
117
|
+
const productDef = repo.add(
|
|
118
|
+
new ProductDefinition("", "", productDefFormation, productDefContext),
|
|
119
|
+
)
|
|
120
|
+
const productDefShape = repo.add(
|
|
121
|
+
new ProductDefinitionShape("", "", productDef),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
// Representation context
|
|
125
|
+
const lengthUnit = repo.add(
|
|
126
|
+
new Unknown("", [
|
|
127
|
+
"( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) )",
|
|
128
|
+
]),
|
|
129
|
+
)
|
|
130
|
+
const angleUnit = repo.add(
|
|
131
|
+
new Unknown("", [
|
|
132
|
+
"( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) )",
|
|
133
|
+
]),
|
|
134
|
+
)
|
|
135
|
+
const solidAngleUnit = repo.add(
|
|
136
|
+
new Unknown("", [
|
|
137
|
+
"( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() )",
|
|
138
|
+
]),
|
|
139
|
+
)
|
|
140
|
+
const uncertainty = repo.add(
|
|
141
|
+
new Unknown("UNCERTAINTY_MEASURE_WITH_UNIT", [
|
|
142
|
+
`LENGTH_MEASURE(1.E-07)`,
|
|
143
|
+
`${lengthUnit}`,
|
|
144
|
+
`'distance_accuracy_value'`,
|
|
145
|
+
`'Maximum Tolerance'`,
|
|
146
|
+
]),
|
|
147
|
+
)
|
|
148
|
+
const geomContext = repo.add(
|
|
149
|
+
new Unknown("", [
|
|
150
|
+
`( GEOMETRIC_REPRESENTATION_CONTEXT(3) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((${uncertainty})) GLOBAL_UNIT_ASSIGNED_CONTEXT((${lengthUnit},${angleUnit},${solidAngleUnit})) REPRESENTATION_CONTEXT('${productName}','3D') )`,
|
|
151
|
+
]),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
// Create board vertices based on outline or rectangular shape
|
|
155
|
+
const outline = pcbBoard?.outline
|
|
156
|
+
let bottomVertices: Ref<VertexPoint>[]
|
|
157
|
+
let topVertices: Ref<VertexPoint>[]
|
|
158
|
+
|
|
159
|
+
if (outline && Array.isArray(outline) && outline.length >= 3) {
|
|
160
|
+
// Use custom outline (points are already relative to board center)
|
|
161
|
+
bottomVertices = outline.map((point) =>
|
|
162
|
+
repo.add(
|
|
163
|
+
new VertexPoint(
|
|
164
|
+
"",
|
|
165
|
+
repo.add(new CartesianPoint("", point.x, point.y, 0)),
|
|
166
|
+
),
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
topVertices = outline.map((point) =>
|
|
170
|
+
repo.add(
|
|
171
|
+
new VertexPoint(
|
|
172
|
+
"",
|
|
173
|
+
repo.add(new CartesianPoint("", point.x, point.y, boardThickness)),
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
} else {
|
|
178
|
+
// Fall back to rectangular shape centered at (boardCenterX, boardCenterY)
|
|
179
|
+
const halfWidth = boardWidth / 2
|
|
180
|
+
const halfHeight = boardHeight / 2
|
|
181
|
+
const corners = [
|
|
182
|
+
[boardCenterX - halfWidth, boardCenterY - halfHeight, 0],
|
|
183
|
+
[boardCenterX + halfWidth, boardCenterY - halfHeight, 0],
|
|
184
|
+
[boardCenterX + halfWidth, boardCenterY + halfHeight, 0],
|
|
185
|
+
[boardCenterX - halfWidth, boardCenterY + halfHeight, 0],
|
|
186
|
+
[boardCenterX - halfWidth, boardCenterY - halfHeight, boardThickness],
|
|
187
|
+
[boardCenterX + halfWidth, boardCenterY - halfHeight, boardThickness],
|
|
188
|
+
[boardCenterX + halfWidth, boardCenterY + halfHeight, boardThickness],
|
|
189
|
+
[boardCenterX - halfWidth, boardCenterY + halfHeight, boardThickness],
|
|
190
|
+
]
|
|
191
|
+
const vertices = corners.map(([x, y, z]) =>
|
|
192
|
+
repo.add(
|
|
193
|
+
new VertexPoint("", repo.add(new CartesianPoint("", x!, y!, z!))),
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
bottomVertices = [vertices[0]!, vertices[1]!, vertices[2]!, vertices[3]!]
|
|
197
|
+
topVertices = [vertices[4]!, vertices[5]!, vertices[6]!, vertices[7]!]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Helper to create edge between vertices
|
|
201
|
+
function createEdge(
|
|
202
|
+
v1: Ref<VertexPoint>,
|
|
203
|
+
v2: Ref<VertexPoint>,
|
|
204
|
+
): Ref<EdgeCurve> {
|
|
205
|
+
const p1 = v1.resolve(repo).pnt.resolve(repo)
|
|
206
|
+
const p2 = v2.resolve(repo).pnt.resolve(repo)
|
|
207
|
+
const dir = repo.add(
|
|
208
|
+
new Direction("", p2.x - p1.x, p2.y - p1.y, p2.z - p1.z),
|
|
209
|
+
)
|
|
210
|
+
const vec = repo.add(new Vector("", dir, 1))
|
|
211
|
+
const line = repo.add(new Line("", v1.resolve(repo).pnt, vec))
|
|
212
|
+
return repo.add(new EdgeCurve("", v1, v2, line, true))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Create board edges
|
|
216
|
+
const bottomEdges: Ref<EdgeCurve>[] = []
|
|
217
|
+
const topEdges: Ref<EdgeCurve>[] = []
|
|
218
|
+
const verticalEdges: Ref<EdgeCurve>[] = []
|
|
219
|
+
|
|
220
|
+
// Bottom edges (connect vertices in a loop)
|
|
221
|
+
for (let i = 0; i < bottomVertices.length; i++) {
|
|
222
|
+
const v1 = bottomVertices[i]!
|
|
223
|
+
const v2 = bottomVertices[(i + 1) % bottomVertices.length]!
|
|
224
|
+
bottomEdges.push(createEdge(v1, v2))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Top edges (connect vertices in a loop)
|
|
228
|
+
for (let i = 0; i < topVertices.length; i++) {
|
|
229
|
+
const v1 = topVertices[i]!
|
|
230
|
+
const v2 = topVertices[(i + 1) % topVertices.length]!
|
|
231
|
+
topEdges.push(createEdge(v1, v2))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Vertical edges (connect bottom to top)
|
|
235
|
+
for (let i = 0; i < bottomVertices.length; i++) {
|
|
236
|
+
verticalEdges.push(createEdge(bottomVertices[i]!, topVertices[i]!))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const origin = repo.add(new CartesianPoint("", 0, 0, 0))
|
|
240
|
+
const xDir = repo.add(new Direction("", 1, 0, 0))
|
|
241
|
+
const zDir = repo.add(new Direction("", 0, 0, 1))
|
|
242
|
+
|
|
243
|
+
// Bottom face (z=0, normal pointing down)
|
|
244
|
+
const bottomFrame = repo.add(
|
|
245
|
+
new Axis2Placement3D(
|
|
246
|
+
"",
|
|
247
|
+
origin,
|
|
248
|
+
repo.add(new Direction("", 0, 0, -1)),
|
|
249
|
+
xDir,
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
const bottomPlane = repo.add(new Plane("", bottomFrame))
|
|
253
|
+
const bottomLoop = repo.add(
|
|
254
|
+
new EdgeLoop(
|
|
255
|
+
"",
|
|
256
|
+
bottomEdges.map((edge) => repo.add(new OrientedEdge("", edge, true))),
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
// Create holes in bottom face
|
|
261
|
+
const bottomHoleLoops: Ref<FaceBound>[] = []
|
|
262
|
+
for (const hole of holes) {
|
|
263
|
+
// Check shape (pcb_hole uses hole_shape, pcb_plated_hole uses shape)
|
|
264
|
+
const holeShape = hole.hole_shape || hole.shape
|
|
265
|
+
if (holeShape === "circle") {
|
|
266
|
+
const holeX = typeof hole.x === "number" ? hole.x : (hole.x as any).value
|
|
267
|
+
const holeY = typeof hole.y === "number" ? hole.y : (hole.y as any).value
|
|
268
|
+
const radius = hole.hole_diameter / 2
|
|
269
|
+
|
|
270
|
+
const holeCenter = repo.add(new CartesianPoint("", holeX, holeY, 0))
|
|
271
|
+
const holeVertex = repo.add(
|
|
272
|
+
new VertexPoint(
|
|
273
|
+
"",
|
|
274
|
+
repo.add(new CartesianPoint("", holeX + radius, holeY, 0)),
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
const holePlacement = repo.add(
|
|
278
|
+
new Axis2Placement3D(
|
|
279
|
+
"",
|
|
280
|
+
holeCenter,
|
|
281
|
+
repo.add(new Direction("", 0, 0, -1)),
|
|
282
|
+
xDir,
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
const holeCircle = repo.add(new Circle("", holePlacement, radius))
|
|
286
|
+
const holeEdge = repo.add(
|
|
287
|
+
new EdgeCurve("", holeVertex, holeVertex, holeCircle, true),
|
|
288
|
+
)
|
|
289
|
+
const holeLoop = repo.add(
|
|
290
|
+
new EdgeLoop("", [repo.add(new OrientedEdge("", holeEdge, false))]),
|
|
291
|
+
)
|
|
292
|
+
bottomHoleLoops.push(repo.add(new FaceBound("", holeLoop, true)))
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const bottomFace = repo.add(
|
|
297
|
+
new AdvancedFace(
|
|
298
|
+
"",
|
|
299
|
+
[
|
|
300
|
+
repo.add(new FaceOuterBound("", bottomLoop, true)),
|
|
301
|
+
...bottomHoleLoops,
|
|
302
|
+
] as any,
|
|
303
|
+
bottomPlane,
|
|
304
|
+
true,
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
// Top face (z=boardThickness, normal pointing up)
|
|
309
|
+
const topOrigin = repo.add(new CartesianPoint("", 0, 0, boardThickness))
|
|
310
|
+
const topFrame = repo.add(new Axis2Placement3D("", topOrigin, zDir, xDir))
|
|
311
|
+
const topPlane = repo.add(new Plane("", topFrame))
|
|
312
|
+
const topLoop = repo.add(
|
|
313
|
+
new EdgeLoop(
|
|
314
|
+
"",
|
|
315
|
+
topEdges.map((edge) => repo.add(new OrientedEdge("", edge, false))),
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
// Create holes in top face
|
|
320
|
+
const topHoleLoops: Ref<FaceBound>[] = []
|
|
321
|
+
for (const hole of holes) {
|
|
322
|
+
// Check shape (pcb_hole uses hole_shape, pcb_plated_hole uses shape)
|
|
323
|
+
const holeShape = hole.hole_shape || hole.shape
|
|
324
|
+
if (holeShape === "circle") {
|
|
325
|
+
const holeX = typeof hole.x === "number" ? hole.x : (hole.x as any).value
|
|
326
|
+
const holeY = typeof hole.y === "number" ? hole.y : (hole.y as any).value
|
|
327
|
+
const radius = hole.hole_diameter / 2
|
|
328
|
+
|
|
329
|
+
const holeCenter = repo.add(
|
|
330
|
+
new CartesianPoint("", holeX, holeY, boardThickness),
|
|
331
|
+
)
|
|
332
|
+
const holeVertex = repo.add(
|
|
333
|
+
new VertexPoint(
|
|
334
|
+
"",
|
|
335
|
+
repo.add(
|
|
336
|
+
new CartesianPoint("", holeX + radius, holeY, boardThickness),
|
|
337
|
+
),
|
|
338
|
+
),
|
|
339
|
+
)
|
|
340
|
+
const holePlacement = repo.add(
|
|
341
|
+
new Axis2Placement3D("", holeCenter, zDir, xDir),
|
|
342
|
+
)
|
|
343
|
+
const holeCircle = repo.add(new Circle("", holePlacement, radius))
|
|
344
|
+
const holeEdge = repo.add(
|
|
345
|
+
new EdgeCurve("", holeVertex, holeVertex, holeCircle, true),
|
|
346
|
+
)
|
|
347
|
+
const holeLoop = repo.add(
|
|
348
|
+
new EdgeLoop("", [repo.add(new OrientedEdge("", holeEdge, true))]),
|
|
349
|
+
)
|
|
350
|
+
topHoleLoops.push(repo.add(new FaceBound("", holeLoop, true)))
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const topFace = repo.add(
|
|
355
|
+
new AdvancedFace(
|
|
356
|
+
"",
|
|
357
|
+
[repo.add(new FaceOuterBound("", topLoop, true)), ...topHoleLoops] as any,
|
|
358
|
+
topPlane,
|
|
359
|
+
true,
|
|
360
|
+
),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
// Create side faces (one for each edge of the outline)
|
|
364
|
+
const sideFaces: Ref<AdvancedFace>[] = []
|
|
365
|
+
for (let i = 0; i < bottomEdges.length; i++) {
|
|
366
|
+
const nextI = (i + 1) % bottomEdges.length
|
|
367
|
+
|
|
368
|
+
// Get points for this side face
|
|
369
|
+
const bottomV1Pnt = bottomVertices[i]!.resolve(repo).pnt
|
|
370
|
+
const bottomV2Pnt = bottomVertices[nextI]!.resolve(repo).pnt
|
|
371
|
+
const bottomV1 = bottomV1Pnt.resolve(repo)
|
|
372
|
+
const bottomV2 = bottomV2Pnt.resolve(repo)
|
|
373
|
+
|
|
374
|
+
// Calculate edge direction and outward normal
|
|
375
|
+
const edgeDir = {
|
|
376
|
+
x: bottomV2.x - bottomV1.x,
|
|
377
|
+
y: bottomV2.y - bottomV1.y,
|
|
378
|
+
z: 0,
|
|
379
|
+
}
|
|
380
|
+
// Normal is perpendicular (rotate 90 degrees clockwise in XY plane for outward facing)
|
|
381
|
+
const normalDir = repo.add(new Direction("", edgeDir.y, -edgeDir.x, 0))
|
|
382
|
+
|
|
383
|
+
// Reference direction along the edge
|
|
384
|
+
const refDir = repo.add(new Direction("", edgeDir.x, edgeDir.y, 0))
|
|
385
|
+
|
|
386
|
+
const sideFrame = repo.add(
|
|
387
|
+
new Axis2Placement3D("", bottomV1Pnt, normalDir, refDir),
|
|
388
|
+
)
|
|
389
|
+
const sidePlane = repo.add(new Plane("", sideFrame))
|
|
390
|
+
const sideLoop = repo.add(
|
|
391
|
+
new EdgeLoop("", [
|
|
392
|
+
repo.add(new OrientedEdge("", bottomEdges[i]!, true)),
|
|
393
|
+
repo.add(new OrientedEdge("", verticalEdges[nextI]!, true)),
|
|
394
|
+
repo.add(new OrientedEdge("", topEdges[i]!, false)),
|
|
395
|
+
repo.add(new OrientedEdge("", verticalEdges[i]!, false)),
|
|
396
|
+
]),
|
|
397
|
+
)
|
|
398
|
+
const sideFace = repo.add(
|
|
399
|
+
new AdvancedFace(
|
|
400
|
+
"",
|
|
401
|
+
[repo.add(new FaceOuterBound("", sideLoop, true))],
|
|
402
|
+
sidePlane,
|
|
403
|
+
true,
|
|
404
|
+
),
|
|
405
|
+
)
|
|
406
|
+
sideFaces.push(sideFace)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Create cylindrical faces for holes
|
|
410
|
+
const holeCylindricalFaces: Ref<AdvancedFace>[] = []
|
|
411
|
+
for (const hole of holes) {
|
|
412
|
+
const holeShape = hole.hole_shape || hole.shape
|
|
413
|
+
if (holeShape === "circle") {
|
|
414
|
+
const holeX = typeof hole.x === "number" ? hole.x : (hole.x as any).value
|
|
415
|
+
const holeY = typeof hole.y === "number" ? hole.y : (hole.y as any).value
|
|
416
|
+
const radius = hole.hole_diameter / 2
|
|
417
|
+
|
|
418
|
+
// Create circular edges at bottom and top
|
|
419
|
+
const bottomHoleCenter = repo.add(new CartesianPoint("", holeX, holeY, 0))
|
|
420
|
+
const bottomHoleVertex = repo.add(
|
|
421
|
+
new VertexPoint(
|
|
422
|
+
"",
|
|
423
|
+
repo.add(new CartesianPoint("", holeX + radius, holeY, 0)),
|
|
424
|
+
),
|
|
425
|
+
)
|
|
426
|
+
const bottomHolePlacement = repo.add(
|
|
427
|
+
new Axis2Placement3D(
|
|
428
|
+
"",
|
|
429
|
+
bottomHoleCenter,
|
|
430
|
+
repo.add(new Direction("", 0, 0, -1)),
|
|
431
|
+
xDir,
|
|
432
|
+
),
|
|
433
|
+
)
|
|
434
|
+
const bottomHoleCircle = repo.add(
|
|
435
|
+
new Circle("", bottomHolePlacement, radius),
|
|
436
|
+
)
|
|
437
|
+
const bottomHoleEdge = repo.add(
|
|
438
|
+
new EdgeCurve(
|
|
439
|
+
"",
|
|
440
|
+
bottomHoleVertex,
|
|
441
|
+
bottomHoleVertex,
|
|
442
|
+
bottomHoleCircle,
|
|
443
|
+
true,
|
|
444
|
+
),
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
const topHoleCenter = repo.add(
|
|
448
|
+
new CartesianPoint("", holeX, holeY, boardThickness),
|
|
449
|
+
)
|
|
450
|
+
const topHoleVertex = repo.add(
|
|
451
|
+
new VertexPoint(
|
|
452
|
+
"",
|
|
453
|
+
repo.add(
|
|
454
|
+
new CartesianPoint("", holeX + radius, holeY, boardThickness),
|
|
455
|
+
),
|
|
456
|
+
),
|
|
457
|
+
)
|
|
458
|
+
const topHolePlacement = repo.add(
|
|
459
|
+
new Axis2Placement3D("", topHoleCenter, zDir, xDir),
|
|
460
|
+
)
|
|
461
|
+
const topHoleCircle = repo.add(new Circle("", topHolePlacement, radius))
|
|
462
|
+
const topHoleEdge = repo.add(
|
|
463
|
+
new EdgeCurve("", topHoleVertex, topHoleVertex, topHoleCircle, true),
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
// Create edge loop for cylindrical surface
|
|
467
|
+
const holeCylinderLoop = repo.add(
|
|
468
|
+
new EdgeLoop("", [
|
|
469
|
+
repo.add(new OrientedEdge("", bottomHoleEdge, true)),
|
|
470
|
+
repo.add(new OrientedEdge("", topHoleEdge, false)),
|
|
471
|
+
]),
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
// Create cylindrical surface for the hole (axis along Z)
|
|
475
|
+
const holeCylinderPlacement = repo.add(
|
|
476
|
+
new Axis2Placement3D("", bottomHoleCenter, zDir, xDir),
|
|
477
|
+
)
|
|
478
|
+
const holeCylinderSurface = repo.add(
|
|
479
|
+
new CylindricalSurface("", holeCylinderPlacement, radius),
|
|
480
|
+
)
|
|
481
|
+
const holeCylinderFace = repo.add(
|
|
482
|
+
new AdvancedFace(
|
|
483
|
+
"",
|
|
484
|
+
[repo.add(new FaceOuterBound("", holeCylinderLoop, true))],
|
|
485
|
+
holeCylinderSurface,
|
|
486
|
+
false,
|
|
487
|
+
),
|
|
488
|
+
)
|
|
489
|
+
holeCylindricalFaces.push(holeCylinderFace)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Collect all faces
|
|
494
|
+
const allFaces = [bottomFace, topFace, ...sideFaces, ...holeCylindricalFaces]
|
|
495
|
+
|
|
496
|
+
// Create closed shell and solid
|
|
497
|
+
const shell = repo.add(new ClosedShell("", allFaces))
|
|
498
|
+
const solid = repo.add(new ManifoldSolidBrep(productName, shell))
|
|
499
|
+
|
|
500
|
+
// Array to hold all solids (board + optional components)
|
|
501
|
+
const allSolids: Ref<ManifoldSolidBrep>[] = [solid]
|
|
502
|
+
|
|
503
|
+
// Generate component mesh if requested
|
|
504
|
+
if (options.includeComponents) {
|
|
505
|
+
const componentSolids = await generateComponentMeshes({
|
|
506
|
+
repo,
|
|
507
|
+
circuitJson,
|
|
508
|
+
boardThickness,
|
|
509
|
+
includeExternalMeshes: options.includeExternalMeshes,
|
|
510
|
+
})
|
|
511
|
+
allSolids.push(...componentSolids)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Add presentation/styling for all solids
|
|
515
|
+
const styledItems: Ref<StyledItem>[] = []
|
|
516
|
+
|
|
517
|
+
for (const solidRef of allSolids) {
|
|
518
|
+
const color = repo.add(new ColourRgb("", 0.2, 0.6, 0.2))
|
|
519
|
+
const fillColor = repo.add(new FillAreaStyleColour("", color))
|
|
520
|
+
const fillStyle = repo.add(new FillAreaStyle("", [fillColor]))
|
|
521
|
+
const surfaceFill = repo.add(new SurfaceStyleFillArea(fillStyle))
|
|
522
|
+
const surfaceSide = repo.add(new SurfaceSideStyle("", [surfaceFill]))
|
|
523
|
+
const surfaceUsage = repo.add(new SurfaceStyleUsage(".BOTH.", surfaceSide))
|
|
524
|
+
const presStyle = repo.add(new PresentationStyleAssignment([surfaceUsage]))
|
|
525
|
+
const styledItem = repo.add(new StyledItem("", [presStyle], solidRef))
|
|
526
|
+
styledItems.push(styledItem)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
repo.add(
|
|
530
|
+
new MechanicalDesignGeometricPresentationRepresentation(
|
|
531
|
+
"",
|
|
532
|
+
styledItems,
|
|
533
|
+
geomContext,
|
|
534
|
+
),
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
// Shape representation with all solids
|
|
538
|
+
const shapeRep = repo.add(
|
|
539
|
+
new AdvancedBrepShapeRepresentation(productName, allSolids, geomContext),
|
|
540
|
+
)
|
|
541
|
+
repo.add(new ShapeDefinitionRepresentation(productDefShape, shapeRep))
|
|
542
|
+
|
|
543
|
+
// Generate and return STEP file text
|
|
544
|
+
return repo.toPartFile({ name: productName })
|
|
545
|
+
}
|