De-emphasizing Workplanes

This commit is contained in:
Roger Maitland 2023-03-07 10:32:37 -05:00
parent caa72c7430
commit af52e032be
19 changed files with 131 additions and 122 deletions

View file

@ -1,26 +1,37 @@
import build123d as bd
"""
# from cadquery import exporters
name: boxes_on_faces.py
by: Gumyr
date: March 6th 2023
desc: Demo adding features to multiple faces in one operation.
license:
Copyright 2023 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.
"""
import build123d as bd
with bd.BuildPart() as bp:
bd.Box(3, 3, 3)
with bd.Workplanes(*bp.faces()):
bd.Box(1, 2, 0.1, rotation=(0, 0, 45))
with bd.BuildSketch(*bp.faces()):
bd.Rectangle(1, 2, rotation=45)
bd.Extrude(amount=0.1)
# exporters.export(
# bp.part,
# "boxes_on_faces.svg",
# opt={
# "width": 250,
# "height": 250,
# "marginLeft": 30,
# "marginTop": 30,
# "showAxes": False,
# "projectionDir": (1, 1, 1),
# # "strokeWidth": 0.1,
# "showHidden": False,
# },
# )
assert abs(bp.part.volume - (3**3 + 6 * (1 * 2 * 0.1)) < 1e-5)
if "show_object" in locals():
show_object(bp.part.wrapped, name="box on faces")

View file

@ -69,11 +69,12 @@ with BuildPart(Plane.XZ) as rail:
)
Fillet(*outside_vertices, radius=fillet + thickness)
Extrude(amount=rail_length)
with Workplanes(rail.faces().filter_by(Axis.Z)[-1]):
with BuildSketch() as slots:
with BuildSketch(rail.faces().filter_by(Axis.Z)[-1]) as slots:
with GridLocations(0, slot_pitch, 1, rail_length // slot_pitch - 1):
SlotOverall(slot_length, slot_width, rotation=90)
Extrude(amount=-height, mode=Mode.SUBTRACT)
assert abs(rail.part.volume - 42462.863388694714) < 1e-5
if "show_object" in locals():
show_object(rail.part.wrapped, name="rail")

View file

@ -42,9 +42,8 @@ with BuildPart() as both:
# Extrude multiple pending faces on multiple faces
with BuildPart() as multiple:
Box(10, 10, 10)
with Workplanes(*multiple.faces()):
with BuildSketch(*multiple.faces()):
with GridLocations(5, 5, 2, 2):
with BuildSketch():
Text("Ω", font_size=3)
Extrude(amount=1)

View file

@ -43,13 +43,12 @@ with BuildPart() as handle:
# Create the cross sections - added to pending_faces
for i in range(segment_count + 1):
with Workplanes(
with BuildSketch(
Plane(
origin=handle_path @ (i / segment_count),
z_dir=handle_path % (i / segment_count),
)
):
with BuildSketch() as section:
) as section:
if i % segment_count == 0:
Circle(1)
else:
@ -61,6 +60,7 @@ with BuildPart() as handle:
# Create the handle by sweeping along the path
Sweep(multisection=True)
assert abs(handle.part.volume - 94.77361455046953) < 1e-5
if "show_object" in locals():
show_object(handle_path.wrapped, name="handle_path")

View file

@ -38,8 +38,9 @@ bundle_diameter = exchanger_diameter - 2 * tube_diameter
fillet_radius = tube_spacing / 3
assert tube_extension > fillet_radius
# Generate list of tube locations
with Workplanes(Plane.XY):
# Build the heat exchanger
with BuildPart() as heat_exchanger:
# Generate list of tube locations
tube_locations = [
l
for l in HexLocations(
@ -49,10 +50,7 @@ with Workplanes(Plane.XY):
)
if l.position.length < bundle_diameter / 2
]
tube_count = len(tube_locations)
# Build the heat exchanger
with BuildPart() as heat_exchanger:
tube_count = len(tube_locations)
with BuildSketch() as tube_plan:
with Locations(*tube_locations):
Circle(radius=tube_diameter / 2)

View file

@ -36,16 +36,17 @@ from build123d import *
# logging.info("Starting pipes test")
with BuildPart() as pipes:
Box(10, 10, 10, rotation=(10, 20, 30))
with Workplanes(*pipes.faces()):
with BuildSketch() as pipe:
box = Box(10, 10, 10, rotation=(10, 20, 30))
with BuildSketch(*box.faces()) as pipe:
Circle(4)
Extrude(amount=-5, mode=Mode.SUBTRACT)
with BuildSketch() as pipe:
with BuildSketch(*box.faces()) as pipe:
Circle(4.5)
Circle(4, mode=Mode.SUBTRACT)
Extrude(amount=10)
Fillet(*pipes.edges(Select.LAST), radius=0.2)
assert abs(pipes.part.volume - 1015.939005681509) < 1e-5
if "show_object" in locals():
show_object(pipes.part.wrapped, name="intersecting pipes")

View file

@ -48,8 +48,7 @@ with BuildPart() as key_cap:
Scale(by=(0.925, 0.925, 0.85), mode=Mode.SUBTRACT)
# Add supporting ribs while leaving room for switch activation
with Workplanes(Plane(origin=(0, 0, 4 * MM))):
with BuildSketch():
with BuildSketch(Plane(origin=(0, 0, 4 * MM))):
Rectangle(15 * MM, 0.5 * MM)
Rectangle(0.5 * MM, 15 * MM)
Circle(radius=5.5 * MM / 2)
@ -58,12 +57,13 @@ with BuildPart() as key_cap:
# Find the face on the bottom of the ribs to build onto
rib_bottom = key_cap.faces().filter_by_position(Axis.Z, 4 * MM, 4 * MM)[0]
# Add the switch socket
with Workplanes(rib_bottom):
with BuildSketch() as cruciform:
with BuildSketch(rib_bottom) as cruciform:
Circle(radius=5.5 * MM / 2)
Rectangle(4.1 * MM, 1.17 * MM, mode=Mode.SUBTRACT)
Rectangle(1.17 * MM, 4.1 * MM, mode=Mode.SUBTRACT)
Extrude(amount=3.5 * MM, mode=Mode.ADD)
assert abs(key_cap.part.volume - 644.8900473617498) < 1e-5
if "show_object" in locals():
show_object(key_cap.part.wrapped, name="key cap", options={"alpha": 0.7})

View file

@ -118,7 +118,7 @@ with BuildPart() as lego:
},
)
# Create a workplane on the top of the block
with Workplanes(lego.faces().sort_by(Axis.Z)[-1]):
with BuildPart(lego.faces().sort_by(Axis.Z)[-1]):
# Create a grid of pips
with GridLocations(lego_unit_size, lego_unit_size, pip_count, 2):
Cylinder(
@ -140,6 +140,7 @@ with BuildPart() as lego:
},
)
assert abs(lego.part.volume - 3212.187337781355) < 1e-5
if "show_object" in locals():
show_object(lego.part.wrapped, name="lego")

View file

@ -31,12 +31,13 @@ from build123d import *
with BuildPart() as art:
slice_count = 10
for i in range(slice_count + 1):
with Workplanes(Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))):
with BuildSketch() as slice:
with BuildSketch(Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))) as slice:
Circle(10 * sin(i * pi / slice_count) + 5)
Loft()
top_bottom = art.faces().filter_by(GeomType.PLANE)
Offset(openings=top_bottom, amount=0.5)
assert abs(art.part.volume - 1306.3405290344635) < 1e-5
if "show_object" in locals():
show_object(art.part.wrapped, name="art")

View file

@ -2,8 +2,10 @@ from build123d import *
with BuildPart() as obj:
Box(5, 5, 1)
with Workplanes(*obj.faces().filter_by(Axis.Z)):
Sphere(1.8, mode=Mode.SUBTRACT)
with BuildPart(*obj.faces().filter_by(Axis.Z), mode=Mode.SUBTRACT):
Sphere(1.8)
assert abs(obj.part.volume - 15.083039190168236) < 1e-5
if "show_object" in locals():
show_object(obj.part)

