Files
cadSet/text-to-cad/models/robots/tom/robot_common/keyed_connection.py
T
2026-07-22 19:38:36 +08:00

233 lines
7.8 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
import math
import build123d
# Extracted from the legacy keyed plate profile.
# Coordinates are relative to the cut center in an arbitrary in-plane basis `(u, v)`.
@dataclass(frozen=True)
class KeyedConnectionArcSpec:
start_u: float
start_v: float
mid_u: float
mid_v: float
end_u: float
end_v: float
KEYED_CONNECTION_REF = "legacy keyed plate profile"
KEYED_CONNECTION_ROTATION_DEG = 45.0
KEYED_CONNECTION_ARC_SPECS_MM: tuple[KeyedConnectionArcSpec, ...] = (
KeyedConnectionArcSpec(-2.634358, -4.828056, 0.0, -5.499997, 2.634358, -4.828056),
KeyedConnectionArcSpec(2.634358, -4.828056, 3.095745, -4.818908, 3.368107, -5.19144),
KeyedConnectionArcSpec(3.368107, -5.19144, 6.081117, -6.081114, 5.191444, -3.368104),
KeyedConnectionArcSpec(5.191444, -3.368104, 4.818911, -3.095741, 4.82806, -2.634354),
KeyedConnectionArcSpec(4.82806, -2.634354, 5.5, 0.000004, 4.82806, 2.634361),
KeyedConnectionArcSpec(4.82806, 2.634361, 4.818911, 3.095748, 5.191444, 3.36811),
KeyedConnectionArcSpec(5.191444, 3.36811, 6.081117, 6.08112, 3.368107, 5.191447),
KeyedConnectionArcSpec(3.368107, 5.191447, 3.095745, 4.818915, 2.634358, 4.828063),
KeyedConnectionArcSpec(2.634358, 4.828063, 0.0, 5.500003, -2.634358, 4.828063),
KeyedConnectionArcSpec(-2.634358, 4.828063, -3.095745, 4.818915, -3.368107, 5.191447),
KeyedConnectionArcSpec(-3.368107, 5.191447, -6.081117, 6.08112, -5.191444, 3.36811),
KeyedConnectionArcSpec(-5.191444, 3.36811, -4.818911, 3.095748, -4.82806, 2.634361),
KeyedConnectionArcSpec(-4.82806, 2.634361, -5.5, 0.000004, -4.82806, -2.634354),
KeyedConnectionArcSpec(-4.82806, -2.634354, -4.818911, -3.095741, -5.191444, -3.368104),
KeyedConnectionArcSpec(-5.191444, -3.368104, -6.081117, -6.081114, -3.368107, -5.19144),
KeyedConnectionArcSpec(-3.368107, -5.19144, -3.095745, -4.818908, -2.634358, -4.828056),
)
# Conservative exact-profile half-span for bend-clearance checks. The exported
# helper rotates this profile 45 degrees by default to match the plate cut
# orientation used across the current parts.
KEYED_CONNECTION_HALF_SPAN_MM = 6.549746
def _to_global_point(
*,
origin: build123d.Vector,
u_dir: build123d.Vector,
v_dir: build123d.Vector,
u: float,
v: float,
) -> build123d.Vector:
return build123d.Vector(
origin.X + (u * u_dir.X) + (v * v_dir.X),
origin.Y + (u * u_dir.Y) + (v * v_dir.Y),
origin.Z + (u * u_dir.Z) + (v * v_dir.Z),
)
def _rotated_in_plane_dirs(
*,
u_dir: build123d.Vector,
v_dir: build123d.Vector,
rotation_degrees: float,
) -> tuple[build123d.Vector, build123d.Vector]:
if abs(rotation_degrees) <= 1e-9:
return u_dir, v_dir
rotation_rad = math.radians(rotation_degrees)
cos_theta = math.cos(rotation_rad)
sin_theta = math.sin(rotation_rad)
rotated_u = (u_dir * cos_theta) + (v_dir * sin_theta)
rotated_v = (v_dir * cos_theta) - (u_dir * sin_theta)
return rotated_u, rotated_v
def make_keyed_connection_wire(
*,
origin: build123d.Vector,
u_dir: build123d.Vector,
v_dir: build123d.Vector,
rotation_degrees: float = KEYED_CONNECTION_ROTATION_DEG,
) -> build123d.Wire:
rotated_u_dir, rotated_v_dir = _rotated_in_plane_dirs(
u_dir=u_dir,
v_dir=v_dir,
rotation_degrees=rotation_degrees,
)
edges = [
build123d.Edge.make_three_point_arc(
_to_global_point(origin=origin, u_dir=rotated_u_dir, v_dir=rotated_v_dir, u=spec.start_u, v=spec.start_v),
_to_global_point(origin=origin, u_dir=rotated_u_dir, v_dir=rotated_v_dir, u=spec.mid_u, v=spec.mid_v),
_to_global_point(origin=origin, u_dir=rotated_u_dir, v_dir=rotated_v_dir, u=spec.end_u, v=spec.end_v),
)
for spec in KEYED_CONNECTION_ARC_SPECS_MM
]
wires = list(build123d.Wire.combine(edges))
if len(wires) != 1:
raise RuntimeError(f"Expected a single keyed-connection wire, found {len(wires)}")
return wires[0]
def make_keyed_connection_face(
*,
origin: build123d.Vector,
u_dir: build123d.Vector,
v_dir: build123d.Vector,
rotation_degrees: float = KEYED_CONNECTION_ROTATION_DEG,
) -> build123d.Face:
return build123d.Face(
make_keyed_connection_wire(
origin=origin,
u_dir=u_dir,
v_dir=v_dir,
rotation_degrees=rotation_degrees,
)
)
def make_keyed_connection_y_aligned_cutter(
*,
center: build123d.Vector,
start_y: float,
direction_y: float,
thickness_mm: float,
cut_extension_mm: float,
rotation_degrees: float = KEYED_CONNECTION_ROTATION_DEG,
) -> build123d.Solid:
length = thickness_mm + (2.0 * cut_extension_mm)
face = make_keyed_connection_face(
origin=build123d.Vector(center.X, start_y, center.Z),
u_dir=build123d.Vector(1.0, 0.0, 0.0),
v_dir=build123d.Vector(0.0, 0.0, 1.0),
rotation_degrees=rotation_degrees,
)
return build123d.Solid.extrude(face, build123d.Vector(0.0, direction_y * length, 0.0))
def make_keyed_connection_x_aligned_cutter(
*,
center: build123d.Vector,
start_x: float,
direction_x: float,
thickness_mm: float,
cut_extension_mm: float,
rotation_degrees: float = KEYED_CONNECTION_ROTATION_DEG,
) -> build123d.Solid:
length = thickness_mm + (2.0 * cut_extension_mm)
face = make_keyed_connection_face(
origin=build123d.Vector(start_x, center.Y, center.Z),
u_dir=build123d.Vector(0.0, 1.0, 0.0),
v_dir=build123d.Vector(0.0, 0.0, 1.0),
rotation_degrees=rotation_degrees,
)
return build123d.Solid.extrude(face, build123d.Vector(direction_x * length, 0.0, 0.0))
def cut_keyed_connection_y_aligned(
shape: build123d.Shape,
*,
center: build123d.Vector,
start_y: float,
direction_y: float,
thickness_mm: float,
cut_extension_mm: float,
rotation_degrees: float = KEYED_CONNECTION_ROTATION_DEG,
) -> build123d.Shape:
return shape.cut(
make_keyed_connection_y_aligned_cutter(
center=center,
start_y=start_y,
direction_y=direction_y,
thickness_mm=thickness_mm,
cut_extension_mm=cut_extension_mm,
rotation_degrees=rotation_degrees,
)
)
def cut_keyed_connection_x_aligned(
shape: build123d.Shape,
*,
center: build123d.Vector,
start_x: float,
direction_x: float,
thickness_mm: float,
cut_extension_mm: float,
rotation_degrees: float = KEYED_CONNECTION_ROTATION_DEG,
) -> build123d.Shape:
return shape.cut(
make_keyed_connection_x_aligned_cutter(
center=center,
start_x=start_x,
direction_x=direction_x,
thickness_mm=thickness_mm,
cut_extension_mm=cut_extension_mm,
rotation_degrees=rotation_degrees,
)
)
def sample_keyed_connection_outline(
*,
center_u: float,
center_v: float,
points_per_arc: int = 16,
rotation_degrees: float = KEYED_CONNECTION_ROTATION_DEG,
) -> list[tuple[float, float]]:
if points_per_arc < 2:
raise ValueError("points_per_arc must be at least 2")
wire = make_keyed_connection_wire(
origin=build123d.Vector(center_u, center_v, 0.0),
u_dir=build123d.Vector(1.0, 0.0, 0.0),
v_dir=build123d.Vector(0.0, 1.0, 0.0),
rotation_degrees=rotation_degrees,
)
points: list[tuple[float, float]] = []
for edge_index, edge in enumerate(wire.edges()):
start_step = 0 if edge_index == 0 else 1
for step in range(start_step, points_per_arc):
point = edge.position_at(step / (points_per_arc - 1))
points.append((point.X, point.Y))
if len(points) > 1 and math.dist(points[0], points[-1]) <= 1e-9:
points.pop()
return points