From f682ebb460bbda0b6e73d2549ae7ed24405482c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=B8hler?= <45562910+seedback@users.noreply.github.com> Date: Thu, 2 Apr 2026 01:07:17 +0200 Subject: [PATCH 1/6] Refactor base_tray_generator --- Trays/functions/base_tray_generator.py | 392 +++++++----------- .../calculate_alternating_cutout_positions.py | 3 +- Trays/functions/cutout_generator.py | 38 +- Trays/functions/full_tray_generator.py | 8 +- Trays/functions/old_base_tray_generator.py | 275 ++++++++++++ 5 files changed, 453 insertions(+), 263 deletions(-) create mode 100644 Trays/functions/old_base_tray_generator.py diff --git a/Trays/functions/base_tray_generator.py b/Trays/functions/base_tray_generator.py index 8e32b80..605fa3d 100644 --- a/Trays/functions/base_tray_generator.py +++ b/Trays/functions/base_tray_generator.py @@ -1,6 +1,10 @@ +import copy + from build123d import * from ocp_vscode import * +# %% + def generate_base_tray( total_width=189.5, @@ -9,267 +13,175 @@ def generate_base_tray( base_heigth=4.2, rail_height=8.4, rail_width=4.8, - flap_center_gap=0.2, + flap_middle_gap=0.2, flap_depth=11.8, hinge_width=2.8, - hinge_height=3.6, + hinge_height=4, hinge_depth=17.5, - hinge_pin_diameter=1.4, + hinge_pin_radius=1.4, hinge_pin_length=3, bottom_chamfer=0.4, hinge_lock_radius=2, - hinge_lock_offset=0.5, + hinge_lock_offset=0.8, hinge_lock_depth=8.3, is_double_tray=False, epsilon=0.001, ): - """Generate tray geometry with all components.""" - # Calculated Parameters - center_width = total_width - 2 * rail_width - center_depth = total_depth - 2 * (flap_depth + flap_center_gap) - - hinge_top_offset = floor_thickness - 0.4 - - flap_width = center_width - flap_center_gap * 2 - - hinge_negative_space = flap_center_gap - hinge_negative_width = hinge_width + 2 * hinge_negative_space - hinge_negative_depth = hinge_depth + hinge_negative_space - hinge_negative_height = hinge_height + hinge_negative_space - hinge_pin_offset = ( - hinge_height - 2 * hinge_pin_diameter + hinge_top_offset) / 2 - hinge_negative_fillet_radius = ( - hinge_pin_diameter + hinge_pin_offset + hinge_negative_space - ) - - hinge_depth += hinge_negative_space - - # Middle - - with BuildPart() as center: - # Middle Box - Box( - center_width, - center_depth / 2, - base_heigth + hinge_top_offset, - align=(Align.CENTER, Align.MAX, Align.MIN), - ) - - # Left Rail - with Locations((-center_width / 2, 0, 0)): - b = Box( - rail_width, - total_depth / 2, - rail_height, - align=(Align.MAX, Align.MAX, Align.MIN), - ) - # Chamfer the two top edges along Y axis - chamfer(b.edges().filter_by(Axis.Y).sort_by(Axis.Z)[-2:], 1) - mirror(center.part, Plane.YZ) - - # Hinge Negative - - # Main box for hinge negative - hinge_negative_offset = (-center_width / 2, - - center_depth / 2 - epsilon, -epsilon) - with BuildPart() as hinge_negative: - with Locations(hinge_negative_offset): - b = Box( - hinge_negative_width, - hinge_negative_depth + epsilon, - hinge_negative_height + hinge_top_offset + epsilon, - align=(Align.MIN, Align.MIN, Align.MIN), - ) - fillet( - b.edges().filter_by(Axis.X)[-1], - radius=hinge_negative_fillet_radius, - ) - mirror(hinge_negative.part, Plane.YZ) - - # Cylinder-pin for hinge negative, to be applied later - with BuildPart() as hinge_negative_pin: - with Locations(hinge_negative_offset): - with Locations(( - -hinge_pin_length, - hinge_depth - 2 * hinge_pin_diameter - - hinge_pin_offset * 2 + hinge_top_offset/2 + epsilon, - hinge_pin_offset - hinge_negative_space + epsilon, - )): - Cylinder( - hinge_pin_diameter + hinge_negative_space, - ( - hinge_pin_length * 2 + hinge_width + - 2 * hinge_negative_space - ), - rotation=(0, 90, 0), - align=(Align.MAX, Align.MIN, Align.MIN), - ) - mirror(hinge_negative_pin.part, Plane.YZ) - - # Final adjustments on Center - - # Apply hinge negative to center - center.part -= hinge_negative.part - - # Apply chamfer to bottom-inner edge of hinge negative - # To allow hinge to rotate back (skip if fails on command line) - try: - bottom_edges = center.faces().sort_by(Axis.Z)[0].edges() - edges_x = bottom_edges.filter_by(Axis.X) - hinge_edges = sorted( - edges_x, key=lambda e: abs(e.center().Y) - )[1:3] - center.part = chamfer( - hinge_edges, - ( - hinge_negative_height - hinge_negative_fillet_radius - - epsilon - ), - angle=45, - ) - except Exception: - # Chamfer failed - skip (cosmetic operation) - pass - - # Apply chamfer to edge around bottom (skip if fails on command line) - try: - bottom_edges = center.faces().sort_by(Axis.Z)[0].edges() - edges_x = bottom_edges.filter_by(Axis.X) - hinge_edges = sorted( - edges_x, key=lambda e: abs(e.center().Y) - )[1:3] - back_edge = sorted(edges_x, key=lambda e: abs(e.center().Y))[:1] - center.part = chamfer( - bottom_edges - hinge_edges - hinge_edges - back_edge, - bottom_chamfer, - ) - except Exception: - # Chamfer failed - skip (cosmetic operation) - pass - - # Apply Hinge negative pin to center - center.part -= hinge_negative_pin.part - - # Flaps - - with BuildPart() as flap: - with Locations((0, -total_depth / 2, 0)): - Box( - flap_width, - flap_depth, - base_heigth + hinge_top_offset, - align=(Align.CENTER, Align.MIN, Align.MIN), - ) - - with BuildPart() as hinge: - with Locations((0, -total_depth / 2, 0)): - # Hinge - with Locations((-flap_width / 2, flap_depth, 0)): - Box( - hinge_width, - hinge_depth, - hinge_height + hinge_top_offset, - align=(Align.MIN, Align.MIN, Align.MIN), - ) - with Locations(( - -hinge_pin_length, - hinge_depth - hinge_pin_diameter * 2 - hinge_pin_offset, - hinge_pin_offset, - )): - Cylinder( - hinge_pin_diameter, - hinge_pin_length * 2 + hinge_width, - rotation=(0, 90, 0), - align=(Align.MAX, Align.MIN, Align.MIN), - ) - fillet( - hinge.edges().filter_by(Axis.X).sort_by(Axis.Y)[-2:], - (hinge_height + hinge_top_offset) / 2 - epsilon, - ) - mirror(hinge.part, Plane.YZ) - - flap.part += hinge.part - - with BuildPart() as hinge_lock: - with Locations(( - -(flap_width / 2 - hinge_lock_radius + hinge_lock_offset), - -total_depth / 2, - (base_heigth + hinge_top_offset) / 2, - )): - Cylinder( - hinge_lock_radius, - hinge_lock_depth, - rotation=(90, 0, 0), - align=(Align.CENTER, Align.CENTER, Align.MAX), - ) - hinge_lock.part = split( - hinge_lock.part, - flap.part.faces().filter_by(Plane.YZ).sort_by(Axis.X)[1], + middle_width = total_width - rail_width*2 + middle_depth = total_depth/2 - (flap_depth + flap_middle_gap) + middle_height = base_heigth + floor_thickness + with BuildPart() as middle_builder: + with Locations((0, epsilon, 0)): + Box(middle_width + epsilon*2, middle_depth + epsilon, + middle_height, align=(Align.CENTER, Align.MAX, Align.MIN)) + RigidJoint("LeftRail", joint_location=(Location((-middle_width/2, 0, 0)))) + RigidJoint("Flap", joint_location=( + Location((0, -middle_depth - flap_middle_gap, 0)))) + tray = middle_builder.part + + with BuildPart() as rail_builder: + with Locations((0, epsilon, 0)): + Box(rail_width, total_depth/2, rail_height + + epsilon, align=(Align.MAX, Align.MAX, Align.MIN)) + chamfer((rail_builder.part.edges() | Axis.Y < Axis.Z)[:2], 1) + RigidJoint("Middle") + + tray.joints["LeftRail"].connect_to(rail_builder.part.joints["Middle"]) + tray += rail_builder.part + tray += mirror(rail_builder.part, Plane.YZ) + + with BuildPart() as flap_builder: + flap_width = middle_width - flap_middle_gap*2 + with Locations((0, 0, 0)): + Box(flap_width, flap_depth, middle_height, + align=(Align.CENTER, Align.MIN, Align.MIN)) + RigidJoint("Middle", joint_location=Location((0, flap_depth, 0))) + RigidJoint("HingeLeft", joint_location=Location( + (-flap_width/2, flap_depth, 0))) + RigidJoint("HingeLockLeft", joint_location=Location( + (-flap_width/2, 0, base_heigth/2 + floor_thickness/2))) + flap = flap_builder.part + + with BuildPart() as hinge_builder: + with Locations((0, -epsilon, 0)): + Box(hinge_width, hinge_depth + epsilon, hinge_height, + align=(Align.MIN, Align.MIN, Align.MIN)) + fillet_edges = (hinge_builder.part.edges() | Axis.X < Axis.Y)[:2] + fillet_radius = hinge_builder.part.max_fillet(fillet_edges, epsilon, 32) + fillet(fillet_edges, fillet_radius) + RigidJoint("Flap") + RigidJoint("HingePin", joint_location=Location( + (hinge_width/2, hinge_depth-fillet_radius, hinge_height/2))) + hinge = hinge_builder.part + + with BuildPart() as hinge_pin_builder: + Cylinder(hinge_pin_radius, hinge_pin_length * + 2+hinge_width, rotation=(0, 90, 0)) + RigidJoint("Hinge") + + flap.joints["HingeLeft"].connect_to(hinge.joints["Flap"]) + flap += hinge + flap += mirror(hinge, Plane.YZ) + + with BuildPart() as hinge_negative_builder: + hinge_negative_width = hinge_width + flap_middle_gap*2 + hinge_negative_depth = hinge_depth + flap_middle_gap + epsilon + hinge_negative_height = hinge_height + flap_middle_gap + with Locations((-flap_middle_gap, -epsilon, 0)): + Box(hinge_negative_width, hinge_negative_depth, + hinge_negative_height, align=(Align.MIN, Align.MIN, Align.MIN)) + sketch_face = (hinge_negative_builder.part.faces() | Plane.YZ < Axis.X)[1] + fillet_edges = (hinge_negative_builder.part.edges() | Axis.X < Axis.Y)[1:2] + fillet(fillet_edges, fillet_radius) + with BuildSketch(sketch_face): + with Locations((-hinge_negative_height/2, hinge_negative_depth/2 - hinge_pin_radius - epsilon)): + Polygon([(0, 0), (0, hinge_negative_height - fillet_radius + hinge_pin_radius), + (hinge_negative_height - fillet_radius + hinge_pin_radius, 0)]) + extrude(amount=-hinge_negative_width) + RigidJoint("Flap") + RigidJoint("HingePin", joint_location=Location( + (hinge_width/2, hinge_depth-fillet_radius, hinge_height/2))) + hinge_negative = hinge_negative_builder.part + + with BuildPart() as hinge_pin_negative_builder: + Cylinder(hinge_pin_radius + flap_middle_gap, hinge_pin_length * + 2+hinge_width+flap_middle_gap*2, rotation=(0, 90, 0)) + RigidJoint("Hinge") + + with BuildPart() as hinge_lock_builder: + Cylinder( + hinge_lock_radius, + hinge_lock_depth, + rotation=(90, 0, 0), + align=(Align.MIN, Align.CENTER, Align.MAX) ) - hinge_lock.part = hinge_lock.part + mirror(hinge_lock.part, Plane.YZ) - - with BuildPart() as hinge_lock_negative: - with Locations(( - -(flap_width / 2 - hinge_lock_radius + hinge_lock_offset), - -total_depth / 2, - (base_heigth + hinge_top_offset) / 2, - )): - Cylinder( - hinge_lock_radius + hinge_negative_space, - hinge_lock_depth, - rotation=(90, 0, 0), - align=(Align.CENTER, Align.CENTER, Align.MAX), - ) - hinge_lock_negative.part = split( - hinge_lock_negative.part, - flap.part.faces().filter_by(Plane.YZ).sort_by(Axis.X)[1], - ) - hinge_lock_negative.part = ( - hinge_lock_negative.part + - mirror(hinge_lock_negative.part, Plane.YZ) + RigidJoint("Flap", joint_location=Location((hinge_lock_offset, 0, 0))) + flap.joints["HingeLockLeft"].connect_to( + hinge_lock_builder.part.joints["Flap"]) + flap += hinge_lock_builder.part + flap += mirror(hinge_lock_builder.part, Plane.YZ) + + with BuildPart() as hinge_lock_negative_builder: + Cylinder( + hinge_lock_radius + flap_middle_gap, + hinge_lock_depth + flap_middle_gap, + rotation=(90, 0, 0), + align=(Align.MIN, Align.CENTER, Align.MAX) ) + RigidJoint("Flap", joint_location=Location( + (hinge_lock_offset + flap_middle_gap, 0, 0))) - flap.part += hinge_lock.part - center.part -= hinge_lock_negative.part - - # Final adjustments for flaps + tray.joints["Flap"].connect_to(flap_builder.part.joints["Middle"]) + flap.joints["HingeLockLeft"].connect_to( + hinge_lock_negative_builder.part.joints["Flap"]) + tray -= hinge_lock_negative_builder.part + tray -= mirror(hinge_lock_negative_builder.part, Plane.YZ) - flap_bottom_chamfer_edges = ( - flap.faces() - .sort_by(Axis.Z)[0] - .edges() - .filter_by(Axis.X) - .sort_by(Axis.Y)[0:2] - ) + flap.joints["HingeLeft"].connect_to(hinge_negative.joints["Flap"]) + tray -= hinge_negative + tray -= mirror(hinge_negative, Plane.YZ) - arc_candidates = flap.edges().filter_by(Plane.YZ) - flap_hinge_arc_edges = ShapeList([ - e for e in arc_candidates - if e.length > 0.001 - ]) - flap_hinge_arc_edges = flap_hinge_arc_edges.sort_by(Axis.Y)[-12:] - - flap_chamfer_edges = flap_bottom_chamfer_edges + flap_hinge_arc_edges + if is_double_tray: + chamfer_edges = tray.edges().filter_by_position( + Axis.Z, -0.01, 0.01).sort_by(Axis.Y)[:-2] + else: + chamfer_edges = tray.edges().filter_by_position(Axis.Z, -0.01, 0.01) + tray = chamfer(chamfer_edges, bottom_chamfer) + + flap.joints["HingeLeft"].connect_to(hinge_negative.joints["Flap"]) + hinge_negative.joints["HingePin"].connect_to( + hinge_pin_negative_builder.part.joints["Hinge"]) + tray -= hinge_pin_negative_builder.part + tray -= mirror(hinge_pin_negative_builder.part, Plane.YZ) + + # Chamfering this way resets joints, so saving and reapplying them + chamfer_edges = flap.edges().filter_by_position(Axis.Z, -0.01, 0.01) + stored_joints = {name: flap.joints[name] for name in flap.joints} + flap = chamfer(chamfer_edges, bottom_chamfer) + for name, joint in stored_joints.items(): + flap.joints[name] = joint + + flap.joints["HingeLeft"].connect_to(hinge.joints["Flap"]) + hinge.joints["HingePin"].connect_to(hinge_pin_builder.part.joints["Hinge"]) + flap += hinge_pin_builder.part + flap += mirror(hinge_pin_builder.part, Plane.YZ) + + part_list = [flap] - try: - flap.part = chamfer(flap_chamfer_edges, 0.4 - epsilon) - except Exception: - # Chamfer failed - skip (cosmetic operation) - pass + if is_double_tray: + tray += mirror(tray, Plane.XZ) + part_list.append(mirror(flap, Plane.XZ)) - # Decide whether it's one or two - flap_compound = flap.part + part_list.append(tray) - if is_double_tray: - center.part += mirror(center.part, Plane.XZ) - flap_compound = Compound([flap.part, mirror(flap.part, Plane.XZ)]) + return Compound([part.translate((0, 0, -floor_thickness)) for part in part_list]) - return Compound([center.part, flap_compound]) # %% - if __name__ == "__main__": - center, flap = generate_base_tray(is_double_tray=True) - show(center, flap) + base_tray = generate_base_tray(is_double_tray=False, total_width=30) + export_step(base_tray, "test.step") + show(base_tray, render_joints=False) + # %% diff --git a/Trays/functions/calculate_cutout_positions/calculate_alternating_cutout_positions.py b/Trays/functions/calculate_cutout_positions/calculate_alternating_cutout_positions.py index 3d5ec32..55f675e 100644 --- a/Trays/functions/calculate_cutout_positions/calculate_alternating_cutout_positions.py +++ b/Trays/functions/calculate_cutout_positions/calculate_alternating_cutout_positions.py @@ -20,7 +20,8 @@ def calculate_alternating_cutout_positions( 'flipped': False, }] - positions = _calculate_initial_positions(usable_area, diameters, edge_offsets, tolerance) + positions = _calculate_initial_positions( + usable_area, diameters, edge_offsets, tolerance) return positions diff --git a/Trays/functions/cutout_generator.py b/Trays/functions/cutout_generator.py index 96d34e3..77d0a89 100644 --- a/Trays/functions/cutout_generator.py +++ b/Trays/functions/cutout_generator.py @@ -14,30 +14,30 @@ def generate_cutout( hinge_diameter=27.5, flap_center_gap=0.2, cutout_edge_spacing=.8, - floor_thickness = .8, - lip_offset = 0, + floor_thickness=.8, + lip_offset=0, epsilon=0.001 ): with BuildPart() as normal_base: with BuildSketch(): c = Circle(base_diameter/2 + tolerance/2, - align=(Align.CENTER, Align.CENTER)) + align=(Align.CENTER, Align.CENTER)) extrude(amount=6, taper=12.5) extrude(c, amount=-6, taper=-12.5) # # Add the slide path for the base - if(base_diameter/2 > flap_depth-cutout_edge_spacing): + if (base_diameter/2 > flap_depth-cutout_edge_spacing): cross_section_result = section(normal_base.part, Plane.XZ) e = extrude(cross_section_result, (base_diameter/2 + tolerance/2) - (flap_depth - cutout_edge_spacing) - flap_center_gap + epsilon) - + normal = copy.deepcopy(normal_base.part) - - normal_base.part = normal_base.part.translate((0,0,-epsilon)) - + + normal_base.part = normal_base.part.translate((0, 0, -epsilon)) + if not lip_offset == 0: - lip_offseter = normal_base.part.translate((0,lip_offset,0)) + lip_offseter = normal_base.part.translate((0, lip_offset, 0)) normal_base.part = normal_base.part.intersect(lip_offseter) - + if isinstance(normal_base.part, ShapeList): normal_base.part = normal_base.part[0] @@ -48,17 +48,18 @@ def generate_cutout( # Get the radius cut out of the adjustor hinge_radius = hinge_diameter/2 - cutout_edge_spacing delta_x = hinge_radius - \ - math.sqrt(math.pow(hinge_radius, 2) - math.pow(2 - floor_thickness + epsilon, 2)) - + math.sqrt(math.pow(hinge_radius, 2) - + math.pow(2 - floor_thickness + epsilon, 2)) + with BuildPart() as lip_adjustor_edge: with BuildSketch(Plane.YZ): - with Locations(( -base_diameter/2 - tolerance/2 + delta_x - hinge_radius, 2 - floor_thickness)): + with Locations((-base_diameter/2 - tolerance/2 + delta_x - hinge_radius, 2 - floor_thickness)): Circle(hinge_radius, align=(Align.CENTER, Align.CENTER)) revolve_axis = Axis( - origin=(0, -tolerance/2 - epsilon, 0), direction=(0, 0, 1)) + origin=(0, -tolerance/2 - epsilon, 0), direction=(0, 0, 1)) revolve(axis=revolve_axis) - - lip_adjustor_edge.part = lip_adjustor_edge.part.translate((0,lip_offset,0)) + + lip_adjustor_edge.part = lip_adjustor_edge.part.translate((0, lip_offset, 0)) lip_adjustor_base.part -= lip_adjustor_edge.part @@ -87,7 +88,7 @@ def generate_cutout( # Subtract flattener using boolean operation normal_base.part = normal_base.part.intersect(flattener.part) - + # Convert normal_base from ShapeList to Shape if necessary if isinstance(normal_base.part, ShapeList): normal_base.part = normal_base.part[0] @@ -109,6 +110,7 @@ def generate_cutout( # %% if __name__ == "__main__": - cutout = generate_cutout(49.6, tolerance=0.55, lip_offset=0.1, floor_thickness=-1) + cutout = generate_cutout( + 49.6, tolerance=0.55, lip_offset=0.1, floor_thickness=0.8) show(cutout) # %% diff --git a/Trays/functions/full_tray_generator.py b/Trays/functions/full_tray_generator.py index 0228ad9..eaeeb45 100644 --- a/Trays/functions/full_tray_generator.py +++ b/Trays/functions/full_tray_generator.py @@ -88,8 +88,8 @@ def generate_full_tray( hinge_lock_radius=2, hinge_lock_offset=0.5, hinge_lock_depth=8.3, - edge_offsets = [], - edge_adjusts = [], + edge_offsets=[], + edge_adjusts=[], is_double_tray=False, epsilon=0.001, tolerance=0.55, @@ -186,9 +186,9 @@ def generate_full_tray( # total_width=80, # edge_offsets=[.1] # ) - + tray_compound, cutout_list = generate_full_tray( - [24.7, 49.6,24.7, 49.6, 24.7], + [24.7, 49.6, 24.7, 49.6, 24.7], is_double_tray=True, edge_offsets=[5, 5], edge_adjusts=[0, -0.85, 0, -0.85, 0], diff --git a/Trays/functions/old_base_tray_generator.py b/Trays/functions/old_base_tray_generator.py new file mode 100644 index 0000000..8e32b80 --- /dev/null +++ b/Trays/functions/old_base_tray_generator.py @@ -0,0 +1,275 @@ +from build123d import * +from ocp_vscode import * + + +def generate_base_tray( + total_width=189.5, + total_depth=66.0, + floor_thickness=0.8, + base_heigth=4.2, + rail_height=8.4, + rail_width=4.8, + flap_center_gap=0.2, + flap_depth=11.8, + hinge_width=2.8, + hinge_height=3.6, + hinge_depth=17.5, + hinge_pin_diameter=1.4, + hinge_pin_length=3, + bottom_chamfer=0.4, + hinge_lock_radius=2, + hinge_lock_offset=0.5, + hinge_lock_depth=8.3, + is_double_tray=False, + epsilon=0.001, +): + """Generate tray geometry with all components.""" + # Calculated Parameters + center_width = total_width - 2 * rail_width + center_depth = total_depth - 2 * (flap_depth + flap_center_gap) + + hinge_top_offset = floor_thickness - 0.4 + + flap_width = center_width - flap_center_gap * 2 + + hinge_negative_space = flap_center_gap + hinge_negative_width = hinge_width + 2 * hinge_negative_space + hinge_negative_depth = hinge_depth + hinge_negative_space + hinge_negative_height = hinge_height + hinge_negative_space + hinge_pin_offset = ( + hinge_height - 2 * hinge_pin_diameter + hinge_top_offset) / 2 + hinge_negative_fillet_radius = ( + hinge_pin_diameter + hinge_pin_offset + hinge_negative_space + ) + + hinge_depth += hinge_negative_space + + # Middle + + with BuildPart() as center: + # Middle Box + Box( + center_width, + center_depth / 2, + base_heigth + hinge_top_offset, + align=(Align.CENTER, Align.MAX, Align.MIN), + ) + + # Left Rail + with Locations((-center_width / 2, 0, 0)): + b = Box( + rail_width, + total_depth / 2, + rail_height, + align=(Align.MAX, Align.MAX, Align.MIN), + ) + # Chamfer the two top edges along Y axis + chamfer(b.edges().filter_by(Axis.Y).sort_by(Axis.Z)[-2:], 1) + mirror(center.part, Plane.YZ) + + # Hinge Negative + + # Main box for hinge negative + hinge_negative_offset = (-center_width / 2, - + center_depth / 2 - epsilon, -epsilon) + with BuildPart() as hinge_negative: + with Locations(hinge_negative_offset): + b = Box( + hinge_negative_width, + hinge_negative_depth + epsilon, + hinge_negative_height + hinge_top_offset + epsilon, + align=(Align.MIN, Align.MIN, Align.MIN), + ) + fillet( + b.edges().filter_by(Axis.X)[-1], + radius=hinge_negative_fillet_radius, + ) + mirror(hinge_negative.part, Plane.YZ) + + # Cylinder-pin for hinge negative, to be applied later + with BuildPart() as hinge_negative_pin: + with Locations(hinge_negative_offset): + with Locations(( + -hinge_pin_length, + hinge_depth - 2 * hinge_pin_diameter - + hinge_pin_offset * 2 + hinge_top_offset/2 + epsilon, + hinge_pin_offset - hinge_negative_space + epsilon, + )): + Cylinder( + hinge_pin_diameter + hinge_negative_space, + ( + hinge_pin_length * 2 + hinge_width + + 2 * hinge_negative_space + ), + rotation=(0, 90, 0), + align=(Align.MAX, Align.MIN, Align.MIN), + ) + mirror(hinge_negative_pin.part, Plane.YZ) + + # Final adjustments on Center + + # Apply hinge negative to center + center.part -= hinge_negative.part + + # Apply chamfer to bottom-inner edge of hinge negative + # To allow hinge to rotate back (skip if fails on command line) + try: + bottom_edges = center.faces().sort_by(Axis.Z)[0].edges() + edges_x = bottom_edges.filter_by(Axis.X) + hinge_edges = sorted( + edges_x, key=lambda e: abs(e.center().Y) + )[1:3] + center.part = chamfer( + hinge_edges, + ( + hinge_negative_height - hinge_negative_fillet_radius - + epsilon + ), + angle=45, + ) + except Exception: + # Chamfer failed - skip (cosmetic operation) + pass + + # Apply chamfer to edge around bottom (skip if fails on command line) + try: + bottom_edges = center.faces().sort_by(Axis.Z)[0].edges() + edges_x = bottom_edges.filter_by(Axis.X) + hinge_edges = sorted( + edges_x, key=lambda e: abs(e.center().Y) + )[1:3] + back_edge = sorted(edges_x, key=lambda e: abs(e.center().Y))[:1] + center.part = chamfer( + bottom_edges - hinge_edges - hinge_edges - back_edge, + bottom_chamfer, + ) + except Exception: + # Chamfer failed - skip (cosmetic operation) + pass + + # Apply Hinge negative pin to center + center.part -= hinge_negative_pin.part + + # Flaps + + with BuildPart() as flap: + with Locations((0, -total_depth / 2, 0)): + Box( + flap_width, + flap_depth, + base_heigth + hinge_top_offset, + align=(Align.CENTER, Align.MIN, Align.MIN), + ) + + with BuildPart() as hinge: + with Locations((0, -total_depth / 2, 0)): + # Hinge + with Locations((-flap_width / 2, flap_depth, 0)): + Box( + hinge_width, + hinge_depth, + hinge_height + hinge_top_offset, + align=(Align.MIN, Align.MIN, Align.MIN), + ) + with Locations(( + -hinge_pin_length, + hinge_depth - hinge_pin_diameter * 2 - hinge_pin_offset, + hinge_pin_offset, + )): + Cylinder( + hinge_pin_diameter, + hinge_pin_length * 2 + hinge_width, + rotation=(0, 90, 0), + align=(Align.MAX, Align.MIN, Align.MIN), + ) + fillet( + hinge.edges().filter_by(Axis.X).sort_by(Axis.Y)[-2:], + (hinge_height + hinge_top_offset) / 2 - epsilon, + ) + mirror(hinge.part, Plane.YZ) + + flap.part += hinge.part + + with BuildPart() as hinge_lock: + with Locations(( + -(flap_width / 2 - hinge_lock_radius + hinge_lock_offset), + -total_depth / 2, + (base_heigth + hinge_top_offset) / 2, + )): + Cylinder( + hinge_lock_radius, + hinge_lock_depth, + rotation=(90, 0, 0), + align=(Align.CENTER, Align.CENTER, Align.MAX), + ) + hinge_lock.part = split( + hinge_lock.part, + flap.part.faces().filter_by(Plane.YZ).sort_by(Axis.X)[1], + ) + hinge_lock.part = hinge_lock.part + mirror(hinge_lock.part, Plane.YZ) + + with BuildPart() as hinge_lock_negative: + with Locations(( + -(flap_width / 2 - hinge_lock_radius + hinge_lock_offset), + -total_depth / 2, + (base_heigth + hinge_top_offset) / 2, + )): + Cylinder( + hinge_lock_radius + hinge_negative_space, + hinge_lock_depth, + rotation=(90, 0, 0), + align=(Align.CENTER, Align.CENTER, Align.MAX), + ) + hinge_lock_negative.part = split( + hinge_lock_negative.part, + flap.part.faces().filter_by(Plane.YZ).sort_by(Axis.X)[1], + ) + hinge_lock_negative.part = ( + hinge_lock_negative.part + + mirror(hinge_lock_negative.part, Plane.YZ) + ) + + flap.part += hinge_lock.part + center.part -= hinge_lock_negative.part + + # Final adjustments for flaps + + flap_bottom_chamfer_edges = ( + flap.faces() + .sort_by(Axis.Z)[0] + .edges() + .filter_by(Axis.X) + .sort_by(Axis.Y)[0:2] + ) + + arc_candidates = flap.edges().filter_by(Plane.YZ) + flap_hinge_arc_edges = ShapeList([ + e for e in arc_candidates + if e.length > 0.001 + ]) + flap_hinge_arc_edges = flap_hinge_arc_edges.sort_by(Axis.Y)[-12:] + + flap_chamfer_edges = flap_bottom_chamfer_edges + flap_hinge_arc_edges + + try: + flap.part = chamfer(flap_chamfer_edges, 0.4 - epsilon) + except Exception: + # Chamfer failed - skip (cosmetic operation) + pass + + # Decide whether it's one or two + flap_compound = flap.part + + if is_double_tray: + center.part += mirror(center.part, Plane.XZ) + flap_compound = Compound([flap.part, mirror(flap.part, Plane.XZ)]) + + return Compound([center.part, flap_compound]) + +# %% + + +if __name__ == "__main__": + center, flap = generate_base_tray(is_double_tray=True) + show(center, flap) +# %% From c3650d68b97ff1f1870db18232cec8f24a02e719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=B8hler?= <45562910+seedback@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:11:09 +0200 Subject: [PATCH 2/6] Adjust cutout placement to account for floor_thickness --- Trays/functions/full_tray_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Trays/functions/full_tray_generator.py b/Trays/functions/full_tray_generator.py index eaeeb45..a32980d 100644 --- a/Trays/functions/full_tray_generator.py +++ b/Trays/functions/full_tray_generator.py @@ -165,7 +165,7 @@ def generate_full_tray( if position['flipped']: cutout = cutout.rotate(Axis.Z, 180) - cutout = cutout.translate((position['x'], position['y'], floor_thickness)) + cutout = cutout.translate((position['x'], position['y'], 0)) cutouts_list.append(cutout) From e5c5661870683d5bf5a86e4cadb57088e8e6907e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=B8hler?= <45562910+seedback@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:13:25 +0200 Subject: [PATCH 3/6] minor change to export path --- Trays/functions/base_tray_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Trays/functions/base_tray_generator.py b/Trays/functions/base_tray_generator.py index 605fa3d..c320730 100644 --- a/Trays/functions/base_tray_generator.py +++ b/Trays/functions/base_tray_generator.py @@ -181,7 +181,7 @@ def generate_base_tray( if __name__ == "__main__": base_tray = generate_base_tray(is_double_tray=False, total_width=30) - export_step(base_tray, "test.step") + export_step(base_tray, "../output/test.step") show(base_tray, render_joints=False) # %% From af44e8afcd0ca0541eb870d51ba42716d4084170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=B8hler?= <45562910+seedback@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:15:24 +0200 Subject: [PATCH 4/6] Delete old_base_tray_generator.py --- Trays/functions/old_base_tray_generator.py | 275 --------------------- 1 file changed, 275 deletions(-) delete mode 100644 Trays/functions/old_base_tray_generator.py diff --git a/Trays/functions/old_base_tray_generator.py b/Trays/functions/old_base_tray_generator.py deleted file mode 100644 index 8e32b80..0000000 --- a/Trays/functions/old_base_tray_generator.py +++ /dev/null @@ -1,275 +0,0 @@ -from build123d import * -from ocp_vscode import * - - -def generate_base_tray( - total_width=189.5, - total_depth=66.0, - floor_thickness=0.8, - base_heigth=4.2, - rail_height=8.4, - rail_width=4.8, - flap_center_gap=0.2, - flap_depth=11.8, - hinge_width=2.8, - hinge_height=3.6, - hinge_depth=17.5, - hinge_pin_diameter=1.4, - hinge_pin_length=3, - bottom_chamfer=0.4, - hinge_lock_radius=2, - hinge_lock_offset=0.5, - hinge_lock_depth=8.3, - is_double_tray=False, - epsilon=0.001, -): - """Generate tray geometry with all components.""" - # Calculated Parameters - center_width = total_width - 2 * rail_width - center_depth = total_depth - 2 * (flap_depth + flap_center_gap) - - hinge_top_offset = floor_thickness - 0.4 - - flap_width = center_width - flap_center_gap * 2 - - hinge_negative_space = flap_center_gap - hinge_negative_width = hinge_width + 2 * hinge_negative_space - hinge_negative_depth = hinge_depth + hinge_negative_space - hinge_negative_height = hinge_height + hinge_negative_space - hinge_pin_offset = ( - hinge_height - 2 * hinge_pin_diameter + hinge_top_offset) / 2 - hinge_negative_fillet_radius = ( - hinge_pin_diameter + hinge_pin_offset + hinge_negative_space - ) - - hinge_depth += hinge_negative_space - - # Middle - - with BuildPart() as center: - # Middle Box - Box( - center_width, - center_depth / 2, - base_heigth + hinge_top_offset, - align=(Align.CENTER, Align.MAX, Align.MIN), - ) - - # Left Rail - with Locations((-center_width / 2, 0, 0)): - b = Box( - rail_width, - total_depth / 2, - rail_height, - align=(Align.MAX, Align.MAX, Align.MIN), - ) - # Chamfer the two top edges along Y axis - chamfer(b.edges().filter_by(Axis.Y).sort_by(Axis.Z)[-2:], 1) - mirror(center.part, Plane.YZ) - - # Hinge Negative - - # Main box for hinge negative - hinge_negative_offset = (-center_width / 2, - - center_depth / 2 - epsilon, -epsilon) - with BuildPart() as hinge_negative: - with Locations(hinge_negative_offset): - b = Box( - hinge_negative_width, - hinge_negative_depth + epsilon, - hinge_negative_height + hinge_top_offset + epsilon, - align=(Align.MIN, Align.MIN, Align.MIN), - ) - fillet( - b.edges().filter_by(Axis.X)[-1], - radius=hinge_negative_fillet_radius, - ) - mirror(hinge_negative.part, Plane.YZ) - - # Cylinder-pin for hinge negative, to be applied later - with BuildPart() as hinge_negative_pin: - with Locations(hinge_negative_offset): - with Locations(( - -hinge_pin_length, - hinge_depth - 2 * hinge_pin_diameter - - hinge_pin_offset * 2 + hinge_top_offset/2 + epsilon, - hinge_pin_offset - hinge_negative_space + epsilon, - )): - Cylinder( - hinge_pin_diameter + hinge_negative_space, - ( - hinge_pin_length * 2 + hinge_width + - 2 * hinge_negative_space - ), - rotation=(0, 90, 0), - align=(Align.MAX, Align.MIN, Align.MIN), - ) - mirror(hinge_negative_pin.part, Plane.YZ) - - # Final adjustments on Center - - # Apply hinge negative to center - center.part -= hinge_negative.part - - # Apply chamfer to bottom-inner edge of hinge negative - # To allow hinge to rotate back (skip if fails on command line) - try: - bottom_edges = center.faces().sort_by(Axis.Z)[0].edges() - edges_x = bottom_edges.filter_by(Axis.X) - hinge_edges = sorted( - edges_x, key=lambda e: abs(e.center().Y) - )[1:3] - center.part = chamfer( - hinge_edges, - ( - hinge_negative_height - hinge_negative_fillet_radius - - epsilon - ), - angle=45, - ) - except Exception: - # Chamfer failed - skip (cosmetic operation) - pass - - # Apply chamfer to edge around bottom (skip if fails on command line) - try: - bottom_edges = center.faces().sort_by(Axis.Z)[0].edges() - edges_x = bottom_edges.filter_by(Axis.X) - hinge_edges = sorted( - edges_x, key=lambda e: abs(e.center().Y) - )[1:3] - back_edge = sorted(edges_x, key=lambda e: abs(e.center().Y))[:1] - center.part = chamfer( - bottom_edges - hinge_edges - hinge_edges - back_edge, - bottom_chamfer, - ) - except Exception: - # Chamfer failed - skip (cosmetic operation) - pass - - # Apply Hinge negative pin to center - center.part -= hinge_negative_pin.part - - # Flaps - - with BuildPart() as flap: - with Locations((0, -total_depth / 2, 0)): - Box( - flap_width, - flap_depth, - base_heigth + hinge_top_offset, - align=(Align.CENTER, Align.MIN, Align.MIN), - ) - - with BuildPart() as hinge: - with Locations((0, -total_depth / 2, 0)): - # Hinge - with Locations((-flap_width / 2, flap_depth, 0)): - Box( - hinge_width, - hinge_depth, - hinge_height + hinge_top_offset, - align=(Align.MIN, Align.MIN, Align.MIN), - ) - with Locations(( - -hinge_pin_length, - hinge_depth - hinge_pin_diameter * 2 - hinge_pin_offset, - hinge_pin_offset, - )): - Cylinder( - hinge_pin_diameter, - hinge_pin_length * 2 + hinge_width, - rotation=(0, 90, 0), - align=(Align.MAX, Align.MIN, Align.MIN), - ) - fillet( - hinge.edges().filter_by(Axis.X).sort_by(Axis.Y)[-2:], - (hinge_height + hinge_top_offset) / 2 - epsilon, - ) - mirror(hinge.part, Plane.YZ) - - flap.part += hinge.part - - with BuildPart() as hinge_lock: - with Locations(( - -(flap_width / 2 - hinge_lock_radius + hinge_lock_offset), - -total_depth / 2, - (base_heigth + hinge_top_offset) / 2, - )): - Cylinder( - hinge_lock_radius, - hinge_lock_depth, - rotation=(90, 0, 0), - align=(Align.CENTER, Align.CENTER, Align.MAX), - ) - hinge_lock.part = split( - hinge_lock.part, - flap.part.faces().filter_by(Plane.YZ).sort_by(Axis.X)[1], - ) - hinge_lock.part = hinge_lock.part + mirror(hinge_lock.part, Plane.YZ) - - with BuildPart() as hinge_lock_negative: - with Locations(( - -(flap_width / 2 - hinge_lock_radius + hinge_lock_offset), - -total_depth / 2, - (base_heigth + hinge_top_offset) / 2, - )): - Cylinder( - hinge_lock_radius + hinge_negative_space, - hinge_lock_depth, - rotation=(90, 0, 0), - align=(Align.CENTER, Align.CENTER, Align.MAX), - ) - hinge_lock_negative.part = split( - hinge_lock_negative.part, - flap.part.faces().filter_by(Plane.YZ).sort_by(Axis.X)[1], - ) - hinge_lock_negative.part = ( - hinge_lock_negative.part + - mirror(hinge_lock_negative.part, Plane.YZ) - ) - - flap.part += hinge_lock.part - center.part -= hinge_lock_negative.part - - # Final adjustments for flaps - - flap_bottom_chamfer_edges = ( - flap.faces() - .sort_by(Axis.Z)[0] - .edges() - .filter_by(Axis.X) - .sort_by(Axis.Y)[0:2] - ) - - arc_candidates = flap.edges().filter_by(Plane.YZ) - flap_hinge_arc_edges = ShapeList([ - e for e in arc_candidates - if e.length > 0.001 - ]) - flap_hinge_arc_edges = flap_hinge_arc_edges.sort_by(Axis.Y)[-12:] - - flap_chamfer_edges = flap_bottom_chamfer_edges + flap_hinge_arc_edges - - try: - flap.part = chamfer(flap_chamfer_edges, 0.4 - epsilon) - except Exception: - # Chamfer failed - skip (cosmetic operation) - pass - - # Decide whether it's one or two - flap_compound = flap.part - - if is_double_tray: - center.part += mirror(center.part, Plane.XZ) - flap_compound = Compound([flap.part, mirror(flap.part, Plane.XZ)]) - - return Compound([center.part, flap_compound]) - -# %% - - -if __name__ == "__main__": - center, flap = generate_base_tray(is_double_tray=True) - show(center, flap) -# %% From 83cdb0ac467ae8dd6e3a5f0507ff61670371d3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=B8hler?= <45562910+seedback@users.noreply.github.com> Date: Thu, 2 Apr 2026 23:27:45 +0200 Subject: [PATCH 5/6] improve cutout generator and linear positioning --- Trays/functions/base_tray_generator.py | 18 ++- .../calculate_linear_cutout_positions.py | 37 +++-- Trays/functions/cutout_generator.py | 142 ++++++------------ Trays/functions/full_tray_generator.py | 61 ++++---- Trays/functions/old_cutout_generator.py | 116 ++++++++++++++ 5 files changed, 231 insertions(+), 143 deletions(-) create mode 100644 Trays/functions/old_cutout_generator.py diff --git a/Trays/functions/base_tray_generator.py b/Trays/functions/base_tray_generator.py index c320730..5f325db 100644 --- a/Trays/functions/base_tray_generator.py +++ b/Trays/functions/base_tray_generator.py @@ -46,7 +46,7 @@ def generate_base_tray( chamfer((rail_builder.part.edges() | Axis.Y < Axis.Z)[:2], 1) RigidJoint("Middle") - tray.joints["LeftRail"].connect_to(rail_builder.part.joints["Middle"]) + tray.joints["LeftRail"].connect_to(rail_builder.joints["Middle"]) tray += rail_builder.part tray += mirror(rail_builder.part, Plane.YZ) @@ -117,7 +117,7 @@ def generate_base_tray( ) RigidJoint("Flap", joint_location=Location((hinge_lock_offset, 0, 0))) flap.joints["HingeLockLeft"].connect_to( - hinge_lock_builder.part.joints["Flap"]) + hinge_lock_builder.joints["Flap"]) flap += hinge_lock_builder.part flap += mirror(hinge_lock_builder.part, Plane.YZ) @@ -131,9 +131,9 @@ def generate_base_tray( RigidJoint("Flap", joint_location=Location( (hinge_lock_offset + flap_middle_gap, 0, 0))) - tray.joints["Flap"].connect_to(flap_builder.part.joints["Middle"]) + tray.joints["Flap"].connect_to(flap_builder.joints["Middle"]) flap.joints["HingeLockLeft"].connect_to( - hinge_lock_negative_builder.part.joints["Flap"]) + hinge_lock_negative_builder.joints["Flap"]) tray -= hinge_lock_negative_builder.part tray -= mirror(hinge_lock_negative_builder.part, Plane.YZ) @@ -150,7 +150,7 @@ def generate_base_tray( flap.joints["HingeLeft"].connect_to(hinge_negative.joints["Flap"]) hinge_negative.joints["HingePin"].connect_to( - hinge_pin_negative_builder.part.joints["Hinge"]) + hinge_pin_negative_builder.joints["Hinge"]) tray -= hinge_pin_negative_builder.part tray -= mirror(hinge_pin_negative_builder.part, Plane.YZ) @@ -162,7 +162,7 @@ def generate_base_tray( flap.joints[name] = joint flap.joints["HingeLeft"].connect_to(hinge.joints["Flap"]) - hinge.joints["HingePin"].connect_to(hinge_pin_builder.part.joints["Hinge"]) + hinge.joints["HingePin"].connect_to(hinge_pin_builder.joints["Hinge"]) flap += hinge_pin_builder.part flap += mirror(hinge_pin_builder.part, Plane.YZ) @@ -173,14 +173,16 @@ def generate_base_tray( part_list.append(mirror(flap, Plane.XZ)) part_list.append(tray) + + hinge_pin_height = hinge_height/2 - floor_thickness - return Compound([part.translate((0, 0, -floor_thickness)) for part in part_list]) + return Compound(children=[part.translate((0, 0, -floor_thickness)) for part in part_list], label=f"tray {total_width}x{total_depth}"), hinge_pin_height # %% if __name__ == "__main__": - base_tray = generate_base_tray(is_double_tray=False, total_width=30) + base_tray, _ = generate_base_tray(is_double_tray=True) export_step(base_tray, "../output/test.step") show(base_tray, render_joints=False) diff --git a/Trays/functions/calculate_cutout_positions/calculate_linear_cutout_positions.py b/Trays/functions/calculate_cutout_positions/calculate_linear_cutout_positions.py index 3cdb7f4..0f6aca7 100644 --- a/Trays/functions/calculate_cutout_positions/calculate_linear_cutout_positions.py +++ b/Trays/functions/calculate_cutout_positions/calculate_linear_cutout_positions.py @@ -1,6 +1,7 @@ # %% import math - +from build123d import * +from ocp_vscode import * # %% @@ -11,6 +12,9 @@ def calculate_linear_cutout_positions( tolerance, is_double_tray=False, ): + with BuildPart() as positions_part: + Box(usable_area["min"]["x"]*-2, usable_area["min"]["y"]*-2, 1) + full_diameters = [] for diameter in diameters: full_diameters.append(diameter + tolerance) @@ -66,34 +70,30 @@ def calculate_linear_cutout_positions( if is_double_tray: take_from_left = not take_from_left - x_positions = calculate_line_positions( + front_positions = calculate_line_positions( usable_area, line_one ) - for i, pos in enumerate(x_positions): + for i, pos in enumerate(front_positions): pos['y'] = usable_area['min']['y'] + (pos['diameter']) / 2 if edge_offsets and i < len(edge_offsets): pos['y'] += edge_offsets[line_one_indices[i]] - pos['flipped'] = False + RigidJoint(f"{i}", positions_part.part, Location((pos["x"], pos["y"], 0))) if is_double_tray: - y_positions = calculate_line_positions( + back_positions = calculate_line_positions( usable_area, line_two, ) - for i, pos in enumerate(y_positions): + for i, pos in enumerate(back_positions): pos['y'] = usable_area['max']['y'] - (pos['diameter']) / 2 if edge_offsets and i < len(edge_offsets): pos['y'] -= edge_offsets[line_two_indices[i]] - pos['flipped'] = True - - positions = x_positions + y_positions - else: - positions = x_positions + RigidJoint(f"{i + len(front_positions)}", positions_part.part, Location((pos["x"], pos["y"], 0), (0,0,180))) - return positions + return positions_part.part def calculate_line_positions( @@ -134,3 +134,16 @@ def calculate_line_positions( positions.append(pos) return positions + +#%% +if __name__ == "__main__": + positions = calculate_linear_cutout_positions( + {"min":{"x":-50, "y":-30}, "max":{"x":50, "y":30}}, + [32, 32, 32], + [0,0,0], + 0.55, + True, + ) + show(positions, render_joints=True) + print(positions.joints) +# %% diff --git a/Trays/functions/cutout_generator.py b/Trays/functions/cutout_generator.py index 77d0a89..a7473df 100644 --- a/Trays/functions/cutout_generator.py +++ b/Trays/functions/cutout_generator.py @@ -12,105 +12,57 @@ def generate_cutout( tolerance=0.55, flap_depth=11.8, hinge_diameter=27.5, - flap_center_gap=0.2, + flap_middle_gap=0.2, cutout_edge_spacing=.8, - floor_thickness=.8, lip_offset=0, + hinge_pin_height=1.2, + taper_angle=12.5, epsilon=0.001 ): - with BuildPart() as normal_base: - with BuildSketch(): - c = Circle(base_diameter/2 + tolerance/2, - align=(Align.CENTER, Align.CENTER)) - extrude(amount=6, taper=12.5) - extrude(c, amount=-6, taper=-12.5) - # # Add the slide path for the base - if (base_diameter/2 > flap_depth-cutout_edge_spacing): - cross_section_result = section(normal_base.part, Plane.XZ) - e = extrude(cross_section_result, (base_diameter/2 + tolerance/2) - - (flap_depth - cutout_edge_spacing) - flap_center_gap + epsilon) - - normal = copy.deepcopy(normal_base.part) - - normal_base.part = normal_base.part.translate((0, 0, -epsilon)) - - if not lip_offset == 0: - lip_offseter = normal_base.part.translate((0, lip_offset, 0)) - normal_base.part = normal_base.part.intersect(lip_offseter) - - if isinstance(normal_base.part, ShapeList): - normal_base.part = normal_base.part[0] - - with BuildPart() as lip_adjustor_base: - with Locations((0, -base_diameter*0.75, -epsilon*2)): - Cylinder(base_diameter, 6, align=(Align.CENTER, Align.CENTER)) - - # Get the radius cut out of the adjustor - hinge_radius = hinge_diameter/2 - cutout_edge_spacing - delta_x = hinge_radius - \ - math.sqrt(math.pow(hinge_radius, 2) - - math.pow(2 - floor_thickness + epsilon, 2)) - - with BuildPart() as lip_adjustor_edge: - with BuildSketch(Plane.YZ): - with Locations((-base_diameter/2 - tolerance/2 + delta_x - hinge_radius, 2 - floor_thickness)): - Circle(hinge_radius, align=(Align.CENTER, Align.CENTER)) - revolve_axis = Axis( - origin=(0, -tolerance/2 - epsilon, 0), direction=(0, 0, 1)) + total_diameter = (base_diameter + tolerance) + total_radius = total_diameter/2 + + with BuildPart() as base_builder: + with BuildSketch(Plane.XY): + Circle(total_radius) + extrude(amount=6, taper=taper_angle) + RigidJoint("Track", joint_location=Location((-total_radius, 0, 0))) + RigidJoint("LipShaper", joint_location=Location((0, -total_radius+epsilon, 0))) + cutout = base_builder.part + + with BuildPart() as track_builder: + cross_section = section(base_builder.part, Plane.XZ) + extrude(cross_section, total_radius - flap_depth + cutout_edge_spacing - flap_middle_gap + epsilon) + RigidJoint("Base", joint_location=Location((-total_radius, 0, 0))) + cutout.joints["Track"].connect_to(track_builder.joints["Base"]) + cutout += track_builder.part + + with BuildPart() as lip_shaper_builder: + revolve_axis = Axis(origin=(0, total_radius + hinge_diameter/2 - lip_offset - tolerance, 0), direction=(0, 0, 1)) + with BuildSketch(Plane.YZ) as revolve_sketch: + Circle(hinge_diameter/2) revolve(axis=revolve_axis) - - lip_adjustor_edge.part = lip_adjustor_edge.part.translate((0, lip_offset, 0)) - - lip_adjustor_base.part -= lip_adjustor_edge.part - - # Keep only the part of the lip adjustor that intersects with the flap - with BuildPart() as lip_box: - with Locations((0, lip_offset - base_diameter/2 - tolerance/2, -1)): - b = Box( - (base_diameter + 5), - (flap_depth - cutout_edge_spacing + epsilon)*2, - 8, - align=(Align.CENTER, Align.CENTER, Align.MIN), - ) - - lip_adjustor_base.part = lip_adjustor_base.part.intersect(lip_box.part) - - # Convert ShapeList to Compound if needed - if isinstance(lip_adjustor_base.part, ShapeList): - lip_adjustor_base.part = lip_adjustor_base.part[0] - - normal_base.part += lip_adjustor_base.part - - # Create flattener - with BuildPart() as flattener: - Box(base_diameter + 5, base_diameter + 5, 5, - align=(Align.CENTER, Align.CENTER, Align.MIN)) - - # Subtract flattener using boolean operation - normal_base.part = normal_base.part.intersect(flattener.part) - - # Convert normal_base from ShapeList to Shape if necessary - if isinstance(normal_base.part, ShapeList): - normal_base.part = normal_base.part[0] - - # # Handle case where subtraction returns a ShapeList - return_compound = Compound([normal_base.part]) - - # show( - # return_compound, flattener, normal, lip_adjustor_base, - # lip_adjustor_edge, lip_box, c, lip_box.faces().sort_by(Axis.Z)[0]. - # center(), - # alphas=[1, 0.5, 1, 0.5, 0.5], - # names=['return', 'flat', 'normal', 'lip_base', 'lip_edge', 'lip_box', - # 'circle', 'lip_box_center']) - - return return_compound - - -# %% - + y_adjust = math.sqrt((hinge_diameter/2)**2 - hinge_pin_height**2) + RigidJoint("Base", joint_location=Location((0, y_adjust, -hinge_pin_height))) + cutout.joints["LipShaper"].connect_to(lip_shaper_builder.joints["Base"]) + + with BuildPart() as lip_box_builder: + box_depth = flap_depth - cutout_edge_spacing + epsilon + with Locations((0,0,3)): + Box(total_diameter*2, box_depth * 2, 6) + RigidJoint("Base") + cutout.joints["LipShaper"].connect_to(lip_box_builder.joints["Base"]) + lip = lip_box_builder.part - lip_shaper_builder.part + lip = (lip.solids() flap_depth-cutout_edge_spacing): + cross_section_result = section(normal_base.part, Plane.XZ) + e = extrude(cross_section_result, (base_diameter/2 + tolerance/2) - + (flap_depth - cutout_edge_spacing) - flap_center_gap + epsilon) + + normal = copy.deepcopy(normal_base.part) + + normal_base.part = normal_base.part.translate((0, 0, -epsilon)) + + if not lip_offset == 0: + lip_offseter = normal_base.part.translate((0, lip_offset, 0)) + normal_base.part = normal_base.part.intersect(lip_offseter) + + if isinstance(normal_base.part, ShapeList): + normal_base.part = normal_base.part[0] + + with BuildPart() as lip_adjustor_base: + with Locations((0, -base_diameter*0.75, -epsilon*2)): + Cylinder(base_diameter, 6, align=(Align.CENTER, Align.CENTER)) + + # Get the radius cut out of the adjustor + hinge_radius = hinge_diameter/2 - cutout_edge_spacing + delta_x = hinge_radius - \ + math.sqrt(math.pow(hinge_radius, 2) - + math.pow(2 - floor_thickness + epsilon, 2)) + + with BuildPart() as lip_adjustor_edge: + with BuildSketch(Plane.YZ): + with Locations((-base_diameter/2 - tolerance/2 + delta_x - hinge_radius, 2 - floor_thickness)): + Circle(hinge_radius, align=(Align.CENTER, Align.CENTER)) + revolve_axis = Axis( + origin=(0, -tolerance/2 - epsilon, 0), direction=(0, 0, 1)) + revolve(axis=revolve_axis) + + lip_adjustor_edge.part = lip_adjustor_edge.part.translate((0, lip_offset, 0)) + + lip_adjustor_base.part -= lip_adjustor_edge.part + + # Keep only the part of the lip adjustor that intersects with the flap + with BuildPart() as lip_box: + with Locations((0, lip_offset - base_diameter/2 - tolerance/2, -1)): + b = Box( + (base_diameter + 5), + (flap_depth - cutout_edge_spacing + epsilon)*2, + 8, + align=(Align.CENTER, Align.CENTER, Align.MIN), + ) + + lip_adjustor_base.part = lip_adjustor_base.part.intersect(lip_box.part) + + # Convert ShapeList to Compound if needed + if isinstance(lip_adjustor_base.part, ShapeList): + lip_adjustor_base.part = lip_adjustor_base.part[0] + + normal_base.part += lip_adjustor_base.part + + # Create flattener + with BuildPart() as flattener: + Box(base_diameter + 5, base_diameter + 5, 5, + align=(Align.CENTER, Align.CENTER, Align.MIN)) + + # Subtract flattener using boolean operation + normal_base.part = normal_base.part.intersect(flattener.part) + + # Convert normal_base from ShapeList to Shape if necessary + if isinstance(normal_base.part, ShapeList): + normal_base.part = normal_base.part[0] + + # # Handle case where subtraction returns a ShapeList + return_compound = Compound([normal_base.part]) + + # show( + # return_compound, flattener, normal, lip_adjustor_base, + # lip_adjustor_edge, lip_box, c, lip_box.faces().sort_by(Axis.Z)[0]. + # center(), + # alphas=[1, 0.5, 1, 0.5, 0.5], + # names=['return', 'flat', 'normal', 'lip_base', 'lip_edge', 'lip_box', + # 'circle', 'lip_box_center']) + + return return_compound + + +# %% + +if __name__ == "__main__": + cutout = generate_cutout( + 49.6, tolerance=0.55, lip_offset=0.1, floor_thickness=0.8) + show(cutout) +# %% From 5b49cd0a176d0d9e33373a16c9dccf49bc2ae2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=B8hler?= <45562910+seedback@users.noreply.github.com> Date: Sun, 5 Apr 2026 13:36:44 +0200 Subject: [PATCH 6/6] Update alternating positioning logic --- .../calculate_alternating_cutout_positions.py | 23 +++++++------ Trays/functions/full_tray_generator.py | 34 +++++++++++++------ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Trays/functions/calculate_cutout_positions/calculate_alternating_cutout_positions.py b/Trays/functions/calculate_cutout_positions/calculate_alternating_cutout_positions.py index 55f675e..d5978a8 100644 --- a/Trays/functions/calculate_cutout_positions/calculate_alternating_cutout_positions.py +++ b/Trays/functions/calculate_cutout_positions/calculate_alternating_cutout_positions.py @@ -1,5 +1,6 @@ # %% import math +from build123d import * def calculate_alternating_cutout_positions( @@ -12,18 +13,20 @@ def calculate_alternating_cutout_positions( for diameter in diameters: full_diameters.append(diameter + tolerance) - if len(diameters) == 1: - return [{ - 'x': 0, - 'diameter': diameters[0], - 'y': usable_area['min']['y'] + diameter/2 - edge_offsets[0], - 'flipped': False, - }] - - positions = _calculate_initial_positions( + positions_list = _calculate_initial_positions( usable_area, diameters, edge_offsets, tolerance) - return positions + # Create a BuildPart with RigidJoints like the linear logic + with BuildPart() as positions_part: + Box(usable_area["min"]["x"]*-2, usable_area["min"]["y"]*-2, 1) + + for i, pos in enumerate(positions_list): + if pos['flipped']: + RigidJoint(f"{i}", positions_part.part, Location((pos["x"], pos["y"], 0), (0,0,180))) + else: + RigidJoint(f"{i}", positions_part.part, Location((pos["x"], pos["y"], 0))) + + return positions_part.part def _calculate_initial_positions( diff --git a/Trays/functions/full_tray_generator.py b/Trays/functions/full_tray_generator.py index e99be56..d1eda2c 100644 --- a/Trays/functions/full_tray_generator.py +++ b/Trays/functions/full_tray_generator.py @@ -16,6 +16,7 @@ from .calculate_cutout_positions.calculate_alternating_cutout_positions import * base_tray_storage = {} +cutout_storage = {} def calculate_usable_area( @@ -150,7 +151,8 @@ def generate_full_tray( cutouts_list = [] for i, position in enumerate(positions.joints): - cutout = (generate_cutout( + # Create cache key from cutout parameters + cutout_key = ( diameters[i], tolerance, flap_depth, @@ -161,18 +163,30 @@ def generate_full_tray( hinge_pin_height, 12.5, epsilon - )) - - print("showing") - show(cutout.children[0], render_joints=True) - print("showed") + ) + + # Check if cutout exists in storage, otherwise generate it + if cutout_key not in cutout_storage: + cutout = generate_cutout( + diameters[i], + tolerance, + flap_depth, + hinge_diameter, + flap_center_gap, + safety_margin[1], + edge_adjusts[i], + hinge_pin_height, + 12.5, + epsilon + ) + cutout_storage[cutout_key] = cutout + else: + cutout = copy.copy(cutout_storage[cutout_key]) - print(cutout.joints) positions.joints[f"{i}"].connect_to(cutout.children[0].joints["Center"]) tray_compound -= cutout.children[0] cutouts_list.append(cutout) - print("Got here2") return tray_compound, cutouts_list, positions @@ -190,9 +204,9 @@ def generate_full_tray( # ) tray_compound, cutouts_list, positions = generate_full_tray( - [32,32,32,32,32,32,32,32,32,32], + [40,40,40,40], is_double_tray=True, - safety_margin=(6.5, 0.4), + safety_margin=(6.5, 0.8), # edge_offsets=[5, 5], edge_adjusts=[0.3], floor_thickness=.8