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.",
    }