Import CAD projects and cad-router v1
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from collections.abc import Sequence
|
||||
|
||||
from build123d import (
|
||||
Box,
|
||||
Color,
|
||||
Compound,
|
||||
Cone,
|
||||
Cylinder,
|
||||
Location,
|
||||
Shape,
|
||||
Sphere,
|
||||
Torus,
|
||||
chamfer,
|
||||
fillet,
|
||||
scale,
|
||||
)
|
||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
|
||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeCylinder
|
||||
from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt, gp_Trsf
|
||||
|
||||
|
||||
DISPLAY_NAME = "Pelican riding a bicycle"
|
||||
|
||||
# Units: millimeters.
|
||||
# Origin: halfway between the wheel hubs on the bicycle center plane.
|
||||
# X: rear-to-front bicycle direction, Y: wheel axle direction, +Z: up.
|
||||
|
||||
COLORS = {
|
||||
"rubber_black": (0.003, 0.003, 0.003, 1.0),
|
||||
"rim_silver": (0.70, 0.72, 0.70, 1.0),
|
||||
"spoke_steel": (0.82, 0.84, 0.82, 1.0),
|
||||
"frame_blue": (0.05, 0.28, 0.75, 1.0),
|
||||
"seat_brown": (0.28, 0.12, 0.05, 1.0),
|
||||
"pelican_white": (0.96, 0.95, 0.88, 1.0),
|
||||
"wing_gray": (0.66, 0.68, 0.64, 1.0),
|
||||
"feather_dark": (0.08, 0.08, 0.075, 1.0),
|
||||
"bill_yellow": (1.00, 0.78, 0.26, 1.0),
|
||||
"pouch_orange": (0.95, 0.45, 0.12, 1.0),
|
||||
"leg_orange": (0.98, 0.50, 0.12, 1.0),
|
||||
"eye_black": (0.0, 0.0, 0.0, 1.0),
|
||||
}
|
||||
|
||||
Vector3 = tuple[float, float, float]
|
||||
|
||||
|
||||
def _v_add(a: Vector3, b: Vector3) -> Vector3:
|
||||
return (a[0] + b[0], a[1] + b[1], a[2] + b[2])
|
||||
|
||||
|
||||
def _v_sub(a: Vector3, b: Vector3) -> Vector3:
|
||||
return (a[0] - b[0], a[1] - b[1], a[2] - b[2])
|
||||
|
||||
|
||||
def _v_mul(a: Vector3, scalar: float) -> Vector3:
|
||||
return (a[0] * scalar, a[1] * scalar, a[2] * scalar)
|
||||
|
||||
|
||||
def _v_dot(a: Vector3, b: Vector3) -> float:
|
||||
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
||||
|
||||
|
||||
def _v_cross(a: Vector3, b: Vector3) -> Vector3:
|
||||
return (
|
||||
a[1] * b[2] - a[2] * b[1],
|
||||
a[2] * b[0] - a[0] * b[2],
|
||||
a[0] * b[1] - a[1] * b[0],
|
||||
)
|
||||
|
||||
|
||||
def _v_len(a: Vector3) -> float:
|
||||
return math.sqrt(_v_dot(a, a))
|
||||
|
||||
|
||||
def _v_norm(a: Vector3) -> Vector3:
|
||||
length = _v_len(a)
|
||||
if length <= 1e-9:
|
||||
raise ValueError("zero-length vector")
|
||||
return (a[0] / length, a[1] / length, a[2] / length)
|
||||
|
||||
|
||||
def _style(shape: Shape, label: str, color_name: str) -> Shape:
|
||||
shape.label = label
|
||||
shape.color = Color(*COLORS[color_name])
|
||||
return shape
|
||||
|
||||
|
||||
def _group(label: str, children: Sequence[Shape], color_name: str | None = None) -> Compound:
|
||||
compound = Compound(obj=list(children), children=list(children), label=label)
|
||||
if color_name is not None:
|
||||
compound.color = Color(*COLORS[color_name])
|
||||
return compound
|
||||
|
||||
|
||||
def _softened(shape: Shape, radius: float) -> Shape:
|
||||
try:
|
||||
return fillet(shape.edges(), radius=radius)
|
||||
except Exception:
|
||||
try:
|
||||
return chamfer(shape.edges(), length=radius * 0.45)
|
||||
except Exception:
|
||||
return shape
|
||||
|
||||
|
||||
def _transformed(shape: Shape, center: Vector3, axis_x: Vector3, axis_y: Vector3, axis_z: Vector3) -> Shape:
|
||||
x_dir = _v_norm(axis_x)
|
||||
y_dir = _v_norm(axis_y)
|
||||
z_dir = _v_norm(axis_z)
|
||||
trsf = gp_Trsf()
|
||||
trsf.SetValues(
|
||||
x_dir[0],
|
||||
y_dir[0],
|
||||
z_dir[0],
|
||||
center[0],
|
||||
x_dir[1],
|
||||
y_dir[1],
|
||||
z_dir[1],
|
||||
center[1],
|
||||
x_dir[2],
|
||||
y_dir[2],
|
||||
z_dir[2],
|
||||
center[2],
|
||||
)
|
||||
return Shape(obj=BRepBuilderAPI_Transform(shape.wrapped, trsf, True).Shape())
|
||||
|
||||
|
||||
def _oriented_box(
|
||||
label: str,
|
||||
center: Vector3,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
axis_x: Vector3,
|
||||
axis_y: Vector3,
|
||||
axis_z: Vector3,
|
||||
color_name: str,
|
||||
*,
|
||||
radius: float = 0.0,
|
||||
) -> Shape:
|
||||
shape = _transformed(Box(size_x, size_y, size_z), center, axis_x, axis_y, axis_z)
|
||||
if radius > 0.0:
|
||||
shape = _softened(shape, radius)
|
||||
return _style(shape, label, color_name)
|
||||
|
||||
|
||||
def _cylinder_between(label: str, p1: Vector3, p2: Vector3, radius: float, color_name: str) -> Shape:
|
||||
axis = _v_sub(p2, p1)
|
||||
length = _v_len(axis)
|
||||
if length <= 1e-6:
|
||||
return _style(Sphere(radius).moved(Location(p1)), label, color_name)
|
||||
direction = _v_norm(axis)
|
||||
wrapped = BRepPrimAPI_MakeCylinder(
|
||||
gp_Ax2(gp_Pnt(*p1), gp_Dir(*direction)),
|
||||
radius,
|
||||
length,
|
||||
).Shape()
|
||||
return _style(Shape(obj=wrapped), label, color_name)
|
||||
|
||||
|
||||
def _cylinder_y(label: str, center: Vector3, radius: float, length: float, color_name: str) -> Shape:
|
||||
return _style(Cylinder(radius, length, rotation=(90, 0, 0)).moved(Location(center)), label, color_name)
|
||||
|
||||
|
||||
def _cylinder_x(label: str, center: Vector3, radius: float, length: float, color_name: str) -> Shape:
|
||||
return _style(Cylinder(radius, length, rotation=(0, 90, 0)).moved(Location(center)), label, color_name)
|
||||
|
||||
|
||||
def _ellipsoid(label: str, center: Vector3, radii: Vector3, color_name: str) -> Shape:
|
||||
shape = scale(Sphere(1.0), by=radii).moved(Location(center))
|
||||
return _style(shape, label, color_name)
|
||||
|
||||
|
||||
def _flat_cone_x(
|
||||
label: str,
|
||||
center: Vector3,
|
||||
length: float,
|
||||
base_radius: float,
|
||||
tip_radius: float,
|
||||
y_scale: float,
|
||||
z_scale: float,
|
||||
color_name: str,
|
||||
) -> Shape:
|
||||
cone = Cone(base_radius, tip_radius, length, rotation=(0, 90, 0))
|
||||
shape = scale(cone, by=(1.0, y_scale, z_scale)).moved(Location(center))
|
||||
return _style(shape, label, color_name)
|
||||
|
||||
|
||||
def _make_wheel(label: str, center: Vector3) -> Compound:
|
||||
cx, cy, cz = center
|
||||
tire_major_radius = 40.0
|
||||
tire_minor_radius = 4.0
|
||||
rim_radius = 31.0
|
||||
spoke_radius = 0.72
|
||||
hub_radius = 4.2
|
||||
spoke_count = 20
|
||||
|
||||
parts: list[Shape] = [
|
||||
_style(
|
||||
Torus(tire_major_radius, tire_minor_radius, rotation=(90, 0, 0)).moved(Location(center)),
|
||||
f"{label}_continuous_black_tire",
|
||||
"rubber_black",
|
||||
),
|
||||
_style(
|
||||
Torus(rim_radius, 1.45, rotation=(90, 0, 0)).moved(Location(center)),
|
||||
f"{label}_silver_rim",
|
||||
"rim_silver",
|
||||
),
|
||||
_cylinder_y(f"{label}_hub_barrel", center, hub_radius, 14.0, "rim_silver"),
|
||||
_cylinder_y(f"{label}_hub_axle_pin", center, 1.8, 24.0, "spoke_steel"),
|
||||
]
|
||||
|
||||
for index in range(spoke_count):
|
||||
theta = index * math.tau / spoke_count
|
||||
rim_point = (cx + math.cos(theta) * (rim_radius - 1.8), cy, cz + math.sin(theta) * (rim_radius - 1.8))
|
||||
opposite_offset = -0.9 if index % 2 == 0 else 0.9
|
||||
hub_point = (cx, cy + opposite_offset, cz)
|
||||
parts.append(_cylinder_between(f"{label}_radial_spoke_{index + 1:02d}", hub_point, rim_point, spoke_radius, "spoke_steel"))
|
||||
|
||||
return _group(label, parts)
|
||||
|
||||
|
||||
def _make_bicycle() -> Compound:
|
||||
rear = (-55.0, 0.0, 44.0)
|
||||
front = (55.0, 0.0, 44.0)
|
||||
bottom = (0.0, 0.0, 49.0)
|
||||
seat_lug = (-15.0, 0.0, 86.0)
|
||||
head_low = (43.0, 0.0, 66.0)
|
||||
head_high = (38.0, 0.0, 88.0)
|
||||
handle_stem = (57.0, 0.0, 99.0)
|
||||
|
||||
parts: list[Shape] = [
|
||||
_make_wheel("rear_wheel", rear),
|
||||
_make_wheel("front_wheel", front),
|
||||
_cylinder_between("frame_seat_tube", bottom, seat_lug, 2.3, "frame_blue"),
|
||||
_cylinder_between("frame_top_tube", seat_lug, head_high, 2.2, "frame_blue"),
|
||||
_cylinder_between("frame_down_tube", bottom, head_low, 2.5, "frame_blue"),
|
||||
_cylinder_between("frame_head_tube", head_low, head_high, 2.6, "frame_blue"),
|
||||
_cylinder_between("frame_left_chain_stay", (bottom[0], -4.2, bottom[2]), (rear[0], -4.2, rear[2]), 1.7, "frame_blue"),
|
||||
_cylinder_between("frame_right_chain_stay", (bottom[0], 4.2, bottom[2]), (rear[0], 4.2, rear[2]), 1.7, "frame_blue"),
|
||||
_cylinder_between("frame_left_seat_stay", (seat_lug[0], -3.8, seat_lug[2]), (rear[0], -3.8, rear[2]), 1.55, "frame_blue"),
|
||||
_cylinder_between("frame_right_seat_stay", (seat_lug[0], 3.8, seat_lug[2]), (rear[0], 3.8, rear[2]), 1.55, "frame_blue"),
|
||||
_cylinder_between("front_left_fork_blade", (head_low[0], -4.0, head_low[2]), (front[0], -4.0, front[2]), 1.7, "frame_blue"),
|
||||
_cylinder_between("front_right_fork_blade", (head_low[0], 4.0, head_low[2]), (front[0], 4.0, front[2]), 1.7, "frame_blue"),
|
||||
_cylinder_between("handlebar_stem", head_high, handle_stem, 1.8, "frame_blue"),
|
||||
_cylinder_y("handlebar_crossbar", (64.0, 0.0, 101.0), 1.6, 38.0, "frame_blue"),
|
||||
_cylinder_y("left_black_grip", (64.0, -22.5, 101.0), 2.2, 8.0, "rubber_black"),
|
||||
_cylinder_y("right_black_grip", (64.0, 22.5, 101.0), 2.2, 8.0, "rubber_black"),
|
||||
_cylinder_between("seat_post", seat_lug, (-17.0, 0.0, 95.0), 1.75, "spoke_steel"),
|
||||
_style(_softened(Box(24.0, 17.0, 5.0).moved(Location((-20.0, 0.0, 97.0))), 1.2), "brown_saddle", "seat_brown"),
|
||||
_style(Torus(7.5, 0.9, rotation=(90, 0, 0)).moved(Location(bottom)), "crank_ring", "spoke_steel"),
|
||||
_cylinder_y("bottom_bracket_axle", bottom, 3.3, 20.0, "rim_silver"),
|
||||
]
|
||||
|
||||
pedal_front = (12.5, -11.0, 38.5)
|
||||
pedal_back = (-12.5, 11.0, 60.5)
|
||||
parts.extend(
|
||||
[
|
||||
_cylinder_between("front_crank_arm", bottom, pedal_front, 1.0, "spoke_steel"),
|
||||
_cylinder_between("rear_crank_arm", bottom, pedal_back, 1.0, "spoke_steel"),
|
||||
_style(_softened(Box(15.0, 4.0, 2.5).moved(Location(pedal_front)), 0.7), "front_pedal_flat", "rubber_black"),
|
||||
_style(_softened(Box(15.0, 4.0, 2.5).moved(Location(pedal_back)), 0.7), "rear_pedal_flat", "rubber_black"),
|
||||
]
|
||||
)
|
||||
|
||||
return _group("bicycle", parts, "frame_blue")
|
||||
|
||||
|
||||
def _make_pelican() -> Compound:
|
||||
parts: list[Shape] = [
|
||||
_ellipsoid("pelican_round_body", (-12.0, 0.0, 120.0), (24.0, 15.0, 29.0), "pelican_white"),
|
||||
_ellipsoid("pelican_chest_belly_highlight", (1.0, 0.0, 112.0), (18.0, 13.0, 20.0), "pelican_white"),
|
||||
_ellipsoid("left_folded_wing", (-22.0, -13.0, 120.0), (19.0, 3.2, 25.0), "wing_gray"),
|
||||
_ellipsoid("right_folded_wing", (-22.0, 13.0, 120.0), (19.0, 3.2, 25.0), "wing_gray"),
|
||||
_ellipsoid("left_dark_wing_tip", (-33.0, -15.7, 104.0), (12.0, 2.2, 10.0), "feather_dark"),
|
||||
_ellipsoid("right_dark_wing_tip", (-33.0, 15.7, 104.0), (12.0, 2.2, 10.0), "feather_dark"),
|
||||
_cylinder_between("curved_neck_lower_segment", (-1.0, 0.0, 139.0), (12.0, 0.0, 151.0), 5.0, "pelican_white"),
|
||||
_cylinder_between("curved_neck_upper_segment", (12.0, 0.0, 151.0), (24.0, 0.0, 157.0), 4.4, "pelican_white"),
|
||||
_ellipsoid("pelican_head", (28.0, 0.0, 160.0), (10.5, 8.1, 9.5), "pelican_white"),
|
||||
_flat_cone_x("long_upper_bill", (52.0, 0.0, 160.5), 48.0, 5.2, 1.1, 1.45, 0.55, "bill_yellow"),
|
||||
_ellipsoid("rounded_lower_bill_pouch", (49.0, 0.0, 151.5), (23.0, 7.0, 7.2), "pouch_orange"),
|
||||
_cylinder_between("dark_bill_seam_left", (31.0, -7.3, 156.2), (70.0, -2.7, 157.0), 0.45, "feather_dark"),
|
||||
_cylinder_between("dark_bill_seam_right", (31.0, 7.3, 156.2), (70.0, 2.7, 157.0), 0.45, "feather_dark"),
|
||||
_ellipsoid("left_eye", (31.5, -7.4, 162.8), (1.45, 0.75, 1.45), "eye_black"),
|
||||
_ellipsoid("right_eye", (31.5, 7.4, 162.8), (1.45, 0.75, 1.45), "eye_black"),
|
||||
_ellipsoid("soft_head_crest", (19.0, 0.0, 168.0), (4.0, 4.0, 5.0), "pelican_white"),
|
||||
]
|
||||
|
||||
tail_roots = [(-34.0, -4.5, 108.0), (-38.0, 0.0, 110.0), (-34.0, 4.5, 108.0)]
|
||||
for index, center in enumerate(tail_roots, start=1):
|
||||
parts.append(_ellipsoid(f"short_tail_feather_{index}", center, (11.0, 2.0, 5.2), "feather_dark"))
|
||||
|
||||
# Bent orange legs and webbed feet make the bird visibly seated on the cycle.
|
||||
left_hip = (-8.0, -5.7, 96.0)
|
||||
left_knee = (2.5, -7.5, 74.0)
|
||||
left_pedal = (12.5, -11.0, 38.5)
|
||||
right_hip = (-13.0, 5.7, 96.0)
|
||||
right_knee = (-19.0, 8.4, 74.0)
|
||||
right_pedal = (-12.5, 11.0, 60.5)
|
||||
parts.extend(
|
||||
[
|
||||
_cylinder_between("left_upper_leg", left_hip, left_knee, 1.8, "leg_orange"),
|
||||
_cylinder_between("left_lower_leg", left_knee, left_pedal, 1.6, "leg_orange"),
|
||||
_cylinder_between("right_upper_leg", right_hip, right_knee, 1.8, "leg_orange"),
|
||||
_cylinder_between("right_lower_leg", right_knee, right_pedal, 1.6, "leg_orange"),
|
||||
_ellipsoid("left_webbed_foot_on_forward_pedal", _v_add(left_pedal, (1.5, 0.0, 2.0)), (8.5, 3.5, 2.2), "leg_orange"),
|
||||
_ellipsoid("right_webbed_foot_on_rear_pedal", _v_add(right_pedal, (-1.5, 0.0, 2.0)), (8.5, 3.5, 2.2), "leg_orange"),
|
||||
]
|
||||
)
|
||||
|
||||
feather_line_specs = [
|
||||
("left_wing_feather_line_1", (-17.0, -16.2, 136.0), (-33.0, -16.2, 112.0)),
|
||||
("left_wing_feather_line_2", (-11.0, -16.4, 126.0), (-34.0, -16.4, 101.0)),
|
||||
("right_wing_feather_line_1", (-17.0, 16.2, 136.0), (-33.0, 16.2, 112.0)),
|
||||
("right_wing_feather_line_2", (-11.0, 16.4, 126.0), (-34.0, 16.4, 101.0)),
|
||||
]
|
||||
for label, p1, p2 in feather_line_specs:
|
||||
parts.append(_cylinder_between(label, p1, p2, 0.55, "feather_dark"))
|
||||
|
||||
return _group("pelican", parts, "pelican_white")
|
||||
|
||||
|
||||
def _make_ground_shadow() -> Compound:
|
||||
# Thin reference pads give the model a stable visual base without merging
|
||||
# into the wheels.
|
||||
front_pad = _style(_softened(Box(92.0, 28.0, 1.0).moved(Location((55.0, 0.0, -2.0))), 1.0), "front_wheel_ground_shadow", "rubber_black")
|
||||
rear_pad = _style(_softened(Box(92.0, 28.0, 1.0).moved(Location((-55.0, 0.0, -2.0))), 1.0), "rear_wheel_ground_shadow", "rubber_black")
|
||||
return _group("subtle_ground_shadow", [front_pad, rear_pad])
|
||||
|
||||
|
||||
def gen_step() -> Compound:
|
||||
bicycle = _make_bicycle()
|
||||
pelican = _make_pelican()
|
||||
ground = _make_ground_shadow()
|
||||
assembly = _group("pelican_riding_bicycle", [bicycle, pelican, ground])
|
||||
assembly.label = "pelican_riding_bicycle"
|
||||
return assembly
|
||||
Reference in New Issue
Block a user