处理一些冲突

This commit is contained in:
2026-09-07 19:40:10 +08:00
63 changed files with 5157 additions and 108 deletions
+42 -10
View File
@@ -66,9 +66,15 @@ class Build123dGeometryAdapter:
@staticmethod
def _wire(edges: list[dict[str, Any]]) -> Wire:
# 将边字典列表(直线/圆弧)组装成 build123d 的 Wire 线框。
# 将边字典列表(直线/圆弧/插值 B 样条)组装成 build123d 的 Wire 线框。
built: list[Edge] = []
for edge in edges:
if edge.get("type") == "bspline":
points = [_vector(point) for point in edge.get("points_mm") or []]
if len(points) < 3:
raise ValueError("bspline contour edge needs at least 3 points")
built.append(Edge.make_spline(points, periodic=False))
continue
start = _vector(edge["start_mm"])
end = _vector(edge["end_mm"])
if edge.get("type") == "arc" and edge.get("center_mm") is not None:
@@ -134,20 +140,38 @@ class Build123dGeometryAdapter:
result: list[Face] = []
for region in regions:
outer = region.get("outer") or []
if len(outer) < 2:
# 闭合插值样条仅有一条边;直线/圆弧轮廓通常由多条边组成。
if len(outer) < 1:
continue
face = Face(self._wire(outer))
holes = [self._wire(hole) for hole in region.get("holes") or [] if len(hole) >= 2]
holes = [self._wire(hole) for hole in region.get("holes") or [] if len(hole) >= 1]
result.append(face.make_holes(holes) if holes else face)
return result
# 2. 退化:仅有单组轮廓边时,直接作为外轮廓建面。
edges = sketch.get("contour_edges_mm") or []
if len(edges) >= 2:
if len(edges) >= 1:
return [Face(self._wire(edges))]
# 3. 最终回退:由工作平面与实体圆生成面(圆环/孔洞处理见 _faces_from_circles)。
plane = PlaneSpec.from_mapping(sketch.get("workplane") or {})
return self._faces_from_circles(sketch.get("entities") or [], plane)
def loft(self, sketches: list[dict[str, Any]]) -> Solid:
"""由多条简单闭合草图轮廓生成实体放样。"""
wires: list[Wire] = []
for index, sketch in enumerate(sketches):
# Solid.make_loft 接收 Wire;复用 faces_for_sketch 保持放样、
# 拉伸和回转的 profile resolver 一致。多区域/内环的截面对应关系
# 尚未由 CDSL 表达,必须显式拒绝而非猜测。
faces = self.faces_for_sketch(sketch)
if len(faces) != 1:
raise ValueError(f"loft profile {index} must resolve to exactly one closed region")
if faces[0].inner_wires():
raise ValueError(f"loft profile {index} must not contain inner loops")
wires.append(faces[0].outer_wire())
if len(wires) < 2:
raise ValueError("loft requires at least two profile sketches")
return Solid.make_loft(wires)
@staticmethod
def _coerce_single_or_compound(result: Any, *, empty_error: str | None = None) -> Any:
"""规整一次布尔结果:None/空视为失败(可选报错),多成员合并为 Compound。"""
@@ -189,14 +213,22 @@ class Build123dGeometryAdapter:
"""
# 1. 采样点到目标的最远命中距离决定穿透余量;没有任何采样点命中
# 说明 profile 与目标面无交叠,无法裁剪(保留 extent_target_not_reached)。
# through_next 的 target 是当前主体,必须先从其面中选出实际命中的
# 下一终止面,不能把整个 body 当作待拉伸的 Face。
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:
points = Build123dGeometryAdapter.profile_sample_points(face)
targets = [target] if isinstance(target, Face) else list(target.faces())
candidates = []
for candidate in targets:
hits = [Build123dGeometryAdapter._forward_intersection_distance(candidate, point, unit) for point in points]
distances = [value for value in hits if value is not None]
if distances:
candidates.append((len(distances), min(distances), candidate, distances))
if not candidates:
raise ValueError("extent target is not reached by the profile")
# 覆盖最多 profile 采样点的面就是本次实体的下一终止面;覆盖数相同
# 时取最近的正向交点,确保相邻面交界处的选择稳定。
_, _, target, distances = max(candidates, key=lambda item: (item[0], -item[1]))
margin = max(distances) + 2.0
# 2. 穿透拉伸 profile,同时把目标面向回推生成体层,二者求交即裁剪体。
# build123d 的布尔交方法名是 intersect(不是 OCC 的 common),