# -*- coding: utf-8 -*- u"""把 topshell_v3.py 里「候选映射逐个试」的切除换成「读草图变换 + 先验平面再下刀」。 起因(2026-09-13 实测):东端墙那一刀 Select4 返回 True、选中数 1、选中面 box 也对, 但 InsertSketch 之后草图却建在了**顶板**上(风口从顶上切穿),靠「切出了东西但不在目标位置」才拦下。 ⇒ 选中数不能证明草图建在哪;**进草图后读 ActiveSketch.ModelToSketchTransform**: 列主序 M[r][c] = a[3c+r],s = M·p + t(米);第 3 行 = 草图法线。 法线轴 ≠ 目标轴 或 墙面上一点的草图 z ≠ 0 ⇒ 退出重进,三次不成就停手。 同一个变换顺带给出精确的 uv 映射,不再需要候选法。 """ import io import sys sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True) P_ = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\topshell_v3.py' s = open(P_, encoding='utf-8').read() a = s.index(u'def try_cut(') b = s.index(u'\n# ── [6b]') NEW = u'''def sketch_xform(): sk = prop(sm, 'ActiveSketch') if not sk: return None arr = list(prop(D(sk), 'ModelToSketchTransform').ArrayData) Mx = [[arr[3 * c + r] for c in range(3)] for r in range(3)] # 列主序 return Mx, arr[9:12] def to_sketch(X, p): u"""模型点 p(mm)→ 草图坐标(米)""" Mx, t = X q = [v / 1000.0 for v in p] return [sum(Mx[r][c] * q[c] for c in range(3)) + t[r] for r in range(3)] def open_face_sketch(face, tag): ax, sign, coord = face for attempt in range(1, 4): if not pick_face(*face): P(u' ⛔ %s 选不到面' % tag); sys.exit(4) sm.InsertSketch(True) X = sketch_xform() ok, nax, sz = False, -1, 99.0 if X: nax = max(range(3), key=lambda i: abs(X[0][2][i])) probe = [0.0, 0.0, 0.0] probe[ax] = coord sz = to_sketch(X, probe)[2] * 1000.0 ok = nax == ax and abs(sz) < 0.01 P(u' %s 进草图 第 %d 次:法线轴 %s(目标 %s)、墙面点草图 z = %.4f mm %s' % (tag, attempt, 'xyz'[nax] if nax >= 0 else '?', 'xyz'[ax], sz, u'✅' if ok else u'✗ 平面不对,退出重进')) if ok: return X sm.InsertSketch(True) P(u' ⛔ %s 三次都没进到目标面,停手' % tag); sys.exit(4) def cut_shapes(tag, face, circles, rects, verify): nbefore = len(faces()) X = open_face_sketch(face, tag) sm.AddToDB = True for p, r in circles: q = to_sketch(X, p) sm.CreateCircleByRadius(q[0], q[1], 0.0, M(r)) for p0, p1 in rects: q0, q1 = to_sketch(X, p0), to_sketch(X, p1) sm.CreateCornerRectangle(q0[0], q0[1], 0.0, q1[0], q1[1], 0.0) sm.AddToDB = False sm.InsertSketch(True) c = do_cut(T + 0.2) ok = verify() P(u' %s 切除 → %-12r 面数 %d→%d 目标 %s' % (tag, prop(c, 'Name') if c else c, nbefore, len(faces()), u'✅' if ok else u'⛔')) if not ok: P(u' ⛔⛔ 目标没出现在设计位置 —— 停手、不存盘'); sys.exit(5) def end_verify(xwall): def v(): cs = cyls() for cy, cz, r in END_CIRCLES: hit = any(abs(rr - r) < 0.01 and ax_ == 0 and abs((fb[1] + fb[4]) / 2 - cy) < 0.05 and abs((fb[2] + fb[5]) / 2 - L(cz)) < 0.05 and min(abs(fb[0] - xwall), abs(fb[3] - xwall)) < 0.05 for rr, ax_, fb in cs) if not hit: return False return True return v def rect_verify(wins, ywall0, ywall1): def v(): ps = planes() for x0, x1, z0, z1 in wins: for xe in (x0, x1): hit = any(ax_ == 0 and abs(fb[0] - xe) < 0.05 and abs(fb[3] - xe) < 0.05 and abs(fb[2] - L(z0)) < 0.05 and abs(fb[5] - L(z1)) < 0.05 and fb[1] >= ywall0 - 0.05 and fb[4] <= ywall1 + 0.05 for ax_, sg, fb, a_, _ in ps) if not hit: return False return True return v P(u'\\n[4] 端墙:Ø60 风口 + 4 × Ø%.1f 风扇孔(孔距 %.1f)' % (2 * R_FAN, 2 * abs(END_CIRCLES[1][0] - FAN_CY))) cut_shapes(u'西端墙', (0, -1, X0), [((X0, cy, L(cz)), r) for cy, cz, r in END_CIRCLES], [], end_verify(X0)) cut_shapes(u'东端墙', (0, +1, X1), [((X1, cy, L(cz)), r) for cy, cz, r in END_CIRCLES], [], end_verify(X1)) P(u'\\n[5] 南墙:2 个 13×9 Type-C 窗口') cut_shapes(u'南墙', (1, -1, Y0), [], [((x0, Y0, L(z0)), (x1, Y0, L(z1))) for x0, x1, z0, z1 in SOUTH_WINDOWS], rect_verify(SOUTH_WINDOWS, Y0, IY0)) P(u'\\n[6] 北墙:J33 窗口 + 风扇出线窗口') cut_shapes(u'北墙', (1, +1, Y1), [], [((x0, Y1, L(z0)), (x1, Y1, L(z1))) for x0, x1, z0, z1 in NORTH_WINDOWS], rect_verify(NORTH_WINDOWS, IY1, Y1)) ''' s = s[:a] + NEW + s[b:] if s.count(u'try_cut(') or s.count(u'X_CANDS'): raise SystemExit(u'⛔ 旧的候选法残留') open(P_, 'w', encoding='utf-8').write(s) print(u'topshell_v3.py 已换成「读草图变换 + 先验平面」版')