|
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