diff --git a/docs/assets/buildline_example_5.svg b/docs/assets/buildline_example_5.svg index eeb26d88..ea53d99f 100644 --- a/docs/assets/buildline_example_5.svg +++ b/docs/assets/buildline_example_5.svg @@ -10,7 +10,7 @@ - + diff --git a/docs/assets/buildline_example_7.svg b/docs/assets/buildline_example_7.svg index 2f4428a9..e6f7ced2 100644 --- a/docs/assets/buildline_example_7.svg +++ b/docs/assets/buildline_example_7.svg @@ -21,5 +21,12 @@ + + + + + + + \ No newline at end of file diff --git a/docs/assets/helix_example.svg b/docs/assets/helix_example.svg index 5ab24c71..bf48766c 100644 --- a/docs/assets/helix_example.svg +++ b/docs/assets/helix_example.svg @@ -1,8 +1,8 @@ - + - - + + @@ -17,8 +17,8 @@ - - + + diff --git a/docs/assets/intersecting_line_example.svg b/docs/assets/intersecting_line_example.svg new file mode 100644 index 00000000..1deae87c --- /dev/null +++ b/docs/assets/intersecting_line_example.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/assets/jern_arc_example.svg b/docs/assets/jern_arc_example.svg index 13dcbf32..0410ecc6 100644 --- a/docs/assets/jern_arc_example.svg +++ b/docs/assets/jern_arc_example.svg @@ -5,7 +5,7 @@ - + diff --git a/docs/assets/polar_line_example.svg b/docs/assets/polar_line_example.svg index bee604be..1bd9d2b1 100644 --- a/docs/assets/polar_line_example.svg +++ b/docs/assets/polar_line_example.svg @@ -5,7 +5,7 @@ - + diff --git a/docs/assets/tangent_arc_example.svg b/docs/assets/tangent_arc_example.svg index 53903ed7..6de57f70 100644 --- a/docs/assets/tangent_arc_example.svg +++ b/docs/assets/tangent_arc_example.svg @@ -6,7 +6,7 @@ - + diff --git a/docs/objects.rst b/docs/objects.rst index 83a007db..9e95cefc 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -111,6 +111,13 @@ The following objects all can be used in BuildLine contexts. Note that +++ Helix defined pitch, radius and height + .. grid-item-card:: :class:`~objects_curve.IntersectingLine` + + .. image:: assets/intersecting_line_example.svg + + +++ + Intersecting line defined by start, direction & other line + .. grid-item-card:: :class:`~objects_curve.JernArc` .. image:: assets/jern_arc_example.svg @@ -185,6 +192,7 @@ Reference .. autoclass:: EllipticalCenterArc .. autoclass:: FilletPolyline .. autoclass:: Helix +.. autoclass:: IntersectingLine .. autoclass:: JernArc .. autoclass:: Line .. autoclass:: PolarLine diff --git a/docs/objects_1d.py b/docs/objects_1d.py index 939cbdec..d4c83322 100644 --- a/docs/objects_1d.py +++ b/docs/objects_1d.py @@ -40,7 +40,7 @@ # [Ex. 5] s = 100 / max(*example_5.line.bounding_box().size) svg = ExportSVG(scale=s) -svg.add_layer("dashed", line_type=LineType.DASHED) +svg.add_layer("dashed", line_type=LineType.ISO_DASH_SPACE) svg.add_shape(example_5.line) svg.add_shape(dot.moved(Location(l1 @ 1))) svg.add_shape(dot.moved(Location(l2 @ 1))) @@ -139,7 +139,7 @@ JernArc((1, 1), (1, 0.5), 2, 100) s = 100 / max(*jern_arc.line.bounding_box().size) svg = ExportSVG(scale=s) -svg.add_layer("dashed", line_type=LineType.DASHED) +svg.add_layer("dashed", line_type=LineType.ISO_DASH_SPACE) svg.add_shape(jern_arc.line) svg.add_shape(dot.moved(Location(Vector((1, 1))))) svg.add_shape(PolarLine((1, 1), 0.5, direction=(1, 0.5)), "dashed") @@ -158,7 +158,7 @@ PolarLine((1, 1), 2.5, 60) s = 100 / max(*polar_line.line.bounding_box().size) svg = ExportSVG(scale=s) -svg.add_layer("dashed", line_type=LineType.DASHED) +svg.add_layer("dashed", line_type=LineType.ISO_DASH_SPACE) svg.add_shape(polar_line.line) svg.add_shape(dot.moved(Location(Vector((1, 1))))) svg.add_shape(PolarLine((1, 1), 4, angle=60), "dashed") @@ -188,7 +188,7 @@ RadiusArc((1, 1), (3, 3), 2) s = 100 / max(*radius_arc.line.bounding_box().size) svg = ExportSVG(scale=s) -svg.add_layer("dashed", line_type=LineType.DASHED) +svg.add_layer("dashed", line_type=LineType.ISO_DASH_SPACE) svg.add_shape(radius_arc.line) svg.add_shape(dot.moved(Location(Vector((1, 1))))) svg.add_shape(dot.moved(Location(Vector((3, 3))))) @@ -219,7 +219,7 @@ TangentArc((1, 1), (3, 3), tangent=(1, 0)) s = 100 / max(*tangent_arc.line.bounding_box().size) svg = ExportSVG(scale=s) -svg.add_layer("dashed", line_type=LineType.DASHED) +svg.add_layer("dashed", line_type=LineType.ISO_DASH_SPACE) svg.add_shape(tangent_arc.line) svg.add_shape(dot.moved(Location(Vector((1, 1))))) svg.add_shape(dot.moved(Location(Vector((3, 3))))) @@ -235,7 +235,18 @@ svg.add_shape(dot.moved(Location(Vector((1.5, 2))))) svg.add_shape(dot.moved(Location(Vector((3, 3))))) svg.write("assets/three_point_arc_example.svg") -# + +with BuildLine() as intersecting_line: + other = Line((2, 0), (2, 2), mode=Mode.PRIVATE) + IntersectingLine((1, 0), (1, 1), other) +s = 100 / max(*intersecting_line.line.bounding_box().size) +svg = ExportSVG(scale=s) +svg.add_layer("dashed", line_type=LineType.ISO_DASH_SPACE) +svg.add_shape(other, "dashed") +svg.add_shape(intersecting_line.line) +svg.add_shape(dot.moved(Location(Vector((1, 0))))) +svg.write("assets/intersecting_line_example.svg") +show(other, intersecting_line) # show_object(example_1.line, name="Ex. 1") # show_object(example_2.line, name="Ex. 2") # show_object(example_3.line, name="Ex. 3") diff --git a/src/build123d/build_common.py b/src/build123d/build_common.py index 1f12b2cd..4f1e4700 100644 --- a/src/build123d/build_common.py +++ b/src/build123d/build_common.py @@ -49,7 +49,7 @@ from abc import ABC, abstractmethod from itertools import product from math import sqrt -from typing import Any, Callable, Iterable, Optional, Union, TypeVar +from typing import Callable, Iterable, Optional, Union, TypeVar from typing_extensions import Self, ParamSpec, Concatenate from build123d.build_enums import Align, Mode, Select @@ -70,6 +70,8 @@ new_edges, ) +# pylint: disable=too-many-lines + # Create a build123d logger to distinguish these logs from application logs. # If the user doesn't configure logging, all build123d logs will be discarded. logging.getLogger("build123d").addHandler(logging.NullHandler()) @@ -140,6 +142,8 @@ class Builder(ABC): """ + # pylint: disable=too-many-instance-attributes + # Context variable used to by Objects and Operations to link to current builder instance _current: contextvars.ContextVar["Builder"] = contextvars.ContextVar( "Builder._current" @@ -301,6 +305,10 @@ def _add_to_context( ValueError: Nothing to intersect with ValueError: Nothing to intersect with """ + # pylint: disable=too-many-locals + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + self.obj_before = self._obj self.to_combine = list(objects) if mode != Mode.PRIVATE and len(objects) > 0: @@ -310,7 +318,7 @@ def _add_to_context( typed[cls] = [obj for obj in objects if isinstance(obj, cls)] # Check for invalid inputs - num_stored = sum([len(t) for t in typed.values()]) + num_stored = sum(len(t) for t in typed.values()) # Generate an exception if not processing exceptions if len(objects) != num_stored and not sys.exc_info()[1]: unsupported = set(objects) - set(v for l in typed.values() for v in l) @@ -325,31 +333,31 @@ def _add_to_context( # Align sketch planar faces with Plane.XY if self._tag == "BuildSketch": aligned = [] - face: Face - for face in typed[Face]: - if not face.is_coplanar(Plane.XY): + new_face: Face + for new_face in typed[Face]: + if not new_face.is_coplanar(Plane.XY): # Try to keep the x direction, if not allow it to be assigned automatically try: plane = Plane( origin=(0, 0, 0), x_dir=(1, 0, 0), - z_dir=face.normal_at(), + z_dir=new_face.normal_at(), ) except: - plane = Plane(origin=(0, 0, 0), z_dir=face.normal_at()) + plane = Plane(origin=(0, 0, 0), z_dir=new_face.normal_at()) - face = plane.to_local_coords(face) - face.move(Location((0, 0, -face.center().Z))) - if face.normal_at().Z > 0: # Flip the face if up-side-down - aligned.append(face) + new_face = plane.to_local_coords(new_face) + new_face.move(Location((0, 0, -new_face.center().Z))) + if new_face.normal_at().Z > 0: # Flip the face if up-side-down + aligned.append(new_face) else: - aligned.append(-face) + aligned.append(-new_face) typed[Face] = aligned # Convert wires to edges - wire: Wire - for wire in typed[Wire]: - typed[Edge].extend(wire.edges()) + new_wire: Wire + for new_wire in typed[Wire]: + typed[Edge].extend(new_wire.edges()) # Allow faces to be combined with solids for section operations if not faces_to_pending: @@ -426,9 +434,7 @@ def _add_to_context( if self._tag == "BuildPart": self._add_to_pending(*typed[Edge]) for plane in WorkplaneList._get_context().workplanes: - global_faces = [ - plane.from_local_coords(face) for face in typed[Face] - ] + global_faces = [plane.from_local_coords(f) for f in typed[Face]] self._add_to_pending(*global_faces, face_plane=plane) elif self._tag == "BuildSketch": self._add_to_pending(*typed[Edge]) @@ -448,8 +454,8 @@ def vertices(self, select: Select = Select.ALL) -> ShapeList[Vertex]: """ vertex_list: list[Vertex] = [] if select == Select.ALL: - for edge in self._obj.edges(): - vertex_list.extend(edge.vertices()) + for obj_edge in self._obj.edges(): + vertex_list.extend(obj_edge.vertices()) elif select == Select.LAST: vertex_list = self.lasts[Vertex] elif select == Select.NEW: @@ -471,11 +477,11 @@ def vertex(self, select: Select = Select.ALL) -> Vertex: Returns: Vertex: Vertex extracted """ - vertices = self.vertices(select) - vertex_count = len(vertices) + all_vertices = self.vertices(select) + vertex_count = len(all_vertices) if vertex_count != 1: warnings.warn(f"Found {vertex_count} vertices, returning first") - return vertices[0] + return all_vertices[0] def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]: """Return Edges @@ -511,11 +517,11 @@ def edge(self, select: Select = Select.ALL) -> Edge: Returns: Edge: Edge extracted """ - edges = self.edges(select) - edge_count = len(edges) + all_edges = self.edges(select) + edge_count = len(all_edges) if edge_count != 1: warnings.warn(f"Found {edge_count} edges, returning first") - return edges[0] + return all_edges[0] def wires(self, select: Select = Select.ALL) -> ShapeList[Wire]: """Return Wires @@ -551,11 +557,11 @@ def wire(self, select: Select = Select.ALL) -> Wire: Returns: Wire: Wire extracted """ - wires = self.wires(select) - wire_count = len(wires) + all_wires = self.wires(select) + wire_count = len(all_wires) if wire_count != 1: warnings.warn(f"Found {wire_count} wires, returning first") - return wires[0] + return all_wires[0] def faces(self, select: Select = Select.ALL) -> ShapeList[Face]: """Return Faces @@ -591,11 +597,11 @@ def face(self, select: Select = Select.ALL) -> Face: Returns: Face: Face extracted """ - faces = self.faces(select) - face_count = len(faces) + all_faces = self.faces(select) + face_count = len(all_faces) if face_count != 1: warnings.warn(f"Found {face_count} faces, returning first") - return faces[0] + return all_faces[0] def solids(self, select: Select = Select.ALL) -> ShapeList[Solid]: """Return Solids @@ -631,11 +637,11 @@ def solid(self, select: Select = Select.ALL) -> Solid: Returns: Solid: Solid extracted """ - solids = self.solids(select) - solid_count = len(solids) + all_solids = self.solids(select) + solid_count = len(all_solids) if solid_count != 1: warnings.warn(f"Found {solid_count} solids, returning first") - return solids[0] + return all_solids[0] def _shapes(self, obj_type: Union[Vertex, Edge, Face, Solid] = None) -> ShapeList: """Extract Shapes""" @@ -819,6 +825,8 @@ def __init__( y_count: int, align: Union[Align, tuple[Align, Align]] = (Align.CENTER, Align.CENTER), ): + # pylint: disable=too-many-locals + diagonal = 4 * apothem / sqrt(3) x_spacing = 3 * diagonal / 4 y_spacing = diagonal * sqrt(3) / 2 @@ -1020,6 +1028,8 @@ class GridLocations(LocationList): ValueError: Either x or y count must be greater than or equal to one. """ + # pylint: disable=too-many-instance-attributes + def __init__( self, x_spacing: float, @@ -1181,16 +1191,16 @@ def localize(cls, *points: VectorLike) -> Union[list[Vector], Vector]: def __gen_context_component_getter( - f: Callable[Concatenate[Builder, P], T] + func: Callable[Concatenate[Builder, P], T] ) -> Callable[P, T]: - @functools.wraps(f) + @functools.wraps(func) def getter(select: Select = Select.ALL): - context = Builder._get_context(f.__name__) + context = Builder._get_context(func.__name__) if not context: raise RuntimeError( - f"{f.__name__}() requires a Builder context to be in scope" + f"{func.__name__}() requires a Builder context to be in scope" ) - return f(context, select) + return func(context, select) return getter @@ -1221,7 +1231,6 @@ def wrapper(self: Vector, vec: VectorLike): vec = WorkplaneList.localize(vec) - origin # type: ignore[union-attr] except AttributeError: # raised from `WorkplaneList._get_context().workplanes[0]` when context is `None` - # TODO make a specific `NoContextError` and raise that from `_get_context()` ? pass return original_op(self, vec)