mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Removed Workplanes
This commit is contained in:
parent
205a7b2aea
commit
c52ccd9174
9 changed files with 128 additions and 151 deletions
|
|
@ -32,14 +32,6 @@ Enums
|
||||||
.. autoclass:: Transition
|
.. autoclass:: Transition
|
||||||
.. autoclass:: Until
|
.. autoclass:: Until
|
||||||
|
|
||||||
**********
|
|
||||||
Workplanes
|
|
||||||
**********
|
|
||||||
|
|
||||||
.. py:module:: build_common
|
|
||||||
|
|
||||||
.. autoclass:: Workplanes
|
|
||||||
|
|
||||||
*********
|
*********
|
||||||
Locations
|
Locations
|
||||||
*********
|
*********
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ Cheat Sheet
|
||||||
|
|
||||||
| :class:`~build_line.BuildLine` :class:`~build_part.BuildPart` :class:`~build_sketch.BuildSketch`
|
| :class:`~build_line.BuildLine` :class:`~build_part.BuildPart` :class:`~build_sketch.BuildSketch`
|
||||||
| :class:`~build_common.GridLocations` :class:`~build_common.HexLocations` :class:`~build_common.Locations` :class:`~build_common.PolarLocations`
|
| :class:`~build_common.GridLocations` :class:`~build_common.HexLocations` :class:`~build_common.Locations` :class:`~build_common.PolarLocations`
|
||||||
| :class:`~build_common.Workplanes`
|
|
||||||
|
|
||||||
.. card:: Objects
|
.. card:: Objects
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ provide multiple output formats, support for multiple languages and can be
|
||||||
integrated with code management tools.
|
integrated with code management tools.
|
||||||
|
|
||||||
****************************************
|
****************************************
|
||||||
Key Concepts (context mode)
|
Key Concepts (builder mode)
|
||||||
****************************************
|
****************************************
|
||||||
|
|
||||||
.. include:: key_concepts.rst
|
.. include:: key_concepts.rst
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
|
There are two primary APIs provided by build123d: builder and algebra. The builder
|
||||||
|
api may be easier for new users as it provides some assistance and shortcuts; however,
|
||||||
|
if you know what a Quaternion is you might prefer the algebra API which allows
|
||||||
|
CAD objects to be created in the style of mathematical equations. Both API can
|
||||||
|
be mixed in the same model with the exception that the algebra API can't be used
|
||||||
|
from within a builder context. As with music, there is no "best" genre or API,
|
||||||
|
use the one you prefer or both if you like.
|
||||||
|
|
||||||
The following key concepts will help new users understand build123d quickly.
|
The following key concepts will help new users understand build123d quickly.
|
||||||
|
|
||||||
Builders
|
Builders
|
||||||
|
|
@ -59,39 +67,36 @@ information - as follows:
|
||||||
In this example, ``Box`` is in the scope of ``part_builder`` while ``Circle``
|
In this example, ``Box`` is in the scope of ``part_builder`` while ``Circle``
|
||||||
is in the scope of ``sketch_builder``.
|
is in the scope of ``sketch_builder``.
|
||||||
|
|
||||||
Workplane Contexts
|
Workplanes
|
||||||
==================
|
==========
|
||||||
|
|
||||||
As build123d is a 3D CAD package one must be able to position objects anywhere. As one
|
As build123d is a 3D CAD package one must be able to position objects anywhere. As one
|
||||||
frequently will work in the same plane for a sequence of operations, a workplane is used
|
frequently will work in the same plane for a sequence of operations, the first parameter(s)
|
||||||
to aid in the location of features. The default workplane in most cases is the XY plane
|
of the builders is a (sequence of) workplane(s) which is (are) used
|
||||||
|
to aid in the location of features. The default workplane in most cases is the ``Plane.XY``
|
||||||
where a tuple of numbers represent positions on the x and y axes. However workplanes can
|
where a tuple of numbers represent positions on the x and y axes. However workplanes can
|
||||||
be generated on any plane which allows users to put a workplane where they are working
|
be generated on any plane which allows users to put a workplane where they are working
|
||||||
and then work in local 2D coordinate space.
|
and then work in local 2D coordinate space.
|
||||||
|
|
||||||
To facilitate this a ``Workplanes`` stateful context is used to create a scope where a given
|
|
||||||
workplane will apply. For example:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
with BuildPart(Plane.XY) as example:
|
with BuildPart(Plane.XY) as example:
|
||||||
with BuildSketch() as bottom:
|
with BuildSketch(example.faces().sort_by(sort_by=Axis.Z)[0]) as bottom:
|
||||||
...
|
...
|
||||||
with Workplanes(Plane.XZ) as vertical:
|
with BuildSketch(Plane.XZ) as vertical:
|
||||||
with BuildSketch() as side:
|
|
||||||
...
|
...
|
||||||
with Workplanes(example.faces().sort_by(SortBy.Z)[-1]):
|
with BuildSketch(example.faces().sort_by(sort_by=Axis.Z)[-1]) as top:
|
||||||
with BuildSketch() as top:
|
|
||||||
...
|
...
|
||||||
|
|
||||||
When ``BuildPart`` is invoked it creates the workplane provided as a parameter (which has a
|
When ``BuildPart`` is invoked it creates the workplane provided as a parameter (which has a
|
||||||
default of the XY plane). The ``bottom`` sketch is therefore created on the XY plane. Subsequently
|
default of the ``Plane.XY``). The ``bottom`` sketch is therefore created on the ``Plane.XY`` but with the
|
||||||
the user has created the ``vertical`` plane (XZ) on which the ``side`` sketch is created. All
|
normal reversed to point down. Subsequently the user has created the ``vertical`` (``Plane.XZ```) sketch.
|
||||||
objects or operations within the scope of a workplane will automatically be orientated with
|
All objects or operations within the scope of a workplane will automatically be orientated with
|
||||||
respect to this plane so the user only has to work with local coordinates.
|
respect to this plane so the user only has to work with local coordinates.
|
||||||
|
|
||||||
Workplanes can be created from faces as well. The ``top`` sketch is positioned on top
|
As shown above, workplanes can be created from faces as well. The ``top`` sketch is
|
||||||
of ``example`` by selecting its faces and finding the one with the greatest z value.
|
positioned on top of ``example`` by selecting its faces and finding the one with the greatest z value.
|
||||||
|
|
||||||
One is not limited to a single workplane at a time. In the following example all six
|
One is not limited to a single workplane at a time. In the following example all six
|
||||||
faces of the first box is used to define workplanes which are then used to position
|
faces of the first box is used to define workplanes which are then used to position
|
||||||
|
|
@ -103,8 +108,9 @@ rotated boxes.
|
||||||
|
|
||||||
with bd.BuildPart() as bp:
|
with bd.BuildPart() as bp:
|
||||||
bd.Box(3, 3, 3)
|
bd.Box(3, 3, 3)
|
||||||
with bd.Workplanes(*bp.faces()):
|
with bd.BuildSketch(*bp.faces()):
|
||||||
bd.Box(1, 2, 0.1, rotation=(0, 0, 45))
|
bd.Rectangle(1, 2, rotation=45)
|
||||||
|
bd.extrude(amount=0.1)
|
||||||
|
|
||||||
This is the result:
|
This is the result:
|
||||||
|
|
||||||
|
|
@ -136,12 +142,12 @@ Note that these contexts are creating Location objects not just simple points. T
|
||||||
isn't obvious until the ``PolarLocations`` context is used which can also rotate objects within
|
isn't obvious until the ``PolarLocations`` context is used which can also rotate objects within
|
||||||
its scope - much as the hour and minute indicator on an analogue clock.
|
its scope - much as the hour and minute indicator on an analogue clock.
|
||||||
|
|
||||||
Also note that the locations are local to the current workplane(s). However, it's easy for a user
|
Also note that the locations are local to the current location(s) - i.e. ``Locations`` can be
|
||||||
to retrieve the global locations relative to the current workplane(s) as follows:
|
nested. It's easy for a user to retrieve the global locations:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
with Workplanes(Plane.XY, Plane.XZ):
|
with Locations(Plane.XY, Plane.XZ):
|
||||||
locs = GridLocations(1, 1, 2, 2)
|
locs = GridLocations(1, 1, 2, 2)
|
||||||
for l in locs:
|
for l in locs:
|
||||||
print(l)
|
print(l)
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ class Hinge(Compound):
|
||||||
add(hinge_profile.part, rotation=(90, 0, 0), mode=Mode.INTERSECT)
|
add(hinge_profile.part, rotation=(90, 0, 0), mode=Mode.INTERSECT)
|
||||||
|
|
||||||
# Create holes for fasteners
|
# Create holes for fasteners
|
||||||
with Workplanes(leaf_builder.part.faces().filter_by(Axis.Y)[-1]):
|
with Locations(leaf_builder.part.faces().filter_by(Axis.Y)[-1]):
|
||||||
with GridLocations(0, length / 3, 1, 3):
|
with GridLocations(0, length / 3, 1, 3):
|
||||||
holes = CounterSinkHole(3 * MM, 5 * MM)
|
holes = CounterSinkHole(3 * MM, 5 * MM)
|
||||||
# Add the hinge pin to the external leaf
|
# Add the hinge pin to the external leaf
|
||||||
|
|
@ -194,7 +194,7 @@ with BuildPart() as box_builder:
|
||||||
with Locations((-15 * CM, 0, 5 * CM)):
|
with Locations((-15 * CM, 0, 5 * CM)):
|
||||||
Box(2 * CM, 12 * CM, 4 * MM, mode=Mode.SUBTRACT)
|
Box(2 * CM, 12 * CM, 4 * MM, mode=Mode.SUBTRACT)
|
||||||
bbox = box.bounding_box()
|
bbox = box.bounding_box()
|
||||||
with Workplanes(
|
with Locations(
|
||||||
Plane(origin=(bbox.min.X, 0, bbox.max.Z - 30 * MM), z_dir=(-1, 0, 0))
|
Plane(origin=(bbox.min.X, 0, bbox.max.Z - 30 * MM), z_dir=(-1, 0, 0))
|
||||||
):
|
):
|
||||||
with GridLocations(0, 40 * MM, 1, 3):
|
with GridLocations(0, 40 * MM, 1, 3):
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ __all__ = [
|
||||||
"HexLocations",
|
"HexLocations",
|
||||||
"PolarLocations",
|
"PolarLocations",
|
||||||
"Locations",
|
"Locations",
|
||||||
"Workplanes",
|
|
||||||
"GridLocations",
|
"GridLocations",
|
||||||
"BuildLine",
|
"BuildLine",
|
||||||
"BuildPart",
|
"BuildPart",
|
||||||
|
|
|
||||||
|
|
@ -186,9 +186,9 @@ class Builder(ABC):
|
||||||
|
|
||||||
# If there are no workplanes, create a default XY plane
|
# If there are no workplanes, create a default XY plane
|
||||||
if not self.workplanes and not WorkplaneList._get_context():
|
if not self.workplanes and not WorkplaneList._get_context():
|
||||||
self.workplanes_context = Workplanes(Plane.XY).__enter__()
|
self.workplanes_context = WorkplaneList(Plane.XY).__enter__()
|
||||||
elif self.workplanes:
|
elif self.workplanes:
|
||||||
self.workplanes_context = Workplanes(*self.workplanes).__enter__()
|
self.workplanes_context = WorkplaneList(*self.workplanes).__enter__()
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
@ -541,27 +541,25 @@ class Builder(ABC):
|
||||||
and self.__class__.__name__ not in operations_apply_to[validating_class]
|
and self.__class__.__name__ not in operations_apply_to[validating_class]
|
||||||
):
|
):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"{self.__class__.__name__} doesn't have a "
|
f"({validating_class} doesn't apply to {operations_apply_to[validating_class]})"
|
||||||
f"{validating_class} object or operation "
|
|
||||||
f"({validating_class} applies to {operations_apply_to[validating_class]})"
|
|
||||||
)
|
)
|
||||||
# Check for valid object inputs
|
# Check for valid object inputs
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
|
operation = (
|
||||||
|
validating_class
|
||||||
|
if isinstance(validating_class, str)
|
||||||
|
else validating_class.__class__.__name__
|
||||||
|
)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
pass
|
pass
|
||||||
elif isinstance(obj, Builder):
|
elif isinstance(obj, Builder):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"{validating_class.__class__.__name__} doesn't accept Builders as input,"
|
f"{operation} doesn't accept Builders as input,"
|
||||||
f" did you intend <{obj.__class__.__name__}>.{obj._obj_name}?"
|
f" did you intend <{obj.__class__.__name__}>.{obj._obj_name}?"
|
||||||
)
|
)
|
||||||
elif isinstance(obj, list):
|
|
||||||
raise RuntimeError(
|
|
||||||
f"{validating_class.__class__.__name__} doesn't accept {type(obj).__name__},"
|
|
||||||
f" did you intend *{obj}?"
|
|
||||||
)
|
|
||||||
elif not isinstance(obj, Shape):
|
elif not isinstance(obj, Shape):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"{validating_class.__class__.__name__} doesn't accept {type(obj).__name__},"
|
f"{operation} doesn't accept {type(obj).__name__},"
|
||||||
f" did you intend <keyword>={obj}?"
|
f" did you intend <keyword>={obj}?"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -915,7 +913,7 @@ class WorkplaneList:
|
||||||
at all time.
|
at all time.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
planes (list[Plane]): list of planes
|
workplanes (sequence of Union[Face, Plane, Location]): objects to become planes
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -924,9 +922,16 @@ class WorkplaneList:
|
||||||
"WorkplaneList._current"
|
"WorkplaneList._current"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, planes: list[Plane]):
|
def __init__(self, *workplanes: Union[Face, Plane, Location]):
|
||||||
self._reset_tok = None
|
self._reset_tok = None
|
||||||
self.workplanes = planes
|
self.workplanes = []
|
||||||
|
for plane in workplanes:
|
||||||
|
if isinstance(plane, Plane):
|
||||||
|
self.workplanes.append(plane)
|
||||||
|
elif isinstance(plane, (Location, Face)):
|
||||||
|
self.workplanes.append(Plane(plane))
|
||||||
|
else:
|
||||||
|
raise ValueError(f"WorkplaneList does not accept {type(plane)}")
|
||||||
self.locations_context = None
|
self.locations_context = None
|
||||||
self.plane_index = 0
|
self.plane_index = 0
|
||||||
|
|
||||||
|
|
@ -998,35 +1003,6 @@ class WorkplaneList:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class Workplanes(WorkplaneList):
|
|
||||||
"""Workplane Context: Workplanes
|
|
||||||
|
|
||||||
Create workplanes from the given sequence of planes.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
objs (Union[Face, Plane, Location]): sequence of faces, planes, or
|
|
||||||
locations to use to define workplanes.
|
|
||||||
Raises:
|
|
||||||
ValueError: invalid input
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *objs: Union[Face, Plane, Location]):
|
|
||||||
# warnings.warn(
|
|
||||||
# "Workplanes may be deprecated - Post on Discord to save it",
|
|
||||||
# DeprecationWarning,
|
|
||||||
# stacklevel=2,
|
|
||||||
# )
|
|
||||||
self.workplanes = []
|
|
||||||
for obj in objs:
|
|
||||||
if isinstance(obj, Plane):
|
|
||||||
self.workplanes.append(obj)
|
|
||||||
elif isinstance(obj, (Location, Face)):
|
|
||||||
self.workplanes.append(Plane(obj))
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Workplanes does not accept {type(obj)}")
|
|
||||||
super().__init__(self.workplanes)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# To avoid import loops, Vector add & sub are monkey-patched
|
# To avoid import loops, Vector add & sub are monkey-patched
|
||||||
def _vector_add(self: Vector, vec: VectorLike) -> Vector:
|
def _vector_add(self: Vector, vec: VectorLike) -> Vector:
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ def add(
|
||||||
|
|
||||||
object_iter = objects if isinstance(objects, Iterable) else [objects]
|
object_iter = objects if isinstance(objects, Iterable) else [objects]
|
||||||
|
|
||||||
validate_inputs(context, None, object_iter)
|
validate_inputs(context, "add", object_iter)
|
||||||
|
|
||||||
if isinstance(context, BuildPart):
|
if isinstance(context, BuildPart):
|
||||||
if rotation is None:
|
if rotation is None:
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ license:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import unittest
|
import unittest
|
||||||
|
from math import pi
|
||||||
from build123d import *
|
from build123d import *
|
||||||
from build123d import Builder, WorkplaneList, LocationList
|
from build123d import Builder, WorkplaneList, LocationList
|
||||||
|
|
||||||
|
|
@ -233,18 +234,30 @@ class TestShapeList(unittest.TestCase):
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
self.assertEqual(len(test.part.vertices()), 8)
|
self.assertEqual(len(test.part.vertices()), 8)
|
||||||
self.assertTrue(isinstance(test.part.vertices(), ShapeList))
|
self.assertTrue(isinstance(test.part.vertices(), ShapeList))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
with BuildPart() as test:
|
||||||
|
Box(1, 1, 1)
|
||||||
|
v = test.vertices("ALL")
|
||||||
|
|
||||||
def test_edges(self):
|
def test_edges(self):
|
||||||
with BuildPart() as test:
|
with BuildPart() as test:
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
self.assertEqual(len(test.part.edges()), 12)
|
self.assertEqual(len(test.part.edges()), 12)
|
||||||
self.assertTrue(isinstance(test.part.edges(), ShapeList))
|
self.assertTrue(isinstance(test.part.edges(), ShapeList))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
with BuildPart() as test:
|
||||||
|
Box(1, 1, 1)
|
||||||
|
v = test.edges("ALL")
|
||||||
|
|
||||||
def test_wires(self):
|
def test_wires(self):
|
||||||
with BuildPart() as test:
|
with BuildPart() as test:
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
self.assertEqual(len(test.wires()), 6)
|
self.assertEqual(len(test.wires()), 6)
|
||||||
self.assertTrue(isinstance(test.wires(), ShapeList))
|
self.assertTrue(isinstance(test.wires(), ShapeList))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
with BuildPart() as test:
|
||||||
|
Box(1, 1, 1)
|
||||||
|
v = test.wires("ALL")
|
||||||
|
|
||||||
def test_wires_last(self):
|
def test_wires_last(self):
|
||||||
with BuildPart() as test:
|
with BuildPart() as test:
|
||||||
|
|
@ -258,12 +271,20 @@ class TestShapeList(unittest.TestCase):
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
self.assertEqual(len(test.part.faces()), 6)
|
self.assertEqual(len(test.part.faces()), 6)
|
||||||
self.assertTrue(isinstance(test.part.faces(), ShapeList))
|
self.assertTrue(isinstance(test.part.faces(), ShapeList))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
with BuildPart() as test:
|
||||||
|
Box(1, 1, 1)
|
||||||
|
v = test.faces("ALL")
|
||||||
|
|
||||||
def test_solids(self):
|
def test_solids(self):
|
||||||
with BuildPart() as test:
|
with BuildPart() as test:
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
self.assertEqual(len(test.part.solids()), 1)
|
self.assertEqual(len(test.part.solids()), 1)
|
||||||
self.assertTrue(isinstance(test.part.solids(), ShapeList))
|
self.assertTrue(isinstance(test.part.solids(), ShapeList))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
with BuildPart() as test:
|
||||||
|
Box(1, 1, 1)
|
||||||
|
v = test.solids("ALL")
|
||||||
|
|
||||||
def test_compounds(self):
|
def test_compounds(self):
|
||||||
with BuildPart() as test:
|
with BuildPart() as test:
|
||||||
|
|
@ -271,6 +292,11 @@ class TestShapeList(unittest.TestCase):
|
||||||
self.assertEqual(len(test.part.compounds()), 1)
|
self.assertEqual(len(test.part.compounds()), 1)
|
||||||
self.assertTrue(isinstance(test.part.compounds(), ShapeList))
|
self.assertTrue(isinstance(test.part.compounds(), ShapeList))
|
||||||
|
|
||||||
|
def test_shapes(self):
|
||||||
|
with BuildPart() as test:
|
||||||
|
Box(1, 1, 1)
|
||||||
|
self.assertIsNone(test._shapes(Compound))
|
||||||
|
|
||||||
|
|
||||||
class TestBuilder(unittest.TestCase):
|
class TestBuilder(unittest.TestCase):
|
||||||
"""Test the Builder base class"""
|
"""Test the Builder base class"""
|
||||||
|
|
@ -287,73 +313,23 @@ class TestBuilder(unittest.TestCase):
|
||||||
make_face()
|
make_face()
|
||||||
self.assertEqual(len(outer.pending_faces), 2)
|
self.assertEqual(len(outer.pending_faces), 2)
|
||||||
|
|
||||||
|
def test_plane_with_no_x(self):
|
||||||
class TestWorkplanes(unittest.TestCase):
|
with BuildPart() as p:
|
||||||
def test_named(self):
|
|
||||||
with Workplanes(Plane.XY) as test:
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
test.workplanes[0].origin.to_tuple(), (0, 0, 0), 5
|
|
||||||
)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
test.workplanes[0].z_dir.to_tuple(), (0, 0, 1), 5
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_locations(self):
|
|
||||||
with Workplanes(Plane.XY):
|
|
||||||
with Locations((0, 0, 1), (0, 0, 2)) as l:
|
|
||||||
with Workplanes(*l.locations) as w:
|
|
||||||
origins = [p.origin.to_tuple() for p in w.workplanes]
|
|
||||||
self.assertTupleAlmostEquals(origins[0], (0, 0, 1), 5)
|
|
||||||
self.assertTupleAlmostEquals(origins[1], (0, 0, 2), 5)
|
|
||||||
self.assertEqual(len(origins), 2)
|
|
||||||
|
|
||||||
def test_grid_locations(self):
|
|
||||||
with Workplanes(Plane(origin=(1, 2, 3))):
|
|
||||||
locs = GridLocations(4, 6, 2, 2).locations
|
|
||||||
self.assertTupleAlmostEquals(locs[0].position.to_tuple(), (-1, -1, 3), 5)
|
|
||||||
self.assertTupleAlmostEquals(locs[1].position.to_tuple(), (-1, 5, 3), 5)
|
|
||||||
self.assertTupleAlmostEquals(locs[2].position.to_tuple(), (3, -1, 3), 5)
|
|
||||||
self.assertTupleAlmostEquals(locs[3].position.to_tuple(), (3, 5, 3), 5)
|
|
||||||
|
|
||||||
def test_conversions(self):
|
|
||||||
loc = Location((1, 2, 3), (23, 45, 67))
|
|
||||||
loc2 = Workplanes(loc).workplanes[0].to_location()
|
|
||||||
self.assertTupleAlmostEquals(loc.to_tuple()[0], loc2.to_tuple()[0], 6)
|
|
||||||
self.assertTupleAlmostEquals(loc.to_tuple()[1], loc2.to_tuple()[1], 6)
|
|
||||||
|
|
||||||
loc = Location((-10, -2, 30), (-123, 145, 267))
|
|
||||||
face = Face.make_rect(1, 1).move(loc)
|
|
||||||
loc2 = Workplanes(face).workplanes[0].to_location()
|
|
||||||
face2 = Face.make_rect(1, 1).move(loc2)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
face.center().to_tuple(), face2.center().to_tuple(), 6
|
|
||||||
)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
face.normal_at(face.center()).to_tuple(),
|
|
||||||
face2.normal_at(face2.center()).to_tuple(),
|
|
||||||
6,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_bad_plane(self):
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
with BuildPart(4):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_locations_after_new_workplane(self):
|
|
||||||
with BuildPart(Plane.XY):
|
|
||||||
with Locations((0, 1, 2), (3, 4, 5)):
|
|
||||||
with BuildPart(Plane.XY.offset(2)):
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
LocationList._get_context().locations[0].position.to_tuple(),
|
|
||||||
(0, 0, 2),
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
|
front = p.faces().sort_by(Axis.X)[-1]
|
||||||
|
with BuildSketch(front):
|
||||||
|
offset(front, amount=-0.1)
|
||||||
|
extrude(amount=0.1)
|
||||||
|
self.assertAlmostEqual(p.part.volume, 1**3 + 0.1 * (1 - 2 * 0.1) ** 2, 4)
|
||||||
|
|
||||||
|
def test_no_workplane(self):
|
||||||
|
with BuildSketch() as s:
|
||||||
|
Circle(1)
|
||||||
|
|
||||||
|
|
||||||
class TestWorkplaneList(unittest.TestCase):
|
class TestWorkplaneList(unittest.TestCase):
|
||||||
def test_iter(self):
|
def test_iter(self):
|
||||||
for i, plane in enumerate(WorkplaneList([Plane.XY, Plane.YZ])):
|
for i, plane in enumerate(WorkplaneList(Plane.XY, Plane.YZ)):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
self.assertTrue(plane == Plane.XY)
|
self.assertTrue(plane == Plane.XY)
|
||||||
elif i == 1:
|
elif i == 1:
|
||||||
|
|
@ -365,35 +341,47 @@ class TestWorkplaneList(unittest.TestCase):
|
||||||
self.assertTupleAlmostEquals(pnts[0].to_tuple(), (0, 1, 2), 5)
|
self.assertTupleAlmostEquals(pnts[0].to_tuple(), (0, 1, 2), 5)
|
||||||
self.assertTupleAlmostEquals(pnts[1].to_tuple(), (0, 2, 3), 5)
|
self.assertTupleAlmostEquals(pnts[1].to_tuple(), (0, 2, 3), 5)
|
||||||
|
|
||||||
|
def test_invalid_workplane(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
WorkplaneList(Vector(1, 1, 1))
|
||||||
|
|
||||||
|
|
||||||
class TestValidateInputs(unittest.TestCase):
|
class TestValidateInputs(unittest.TestCase):
|
||||||
# def test_no_builder(self):
|
|
||||||
# with self.assertRaises(RuntimeError):
|
|
||||||
# Circle(1)
|
|
||||||
|
|
||||||
def test_wrong_builder(self):
|
def test_wrong_builder(self):
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError) as rte:
|
||||||
with BuildPart():
|
with BuildPart():
|
||||||
Circle(1)
|
Circle(1)
|
||||||
|
self.assertEqual(
|
||||||
|
"BuildPart doesn't have a Circle object or operation (Circle applies to ['BuildSketch'])",
|
||||||
|
str(rte.exception),
|
||||||
|
)
|
||||||
|
|
||||||
def test_bad_builder_input(self):
|
def test_bad_builder_input(self):
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError) as rte:
|
||||||
with BuildPart() as p:
|
with BuildPart() as p:
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
with BuildSketch():
|
with BuildSketch():
|
||||||
add(p)
|
add(p)
|
||||||
|
self.assertEqual(
|
||||||
|
"add doesn't accept Builders as input, did you intend <BuildPart>.part?",
|
||||||
|
str(rte.exception),
|
||||||
|
)
|
||||||
|
|
||||||
def test_no_sequence(self):
|
def test_no_sequence(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError) as rte:
|
||||||
with BuildPart() as p:
|
with BuildPart() as p:
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
fillet([None, None], radius=1)
|
fillet([None, None], radius=1)
|
||||||
|
self.assertEqual("3D fillet operation takes only Edges", str(rte.exception))
|
||||||
|
|
||||||
def test_wrong_type(self):
|
def test_wrong_type(self):
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError) as rte:
|
||||||
with BuildPart() as p:
|
with BuildPart() as p:
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
fillet(4, radius=1)
|
fillet(4, radius=1)
|
||||||
|
self.assertEqual(
|
||||||
|
"fillet doesn't accept int, did you intend <keyword>=4?", str(rte.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestBuilderExit(unittest.TestCase):
|
class TestBuilderExit(unittest.TestCase):
|
||||||
|
|
@ -522,6 +510,23 @@ class TestLocations(unittest.TestCase):
|
||||||
loc.orientation.to_tuple(), Location(Plane.XZ).orientation.to_tuple(), 5
|
loc.orientation.to_tuple(), Location(Plane.XZ).orientation.to_tuple(), 5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_from_plane(self):
|
||||||
|
with BuildPart():
|
||||||
|
loc = Locations(Plane.XY.offset(1)).locations[0]
|
||||||
|
self.assertTupleAlmostEquals(loc.position.to_tuple(), (0, 0, 1), 5)
|
||||||
|
|
||||||
|
def test_from_axis(self):
|
||||||
|
with BuildPart():
|
||||||
|
loc = Locations(Axis((1, 1, 1), (0, 0, 1))).locations[0]
|
||||||
|
self.assertTupleAlmostEquals(loc.position.to_tuple(), (1, 1, 1), 5)
|
||||||
|
|
||||||
|
def test_multiplication(self):
|
||||||
|
circles = GridLocations(2, 2, 2, 2) * Circle(1)
|
||||||
|
self.assertEqual(len(circles), 4)
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
GridLocations(2, 2, 2, 2) * "error"
|
||||||
|
|
||||||
|
|
||||||
class TestVectorExtensions(unittest.TestCase):
|
class TestVectorExtensions(unittest.TestCase):
|
||||||
def test_vector_localization(self):
|
def test_vector_localization(self):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue