diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 2bccaa64..fdb5ebac 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -14,5 +14,5 @@ runs: - name: Install Requirements shell: bash run: | - pip install wheel mypy pytest pytest-benchmark pytest-cov pylint + pip install wheel mypy pytest pytest-cov pylint pip install . diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..7332a561 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,26 @@ +name: benchmarks + +on: [push, pull_request, workflow_dispatch] +jobs: + + tests: + strategy: + fail-fast: false + matrix: + python-version: [ + # "3.10", + # "3.11", + "3.12", + ] + os: [macos-13, macos-14, ubuntu-latest, windows-latest] + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup/ + with: + python-version: ${{ matrix.python-version }} + - name: benchmark + run: | + pip install pytest-benchmark + python -m pytest --benchmark-only diff --git a/tests/test_benchmarks.py b/tests/test_benchmarks.py new file mode 100644 index 00000000..7f9484ff --- /dev/null +++ b/tests/test_benchmarks.py @@ -0,0 +1,677 @@ +import pytest +import importlib +from math import sqrt +from build123d import * + + +pytest_benchmark = pytest.importorskip("pytest_benchmark") + +def test_ppp_0101(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-01 Bearing Bracket + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + with BuildPart() as p: + with BuildSketch() as s: + Rectangle(115, 50) + with Locations((5 / 2, 0)): + SlotOverall(90, 12, mode=Mode.SUBTRACT) + extrude(amount=15) + + with BuildSketch(Plane.XZ.offset(50 / 2)) as s3: + with Locations((-115 / 2 + 26, 15)): + SlotOverall(42 + 2 * 26 + 12, 2 * 26, rotation=90) + zz = extrude(amount=-12) + split(bisect_by=Plane.XY) + edgs = p.part.edges().filter_by(Axis.Y).group_by(Axis.X)[-2] + fillet(edgs, 9) + + with Locations(zz.faces().sort_by(Axis.Y)[0]): + with Locations((42 / 2 + 6, 0)): + CounterBoreHole(24 / 2, 34 / 2, 4) + mirror(about=Plane.XZ) + + with BuildSketch() as s4: + RectangleRounded(115, 50, 6) + extrude(amount=80, mode=Mode.INTERSECT) + # fillet does not work right, mode intersect is safer + + with BuildSketch(Plane.YZ) as s4: + with BuildLine() as bl: + l1 = Line((0, 0), (18 / 2, 0)) + l2 = PolarLine(l1 @ 1, 8, 60, length_mode=LengthMode.VERTICAL) + l3 = Line(l2 @ 1, (0, 8)) + mirror(about=Plane.YZ) + make_face() + extrude(amount=115 / 2, both=True, mode=Mode.SUBTRACT) + + print(f"\npart mass = {p.part.volume*densa:0.2f}") + assert p.part.volume * densa == pytest.approx(797.15, 0.01) + + benchmark(model) + + +def test_ppp_0102(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-02 Post Cap + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + # TTT Party Pack 01: PPP0102, mass(abs) = 43.09g + with BuildPart() as p: + with BuildSketch(Plane.XZ) as sk1: + Rectangle(49, 48 - 8, align=(Align.CENTER, Align.MIN)) + Rectangle(9, 48, align=(Align.CENTER, Align.MIN)) + with Locations((9 / 2, 40)): + Ellipse(20, 8) + split(bisect_by=Plane.YZ) + revolve(axis=Axis.Z) + + with BuildSketch(Plane.YZ.offset(-15)) as xc1: + with Locations((0, 40 / 2 - 17)): + Ellipse(10 / 2, 4 / 2) + with BuildLine(Plane.XZ) as l1: + CenterArc((-15, 40 / 2), 17, 90, 180) + sweep(path=l1) + + fillet( + p.edges().filter_by(GeomType.CIRCLE, reverse=True).group_by(Axis.X)[0], + 1, + ) + + with BuildLine(mode=Mode.PRIVATE) as lc1: + PolarLine( + (42 / 2, 0), 37, 94, length_mode=LengthMode.VERTICAL + ) # construction line + + pts = [ + (0, 0), + (42 / 2, 0), + ((lc1.line @ 1).X, (lc1.line @ 1).Y), + (0, (lc1.line @ 1).Y), + ] + with BuildSketch(Plane.XZ) as sk2: + Polygon(*pts, align=None) + fillet(sk2.vertices().group_by(Axis.X)[1], 3) + revolve(axis=Axis.Z, mode=Mode.SUBTRACT) + + # print(f"\npart mass = {p.part.volume*densa:0.2f}") + assert p.part.volume * densc == pytest.approx(43.09, 0.01) + + benchmark(model) + + +def test_ppp_0103(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-03 C Clamp Base + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + with BuildPart() as ppp0103: + with BuildSketch() as sk1: + RectangleRounded(34 * 2, 95, 18) + with Locations((0, -2)): + RectangleRounded((34 - 16) * 2, 95 - 18 - 14, 7, mode=Mode.SUBTRACT) + with Locations((-34 / 2, 0)): + Rectangle(34, 95, 0, mode=Mode.SUBTRACT) + extrude(amount=16) + with BuildSketch(Plane.XZ.offset(-95 / 2)) as cyl1: + with Locations((0, 16 / 2)): + Circle(16 / 2) + extrude(amount=18) + with BuildSketch(Plane.XZ.offset(95 / 2 - 14)) as cyl2: + with Locations((0, 16 / 2)): + Circle(16 / 2) + extrude(amount=23) + with Locations(Plane.XZ.offset(95 / 2 + 9)): + with Locations((0, 16 / 2)): + CounterSinkHole(5.5 / 2, 11.2 / 2, None, 90) + + assert ppp0103.part.volume * densb == pytest.approx(96.13, 0.01) + + benchmark(model) + + +def test_ppp_0104(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-04 Angle Bracket + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + d1, d2, d3 = 38, 26, 16 + h1, h2, h3, h4 = 20, 8, 7, 23 + w1, w2, w3 = 80, 10, 5 + f1, f2, f3 = 4, 10, 5 + sloth1, sloth2 = 18, 12 + slotw1, slotw2 = 17, 14 + + with BuildPart() as p: + with BuildSketch() as s: + Circle(d1 / 2) + extrude(amount=h1) + with BuildSketch(Plane.XY.offset(h1)) as s2: + Circle(d2 / 2) + extrude(amount=h2) + with BuildSketch(Plane.YZ) as s3: + Rectangle(d1 + 15, h3, align=(Align.CENTER, Align.MIN)) + extrude(amount=w1 - d1 / 2) + # fillet workaround \/ + ped = p.part.edges().group_by(Axis.Z)[2].filter_by(GeomType.CIRCLE) + fillet(ped, f1) + with BuildSketch(Plane.YZ) as s3a: + Rectangle(d1 + 15, 15, align=(Align.CENTER, Align.MIN)) + Rectangle(d1, 15, mode=Mode.SUBTRACT, align=(Align.CENTER, Align.MIN)) + extrude(amount=w1 - d1 / 2, mode=Mode.SUBTRACT) + # end fillet workaround /\ + with BuildSketch() as s4: + Circle(d3 / 2) + extrude(amount=h1 + h2, mode=Mode.SUBTRACT) + with BuildSketch() as s5: + with Locations((w1 - d1 / 2 - w2 / 2, 0)): + Rectangle(w2, d1) + extrude(amount=-h4) + fillet(p.part.edges().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1], f2) + fillet(p.part.edges().group_by(Axis.X)[-4].sort_by(Axis.Z)[-2], f3) + pln = Plane.YZ.offset(w1 - d1 / 2) + with BuildSketch(pln) as s6: + with Locations((0, -h4)): + SlotOverall(slotw1 * 2, sloth1, 90) + extrude(amount=-w3, mode=Mode.SUBTRACT) + with BuildSketch(pln) as s6b: + with Locations((0, -h4)): + SlotOverall(slotw2 * 2, sloth2, 90) + extrude(amount=-w2, mode=Mode.SUBTRACT) + + # print(f"\npart mass = {p.part.volume*densa:0.2f}") + assert p.part.volume * densa == pytest.approx(310.00, 0.01) + + benchmark(model) + + +def test_ppp_0105(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-05 Paste Sleeve + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + with BuildPart() as p: + with BuildSketch() as s: + SlotOverall(45, 38) + offset(amount=3) + with BuildSketch(Plane.XY.offset(133 - 30)) as s2: + SlotOverall(60, 4) + offset(amount=3) + loft() + + with BuildSketch() as s3: + SlotOverall(45, 38) + with BuildSketch(Plane.XY.offset(133 - 30)) as s4: + SlotOverall(60, 4) + loft(mode=Mode.SUBTRACT) + + extrude(p.part.faces().sort_by(Axis.Z)[0], amount=30) + + # print(f"\npart mass = {p.part.volume*densc:0.2f}") + assert p.part.volume * densc == pytest.approx(57.08, 0.01) + + benchmark(model) + + +def test_ppp_0106(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-06 Bearing Jig + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + r1, r2, r3, r4, r5 = 30 / 2, 13 / 2, 12 / 2, 10, 6 # radii used + x1 = 44 # lengths used + y1, y2, y3, y4, y_tot = 36, 36 - 22 / 2, 22 / 2, 42, 69 # widths used + + with BuildSketch(Location((0, -r1, y3))) as sk_body: + with BuildLine() as l: + c1 = Line((r1, 0), (r1, y_tot), mode=Mode.PRIVATE) # construction line + m1 = Line((0, y_tot), (x1 / 2, y_tot)) + m2 = JernArc(m1 @ 1, m1 % 1, r4, -90 - 45) + m3 = IntersectingLine(m2 @ 1, m2 % 1, c1) + m4 = Line(m3 @ 1, (r1, r1)) + m5 = JernArc(m4 @ 1, m4 % 1, r1, -90) + m6 = Line(m5 @ 1, m1 @ 0) + mirror(make_face(l.line), Plane.YZ) + fillet(sk_body.vertices().group_by(Axis.Y)[1], 12) + with Locations((x1 / 2, y_tot - 10), (-x1 / 2, y_tot - 10)): + Circle(r2, mode=Mode.SUBTRACT) + # Keyway + with Locations((0, r1)): + Circle(r3, mode=Mode.SUBTRACT) + Rectangle(4, 3 + 6, align=(Align.CENTER, Align.MIN), mode=Mode.SUBTRACT) + + with BuildPart() as p: + Box(200, 200, 22) # Oversized plate + # Cylinder underneath + Cylinder(r1, y2, align=(Align.CENTER, Align.CENTER, Align.MAX)) + fillet(p.edges(Select.NEW), r5) # Weld together + extrude(sk_body.sketch, amount=-y1, mode=Mode.INTERSECT) # Cut to shape + # Remove slot + with Locations((0, y_tot - r1 - y4, 0)): + Box( + y_tot, + y_tot, + 10, + align=(Align.CENTER, Align.MIN, Align.CENTER), + mode=Mode.SUBTRACT, + ) + + # print(f"\npart mass = {p.part.volume*densa:0.2f}") + assert p.part.volume * densa == pytest.approx(328.02, 0.01) + + benchmark(model) + + +def test_ppp_0107(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-07 Flanged Hub + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + with BuildPart() as p: + with BuildSketch() as s: + Circle(130 / 2) + extrude(amount=8) + with BuildSketch(Plane.XY.offset(8)) as s2: + Circle(84 / 2) + extrude(amount=25 - 8) + with BuildSketch(Plane.XY.offset(25)) as s3: + Circle(35 / 2) + extrude(amount=52 - 25) + with BuildSketch() as s4: + Circle(73 / 2) + extrude(amount=18, mode=Mode.SUBTRACT) + pln2 = p.part.faces().sort_by(Axis.Z)[5] + with BuildSketch(Plane.XY.offset(52)) as s5: + Circle(20 / 2) + extrude(amount=-52, mode=Mode.SUBTRACT) + fillet( + p.part.edges() + .filter_by(GeomType.CIRCLE) + .sort_by(Axis.Z)[2:-2] + .sort_by(SortBy.RADIUS)[1:], + 3, + ) + pln = Plane(pln2) + pln.origin = pln.origin + Vector(20 / 2, 0, 0) + pln = pln.rotated((0, 45, 0)) + pln = pln.offset(-25 + 3 + 0.10) + with BuildSketch(pln) as s6: + Rectangle((73 - 35) / 2 * 1.414 + 5, 3) + zz = extrude(amount=15, taper=-20 / 2, mode=Mode.PRIVATE) + zz2 = split(zz, bisect_by=Plane.XY.offset(25), mode=Mode.PRIVATE) + zz3 = split(zz2, bisect_by=Plane.YZ.offset(35 / 2 - 1), mode=Mode.PRIVATE) + with PolarLocations(0, 3): + add(zz3) + with Locations(Plane.XY.offset(8)): + with PolarLocations(107.95 / 2, 6): + CounterBoreHole(6 / 2, 13 / 2, 4) + + # print(f"\npart mass = {p.part.volume*densb:0.2f}") + assert p.part.volume * densb == pytest.approx(372.99, 0.01) + + benchmark(model) + + +def test_ppp_0108(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-08 Tie Plate + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + with BuildPart() as p: + with BuildSketch() as s1: + Rectangle(188 / 2 - 33, 162, align=(Align.MIN, Align.CENTER)) + with Locations((188 / 2 - 33, 0)): + SlotOverall(190, 33 * 2, rotation=90) + mirror(about=Plane.YZ) + with GridLocations(188 - 2 * 33, 190 - 2 * 33, 2, 2): + Circle(29 / 2, mode=Mode.SUBTRACT) + Circle(84 / 2, mode=Mode.SUBTRACT) + extrude(amount=16) + + with BuildPart() as p2: + with BuildSketch(Plane.XZ) as s2: + with BuildLine() as l1: + l1 = Polyline( + (222 / 2 + 14 - 40 - 40, 0), + (222 / 2 + 14 - 40, -35 + 16), + (222 / 2 + 14, -35 + 16), + (222 / 2 + 14, -35 + 16 + 30), + (222 / 2 + 14 - 40 - 40, -35 + 16 + 30), + close=True, + ) + make_face() + with Locations((222 / 2, -35 + 16 + 14)): + Circle(11 / 2, mode=Mode.SUBTRACT) + extrude(amount=20 / 2, both=True) + with BuildSketch() as s3: + with Locations(l1 @ 0): + Rectangle(40 + 40, 8, align=(Align.MIN, Align.CENTER)) + with Locations((40, 0)): + Rectangle(40, 20, align=(Align.MIN, Align.CENTER)) + extrude(amount=30, both=True, mode=Mode.INTERSECT) + mirror(about=Plane.YZ) + + # print(f"\npart mass = {p.part.volume*densa:0.2f}") + assert p.part.volume * densa == pytest.approx(3387.06, 0.01) + + benchmark(model) + + +def test_ppp_0109(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-09 Corner Tie + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + with BuildPart() as ppp109: + with BuildSketch() as one: + Rectangle(69, 75, align=(Align.MAX, Align.CENTER)) + fillet(one.vertices().group_by(Axis.X)[0], 17) + extrude(amount=13) + centers = [ + arc.arc_center + for arc in ppp109.edges() + .filter_by(GeomType.CIRCLE) + .group_by(Axis.Z)[-1] + ] + with Locations(*centers): + CounterBoreHole( + radius=8 / 2, counter_bore_radius=15 / 2, counter_bore_depth=4 + ) + + with BuildSketch(Plane.YZ) as two: + with Locations((0, 45)): + Circle(15) + with BuildLine() as bl: + c = Line((75 / 2, 0), (75 / 2, 60), mode=Mode.PRIVATE) + u = two.edge().find_tangent(75 / 2 + 90)[ + 0 + ] # where is the slope 75/2? + l1 = IntersectingLine( + two.edge().position_at(u), -two.edge().tangent_at(u), other=c + ) + Line(l1 @ 0, (0, 45)) + Polyline((0, 0), c @ 0, l1 @ 1) + mirror(about=Plane.YZ) + make_face() + with Locations((0, 45)): + Circle(12 / 2, mode=Mode.SUBTRACT) + extrude(amount=-13) + + with BuildSketch( + Plane((0, 0, 0), x_dir=(1, 0, 0), z_dir=(1, 0, 1)) + ) as three: + Rectangle(45 * 2 / sqrt(2) - 37.5, 75, align=(Align.MIN, Align.CENTER)) + with Locations(three.edges().sort_by(Axis.X)[-1].center()): + Circle(37.5) + Circle(33 / 2, mode=Mode.SUBTRACT) + split(bisect_by=Plane.YZ) + extrude(amount=6) + f = ppp109.faces().filter_by(Axis((0, 0, 0), (-1, 0, 1)))[0] + # extrude(f, until=Until.NEXT) # throws a warning + extrude(f, amount=10) + fillet(ppp109.edge(Select.NEW), 16) + + # print(f"\npart mass = {ppp109.part.volume*densb:0.2f}") + assert ppp109.part.volume * densb == pytest.approx(307.23, 0.01) + + benchmark(model) + + +def test_ppp_0110(benchmark): + def model(): + """ + Too Tall Toby Party Pack 01-10 Light Cap + """ + + densa = 7800 / 1e6 # carbon steel density g/mm^3 + densb = 2700 / 1e6 # aluminum alloy + densc = 1020 / 1e6 # ABS + + with BuildPart() as p: + with BuildSketch() as s: + with BuildLine() as l: + n1 = JernArc((0, 46), (1, 0), 40, -95) + n2 = Line((0, 0), (42, 0)) + make_hull() + # hack to keep arc vertex off revolution axis + split(bisect_by=Plane.XZ.offset(-45.9999), keep=Keep.TOP) + + revolve(s.sketch, axis=Axis.Y, revolution_arc=90) + extrude(faces().sort_by(Axis.Z)[-1], amount=50) + mirror(about=Plane(faces().sort_by(Axis.Z)[-1])) + mirror(about=Plane.YZ) + + with BuildPart() as p2: + add(p.part) + offset(amount=-8) + + with BuildPart() as pzzz: + add(p2.part) + split(bisect_by=Plane.XZ.offset(-46 + 16), keep=Keep.TOP) + fillet(faces().sort_by(Axis.Y)[-1].edges(), 12) + + with BuildPart() as p3: + with BuildSketch(Plane.XZ) as s2: + add(p.part.faces().sort_by(Axis.Y)[0]) + offset(amount=-8) + loft([pzzz.part.faces().sort_by(Axis.Y)[0], s2.sketch.face()]) + + with BuildPart() as ppp0110: + add(p.part) + add(pzzz.part, mode=Mode.SUBTRACT) + add(p3.part, mode=Mode.SUBTRACT) + + # print(f"\npart mass = {ppp0110.part.volume*densc:0.2f}") # 211.30 g is correct + assert ppp0110.part.volume * densc == pytest.approx(211, 1.00) + + benchmark(model) + + +def test_ttt_23_02_02(benchmark): + def model(): + """ + Creation of a complex sheet metal part + + name: ttt_sm_hanger.py + by: Gumyr + date: July 17, 2023 + + desc: + This example implements the sheet metal part described in Too Tall Toby's + sm_hanger CAD challenge. + + Notably, a BuildLine/Curve object is filleted by providing all the vertices + and allowing the fillet operation filter out the end vertices. The + make_brake_formed operation is used both in Algebra and Builder mode to + create a sheet metal part from just an outline and some dimensions. + 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. + + """ + densa = 7800 / 1e6 # carbon steel density g/mm^3 + sheet_thickness = 4 * MM + + # Create the main body from a side profile + with BuildPart() as side: + d = Vector(1, 0, 0).rotate(Axis.Y, 60) + with BuildLine(Plane.XZ) as side_line: + l1 = Line((0, 65), (170 / 2, 65)) + l2 = PolarLine( + l1 @ 1, length=65, direction=d, length_mode=LengthMode.VERTICAL + ) + l3 = Line(l2 @ 1, (170 / 2, 0)) + fillet(side_line.vertices(), 7) + make_brake_formed( + thickness=sheet_thickness, + station_widths=[40, 40, 40, 112.52 / 2, 112.52 / 2, 112.52 / 2], + side=Side.RIGHT, + ) + fe = side.edges().filter_by(Axis.Z).group_by(Axis.Z)[0].sort_by(Axis.Y)[-1] + fillet(fe, radius=7) + + # Create the "wings" at the top + with BuildPart() as wing: + with BuildLine(Plane.YZ) as wing_line: + l1 = Line((0, 65), (80 / 2 + 1.526 * sheet_thickness, 65)) + PolarLine( + l1 @ 1, 20.371288916, direction=Vector(0, 1, 0).rotate(Axis.X, -75) + ) + fillet(wing_line.vertices(), 7) + make_brake_formed( + thickness=sheet_thickness, + station_widths=110 / 2, + side=Side.RIGHT, + ) + bottom_edge = wing.edges().group_by(Axis.X)[-1].sort_by(Axis.Z)[0] + fillet(bottom_edge, radius=7) + + # Create the tab at the top in Algebra mode + tab_line = Plane.XZ * Polyline( + (20, 65 - sheet_thickness), (56 / 2, 65 - sheet_thickness), (56 / 2, 88) + ) + tab_line = fillet(tab_line.vertices(), 7) + tab = make_brake_formed(sheet_thickness, 8, tab_line, Side.RIGHT) + tab = fillet( + tab.edges().filter_by(Axis.X).group_by(Axis.Z)[-1].sort_by(Axis.Y)[-1], 5 + ) + tab -= Pos((0, 0, 80)) * Rot(0, 90, 0) * Hole(5, 100) + + # Combine the parts together + with BuildPart() as sm_hanger: + add([side.part, wing.part]) + mirror(about=Plane.XZ) + with BuildSketch(Plane.XY.offset(65)) as h1: + with Locations((20, 0)): + Rectangle(30, 30, align=(Align.MIN, Align.CENTER)) + fillet(h1.vertices().group_by(Axis.X)[-1], 7) + SlotCenterPoint((154, 0), (154 / 2, 0), 20) + extrude(amount=-40, mode=Mode.SUBTRACT) + with BuildSketch() as h2: + SlotCenterPoint((206, 0), (206 / 2, 0), 20) + extrude(amount=40, mode=Mode.SUBTRACT) + add(tab) + mirror(about=Plane.YZ) + mirror(about=Plane.XZ) + + # print(f"Mass: {sm_hanger.part.volume*7800*1e-6:0.1f} g") + assert sm_hanger.part.volume * densa == pytest.approx(1028, 10) + + benchmark(model) + +# def test_ttt_23_T_24(benchmark): +# excluding because it requires sympy + +def test_ttt_24_SPO_06(benchmark): + def model(): + densa = 7800 / 1e6 # carbon steel density g/mm^3 + + with BuildPart() as p: + with BuildSketch() as xy: + with BuildLine(): + l1 = ThreePointArc((5 / 2, -1.25), (5.5 / 2, 0), (5 / 2, 1.25)) + Polyline(l1 @ 0, (0, -1.25), (0, 1.25), l1 @ 1) + make_face() + extrude(amount=4) + + with BuildSketch(Plane.YZ) as yz: + Trapezoid(2.5, 4, 90 - 6, align=(Align.CENTER, Align.MIN)) + _, arc_center, arc_radius = full_round( + yz.edges().sort_by(SortBy.LENGTH)[0] + ) + extrude(amount=10, mode=Mode.INTERSECT) + + # To avoid OCCT problems, don't attempt to extend the top arc, remove instead + with BuildPart(mode=Mode.SUBTRACT) as internals: + y = p.edges().filter_by(Axis.X).sort_by(Axis.Z)[-1].center().Z + + with BuildSketch(Plane.YZ.offset(4.25 / 2)) as yz: + Trapezoid(2.5, y, 90 - 6, align=(Align.CENTER, Align.MIN)) + with Locations(arc_center): + Circle(arc_radius, mode=Mode.SUBTRACT) + extrude(amount=-(4.25 - 3.5) / 2) + + with BuildSketch(Plane.YZ.offset(3.5 / 2)) as yz: + Trapezoid(2.5, 4, 90 - 6, align=(Align.CENTER, Align.MIN)) + extrude(amount=-3.5 / 2) + + with BuildSketch(Plane.XZ.offset(-2)) as xz: + with Locations((0, 4)): + RectangleRounded(4.25, 7.5, 0.5) + extrude(amount=4, mode=Mode.INTERSECT) + + with Locations( + p.faces(Select.LAST).filter_by(GeomType.PLANE).sort_by(Axis.Z)[-1] + ): + CounterBoreHole(0.625 / 2, 1.25 / 2, 0.5) + + with BuildSketch(Plane.YZ) as rib: + with Locations((0, 0.25)): + Trapezoid(0.5, 1, 90 - 8, align=(Align.CENTER, Align.MIN)) + full_round(rib.edges().sort_by(SortBy.LENGTH)[0]) + extrude(amount=4.25 / 2) + + mirror(about=Plane.YZ) + + # part = scale(p.part, IN) + # print(f"\npart weight = {part.volume*7800e-6/LB:0.2f} lbs") + assert p.part.scale(IN).volume * densa / LB == pytest.approx(3.92, 0.03) + + benchmark(model)