Split project into frontend and backend

This commit is contained in:
2026-08-13 16:24:36 +08:00
commit f051146486
274 changed files with 953932 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,190 @@
from dataclasses import dataclass
from pathlib import Path
import math
import cadquery as cq
# All dimensions are millimetres. The main body is a revolved radial section.
INNER_BOTTOM_RADIUS = 60.0
INNER_STEP_RADIUS = 57.5
INNER_SHOULDER_RADIUS = 70.0
INNER_WALL_RADIUS = 79.0
BOTTOM_OUTER_RADIUS = 77.5
BODY_OUTER_RADIUS = 82.5
TOP_OUTER_RADIUS = 85.0
BASE_MIN_Z = -18.0
BASE_MAX_Z = 92.0
BASE_HEIGHT = BASE_MAX_Z - BASE_MIN_Z
@dataclass(frozen=True)
class HousingScale:
radial_scale: float = 1.0
axial_scale: float = 1.0
source: str = "default_template_scale"
def scaled_dimensions(scale: HousingScale) -> dict[str, float]:
return {
"inner_bottom_radius": INNER_BOTTOM_RADIUS * scale.radial_scale,
"inner_step_radius": INNER_STEP_RADIUS * scale.radial_scale,
"inner_shoulder_radius": INNER_SHOULDER_RADIUS * scale.radial_scale,
"inner_wall_radius": INNER_WALL_RADIUS * scale.radial_scale,
"bottom_outer_radius": BOTTOM_OUTER_RADIUS * scale.radial_scale,
"body_outer_radius": BODY_OUTER_RADIUS * scale.radial_scale,
"top_outer_radius": TOP_OUTER_RADIUS * scale.radial_scale,
"min_z": BASE_MIN_Z * scale.axial_scale,
"max_z": BASE_MAX_Z * scale.axial_scale,
"height": BASE_HEIGHT * scale.axial_scale,
"radial_scale": scale.radial_scale,
"axial_scale": scale.axial_scale,
"source": scale.source,
}
def scale_from_motor(
*,
motor_radial_diameter_mm: float,
motor_axis_length_mm: float,
radial_clearance_mm: float = 14.0,
reducer_axial_allowance_mm: float = 42.0,
min_radial_scale: float = 0.45,
max_radial_scale: float = 1.50,
min_axial_scale: float = 0.55,
max_axial_scale: float = 1.80,
) -> HousingScale:
target_inner_radius = motor_radial_diameter_mm / 2.0 + radial_clearance_mm
radial_scale = target_inner_radius / INNER_WALL_RADIUS
target_height = motor_axis_length_mm + reducer_axial_allowance_mm
axial_scale = target_height / BASE_HEIGHT
return HousingScale(
radial_scale=max(min_radial_scale, min(max_radial_scale, radial_scale)),
axial_scale=max(min_axial_scale, min(max_axial_scale, axial_scale)),
source="computed_from_motor_bbox",
)
def _polar_points(radius: float, start_degrees: float, count: int):
return [
(
radius * math.cos(math.radians(start_degrees + 360.0 * index / count)),
radius * math.sin(math.radians(start_degrees + 360.0 * index / count)),
)
for index in range(count)
]
def _radial_point(radius: float, angle_degrees: float, z: float) -> cq.Vector:
angle = math.radians(angle_degrees)
return cq.Vector(radius * math.cos(angle), radius * math.sin(angle), z)
def _radial_direction(angle_degrees: float) -> cq.Vector:
angle = math.radians(angle_degrees)
return cq.Vector(math.cos(angle), math.sin(angle), 0.0)
def build_body(scale: HousingScale = HousingScale()):
rd = scale.radial_scale
zd = scale.axial_scale
def r(value: float) -> float:
return value * rd
def z(value: float) -> float:
return value * zd
# Exact circular-arc midpoints for the two revolved outer blends.
lower_arc_mid = (r(78.8397459621556), z(-8.66025403784439))
upper_arc_mid = (r(83.1458565330652), z(71.9211556282713))
radial_section = (
cq.Workplane("XZ")
.moveTo(r(INNER_BOTTOM_RADIUS), z(-18.0))
.lineTo(r(BOTTOM_OUTER_RADIUS), z(-18.0))
.lineTo(r(BOTTOM_OUTER_RADIUS), z(-13.6602540378))
.threePointArc(lower_arc_mid, (r(BODY_OUTER_RADIUS), z(-5.0)))
.lineTo(r(BODY_OUTER_RADIUS), z(68.3856217223))
.threePointArc(upper_arc_mid, (r(TOP_OUTER_RADIUS), z(75.0)))
.lineTo(r(TOP_OUTER_RADIUS), z(92.0))
.lineTo(r(INNER_WALL_RADIUS), z(92.0))
.lineTo(r(INNER_WALL_RADIUS), z(0.0))
.lineTo(r(INNER_SHOULDER_RADIUS), z(0.0))
.lineTo(r(INNER_SHOULDER_RADIUS), z(-3.0))
.lineTo(r(INNER_STEP_RADIUS), z(-3.0))
.lineTo(r(INNER_STEP_RADIUS), z(-5.0))
.lineTo(r(INNER_BOTTOM_RADIUS), z(-5.0))
.close()
)
return radial_section.revolve(360.0, (0.0, 0.0), (0.0, 1.0))
def build(scale: HousingScale = HousingScale()):
rd = scale.radial_scale
zd = scale.axial_scale
def r(value: float) -> float:
return value * rd
def z(value: float) -> float:
return value * zd
part = build_body(scale)
# Ten Ø5 blind drilled holes, equally spaced on a Ø135 pitch circle.
drill_tip_height = z(1.5021515475689)
for x, y in _polar_points(r(67.5), 18.0, 10):
straight_hole = cq.Solid.makeCylinder(
r(2.5), z(10.0), cq.Vector(x, y, z(-18.0)), cq.Vector(0.0, 0.0, 1.0)
)
drill_tip = cq.Solid.makeCone(
r(2.5),
0.0,
drill_tip_height,
cq.Vector(x, y, z(-8.0)),
cq.Vector(0.0, 0.0, 1.0),
)
part = part.cut(straight_hole).cut(drill_tip)
# Four Ø2.5 blind holes opening at z=0, with 118-degree drill tips.
small_tip_height = z(0.75107577378445)
for x, y in _polar_points(r(71.875), 0.0, 4):
straight_hole = cq.Solid.makeCylinder(
r(1.25), z(4.24892422621555), cq.Vector(x, y, z(0.0)), cq.Vector(0.0, 0.0, -1.0)
)
drill_tip = cq.Solid.makeCone(
r(1.25),
0.0,
small_tip_height,
cq.Vector(x, y, z(-4.24892422621555)),
cq.Vector(0.0, 0.0, -1.0),
)
part = part.cut(straight_hole).cut(drill_tip)
# Eight radial Ø6.6 holes with 90-degree countersinks around the top rim.
for angle in range(0, 360, 45):
direction = _radial_direction(float(angle))
straight_hole = cq.Solid.makeCylinder(
r(3.3), r(3.9), _radial_point(r(78.0), float(angle), z(85.1)), direction
)
countersink = cq.Solid.makeCone(
r(3.3),
r(6.4),
r(3.1),
_radial_point(r(81.9), float(angle), z(85.1)),
direction,
)
part = part.cut(straight_hole).cut(countersink)
return part
def export_scaled(output: Path, scale: HousingScale) -> None:
cq.exporters.export(build(scale), str(output))
if __name__ == "__main__":
output = Path(__file__).resolve().with_name("gear_housing.step")
export_scaled(output, HousingScale())
print(output)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff