feat: v0.6 分割树架构——横梃竖梃完整编辑
- 逆向确认原站设计思路: 基底多边形+mm.sp分割树(polyId指向作用区域), 运行时重放得到最终窗格(254套样本验证重放语义) - 前端 replayShape: 分割树重放还原真实网格(平开1矩形→4-7格) - 中梃编辑: 点击选中高亮/拖动移动/删除(直接操作sp源真值后重放) - 加梃升级: polyId.idx 精确指向被点窗格 - Python报价算料同语义重放: 中梃长度精确到实际作用跨度 - 方案文档§3.3: 原站设计思路+编辑器完成设计
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from huamenchuang.core.drawing import DrawingData, parse_drawing
|
||||
from huamenchuang.core.drawing import parse_drawing, replay_shape
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -45,18 +45,23 @@ def _region_bbox(region_pts):
|
||||
return min(xs), min(ys), max(xs), max(ys)
|
||||
|
||||
|
||||
def takeoff(dd: DrawingData) -> TakeoffResult:
|
||||
"""从解析后的 drawing_data 提取算料清单。"""
|
||||
def takeoff(dd) -> TakeoffResult:
|
||||
"""从解析后的 drawing_data 提取算料清单(基于分割树重放后的真实窗格)。"""
|
||||
result = TakeoffResult()
|
||||
xs, ys = [], []
|
||||
|
||||
for shape in dd.get("sm", []):
|
||||
regions = [[(seg["ps"]["x"], seg["ps"]["y"]) for seg in r]
|
||||
for r in ((shape.get("polygon") or {}).get("polygon") or [])]
|
||||
# 重放分割树得到真实窗格; 空区域基底的形状跳过
|
||||
regions = replay_shape(shape)
|
||||
if not regions:
|
||||
continue
|
||||
region_pts = [[(seg["ps"]["x"], seg["ps"]["y"]) for seg in r] for r in regions]
|
||||
region_bbs = []
|
||||
for pts in region_pts:
|
||||
xs_ = [p[0] for p in pts]; ys_ = [p[1] for p in pts]
|
||||
region_bbs.append((min(xs_), min(ys_), max(xs_), max(ys_)))
|
||||
# 形状外框 bbox
|
||||
all_pts = [p for r in regions for p in r]
|
||||
all_pts = [p for r in region_pts for p in r]
|
||||
x0, y0, x1, y1 = _region_bbox(all_pts)
|
||||
w, h = round(x1 - x0), round(y1 - y0)
|
||||
xs.extend([x0, x1])
|
||||
@@ -64,16 +69,26 @@ def takeoff(dd: DrawingData) -> TakeoffResult:
|
||||
result.bars.append(BarItem("frame", w, 2, "外框-宽"))
|
||||
result.bars.append(BarItem("frame", h, 2, "外框-高"))
|
||||
|
||||
# 中梃:投影长度(竖梃=框高,横梃=框宽)
|
||||
# 中梃: 分割线在其作用区域 bbox 内的投影长度(精确到该梃实际跨度)
|
||||
for sp in ((shape.get("mm") or {}).get("sp") or []):
|
||||
norm = sp["line"]["norm"]
|
||||
if abs(norm["x"]) >= abs(norm["y"]):
|
||||
result.bars.append(BarItem("mullion", h, 1, "竖梃"))
|
||||
else:
|
||||
result.bars.append(BarItem("mullion", w, 1, "横梃"))
|
||||
line = sp.get("line") or {}
|
||||
n, pt = line.get("norm"), line.get("pt")
|
||||
if not (isinstance(n, dict) and isinstance(pt, dict)):
|
||||
continue
|
||||
axis = "v" if abs(n.get("x", 0)) >= abs(n.get("y", 0)) else "h"
|
||||
pos = pt.get("x") if axis == "v" else pt.get("y")
|
||||
# 取该梃与重放区域相交的最长投影
|
||||
best = 0.0
|
||||
for bb in region_bbs:
|
||||
bx0, by0, bx1, by1 = bb
|
||||
lo, hi = (bx0, bx1) if axis == "v" else (by0, by1)
|
||||
if lo - 1 <= pos <= hi + 1:
|
||||
best = max(best, hi - lo)
|
||||
if best > 0:
|
||||
result.bars.append(BarItem("mullion", round(best), 1, "竖梃" if axis == "v" else "横梃"))
|
||||
|
||||
# 玻璃面积
|
||||
result.glass_area_m2 += sum(_shoelace_area(r) for r in regions) / 1_000_000
|
||||
# 玻璃面积(重放后各窗格之和)
|
||||
result.glass_area_m2 += sum(_shoelace_area(r) for r in region_pts) / 1_000_000
|
||||
|
||||
# 扇框
|
||||
for sub in (shape.get("sm") or []):
|
||||
|
||||
Reference in New Issue
Block a user