|
1 | | -from typing import Union, Iterable, Tuple, Dict, overload, Optional, Any, List, cast |
| 1 | +from typing import ( |
| 2 | + Union, |
| 3 | + Iterable, |
| 4 | + Iterator, |
| 5 | + Tuple, |
| 6 | + Dict, |
| 7 | + overload, |
| 8 | + Optional, |
| 9 | + Any, |
| 10 | + List, |
| 11 | + cast, |
| 12 | +) |
2 | 13 | from typing_extensions import Protocol |
3 | 14 | from math import degrees |
4 | 15 |
|
|
12 | 23 | from OCP.Quantity import Quantity_ColorRGBA |
13 | 24 | from OCP.BRepAlgoAPI import BRepAlgoAPI_Fuse |
14 | 25 | from OCP.TopTools import TopTools_ListOfShape |
15 | | -from OCP.BOPAlgo import BOPAlgo_GlueEnum |
| 26 | +from OCP.BOPAlgo import BOPAlgo_GlueEnum, BOPAlgo_MakeConnected |
16 | 27 | from OCP.TopoDS import TopoDS_Shape |
17 | 28 |
|
18 | 29 | from vtkmodules.vtkRenderingCore import ( |
|
21 | 32 | vtkRenderer, |
22 | 33 | ) |
23 | 34 |
|
| 35 | +from vtkmodules.vtkFiltersExtraction import vtkExtractCellsByType |
| 36 | +from vtkmodules.vtkCommonDataModel import VTK_TRIANGLE, VTK_LINE, VTK_VERTEX |
| 37 | + |
24 | 38 | from .geom import Location |
25 | | -from .shapes import Shape, Compound |
| 39 | +from .shapes import Shape, Solid, Compound |
26 | 40 | from .exporters.vtk import toString |
27 | 41 | from ..cq import Workplane |
28 | 42 |
|
@@ -131,6 +145,14 @@ def children(self) -> Iterable["AssemblyProtocol"]: |
131 | 145 | def traverse(self) -> Iterable[Tuple[str, "AssemblyProtocol"]]: |
132 | 146 | ... |
133 | 147 |
|
| 148 | + def __iter__( |
| 149 | + self, |
| 150 | + loc: Optional[Location] = None, |
| 151 | + name: Optional[str] = None, |
| 152 | + color: Optional[Color] = None, |
| 153 | + ) -> Iterator[Tuple[Shape, str, Location, Optional[Color]]]: |
| 154 | + ... |
| 155 | + |
134 | 156 |
|
135 | 157 | def setName(l: TDF_Label, name: str, tool): |
136 | 158 |
|
@@ -227,75 +249,93 @@ def _toCAF(el, ancestor, color) -> TDF_Label: |
227 | 249 |
|
228 | 250 | def toVTK( |
229 | 251 | assy: AssemblyProtocol, |
230 | | - renderer: vtkRenderer = vtkRenderer(), |
231 | | - loc: Location = Location(), |
232 | 252 | color: Tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0), |
233 | 253 | tolerance: float = 1e-3, |
234 | 254 | angularTolerance: float = 0.1, |
235 | 255 | ) -> vtkRenderer: |
236 | 256 |
|
237 | | - loc = loc * assy.loc |
238 | | - trans, rot = loc.toTuple() |
| 257 | + renderer = vtkRenderer() |
| 258 | + |
| 259 | + for shape, _, loc, col_ in assy: |
| 260 | + |
| 261 | + col = col_.toTuple() if col_ else color |
| 262 | + trans, rot = loc.toTuple() |
| 263 | + |
| 264 | + data = shape.toVtkPolyData(tolerance, angularTolerance) |
| 265 | + |
| 266 | + # extract faces |
| 267 | + extr = vtkExtractCellsByType() |
| 268 | + extr.SetInputDataObject(data) |
| 269 | + |
| 270 | + extr.AddCellType(VTK_LINE) |
| 271 | + extr.AddCellType(VTK_VERTEX) |
| 272 | + extr.Update() |
| 273 | + data_edges = extr.GetOutput() |
| 274 | + |
| 275 | + # extract edges |
| 276 | + extr = vtkExtractCellsByType() |
| 277 | + extr.SetInputDataObject(data) |
239 | 278 |
|
240 | | - if assy.color: |
241 | | - color = assy.color.toTuple() |
| 279 | + extr.AddCellType(VTK_TRIANGLE) |
| 280 | + extr.Update() |
| 281 | + data_faces = extr.GetOutput() |
242 | 282 |
|
243 | | - if assy.shapes: |
244 | | - data = Compound.makeCompound(assy.shapes).toVtkPolyData( |
245 | | - tolerance, angularTolerance |
246 | | - ) |
| 283 | + # remove normals from edges |
| 284 | + data_edges.GetPointData().RemoveArray("Normals") |
247 | 285 |
|
| 286 | + # add both to the renderer |
248 | 287 | mapper = vtkMapper() |
249 | | - mapper.SetInputData(data) |
| 288 | + mapper.AddInputDataObject(data_faces) |
250 | 289 |
|
251 | 290 | actor = vtkActor() |
252 | 291 | actor.SetMapper(mapper) |
253 | 292 | actor.SetPosition(*trans) |
254 | 293 | actor.SetOrientation(*map(degrees, rot)) |
255 | | - actor.GetProperty().SetColor(*color[:3]) |
256 | | - actor.GetProperty().SetOpacity(color[3]) |
| 294 | + actor.GetProperty().SetColor(*col[:3]) |
| 295 | + actor.GetProperty().SetOpacity(col[3]) |
257 | 296 |
|
258 | 297 | renderer.AddActor(actor) |
259 | 298 |
|
260 | | - for child in assy.children: |
261 | | - renderer = toVTK(child, renderer, loc, color, tolerance, angularTolerance) |
| 299 | + mapper = vtkMapper() |
| 300 | + mapper.AddInputDataObject(data_edges) |
| 301 | + |
| 302 | + actor = vtkActor() |
| 303 | + actor.SetMapper(mapper) |
| 304 | + actor.SetPosition(*trans) |
| 305 | + actor.SetOrientation(*map(degrees, rot)) |
| 306 | + actor.GetProperty().SetColor(0, 0, 0) |
| 307 | + actor.GetProperty().SetLineWidth(2) |
| 308 | + |
| 309 | + renderer.AddActor(actor) |
262 | 310 |
|
263 | 311 | return renderer |
264 | 312 |
|
265 | 313 |
|
266 | 314 | def toJSON( |
267 | 315 | assy: AssemblyProtocol, |
268 | | - loc: Location = Location(), |
269 | 316 | color: Tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0), |
270 | 317 | tolerance: float = 1e-3, |
271 | 318 | ) -> List[Dict[str, Any]]: |
272 | 319 | """ |
273 | 320 | Export an object to a structure suitable for converting to VTK.js JSON. |
274 | 321 | """ |
275 | 322 |
|
276 | | - loc = loc * assy.loc |
277 | | - trans, rot = loc.toTuple() |
278 | | - |
279 | | - if assy.color: |
280 | | - color = assy.color.toTuple() |
281 | | - |
282 | 323 | rv = [] |
283 | 324 |
|
284 | | - if assy.shapes: |
| 325 | + for shape, _, loc, col_ in assy: |
| 326 | + |
285 | 327 | val: Any = {} |
286 | 328 |
|
287 | 329 | data = toString(Compound.makeCompound(assy.shapes), tolerance) |
| 330 | + trans, rot = loc.toTuple() |
288 | 331 |
|
289 | 332 | val["shape"] = data |
290 | | - val["color"] = color |
| 333 | + val["color"] = col_.toTuple() if col_ else color |
291 | 334 | val["position"] = trans |
292 | 335 | val["orientation"] = rot |
293 | 336 |
|
294 | 337 | rv.append(val) |
295 | 338 |
|
296 | | - for child in assy.children: |
297 | | - rv.extend(toJSON(child, loc, color, tolerance)) |
298 | | - |
299 | 339 | return rv |
300 | 340 |
|
301 | 341 |
|
@@ -331,19 +371,9 @@ def toFusedCAF( |
331 | 371 | shapes: List[Shape] = [] |
332 | 372 | colors = [] |
333 | 373 |
|
334 | | - def extract_shapes(assy, parent_loc=None, parent_color=None): |
335 | | - |
336 | | - loc = parent_loc * assy.loc if parent_loc else assy.loc |
337 | | - color = assy.color if assy.color else parent_color |
338 | | - |
339 | | - for shape in assy.shapes: |
340 | | - shapes.append(shape.moved(loc).copy()) |
341 | | - colors.append(color) |
342 | | - |
343 | | - for ch in assy.children: |
344 | | - extract_shapes(ch, loc, color) |
345 | | - |
346 | | - extract_shapes(assy) |
| 374 | + for shape, _, loc, color in assy: |
| 375 | + shapes.append(shape.moved(loc).copy()) |
| 376 | + colors.append(color) |
347 | 377 |
|
348 | 378 | # Initialize with a dummy value for mypy |
349 | 379 | top_level_shape = cast(TopoDS_Shape, None) |
@@ -411,3 +441,37 @@ def extract_shapes(assy, parent_loc=None, parent_color=None): |
411 | 441 | color_tool.SetColor(cur_lbl, color.wrapped, XCAFDoc_ColorGen) |
412 | 442 |
|
413 | 443 | return top_level_lbl, doc |
| 444 | + |
| 445 | + |
| 446 | +def imprint(assy: AssemblyProtocol) -> Tuple[Shape, Dict[Shape, Tuple[str, ...]]]: |
| 447 | + """ |
| 448 | + Imprint all the solids and construct a dictionary mapping imprinted solids to names from the input assy. |
| 449 | + """ |
| 450 | + |
| 451 | + # make the id map |
| 452 | + id_map = {} |
| 453 | + |
| 454 | + for obj, name, loc, _ in assy: |
| 455 | + for s in obj.moved(loc).Solids(): |
| 456 | + id_map[s] = name |
| 457 | + |
| 458 | + # connect topologically |
| 459 | + bldr = BOPAlgo_MakeConnected() |
| 460 | + bldr.SetRunParallel(True) |
| 461 | + bldr.SetUseOBB(True) |
| 462 | + |
| 463 | + for obj in id_map: |
| 464 | + bldr.AddArgument(obj.wrapped) |
| 465 | + |
| 466 | + bldr.Perform() |
| 467 | + res = Shape(bldr.Shape()) |
| 468 | + |
| 469 | + # make the connected solid -> id map |
| 470 | + origins: Dict[Shape, Tuple[str, ...]] = {} |
| 471 | + |
| 472 | + for s in res.Solids(): |
| 473 | + ids = tuple(id_map[Solid(el)] for el in bldr.GetOrigins(s.wrapped)) |
| 474 | + # if GetOrigins yields nothing, solid was not modified |
| 475 | + origins[s] = ids if ids else (id_map[s],) |
| 476 | + |
| 477 | + return res, origins |
0 commit comments