File size: 3,040 Bytes
6de1b61 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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.",
}
|