Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pyproject.toml add optional dependencies, jupyter_tools is optional #820

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,44 @@ dependencies = [
"Documentation" = "https://build123d.readthedocs.io/en/latest/index.html"
"Bug Tracker" = "https://github.com/gumyr/build123d/issues"

[project.optional-dependencies]
# enable optional use of jupyter_tools.display that provides a basic viewer
# for use in an e.g. jupyter-lab notebook
jupyter_tools = [
"VTK==9.2.6",
]

# enable the optional ocp_vscode visualization package
ocp_vscode = [
"ocp_vscode",
]

# development dependencies
development = [
"wheel",
"pytest",
"pytest-cov",
"pylint",
"mypy",
"black",
]

# dependencies to build the docs
docs = [
"sphinx",
"sphinx-design",
"sphinx-copybutton",
"sphinx-hoverxref",
]

# all dependencies
all = [
"build123d[jupyter_tools]",
"build123d[ocp_vscode]",
"build123d[development]",
"build123d[docs]",
]

[tool.setuptools.packages.find]
where = ["src"]
# exclude build123d._dev from wheels
Expand Down
19 changes: 13 additions & 6 deletions src/build123d/build_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,23 +429,30 @@ def _add_to_context(
if mode == Mode.ADD:
if self._obj is None:
if len(typed[self._shape]) == 1:
self._obj = typed[self._shape][0]
combined = typed[self._shape][0]
else:
self._obj = (
combined = (
typed[self._shape].pop().fuse(*typed[self._shape])
)
else:
self._obj = self._obj.fuse(*typed[self._shape])
combined = self._obj.fuse(*typed[self._shape])
elif mode == Mode.SUBTRACT:
if self._obj is None:
raise RuntimeError("Nothing to subtract from")
self._obj = self._obj.cut(*typed[self._shape])
combined = self._obj.cut(*typed[self._shape])
elif mode == Mode.INTERSECT:
if self._obj is None:
raise RuntimeError("Nothing to intersect with")
self._obj = self._obj.intersect(*typed[self._shape])
combined = self._obj.intersect(*typed[self._shape])
elif mode == Mode.REPLACE:
self._obj = Compound(list(typed[self._shape]))
combined = self._sub_class(list(typed[self._shape]))

# If the boolean operation created a list, convert back
self._obj = (
self._sub_class(combined)
if isinstance(combined, list)
else combined
)

if self._obj is not None and clean:
self._obj = self._obj.clean()
Expand Down
25 changes: 15 additions & 10 deletions src/build123d/drafting.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from build123d.objects_sketch import BaseSketchObject, Polygon, Text
from build123d.operations_generic import fillet, mirror, sweep
from build123d.operations_sketch import make_face, trace
from build123d.topology import Compound, Edge, Sketch, Vertex, Wire
from build123d.topology import Compound, Curve, Edge, Sketch, Vertex, Wire


class ArrowHead(BaseSketchObject):
Expand Down Expand Up @@ -439,23 +439,25 @@ def __init__(
overage = shaft_length + draft.pad_around_text + label_length / 2
label_u_values = [0.5, -overage / path_length, 1 + overage / path_length]

# d_lines = Sketch(children=arrows[0])
d_lines = {}
# for arrow_pair in arrow_shapes:
for u_value in label_u_values:
d_line = Sketch()
for add_arrow, arrow_shape in zip(arrows, arrow_shapes):
if add_arrow:
d_line += arrow_shape
select_arrow_shapes = [
arrow_shape
for add_arrow, arrow_shape in zip(arrows, arrow_shapes)
if add_arrow
]
d_line = Sketch(select_arrow_shapes)
flip_label = path_obj.tangent_at(u_value).get_angle(Vector(1, 0, 0)) >= 180
loc = Draft._sketch_location(path_obj, u_value, flip_label)
placed_label = label_shape.located(loc)
self_intersection = d_line.intersect(placed_label).area
self_intersection = Sketch.intersect(d_line, placed_label).area
d_line += placed_label
bbox_size = d_line.bounding_box().size

# Minimize size while avoiding intersections
common_area = 0.0 if sketch is None else d_line.intersect(sketch).area
common_area = (
0.0 if sketch is None else Sketch.intersect(d_line, sketch).area
)
common_area += self_intersection
score = (d_line.area - 10 * common_area) / bbox_size.X
d_lines[d_line] = score
Expand Down Expand Up @@ -702,7 +704,10 @@ def __init__(
)
bf_pnt3 = box_frame_curve.edges().sort_by(Axis.X)[0] @ (1 / 3)
bf_pnt4 = box_frame_curve.edges().sort_by(Axis.X)[0] @ (2 / 3)
box_frame_curve += Edge.make_line(bf_pnt3, (bf_pnt2.X, bf_pnt3.Y))
box_frame_curve = Curve() + [
box_frame_curve,
Edge.make_line(bf_pnt3, (bf_pnt2.X, bf_pnt3.Y)),
]
box_frame_curve += Edge.make_line(bf_pnt4, (bf_pnt2.X, bf_pnt4.Y))
bf_pnt5 = box_frame_curve.edges().sort_by(Axis.Y)[-1] @ (1 / 3)
bf_pnt6 = box_frame_curve.edges().sort_by(Axis.Y)[-1] @ (2 / 3)
Expand Down
23 changes: 10 additions & 13 deletions src/build123d/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,14 @@
from OCP.TopExp import TopExp_Explorer # type: ignore
from typing_extensions import Self

from build123d.build_enums import Unit
from build123d.geometry import TOLERANCE, Color
from build123d.build_enums import Unit, GeomType
from build123d.geometry import TOLERANCE, Color, Vector, VectorLike
from build123d.topology import (
BoundBox,
Compound,
Edge,
Wire,
GeomType,
Shape,
Vector,
VectorLike,
)
from build123d.build_common import UNITS_PER_METER

Expand Down Expand Up @@ -682,7 +679,7 @@ def _convert_line(self, edge: Edge, attribs: dict):

def _convert_circle(self, edge: Edge, attribs: dict):
"""Converts a Circle object into a DXF circle entity."""
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
circle = curve.Circle()
center = self._convert_point(circle.Location())
radius = circle.Radius()
Expand Down Expand Up @@ -710,7 +707,7 @@ def _convert_circle(self, edge: Edge, attribs: dict):

def _convert_ellipse(self, edge: Edge, attribs: dict):
"""Converts an Ellipse object into a DXF ellipse entity."""
geom = edge._geom_adaptor()
geom = edge.geom_adaptor()
ellipse = geom.Ellipse()
minor_radius = ellipse.MinorRadius()
major_radius = ellipse.MajorRadius()
Expand Down Expand Up @@ -743,7 +740,7 @@ def _convert_bspline(self, edge: Edge, attribs):

# This pulls the underlying Geom_BSplineCurve out of the Edge.
# The adaptor also supplies a parameter range for the curve.
adaptor = edge._geom_adaptor()
adaptor = edge.geom_adaptor()
curve = adaptor.Curve().Curve()
u1 = adaptor.FirstParameter()
u2 = adaptor.LastParameter()
Expand Down Expand Up @@ -1157,7 +1154,7 @@ def _path_point(self, pt: Union[gp_Pnt, Vector]) -> complex:
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

def _line_segment(self, edge: Edge, reverse: bool) -> PT.Line:
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
fp = curve.FirstParameter()
lp = curve.LastParameter()
(u0, u1) = (lp, fp) if reverse else (fp, lp)
Expand Down Expand Up @@ -1187,7 +1184,7 @@ def _line_element(self, edge: Edge) -> ET.Element:

def _circle_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
# pylint: disable=too-many-locals
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
circle = curve.Circle()
radius = circle.Radius()
x_axis = circle.XAxis().Direction()
Expand Down Expand Up @@ -1215,7 +1212,7 @@ def _circle_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
def _circle_element(self, edge: Edge) -> ET.Element:
"""Converts a Circle object into an SVG circle element."""
if edge.is_closed:
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
circle = curve.Circle()
radius = circle.Radius()
center = circle.Location()
Expand All @@ -1233,7 +1230,7 @@ def _circle_element(self, edge: Edge) -> ET.Element:

def _ellipse_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
# pylint: disable=too-many-locals
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
ellipse = curve.Ellipse()
minor_radius = ellipse.MinorRadius()
major_radius = ellipse.MajorRadius()
Expand Down Expand Up @@ -1276,7 +1273,7 @@ def _bspline_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:

# This pulls the underlying Geom_BSplineCurve out of the Edge.
# The adaptor also supplies a parameter range for the curve.
adaptor = edge._geom_adaptor()
adaptor = edge.geom_adaptor()
spline = adaptor.Curve().Curve()
u1 = adaptor.FirstParameter()
u2 = adaptor.LastParameter()
Expand Down
4 changes: 2 additions & 2 deletions src/build123d/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ def __eq__(self, other: object) -> bool:

def __hash__(self) -> int:
"""Hash of Vector"""
return hash(self.X) + hash(self.Y) + hash(self.Z)
return hash((round(self.X, 6), round(self.Y, 6), round(self.Z, 6)))

def __copy__(self) -> Vector:
"""Return copy of self"""
Expand Down Expand Up @@ -963,7 +963,7 @@ def find_outside_box_2d(bb1: BoundBox, bb2: BoundBox) -> Optional[BoundBox]:
return result

@classmethod
def _from_topo_ds(
def from_topo_ds(
cls,
shape: TopoDS_Shape,
tolerance: float = None,
Expand Down
2 changes: 1 addition & 1 deletion src/build123d/importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def import_brep(file_name: Union[PathLike, str, bytes]) -> Shape:
if shape.IsNull():
raise ValueError(f"Could not import {file_name}")

return Shape.cast(shape)
return Compound.cast(shape)


def import_step(filename: Union[PathLike, str, bytes]) -> Compound:
Expand Down
77 changes: 73 additions & 4 deletions src/build123d/jupyter_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,21 @@
from json import dumps
from typing import Any, Dict, List
from IPython.display import Javascript
from vtkmodules.vtkIOXML import vtkXMLPolyDataWriter
from build123d.topology import Shape

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

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

Check warning on line 42 in src/build123d/jupyter_tools.py

View check run for this annotation

Codecov / codecov/patch

src/build123d/jupyter_tools.py#L42

Added line #L42 was not covered by tests
# TODO: handle this more gracefully
print("VTK is not installed so jupyter_tools.display is not available")

Check warning on line 44 in src/build123d/jupyter_tools.py

View check run for this annotation

Codecov / codecov/patch

src/build123d/jupyter_tools.py#L44

Added line #L44 was not covered by tests

DEFAULT_COLOR = [1, 0.8, 0, 1]

Expand Down Expand Up @@ -177,6 +191,61 @@
)


def to_vtk_poly_data(
shape: Shape,
tolerance: float = None,
angular_tolerance: float = None,
normals: bool = False,
) -> vtkPolyData:
"""Convert shape to vtkPolyData

Args:
shape: Shape:
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
"""
vtk_shape = IVtkOCC_Shape(shape.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:
Expand All @@ -193,12 +262,12 @@
Returns:
str: vtkpoly str
"""
if not hasattr(shape, "wrapped"):
if not isinstance(shape, Shape):
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.SetInputData(to_vtk_poly_data(shape, tolerance, angular_tolerance, True))
writer.Write()

return writer.GetOutputString()
Expand All @@ -218,7 +287,7 @@
"""
payload: List[Dict[str, Any]] = []

if not hasattr(shape, "wrapped"): # Is a "Shape"
if not isinstance(shape, Shape): # Is a "Shape"
raise ValueError(f"Type {type(shape)} is not supported")

payload.append(
Expand Down
2 changes: 1 addition & 1 deletion src/build123d/objects_sketch.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
Vector,
VectorLike,
to_align_offset,
TOLERANCE,
)
from build123d.topology import (
Compound,
Expand All @@ -52,7 +53,6 @@
Sketch,
Wire,
tuplify,
TOLERANCE,
topo_explore_common_vertex,
)

Expand Down
Loading
Loading