308 lines
9.5 KiB
Python
308 lines
9.5 KiB
Python
from __future__ import annotations
|
|
|
|
from math import sqrt
|
|
|
|
from build123d import *
|
|
from cadpy.assembly import AssemblyHelper
|
|
|
|
|
|
# Units: millimeters.
|
|
# Origin: base plate center, XY on plate center plane, +Z upward.
|
|
# The cone uses a shallow visible seat because it is taller than the 10 mm base.
|
|
# The triangular prism uses a full-depth V-groove so the matching angled faces
|
|
# visibly nest while the part remains a separate component.
|
|
# Two seats are intentionally over-inserted to exercise the viewer's collision
|
|
# analysis overlay. The square block is exactly seated to exercise contact
|
|
# surface highlighting.
|
|
|
|
BASE_LENGTH = 120.0
|
|
BASE_WIDTH = 70.0
|
|
BASE_THICKNESS = 10.0
|
|
BASE_TOP_Z = BASE_THICKNESS / 2
|
|
CLEARANCE = 0.2
|
|
|
|
PEG_DIAMETER = 16.0
|
|
PEG_HEIGHT = 25.0
|
|
BLOCK_SIZE = 18.0
|
|
CONE_BASE_DIAMETER = 22.0
|
|
CONE_HEIGHT = 25.0
|
|
SPHERE_DIAMETER = 20.0
|
|
PRISM_LENGTH = 35.0
|
|
PRISM_SIDE = 18.0
|
|
PRISM_HEIGHT = sqrt(3.0) * PRISM_SIDE / 2.0
|
|
|
|
POCKET_DEPTH = 3.0
|
|
CONE_SEAT_DEPTH = 5.0
|
|
SPHERE_CUP_RADIUS = SPHERE_DIAMETER / 2.0 + CLEARANCE / 2.0
|
|
V_GROOVE_DEPTH = BASE_THICKNESS
|
|
V_GROOVE_SIDE_CLEARANCE = CLEARANCE / 2.0
|
|
V_GROOVE_BOTTOM_WIDTH = 4.0 * V_GROOVE_SIDE_CLEARANCE / sqrt(3.0)
|
|
V_GROOVE_TOP_WIDTH = 2.0 * (V_GROOVE_DEPTH + 2.0 * V_GROOVE_SIDE_CLEARANCE) / sqrt(3.0)
|
|
PEG_COLLISION_DEPTH = 0.8
|
|
PRISM_COLLISION_DEPTH = 1.0
|
|
BLOCK_CONTACT_OFFSET = 0.0
|
|
|
|
CUBE_TOP_PAD_SIZE = 14.0
|
|
CUBE_TOP_PAD_HEIGHT = 2.5
|
|
CUBE_TOP_POST_RADIUS = 1.8
|
|
CUBE_TOP_POST_HEIGHT = 7.0
|
|
CUBE_TOP_POST_SPACING = 8.0
|
|
CUBE_TOP_CAP_LENGTH = 13.0
|
|
CUBE_TOP_CAP_WIDTH = 4.5
|
|
CUBE_TOP_CAP_HEIGHT = 2.5
|
|
CUBE_TOP_BEAD_DIAMETER = 4.5
|
|
|
|
STATIONS = {
|
|
"cylindrical_peg": (-48.0, 0.0),
|
|
"square_block": (-24.0, 0.0),
|
|
"cone": (0.0, 0.0),
|
|
"sphere": (24.0, 0.0),
|
|
"triangular_prism": (48.0, 0.0),
|
|
}
|
|
|
|
|
|
def _triangular_prism(side: float, length: float):
|
|
height = sqrt(3.0) * side / 2.0
|
|
with BuildPart() as prism:
|
|
with BuildSketch(Plane.XZ):
|
|
Polygon(
|
|
(-side / 2.0, height / 2.0),
|
|
(side / 2.0, height / 2.0),
|
|
(0.0, -height / 2.0),
|
|
)
|
|
extrude(amount=length)
|
|
return prism.part.moved(Location((0.0, length / 2.0, 0.0)))
|
|
|
|
|
|
def _v_groove_tool(depth: float, length: float, side_clearance: float):
|
|
top_half_width = (depth + 2.0 * side_clearance) / sqrt(3.0)
|
|
bottom_half_width = 2.0 * side_clearance / sqrt(3.0)
|
|
with BuildPart() as groove:
|
|
with BuildSketch(Plane.XZ):
|
|
Polygon(
|
|
(-top_half_width, 0.0),
|
|
(top_half_width, 0.0),
|
|
(bottom_half_width, -depth),
|
|
(-bottom_half_width, -depth),
|
|
)
|
|
extrude(amount=length)
|
|
return groove.part.moved(Location((0.0, length / 2.0, 0.0)))
|
|
|
|
|
|
def _make_base():
|
|
base = Box(BASE_LENGTH, BASE_WIDTH, BASE_THICKNESS)
|
|
|
|
peg_x, peg_y = STATIONS["cylindrical_peg"]
|
|
peg_pocket = Cylinder(
|
|
radius=(PEG_DIAMETER + CLEARANCE) / 2.0,
|
|
height=POCKET_DEPTH + 0.4,
|
|
).moved(Location((peg_x, peg_y, BASE_TOP_Z - POCKET_DEPTH / 2.0 + 0.2)))
|
|
base = base - peg_pocket
|
|
|
|
block_x, block_y = STATIONS["square_block"]
|
|
block_pocket = Box(
|
|
BLOCK_SIZE + CLEARANCE,
|
|
BLOCK_SIZE + CLEARANCE,
|
|
POCKET_DEPTH + 0.4,
|
|
).moved(Location((block_x, block_y, BASE_TOP_Z - POCKET_DEPTH / 2.0 + 0.2)))
|
|
base = base - block_pocket
|
|
|
|
cone_x, cone_y = STATIONS["cone"]
|
|
cone_seat = Cone(
|
|
0.0,
|
|
(CONE_BASE_DIAMETER + CLEARANCE) / 2.0,
|
|
CONE_SEAT_DEPTH,
|
|
).moved(Location((cone_x, cone_y, BASE_TOP_Z - CONE_SEAT_DEPTH / 2.0)))
|
|
base = base - cone_seat
|
|
|
|
sphere_x, sphere_y = STATIONS["sphere"]
|
|
sphere_cup = Sphere(SPHERE_CUP_RADIUS).moved(Location((sphere_x, sphere_y, BASE_TOP_Z)))
|
|
base = base - sphere_cup
|
|
|
|
prism_x, prism_y = STATIONS["triangular_prism"]
|
|
groove = _v_groove_tool(
|
|
V_GROOVE_DEPTH,
|
|
PRISM_LENGTH + CLEARANCE,
|
|
V_GROOVE_SIDE_CLEARANCE,
|
|
).moved(Location((prism_x, prism_y, 0.0)))
|
|
base = base - groove
|
|
|
|
base.label = "base_plate"
|
|
base.color = Color(0.58, 0.62, 0.66, 1.0)
|
|
return base
|
|
|
|
|
|
def _make_peg():
|
|
peg = Cylinder(radius=PEG_DIAMETER / 2.0, height=PEG_HEIGHT)
|
|
peg.label = "cylindrical_peg"
|
|
peg.color = Color(0.86, 0.24, 0.20, 1.0)
|
|
return peg
|
|
|
|
|
|
def _make_block():
|
|
block = Box(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
|
|
block.label = "square_block"
|
|
block.color = Color(0.18, 0.53, 0.88, 1.0)
|
|
return block
|
|
|
|
|
|
def _make_cone():
|
|
cone = Cone(0.0, CONE_BASE_DIAMETER / 2.0, CONE_HEIGHT)
|
|
cone.label = "cone"
|
|
cone.color = Color(0.95, 0.64, 0.16, 1.0)
|
|
return cone
|
|
|
|
|
|
def _make_sphere():
|
|
sphere = Sphere(SPHERE_DIAMETER / 2.0)
|
|
sphere.label = "sphere"
|
|
sphere.color = Color(0.45, 0.72, 0.34, 1.0)
|
|
return sphere
|
|
|
|
|
|
def _make_prism():
|
|
prism = _triangular_prism(PRISM_SIDE, PRISM_LENGTH)
|
|
prism.label = "triangular_prism"
|
|
prism.color = Color(0.58, 0.34, 0.74, 1.0)
|
|
return prism
|
|
|
|
|
|
def _make_cube_top_pad():
|
|
pad = Box(CUBE_TOP_PAD_SIZE, CUBE_TOP_PAD_SIZE, CUBE_TOP_PAD_HEIGHT).moved(
|
|
Location((0.0, 0.0, CUBE_TOP_PAD_HEIGHT / 2.0))
|
|
)
|
|
pad.label = "cube_top_pad"
|
|
pad.color = Color(0.10, 0.14, 0.20, 1.0)
|
|
return pad
|
|
|
|
|
|
def _make_cube_top_post(x_offset: float, name: str):
|
|
post = Cylinder(radius=CUBE_TOP_POST_RADIUS, height=CUBE_TOP_POST_HEIGHT).moved(
|
|
Location((x_offset, 0.0, CUBE_TOP_PAD_HEIGHT + CUBE_TOP_POST_HEIGHT / 2.0))
|
|
)
|
|
post.label = name
|
|
post.color = Color(0.84, 0.30, 0.50, 1.0)
|
|
return post
|
|
|
|
|
|
def _make_cube_top_cap():
|
|
cap = Box(CUBE_TOP_CAP_LENGTH, CUBE_TOP_CAP_WIDTH, CUBE_TOP_CAP_HEIGHT).moved(
|
|
Location(
|
|
(
|
|
0.0,
|
|
0.0,
|
|
CUBE_TOP_PAD_HEIGHT + CUBE_TOP_POST_HEIGHT + CUBE_TOP_CAP_HEIGHT / 2.0,
|
|
)
|
|
)
|
|
)
|
|
cap.label = "cube_top_bridge_cap"
|
|
cap.color = Color(0.12, 0.70, 0.74, 1.0)
|
|
return cap
|
|
|
|
|
|
def _make_cube_top_bead():
|
|
bead_radius = CUBE_TOP_BEAD_DIAMETER / 2.0
|
|
bead = Sphere(bead_radius).moved(
|
|
Location(
|
|
(
|
|
0.0,
|
|
0.0,
|
|
CUBE_TOP_PAD_HEIGHT + CUBE_TOP_POST_HEIGHT + CUBE_TOP_CAP_HEIGHT + bead_radius,
|
|
)
|
|
)
|
|
)
|
|
bead.label = "cube_top_bead"
|
|
bead.color = Color(0.98, 0.86, 0.20, 1.0)
|
|
return bead
|
|
|
|
|
|
def _make_cube_top_subassembly():
|
|
subassembly = AssemblyHelper("cube_top_subassembly")
|
|
subassembly.add(_make_cube_top_pad(), "cube_top_pad")
|
|
subassembly.add(
|
|
_make_cube_top_post(-CUBE_TOP_POST_SPACING / 2.0, "cube_top_left_post"),
|
|
"cube_top_left_post",
|
|
)
|
|
subassembly.add(
|
|
_make_cube_top_post(CUBE_TOP_POST_SPACING / 2.0, "cube_top_right_post"),
|
|
"cube_top_right_post",
|
|
)
|
|
subassembly.add(_make_cube_top_cap(), "cube_top_bridge_cap")
|
|
subassembly.add(_make_cube_top_bead(), "cube_top_bead")
|
|
return subassembly.build()
|
|
|
|
|
|
def gen_step():
|
|
asm = AssemblyHelper("basic_shape_mating_test_fixture")
|
|
|
|
base = asm.add(_make_base(), "base_plate")
|
|
peg = asm.add(_make_peg(), "cylindrical_peg")
|
|
block = asm.add(_make_block(), "square_block")
|
|
cone = asm.add(_make_cone(), "cone")
|
|
sphere = asm.add(_make_sphere(), "sphere")
|
|
prism = asm.add(_make_prism(), "triangular_prism")
|
|
cube_top_subassembly = asm.add(
|
|
_make_cube_top_subassembly(),
|
|
"cube_top_subassembly",
|
|
)
|
|
|
|
peg_x, peg_y = STATIONS["cylindrical_peg"]
|
|
peg_seat = asm.rigid_frame(base, "peg_pocket_floor", Location((peg_x, peg_y, BASE_TOP_Z - POCKET_DEPTH)))
|
|
peg_bottom = asm.rigid_frame(peg, "bottom_center", Location((0.0, 0.0, -PEG_HEIGHT / 2.0)))
|
|
asm.face_to_face(peg_seat, peg_bottom, offset=-PEG_COLLISION_DEPTH)
|
|
|
|
block_x, block_y = STATIONS["square_block"]
|
|
block_seat = asm.rigid_frame(
|
|
base,
|
|
"block_pocket_floor",
|
|
Location((block_x, block_y, BASE_TOP_Z - POCKET_DEPTH)),
|
|
)
|
|
block_bottom = asm.rigid_frame(block, "bottom_center", Location((0.0, 0.0, -BLOCK_SIZE / 2.0)))
|
|
asm.face_to_face(block_seat, block_bottom, offset=BLOCK_CONTACT_OFFSET)
|
|
|
|
asm.rigid_frame(block, "top_center", Location((0.0, 0.0, BLOCK_SIZE / 2.0)))
|
|
cube_top_seat = asm.rigid_frame(
|
|
base,
|
|
"cube_top_subassembly_seat",
|
|
Location((block_x, block_y, BASE_TOP_Z - POCKET_DEPTH + BLOCK_CONTACT_OFFSET + BLOCK_SIZE)),
|
|
)
|
|
cube_top_subassembly_bottom = asm.rigid_frame(
|
|
cube_top_subassembly,
|
|
"bottom_center",
|
|
Location((0.0, 0.0, 0.0)),
|
|
)
|
|
asm.face_to_face(
|
|
cube_top_seat,
|
|
cube_top_subassembly_bottom,
|
|
label="cube_top_subassembly_on_square_block",
|
|
)
|
|
|
|
cone_x, cone_y = STATIONS["cone"]
|
|
cone_tip_seat = asm.rigid_frame(
|
|
base,
|
|
"cone_countersink_tip",
|
|
Location((cone_x, cone_y, BASE_TOP_Z - CONE_SEAT_DEPTH)),
|
|
)
|
|
cone_tip = asm.rigid_frame(cone, "tip", Location((0.0, 0.0, -CONE_HEIGHT / 2.0)))
|
|
asm.coaxial(cone_tip_seat, cone_tip, offset=0.1)
|
|
|
|
sphere_x, sphere_y = STATIONS["sphere"]
|
|
sphere_cup_center = asm.rigid_frame(base, "sphere_cup_center", Location((sphere_x, sphere_y, BASE_TOP_Z)))
|
|
sphere_center = asm.rigid_frame(sphere, "center", Location((0.0, 0.0, 0.0)))
|
|
asm.coaxial(sphere_cup_center, sphere_center, offset=0.1)
|
|
|
|
prism_x, prism_y = STATIONS["triangular_prism"]
|
|
prism_v_bottom = asm.rigid_frame(
|
|
base,
|
|
"v_groove_bottom",
|
|
Location((prism_x, prism_y, BASE_TOP_Z - V_GROOVE_DEPTH)),
|
|
)
|
|
prism_bottom_vertex = asm.rigid_frame(
|
|
prism,
|
|
"bottom_vertex",
|
|
Location((0.0, 0.0, -PRISM_HEIGHT / 2.0)),
|
|
)
|
|
asm.face_to_face(prism_v_bottom, prism_bottom_vertex, offset=-PRISM_COLLISION_DEPTH)
|
|
|
|
return asm.build()
|