sheet_bend 的一个文件

This commit is contained in:
2026-09-08 10:03:03 +08:00
parent c524f0213c
commit f48ba19dfa
+105
View File
@@ -355,6 +355,111 @@ class ThreadSpec:
) )
@dataclass(frozen=True)
class BendLeg:
"""One straight wing of a bent sheet-metal chain (runtime-neutral).
``leg_mm`` is the straight mid-plane distance from the previous fold vertex
to the fold vertex leaving this wing (the unfolded chord length between the
two surrounding fold vertices). When the wing has a following wing,
``bend_angle_deg`` is the required *interior* angle between the two wings
(0 < angle < 180, 90 = a right-angle bend), ``inner_radius_mm`` the inner
bend-surface fillet radius (>= 0; the outer radius is always
``inner_radius_mm + thickness_mm``) and ``side`` the fold direction
(+1 / -1). A trailing wing carries no fold; stray fold fields on the last
wing are ignored for tolerance.
"""
leg_mm: float
bend_angle_deg: float | None = None
inner_radius_mm: float = 0.0
side: int = 1
@dataclass(frozen=True)
class BendSpec:
"""Runtime-neutral definition of a sheet-metal bend (``bend_add``).
The part is described by its mid-plane centreline: ``chain`` lists the
straight wings joined by equal-thickness bend corners. ``thickness_mm``
and ``width_mm`` are the sheet thickness and the full length along the fold
(width) axis. ``frame.origin_mm`` anchors the start of the first wing's
mid-plane path, ``frame.x_dir`` is the fold/width axis and
``frame.normal`` is the mid-plane normal of the first wing; the first wing
extends along ``normal x x_dir``.
The spec deliberately contains no OCC planes or shapes. The adapter turns
this definition into a bent solid keeping source-contract parsing separate
from B-rep work (same pattern as :class:`ThreadSpec`).
"""
thickness_mm: float
width_mm: float
frame: PlaneSpec
chain: tuple[BendLeg, ...]
@classmethod
def from_feature(cls, params: dict[str, Any]) -> "BendSpec":
try:
thickness = float(params.get("thickness_mm") or 0.0)
width = float(params.get("width_mm") or 0.0)
except (TypeError, ValueError) as error:
raise ValueError("bend thickness/width must be numeric") from error
if thickness <= 0 or width <= 0:
raise ValueError("bend requires positive thickness_mm and width_mm in millimetres")
raw_chain = params.get("chain")
if not isinstance(raw_chain, (list, tuple)) or not raw_chain:
raise ValueError("bend requires a non-empty chain of wing segments")
chain: list[BendLeg] = []
for index, raw_leg in enumerate(raw_chain):
if not isinstance(raw_leg, dict):
raise ValueError(f"bend chain[{index}] must be an object with leg_mm")
try:
leg = float(raw_leg.get("leg_mm") or 0.0)
except (TypeError, ValueError) as error:
raise ValueError(f"bend chain[{index}].leg_mm must be numeric") from error
if leg <= 0:
raise ValueError(f"bend chain[{index}].leg_mm must be positive")
bend_angle: float | None = None
if index < len(raw_chain) - 1:
raw_angle = raw_leg.get("bend_angle_deg")
if raw_angle is None:
raise ValueError(
f"bend chain[{index}] needs bend_angle_deg for the fold towards the next wing"
)
try:
bend_angle = float(raw_angle)
except (TypeError, ValueError) as error:
raise ValueError(f"bend chain[{index}].bend_angle_deg must be numeric") from error
if not 0 < bend_angle < 180:
raise ValueError("bend interior angle must be between 0 and 180 degrees")
radius = float(raw_leg.get("inner_radius_mm") or 0.0)
raw_side = raw_leg.get("side")
side = 1 if raw_side is None else int(raw_side)
if radius < 0:
raise ValueError(f"bend chain[{index}].inner_radius_mm must be non-negative")
if side not in (1, -1):
raise ValueError(f"bend chain[{index}].side must be +1 or -1")
chain.append(BendLeg(
leg_mm=leg,
bend_angle_deg=bend_angle,
inner_radius_mm=radius,
side=side,
))
raw_frame = params.get("frame")
if isinstance(raw_frame, dict):
frame = PlaneSpec.from_mapping(raw_frame)
else:
# frame 缺省:首翼沿世界 +X 延伸、厚度沿 +Y、折痕沿 +Z(贴 XY 平面)。
frame = PlaneSpec(
origin_mm=(0.0, 0.0, 0.0),
x_dir=(0.0, 0.0, 1.0),
y_dir=(1.0, 0.0, 0.0),
normal=(0.0, 1.0, 0.0),
)
return cls(thickness_mm=thickness, width_mm=width, frame=frame, chain=tuple(chain))
@dataclass(frozen=True) @dataclass(frozen=True)
class RuntimeDiagnostic: class RuntimeDiagnostic:
code: str code: str