View file

@ -37,7 +37,7 @@ with BuildPart() as pillow_block:
Rectangle(width, height)
Fillet(*plan.vertices(), radius=5)
Extrude(amount=thickness)
with Workplanes(pillow_block.faces().filter_by(Axis.Z)[-1]):
with Locations((0, 0, thickness)):
CounterBoreHole(bearing_axle_radius, bearing_radius, bearing_thickness)
with GridLocations(width - 2 * padding, height - 2 * padding, 2, 2):
CounterBoreHole(screw_shaft_radius, screw_head_radius, screw_head_height)

View file

@ -118,6 +118,8 @@ class Add(Compound):
new_edges.extend(new_wire.edges())
# Add the pending Edges in one group
if not LocationList._get_context():
raise RuntimeError("There is no active Locations context")
located_edges = [
edge.moved(location)
for edge in new_edges

View file

@ -194,10 +194,6 @@ class BuildLine(Builder):
@classmethod
def _get_context(cls, caller=None) -> "BuildLine":
"""Return the instance of the current builder"""
logger.info(
"Context requested by %s",
type(inspect.currentframe().f_back.f_locals["self"]).__name__,
)
result = cls._current.get(None)
if caller is not None and result is None:
@ -207,6 +203,11 @@ class BuildLine(Builder):
)
raise RuntimeError("No valid context found")
logger.info(
"Context requested by %s",
type(inspect.currentframe().f_back.f_locals["self"]).__name__,
)
return result

View file

@ -233,10 +233,6 @@ class BuildPart(Builder):
@classmethod
def _get_context(cls, caller=None) -> BuildPart:
"""Return the instance of the current builder"""
logger.info(
"Context requested by %s",
type(inspect.currentframe().f_back.f_locals["self"]).__name__,
)
result = cls._current.get(None)
if caller is not None and result is None:
@ -246,6 +242,11 @@ class BuildPart(Builder):
)
raise RuntimeError("No valid context found")
logger.info(
"Context requested by %s",
type(inspect.currentframe().f_back.f_locals["self"]).__name__,
)
return result
@ -291,6 +292,8 @@ class BasePartObject(Compound):
align_offset.append(-bbox.max.to_tuple()[i])
solid.move(Location(Vector(*align_offset)))
if not LocationList._get_context():
raise RuntimeError("No valid context found")
new_solids = [
solid.moved(location * rotate)
for location in LocationList._get_context().locations

View file

@ -8,15 +8,6 @@ date: July 12th 2022
desc:
This python module is a library used to build planar sketches.
Instead of existing constraints how about constraints that return locations
on objects:
- two circles: c1, c2
- "line tangent to c1 & c2" : 4 locations on each circle
- these would be construction geometry
- user sorts to select the ones they want
- uses these points to build geometry
- how many constraints are currently implemented?
license:
Copyright 2022 Gumyr
@ -209,10 +200,6 @@ class BuildSketch(Builder):
@classmethod
def _get_context(cls, caller=None) -> BuildSketch:
"""Return the instance of the current builder"""
logger.info(
"Context requested by %s",
type(inspect.currentframe().f_back.f_locals["self"]).__name__,
)
result = cls._current.get(None)
if caller is not None and result is None:
@ -222,6 +209,11 @@ class BuildSketch(Builder):
)
raise RuntimeError("No valid context found")
logger.info(
"Context requested by %s",
type(inspect.currentframe().f_back.f_locals["self"]).__name__,
)
return result

View file

@ -343,18 +343,19 @@ class TestWorkplanes(unittest.TestCase):
def test_bad_plane(self):
with self.assertRaises(ValueError):
with Workplanes(4):
with BuildPart(4):
pass
def test_locations_after_new_workplane(self):
with Workplanes(Plane.XY):
with BuildPart(Plane.XY):
with Locations((0, 1, 2), (3, 4, 5)):
with Workplanes(Plane.XY.offset(2)):
with BuildPart(Plane.XY.offset(2)):
self.assertTupleAlmostEquals(
LocationList._get_context().locations[0].position.to_tuple(),
(0, 0, 2),
5,
)
Box(1, 1, 1)
class TestWorkplaneList(unittest.TestCase):
@ -366,8 +367,8 @@ class TestWorkplaneList(unittest.TestCase):
self.assertTrue(plane == Plane.YZ)
def test_localize(self):
with Workplanes(Plane.YZ):
pnts = Workplanes._get_context().localize((1, 2), (2, 3))
with BuildLine(Plane.YZ):
pnts = WorkplaneList.localize((1, 2), (2, 3))
self.assertTupleAlmostEquals(pnts[0].to_tuple(), (0, 1, 2), 5)
self.assertTupleAlmostEquals(pnts[1].to_tuple(), (0, 2, 3), 5)

View file

@ -214,10 +214,10 @@ class BuildLineTests(unittest.TestCase):
bl.solids()
def test_no_applies_to(self):
with self.assertRaises(RuntimeError):
BuildLine._get_context(
Compound.make_compound([Face.make_rect(1, 1)]).wrapped
)
# with self.assertRaises(RuntimeError):
# BuildLine._get_context(
# Compound.make_compound([Face.make_rect(1, 1)]).wrapped
# )
with self.assertRaises(RuntimeError):
Line((0, 0), (1, 1))

View file

@ -87,8 +87,7 @@ class TestBuildPart(unittest.TestCase):
with BuildPart() as test:
Box(10, 10, 10)
self.assertEqual(len(test.faces()), 6)
with Workplanes(test.faces().filter_by(Axis.Z)[-1]):
with BuildSketch():
with BuildSketch(test.faces().filter_by(Axis.Z)[-1]):
Rectangle(5, 5)
Extrude(amount=5)
self.assertEqual(len(test.faces()), 11)
@ -133,8 +132,7 @@ class TestBuildPart(unittest.TestCase):
def test_add_pending_faces(self):
with BuildPart() as test:
Box(100, 100, 100)
with Workplanes(*test.faces()):
with BuildSketch():
with BuildSketch(*test.faces()):
with PolarLocations(10, 5):
Circle(2)
self.assertEqual(len(test.pending_faces), 30)
@ -182,10 +180,10 @@ class TestBuildPartExceptions(unittest.TestCase):
Sphere(10, mode=Mode.INTERSECT)
def test_no_applies_to(self):
with self.assertRaises(RuntimeError):
BuildPart._get_context(
Compound.make_compound([Face.make_rect(1, 1)]).wrapped
)
# with self.assertRaises(RuntimeError):
# BuildPart._get_context(
# Compound.make_compound([Face.make_rect(1, 1)]).wrapped
# )
with self.assertRaises(RuntimeError):
Box(1, 1, 1)
@ -289,8 +287,7 @@ class TestLoft(unittest.TestCase):
with BuildPart() as test:
slice_count = 10
for i in range(slice_count + 1):
with Workplanes(Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))):
with BuildSketch():
with BuildSketch(Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))):
Circle(10 * sin(i * pi / slice_count) + 5)
Loft()
self.assertLess(test.part.volume, 225 * pi * 30, 5)
@ -427,13 +424,12 @@ class TestSweep(unittest.TestCase):
)
handle_path = handle_center_line.wires()[0]
for i in range(segment_count + 1):
with Workplanes(
with BuildSketch(
Plane(
origin=handle_path @ (i / segment_count),
z_dir=handle_path % (i / segment_count),
)
):
with BuildSketch() as section:
) as section:
if i % segment_count == 0:
Circle(1)
else:

View file

@ -141,10 +141,10 @@ class TestBuildSketchExceptions(unittest.TestCase):
Circle(10, mode=Mode.INTERSECT)
def test_no_applies_to(self):
with self.assertRaises(RuntimeError):
BuildSketch._get_context(
Compound.make_compound([Face.make_rect(1, 1)]).wrapped
)
# with self.assertRaises(RuntimeError):
# BuildSketch._get_context(
# Compound.make_compound([Face.make_rect(1, 1)]).wrapped
# )
with self.assertRaises(RuntimeError):
Circle(1)