| from __future__ import annotations |
|
|
| import re |
| from typing import Any |
|
|
| from .models import Design |
|
|
|
|
| def clamp(value: float, low: float, high: float) -> float: |
| return max(low, min(high, value)) |
|
|
|
|
| def infer_load_case(prompt: str, design: Design) -> dict[str, Any]: |
| """Resolve natural-language load details to simulation boundary data. |
| |
| The LLM can propose load location through the design fields. This manager |
| resolves that proposal into a concrete point/region and records assumptions. |
| If the prompt explicitly says torque, cyclic, impact, chair, motor, or fixture, |
| those details alter the load case. |
| """ |
|
|
| text = (prompt or "").lower() |
| load_x = clamp(abs(design.load_point_x_mm), 5, design.base_length_mm) |
| load_y = clamp(design.load_point_y_mm, -design.base_width_mm / 2, design.base_width_mm / 2) |
| nearest_boss = min( |
| [feature for feature in design.features if feature.type == "boss"], |
| key=lambda feature: (feature.x - load_x) ** 2 + (feature.y - load_y) ** 2, |
| default=None, |
| ) |
| load_z = design.base_thickness_mm |
| load_region_hint = "tip_boss" |
| if nearest_boss is not None: |
| load_x = nearest_boss.x |
| load_y = nearest_boss.y |
| load_z = design.base_thickness_mm + max(nearest_boss.height, 1) |
| load_region_hint = "top_of_nearest_load_boss" |
| factor = 3.0 if "impact" in text else 1.5 if ("cyclic" in text or "fatigue" in text) else 1.0 |
|
|
| torque_match = re.search(r"(\d+(?:\.\d+)?)\s*(?:n\s*[-*]?\s*m|nm|newton\s*meter)", text) |
| if torque_match: |
| torque_nm = float(torque_match.group(1)) |
| equivalent_force = torque_nm * 1000 / max(load_x, 1) |
| return { |
| "type": "torque_as_force_couple_proxy", |
| "effective_load_n": equivalent_force * factor, |
| "nominal_load_n": equivalent_force, |
| "factor": factor, |
| "load_point": [load_x, load_y, load_z], |
| "vector_n": [0, 0, -round(equivalent_force * factor, 4)], |
| "fixed_region": "left_face", |
| "load_region": "shaft_proxy_or_" + load_region_hint, |
| "note": f"{torque_nm} Nm converted to equivalent force at {load_x:.1f} mm lever arm.", |
| } |
|
|
| if "chair" in text or "seat" in text: |
| load_type = "distributed_downward_proxy" |
| load_region = "seat_surface_proxy" |
| elif "motor" in text: |
| load_type = "motor_mount_tip_or_boss_load" |
| load_region = "motor_boss_or_tip" |
| else: |
| load_type = "cantilever_tip_load" |
| load_region = load_region_hint |
|
|
| return { |
| "type": load_type, |
| "effective_load_n": abs(design.load_newtons) * factor, |
| "nominal_load_n": abs(design.load_newtons), |
| "factor": factor, |
| "load_point": [load_x, load_y, load_z], |
| "vector_n": [0, 0, -round(abs(design.load_newtons) * factor, 4)], |
| "fixed_region": "left_face", |
| "load_region": load_region, |
| "note": "Defaulted to fixed left face and downward load at the model-proposed tip/load point.", |
| } |
|
|