feat(engine): 支持 up_to_surface 非均匀 profile 裁剪拉伸
为 up_to_surface 终止条件新增裁剪语义,通过穿透拉伸与目标面布尔求交保留可达材料,修复非均匀相交时错误拒绝特征的问题。
This commit is contained in:
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import math
|
||||
from typing import Any, Iterable
|
||||
|
||||
from build123d import Axis, Edge, Face, Plane, Solid, Vector, Wire, export_step
|
||||
from build123d import Axis, Compound, Edge, Face, Plane, ShapeList, Solid, Vector, Wire, export_step
|
||||
|
||||
from .runtime_types import AxisSpec, HoleSpec, PlaneSpec, TopologyRecord, Vector3, canonical_plane_signature
|
||||
|
||||
@@ -151,6 +151,43 @@ class Build123dGeometryAdapter:
|
||||
# 沿给定方向向量拉伸一个面,生成实体。
|
||||
return Solid.extrude(face, _vector(direction))
|
||||
|
||||
@staticmethod
|
||||
def extrude_trimmed(face: Face, target: Any, direction: Vector3) -> Any:
|
||||
"""Extrude the profile to the target face, trimming unreached regions.
|
||||
|
||||
Issue #5: when a profile intersects the up_to_surface target
|
||||
non-uniformly (part of the profile reaches the face, part hangs
|
||||
outside it), a plain vector extrusion is wrong. The CAD semantics is
|
||||
to keep only the material between the profile and the target. We
|
||||
pierce the profile through the target, push the target face backward
|
||||
by the same margin to build a slab, and keep their boolean common
|
||||
(intersection) as the trimmed solid.
|
||||
"""
|
||||
# 1. 采样点到目标的最远命中距离决定穿透余量;没有任何采样点命中
|
||||
# 说明 profile 与目标面无交叠,无法裁剪(保留 extent_target_not_reached)。
|
||||
unit = _vector(direction).normalized()
|
||||
hits = [
|
||||
Build123dGeometryAdapter._forward_intersection_distance(target, point, unit)
|
||||
for point in Build123dGeometryAdapter.profile_sample_points(face)
|
||||
]
|
||||
distances = [value for value in hits if value is not None]
|
||||
if not distances:
|
||||
raise ValueError("extent target is not reached by the profile")
|
||||
margin = max(distances) + 2.0
|
||||
# 2. 穿透拉伸 profile,同时把目标面向回推生成体层,二者求交即裁剪体。
|
||||
# build123d 的布尔交方法名是 intersect(不是 OCC 的 common),
|
||||
# 且多实体结果返回 ShapeList,需要规整为单个 Solid / Compound。
|
||||
pierced = Solid.extrude(face, unit * margin)
|
||||
slab = Solid.extrude(target, -unit * margin)
|
||||
trimmed = pierced.intersect(slab)
|
||||
if isinstance(trimmed, ShapeList):
|
||||
members = list(trimmed)
|
||||
# build123d 类型桩未声明 make_compound,但运行时存在(宽泛类型桩噪音)。
|
||||
trimmed = members[0] if len(members) == 1 else Compound.make_compound(members) # pyright: ignore[reportAttributeAccessIssue]
|
||||
if trimmed is None or (hasattr(trimmed, "is_empty") and trimmed.is_empty()):
|
||||
raise ValueError("extent target produced an empty trimmed solid")
|
||||
return trimmed
|
||||
|
||||
@staticmethod
|
||||
def body_center(body: Any) -> Vector3:
|
||||
# 取主体包围盒的中心坐标,作为体心的近似。
|
||||
@@ -236,8 +273,9 @@ class Build123dGeometryAdapter:
|
||||
return Solid.revolve(face, angle_deg, Build123dGeometryAdapter.axis(axis))
|
||||
|
||||
@staticmethod
|
||||
def fuse(body: Any | None, solid: Solid) -> Any:
|
||||
def fuse(body: Any | None, solid: Any) -> Any:
|
||||
# 布尔并:没有既有主体时,直接以该实体作为新主体。
|
||||
# 实参类型放宽为 Any:build123d 的布尔结果可能是 Solid 或 Compound。
|
||||
return solid if body is None else body.fuse(solid)
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user