feat: integrate SimpleCADAPI 2.0.2 CAD workflows

This commit is contained in:
Jerry
2026-08-03 11:17:05 +08:00
parent b5738e9109
commit c3a0f269b7
481 changed files with 110229 additions and 12826 deletions
@@ -178,7 +178,62 @@ def _semantic_parameter_candidates(
)
for item in repeated
}
if len(unique_centers) >= 3:
inferred_gear_kind = semantic_summary.get("inferred_gear_kind")
inferred_tooth_count = int(
semantic_summary.get("inferred_tooth_count", 0)
)
if inferred_gear_kind and inferred_tooth_count >= 3:
transverse_span = max(bbox_size[index] for index in transverse_axes)
if inferred_gear_kind == "internal_spur_gear" and coaxial:
internal_tip_diameter = 2.0 * min(
float(item["radius"]) for item in coaxial
)
inferred_module = internal_tip_diameter / (
inferred_tooth_count - 2.0
)
else:
inferred_module = transverse_span / (
inferred_tooth_count + 2.0
)
candidates.extend(
[
_parameter_candidate(
"tooth_count",
inferred_tooth_count,
"count",
0.9,
["equal_angular_tooth_pattern"],
"gear_tooth_count_changes",
"paired_equal_radius_cylindrical_tooth_flanks",
["gear_teeth"],
),
_parameter_candidate(
"gear_module",
inferred_module,
"mm",
0.72,
["spur_gear_body"],
"gear_pitch_and_tooth_scale_changes",
(
"internal_tip_diameter_and_tooth_count"
if inferred_gear_kind == "internal_spur_gear"
else "external_envelope_and_tooth_count"
),
["module"],
),
_parameter_candidate(
"gear_width",
bbox_size[{"x": 0, "y": 1, "z": 2}[dominant_axis]],
"mm",
0.96,
["spur_gear_body"],
"gear_axial_width_changes",
"dominant_axis_bounding_span",
["face_width"],
),
]
)
elif len(unique_centers) >= 3:
member_radius = float(repeated[0]["radius"])
offsets = [
math.hypot(
@@ -419,6 +474,41 @@ def _semantic_summary(
and pattern_spread / pattern_mean_offset <= 0.08
)
# Spur gears exported by common parametric generators often expose two
# equal-radius cylindrical flank faces per tooth. Without this guard the
# generic repeated-cylinder detector labels those flanks as a bolt-hole
# pattern and invents hole_count / bolt_circle_diameter parameters.
#
# This is intentionally a conservative final-B-Rep classifier. It does
# not claim that the source history contained a gear feature.
gear_like_surface_mix = bool(
has_rotational_stack
and has_circular_pattern
and len(largest_repeated_group) >= 12
and len(largest_repeated_group) % 2 == 0
and len(planes) <= 3
and not cones
and not toruses
and not spheres
and not other_surfaces
and dominant_span / max(transverse_span, 1e-9) <= 0.6
)
inferred_tooth_count = (
len(largest_repeated_group) // 2 if gear_like_surface_mix else 0
)
centered_radius_ratio = _ratio(
smallest_coaxial_radius, transverse_span
)
inferred_gear_kind = (
"internal_spur_gear"
if gear_like_surface_mix and centered_radius_ratio >= 0.35
else "external_spur_gear"
if gear_like_surface_mix
else None
)
if gear_like_surface_mix:
has_repeated_axial_holes = False
# Multiple radii sharing a transverse center are a robust final-B-Rep
# signal for stepped bores/counterbores, without claiming operation order.
transverse_groups: dict[tuple[int, int, int], set[float]] = defaultdict(set)
@@ -479,7 +569,22 @@ def _semantic_summary(
if other_surfaces:
features.append({"type": "freeform_surface_region"})
if has_circular_pattern:
features.append({"type": "circular_equal_radius_pattern"})
features.append(
{
"type": (
"equal_angular_tooth_pattern"
if gear_like_surface_mix
else "circular_equal_radius_pattern"
)
}
)
if inferred_gear_kind:
features.extend(
[
{"type": "spur_gear_body"},
{"type": inferred_gear_kind},
]
)
if has_stepped_cylindrical_passage:
features.append({"type": "stepped_cylindrical_passage"})
if has_rotational_stack:
@@ -515,7 +620,18 @@ def _semantic_summary(
)
if has_circular_pattern:
constraints.append(
{"id": "common_pattern_radius", "type": "radial_pattern"}
{
"id": (
"equal_angular_tooth_pattern"
if gear_like_surface_mix
else "common_pattern_radius"
),
"type": (
"gear_tooth_pattern"
if gear_like_surface_mix
else "radial_pattern"
),
}
)
if has_multi_axis_passages:
constraints.append(
@@ -526,7 +642,9 @@ def _semantic_summary(
{"id": "shared_passage_axis", "type": "coaxial"}
)
if has_rotational_stack and has_repeated_axial_holes:
if inferred_gear_kind:
family = inferred_gear_kind
elif has_rotational_stack and has_repeated_axial_holes:
family = "flanged_rotational_part"
elif has_rotational_stack and elongated and len(distinct_coaxial_radii) >= 3:
family = "hollow_or_stepped_shaft"
@@ -622,6 +740,8 @@ def _semantic_summary(
else 0,
"parallel_cone_surface_count": len(parallel_cones),
"has_multi_axis_passages": has_multi_axis_passages,
"inferred_gear_kind": inferred_gear_kind,
"inferred_tooth_count": inferred_tooth_count,
"shape_class": (
"elongated" if elongated else "plate_like" if plate_like else "compact"
if compact