Skip to content

Commit

Permalink
Merge pull request #887 from bernhard-42/encaps_vtk
Browse files Browse the repository at this point in the history
Encaps vtk
  • Loading branch information
gumyr authored Jan 29, 2025
2 parents 8fe3ec1 + ee2a372 commit 6f8dbae
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 97 deletions.
31 changes: 2 additions & 29 deletions src/build123d/jupyter_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
import os
import uuid
from string import Template
from typing import Any, Dict, List
from typing import Any
from IPython.display import HTML
from vtkmodules.vtkIOXML import vtkXMLPolyDataWriter
from build123d.vtk_tools import to_vtkpoly_string

DEFAULT_COLOR = [1, 0.8, 0, 1]

Expand All @@ -39,33 +39,6 @@
TEMPLATE_JS = f.read()


def to_vtkpoly_string(
shape: Any, tolerance: float = 1e-3, angular_tolerance: float = 0.1
) -> str:
"""to_vtkpoly_string
Args:
shape (Shape): object to convert
tolerance (float, optional): Defaults to 1e-3.
angular_tolerance (float, optional): Defaults to 0.1.
Raises:
ValueError: not a valid Shape
Returns:
str: vtkpoly str
"""
if not hasattr(shape, "wrapped"):
raise ValueError(f"Type {type(shape)} is not supported")

writer = vtkXMLPolyDataWriter()
writer.SetWriteToOutputString(True)
writer.SetInputData(shape.to_vtk_poly_data(tolerance, angular_tolerance, True))
writer.Write()

return writer.GetOutputString()


def shape_to_html(shape: Any) -> HTML:
"""shape_to_html
Expand Down
63 changes: 0 additions & 63 deletions src/build123d/topology/shape_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
import OCP.GeomAbs as ga
import OCP.TopAbs as ta
from IPython.lib.pretty import pretty, RepresentationPrinter
from OCP.Aspect import Aspect_TOL_SOLID
from OCP.BOPAlgo import BOPAlgo_GlueEnum
from OCP.BRep import BRep_Tool
from OCP.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
Expand Down Expand Up @@ -104,10 +103,6 @@
from OCP.Geom import Geom_Line
from OCP.GeomAPI import GeomAPI_ProjectPointOnSurf
from OCP.GeomLib import GeomLib_IsPlanarSurface
from OCP.IVtkOCC import IVtkOCC_Shape, IVtkOCC_ShapeMesher
from OCP.IVtkVTK import IVtkVTK_ShapeData
from OCP.Prs3d import Prs3d_IsoAspect
from OCP.Quantity import Quantity_Color
from OCP.ShapeAnalysis import ShapeAnalysis_Curve
from OCP.ShapeCustom import ShapeCustom, ShapeCustom_RestrictionParameters
from OCP.ShapeFix import ShapeFix_Shape
Expand Down Expand Up @@ -152,8 +147,6 @@
from typing_extensions import Self

from typing import Literal
from vtkmodules.vtkCommonDataModel import vtkPolyData
from vtkmodules.vtkFiltersCore import vtkPolyDataNormals, vtkTriangleFilter


if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -1938,62 +1931,6 @@ def to_splines(

return self.__class__.cast(result)

def to_vtk_poly_data(
self,
tolerance: float | None = None,
angular_tolerance: float | None = None,
normals: bool = False,
) -> vtkPolyData:
"""Convert shape to vtkPolyData
Args:
tolerance: float:
angular_tolerance: float: (Default value = 0.1)
normals: bool: (Default value = True)
Returns: data object in VTK consisting of points, vertices, lines, and polygons
"""
if self.wrapped is None:
raise ValueError("Cannot convert an empty shape")

vtk_shape = IVtkOCC_Shape(self.wrapped)
shape_data = IVtkVTK_ShapeData()
shape_mesher = IVtkOCC_ShapeMesher()

drawer = vtk_shape.Attributes()
drawer.SetUIsoAspect(Prs3d_IsoAspect(Quantity_Color(), Aspect_TOL_SOLID, 1, 0))
drawer.SetVIsoAspect(Prs3d_IsoAspect(Quantity_Color(), Aspect_TOL_SOLID, 1, 0))

if tolerance:
drawer.SetDeviationCoefficient(tolerance)

if angular_tolerance:
drawer.SetDeviationAngle(angular_tolerance)

shape_mesher.Build(vtk_shape, shape_data)

vtk_poly_data = shape_data.getVtkPolyData()

# convert to triangles and split edges
t_filter = vtkTriangleFilter()
t_filter.SetInputData(vtk_poly_data)
t_filter.Update()

return_value = t_filter.GetOutput()

# compute normals
if normals:
n_filter = vtkPolyDataNormals()
n_filter.SetComputePointNormals(True)
n_filter.SetComputeCellNormals(True)
n_filter.SetFeatureAngle(360)
n_filter.SetInputData(return_value)
n_filter.Update()

return_value = n_filter.GetOutput()

return return_value

def transform_geometry(self, t_matrix: Matrix) -> Self:
"""Apply affine transform
Expand Down
149 changes: 149 additions & 0 deletions src/build123d/vtk_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""
build123d topology
name: vtk_tools.py
by: Gumyr
date: January 07, 2025
desc:
This module defines the foundational classes and methods for the build123d CAD library, enabling
detailed geometric operations and 3D modeling capabilities. It provides a hierarchy of classes
representing various geometric entities like vertices, edges, wires, faces, shells, solids, and
compounds. These classes are designed to work seamlessly with the OpenCascade Python bindings,
leveraging its robust CAD kernel.
Key Features:
- **Shape Base Class:** Implements core functionalities such as transformations (rotation,
translation, scaling), geometric queries, and boolean operations (cut, fuse, intersect).
- **Custom Utilities:** Includes helper classes like `ShapeList` for advanced filtering, sorting,
and grouping of shapes, and `GroupBy` for organizing shapes by specific criteria.
- **Type Safety:** Extensive use of Python typing features ensures clarity and correctness in type
handling.
- **Advanced Geometry:** Supports operations like finding intersections, computing bounding boxes,
projecting faces, and generating triangulated meshes.
The module is designed for extensibility, enabling developers to build complex 3D assemblies and
perform detailed CAD operations programmatically while maintaining a clean and structured API.
license:
Copyright 2025 Gumyr
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

from typing import Any
import warnings

from OCP.Aspect import Aspect_TOL_SOLID
from OCP.Prs3d import Prs3d_IsoAspect
from OCP.Quantity import Quantity_Color

HAS_VTK = True
try:
from OCP.IVtkOCC import IVtkOCC_Shape, IVtkOCC_ShapeMesher
from OCP.IVtkVTK import IVtkVTK_ShapeData
from vtkmodules.vtkCommonDataModel import vtkPolyData
from vtkmodules.vtkFiltersCore import vtkPolyDataNormals, vtkTriangleFilter
from vtkmodules.vtkIOXML import vtkXMLPolyDataWriter
except ImportError:
HAS_VTK = False


def to_vtk_poly_data(
obj,
tolerance: float | None = None,
angular_tolerance: float | None = None,
normals: bool = False,
) -> "vtkPolyData":
"""Convert shape to vtkPolyData
Args:
tolerance: float:
angular_tolerance: float: (Default value = 0.1)
normals: bool: (Default value = True)
Returns: data object in VTK consisting of points, vertices, lines, and polygons
"""
if not HAS_VTK:
warnings.warn("VTK not supported", stacklevel=2)

if obj.wrapped is None:
raise ValueError("Cannot convert an empty shape")

vtk_shape = IVtkOCC_Shape(obj.wrapped)
shape_data = IVtkVTK_ShapeData()
shape_mesher = IVtkOCC_ShapeMesher()

drawer = vtk_shape.Attributes()
drawer.SetUIsoAspect(Prs3d_IsoAspect(Quantity_Color(), Aspect_TOL_SOLID, 1, 0))
drawer.SetVIsoAspect(Prs3d_IsoAspect(Quantity_Color(), Aspect_TOL_SOLID, 1, 0))

if tolerance:
drawer.SetDeviationCoefficient(tolerance)

if angular_tolerance:
drawer.SetDeviationAngle(angular_tolerance)

shape_mesher.Build(vtk_shape, shape_data)

vtk_poly_data = shape_data.getVtkPolyData()

# convert to triangles and split edges
t_filter = vtkTriangleFilter()
t_filter.SetInputData(vtk_poly_data)
t_filter.Update()

return_value = t_filter.GetOutput()

# compute normals
if normals:
n_filter = vtkPolyDataNormals()
n_filter.SetComputePointNormals(True)
n_filter.SetComputeCellNormals(True)
n_filter.SetFeatureAngle(360)
n_filter.SetInputData(return_value)
n_filter.Update()

return_value = n_filter.GetOutput()

return return_value


def to_vtkpoly_string(
shape: Any, tolerance: float = 1e-3, angular_tolerance: float = 0.1
) -> str:
"""to_vtkpoly_string
Args:
shape (Shape): object to convert
tolerance (float, optional): Defaults to 1e-3.
angular_tolerance (float, optional): Defaults to 0.1.
Raises:
ValueError: not a valid Shape
Returns:
str: vtkpoly str
"""
if not hasattr(shape, "wrapped"):
raise ValueError(f"Type {type(shape)} is not supported")

writer = vtkXMLPolyDataWriter()
writer.SetWriteToOutputString(True)
writer.SetInputData(to_vtk_poly_data(shape, tolerance, angular_tolerance, True))
writer.Write()

return writer.GetOutputString()
5 changes: 3 additions & 2 deletions tests/test_direct_api/test_jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
import unittest

from build123d.geometry import Vector
from build123d.jupyter_tools import to_vtkpoly_string, shape_to_html
from build123d.jupyter_tools import shape_to_html
from build123d.vtk_tools import to_vtkpoly_string
from build123d.topology import Solid


Expand All @@ -43,7 +44,7 @@ def test_repr_html(self):
assert "function render" in html1

def test_display_error(self):
with self.assertRaises(AttributeError):
with self.assertRaises(TypeError):
shape_to_html(Vector())

with self.assertRaises(ValueError):
Expand Down
7 changes: 4 additions & 3 deletions tests/test_direct_api/test_vtk_poly_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import unittest

from build123d.topology import Solid
from build123d.vtk_tools import to_vtk_poly_data
from vtkmodules.vtkCommonDataModel import vtkPolyData
from vtkmodules.vtkFiltersCore import vtkTriangleFilter

Expand All @@ -40,8 +41,8 @@ def setUp(self):

def test_to_vtk_poly_data(self):
# Generate VTK data
vtk_data = self.object_under_test.to_vtk_poly_data(
tolerance=0.1, angular_tolerance=0.2, normals=True
vtk_data = to_vtk_poly_data(
self.object_under_test, tolerance=0.1, angular_tolerance=0.2, normals=True
)

# Verify the result is of type vtkPolyData
Expand Down Expand Up @@ -78,7 +79,7 @@ def test_empty_shape(self):
# Test handling of empty shape
empty_object = Solid() # Create an empty object
with self.assertRaises(ValueError) as context:
empty_object.to_vtk_poly_data()
to_vtk_poly_data(empty_object)

self.assertEqual(str(context.exception), "Cannot convert an empty shape")

Expand Down

0 comments on commit 6f8dbae

Please sign in to comment.