This commit is contained in:
2026-08-14 10:26:28 +08:00
parent f051146486
commit 7d12459a32
44 changed files with 900211 additions and 15295 deletions
+165 -35
View File
@@ -12,14 +12,16 @@ from ..models import read_json, sha256_json, write_json
from ..parameter_solver import load_requirement
from .datum_inference import infer_step_datum_axis
from .generator import JointModuleError, run_joint_module
from .models import JointModuleManifest
from .models import JointModuleManifest, JointPlatformProfile
PACKAGE_ROOT = Path(__file__).resolve().parents[2]
DEFAULT_PLATFORM_PROFILE = (
PACKAGE_ROOT / "input" / "assets" / "platforms" / "actuator_v2_fixed_platform.json"
)
AUTO_CONTROLLED_OUTPUTS = [
"auto_design_input.json",
"auto_design_report.json",
"generated_housing.step",
"generated_reducer_requirement.json",
"generated_joint_requirement.json",
"auto_generation_error.json",
@@ -93,6 +95,27 @@ def _resolve_path(path_text: str, *, requirement_path: Path | None = None) -> Pa
return path.resolve()
def load_platform_assets(platform_profile: Path) -> tuple[JointPlatformProfile, dict[str, Path]]:
if not platform_profile.exists():
raise AutoJointDesignError(f"missing_joint_platform_profile: {platform_profile}")
profile = JointPlatformProfile.model_validate(read_json(platform_profile))
paths = {
"motor_step": _resolve_path(profile.motor_step, requirement_path=platform_profile),
"housing_step": _resolve_path(profile.housing_step, requirement_path=platform_profile),
}
if profile.chip_step is not None:
paths["chip_step"] = _resolve_path(profile.chip_step, requirement_path=platform_profile)
if profile.reference_assembly_step is not None:
paths["reference_assembly_step"] = _resolve_path(
profile.reference_assembly_step,
requirement_path=platform_profile,
)
missing = {name: str(path) for name, path in paths.items() if not path.exists()}
if missing:
raise AutoJointDesignError(f"fixed_platform_asset_missing: {missing}")
return profile, paths
def _axis_extent(metrics: Any) -> float:
axis = metrics.inferred_axis
if axis is None:
@@ -195,6 +218,60 @@ def compute_auto_envelope(
}, housing_module
def compute_fixed_platform_envelope(
*,
platform_profile: Path,
radial_clearance_mm: float,
reducer_radial_clearance_mm: float,
) -> tuple[AutoJointEnvelope, dict[str, Any], JointPlatformProfile]:
profile, paths = load_platform_assets(platform_profile)
motor_metrics = infer_step_datum_axis(
component_id="motor",
step_path=paths["motor_step"],
role="motor",
)
housing_metrics = infer_step_datum_axis(
component_id="housing",
step_path=paths["housing_step"],
role="housing",
)
motor_radial_diameter = _radial_diameter(motor_metrics)
motor_axis_length = _axis_extent(motor_metrics)
housing_radial_diameter = _radial_diameter(housing_metrics)
housing_axis_length = _axis_extent(housing_metrics)
housing_inner_diameter = max(24.0, housing_radial_diameter - 2.0 * radial_clearance_mm)
reducer_max_outer = max(24.0, housing_inner_diameter - 2.0 * reducer_radial_clearance_mm)
envelope = AutoJointEnvelope(
motor_radial_diameter_mm=motor_radial_diameter,
motor_axis_length_mm=motor_axis_length,
housing_inner_diameter_mm=housing_inner_diameter,
housing_outer_diameter_mm=housing_radial_diameter,
housing_axis_length_mm=housing_axis_length,
reducer_max_outer_diameter_mm=reducer_max_outer,
target_ratio=0.0,
module_candidates_mm=_module_candidates_for_outer_diameter(reducer_max_outer),
planet_count_candidates=[3, 4],
)
envelope = AutoJointEnvelope(
**{
**envelope.__dict__,
"target_ratio": _ratio_from_envelope(envelope),
}
)
return envelope, {
"platform_profile": str(platform_profile.resolve()),
"platform_id": profile.platform_id,
"asset_paths": {name: str(path.resolve()) for name, path in paths.items()},
"motor_metrics": motor_metrics.model_dump(mode="json"),
"housing_metrics": housing_metrics.model_dump(mode="json"),
"housing_dimensions": {
"estimated_inner_diameter_mm": housing_inner_diameter,
"outer_diameter_mm": housing_radial_diameter,
"axis_length_mm": housing_axis_length,
},
}, profile
def export_generated_housing(
*,
housing_module: Any,
@@ -214,7 +291,33 @@ def export_generated_housing(
raise AutoJointDesignError(f"generated_housing_step_missing_or_empty: {out_path}")
def build_reducer_requirement(envelope: AutoJointEnvelope, *, tooth_form: str = "spur") -> dict[str, Any]:
def _cascade_requirement_template() -> dict[str, Any]:
template = (
PACKAGE_ROOT
/ "input"
/ "requirements"
/ "reducers"
/ "simple_2k_h_cascade"
/ "simple_2kh_cascade_ratio_9_spur_compact.json"
)
return read_json(template)
def build_reducer_requirement(
envelope: AutoJointEnvelope,
*,
tooth_form: str = "spur",
topology_family: str = "simple_2k_h",
) -> dict[str, Any]:
if topology_family == "simple_2k_h_cascade":
payload = _cascade_requirement_template()
payload["tooth_form"] = tooth_form
payload.setdefault("constraints", {})["max_outer_diameter_mm"] = (
envelope.reducer_max_outer_diameter_mm
)
return payload
if topology_family != "simple_2k_h":
raise AutoJointDesignError(f"unsupported_auto_joint_reducer_family: {topology_family}")
return {
"target_ratio": envelope.target_ratio,
"ratio_tolerance": 0.000001,
@@ -245,21 +348,29 @@ def build_reducer_requirement(envelope: AutoJointEnvelope, *, tooth_form: str =
def build_joint_requirement(
*,
motor_step: Path,
housing_step: Path,
platform_profile: Path,
reducer_requirement: Path,
topology_family: str = "simple_2k_h",
) -> dict[str, Any]:
if topology_family == "simple_2k_h_cascade":
motor_relation = "motor_output_to_s1_sun_input"
housing_relation = "stage_rings_fixed_to_housing"
joint_output = "s2_carrier_output"
else:
motor_relation = "motor_output_to_sun_keyed_input"
housing_relation = "ring_fixed_to_housing"
joint_output = "carrier_output_shaft"
return {
"module_type": "planetary_joint_module",
"motor_step": str(motor_step.resolve()),
"housing_step": str(housing_step.resolve()),
"platform_profile": str(platform_profile.resolve()),
"reducer_requirement": str(reducer_requirement.resolve()),
"placement_policy": "infer_then_write_editable_json",
"motor_to_reducer_relation": "motor_output_to_sun_keyed_input",
"housing_relation": "ring_fixed_to_housing",
"joint_output": "carrier_output_shaft",
"motor_to_reducer_relation": motor_relation,
"housing_relation": housing_relation,
"joint_output": joint_output,
"motor_placement_policy": "external_flush_input_end",
"input_shaft_insertion_depth_mm": 4.0,
"output_extension_mm": 8.0,
"output_extension_mm": 14.0,
}
@@ -273,51 +384,71 @@ def prepare_auto_dir(out_dir: Path) -> None:
def run_auto_joint_design(
*,
motor_step: Path,
housing_source: Path,
out_dir: Path,
platform_profile: Path = DEFAULT_PLATFORM_PROFILE,
motor_step: Path | None = None,
housing_source: Path | None = None,
tooth_form: str = "spur",
radial_clearance_mm: float = 14.0,
reducer_axial_allowance_mm: float = 42.0,
reducer_axial_allowance_mm: float = 28.0,
reducer_radial_clearance_mm: float = 8.0,
reducer_requirement_overrides: dict[str, Any] | None = None,
) -> JointModuleManifest:
prepare_auto_dir(out_dir)
if not motor_step.exists():
raise AutoJointDesignError(f"missing_motor_step: {motor_step}")
if not housing_source.exists():
raise AutoJointDesignError(f"missing_housing_source: {housing_source}")
envelope, evidence, housing_module = compute_auto_envelope(
motor_step=motor_step,
housing_source=housing_source,
platform_profile = platform_profile.resolve()
envelope, evidence, _profile = compute_fixed_platform_envelope(
platform_profile=platform_profile,
radial_clearance_mm=radial_clearance_mm,
reducer_axial_allowance_mm=reducer_axial_allowance_mm,
reducer_radial_clearance_mm=reducer_radial_clearance_mm,
)
housing_step = out_dir / "generated_housing.step"
export_generated_housing(
housing_module=housing_module,
scale_payload=evidence["housing_scale"],
out_path=housing_step,
)
reducer_requirement_path = out_dir / "generated_reducer_requirement.json"
joint_requirement_path = out_dir / "generated_joint_requirement.json"
reducer_requirement_payload = build_reducer_requirement(envelope, tooth_form=tooth_form)
overrides = reducer_requirement_overrides or {}
topology_family = str(overrides.get("topology_family") or "simple_2k_h")
reducer_requirement_payload = build_reducer_requirement(
envelope,
tooth_form=tooth_form,
topology_family=topology_family,
)
for name in {
"target_ratio",
"tooth_form",
"helix_angle_deg",
"pressure_angle_deg",
"face_width_mm",
"backlash_mm",
"ring_rim_thickness_mm",
}:
if name in overrides:
reducer_requirement_payload[name] = overrides[name]
if "module_mm" in overrides:
reducer_requirement_payload["module_candidates_mm"] = [float(overrides["module_mm"])]
if "planet_count" in overrides:
reducer_requirement_payload["planet_count_candidates"] = [int(overrides["planet_count"])]
if "max_outer_diameter_mm" in overrides:
reducer_requirement_payload["constraints"]["max_outer_diameter_mm"] = float(
overrides["max_outer_diameter_mm"]
)
if reducer_requirement_payload.get("tooth_form") == "helical":
reducer_requirement_payload.setdefault("helix_angle_deg", 15.0)
else:
reducer_requirement_payload.pop("helix_angle_deg", None)
write_json(reducer_requirement_path, reducer_requirement_payload)
# Validate before launching the heavy generation path.
load_requirement(reducer_requirement_path)
joint_requirement_payload = build_joint_requirement(
motor_step=motor_step,
housing_step=housing_step,
platform_profile=platform_profile,
reducer_requirement=reducer_requirement_path,
topology_family=str(reducer_requirement_payload.get("topology_family") or topology_family),
)
write_json(joint_requirement_path, joint_requirement_payload)
write_json(
out_dir / "auto_design_input.json",
{
"motor_step": str(motor_step.resolve()),
"housing_source": str(housing_source.resolve()),
"platform_profile": str(platform_profile.resolve()),
"ignored_legacy_motor_step": str(motor_step.resolve()) if motor_step else None,
"ignored_legacy_housing_source": str(housing_source.resolve()) if housing_source else None,
"tooth_form": tooth_form,
"radial_clearance_mm": radial_clearance_mm,
"reducer_axial_allowance_mm": reducer_axial_allowance_mm,
@@ -332,7 +463,6 @@ def run_auto_joint_design(
"envelope": envelope.__dict__,
"evidence": evidence,
"generated_files": {
"housing_step": str(housing_step.resolve()),
"reducer_requirement": str(reducer_requirement_path.resolve()),
"joint_requirement": str(joint_requirement_path.resolve()),
},