Cascade-Cleanup: wand_centerline + wand_outline beim wand_axis-Delete mit-loeschen
Bug: nach _Delete einer Wand blieben die Hilfslinien (Centerline, Outline) als Orphan-Curves stehen. _find_all_volumes filtert nur VOLUME_TYPES, in denen Centerline/Outline nicht sind. Fix: bei wand_axis-Delete-Event zusaetzlich alle wand_centerline/wand_outline Curves mit derselben wall_id ID-Liste ergaenzen → nach 500ms cascade-delete raeumt sie mit weg. Layered Cluster + per-Layer BooleanUnion via _build_cluster_layered_breps + erweiterter _regen_cluster_anchor sind im selben commit drin (siehe diff vor diesem Bugfix).
This commit is contained in:
+192
-40
@@ -2601,6 +2601,110 @@ def _build_cluster_union_brep(doc, cluster_ids, uk, ok):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _build_cluster_layered_breps(doc, cluster_ids, layers_def, uk, ok):
|
||||||
|
"""Per-Layer-Cluster: fuer jeden Layer-Index die per-Wand Schicht-Breps
|
||||||
|
sammeln + BooleanUnion. Liefert Liste [(brep, color, name)] — eine pro
|
||||||
|
Layer-Index, in derselben Reihenfolge wie layers_def.
|
||||||
|
|
||||||
|
Voraussetzung: alle Cluster-Member haben kompatible Layer-Struktur
|
||||||
|
(= gleich viele Schichten, gleiche dicken; sichergestellt durch
|
||||||
|
_wand_chain_compat in der Cluster-BFS)."""
|
||||||
|
if not cluster_ids or not layers_def: return []
|
||||||
|
walls = {}
|
||||||
|
for wid in cluster_ids:
|
||||||
|
ax = _find_axis(doc, wid)
|
||||||
|
if ax is None: continue
|
||||||
|
wm = _read_meta(ax)
|
||||||
|
if wm is None: continue
|
||||||
|
g = ax.Geometry
|
||||||
|
if not isinstance(g, rg.Curve): continue
|
||||||
|
walls[wid] = (ax, wm, g)
|
||||||
|
if not walls: return []
|
||||||
|
eps = 0.001
|
||||||
|
tol = doc.ModelAbsoluteTolerance
|
||||||
|
# Pre-compute Achse-Extensions pro Wand (gleich fuer alle Layer)
|
||||||
|
extensions = {}
|
||||||
|
for wid, (ax, wm, g) in walls.items():
|
||||||
|
ext_start = 0.0; ext_end = 0.0
|
||||||
|
my_start = g.PointAtStart; my_end = g.PointAtEnd
|
||||||
|
for nid, (n_ax, n_meta, n_g) in walls.items():
|
||||||
|
if nid == wid: continue
|
||||||
|
n_half = float(n_meta.get("dicke", 0.0)) * 0.5
|
||||||
|
n_start = n_g.PointAtStart; n_end = n_g.PointAtEnd
|
||||||
|
if (my_start.DistanceTo(n_start) < eps or
|
||||||
|
my_start.DistanceTo(n_end) < eps):
|
||||||
|
ext_start = max(ext_start, n_half)
|
||||||
|
if (my_end.DistanceTo(n_start) < eps or
|
||||||
|
my_end.DistanceTo(n_end) < eps):
|
||||||
|
ext_end = max(ext_end, n_half)
|
||||||
|
for my_ep, is_start in ((my_start, True), (my_end, False)):
|
||||||
|
rc, t = n_g.ClosestPoint(my_ep)
|
||||||
|
if not rc: continue
|
||||||
|
if n_g.PointAt(t).DistanceTo(my_ep) > eps: continue
|
||||||
|
if (n_start.DistanceTo(my_ep) < eps or
|
||||||
|
n_end.DistanceTo(my_ep) < eps):
|
||||||
|
continue
|
||||||
|
if is_start: ext_start = max(ext_start, n_half)
|
||||||
|
else: ext_end = max(ext_end, n_half)
|
||||||
|
extensions[wid] = (ext_start, ext_end)
|
||||||
|
|
||||||
|
out = []
|
||||||
|
for layer_idx, layer in enumerate(layers_def):
|
||||||
|
layer_breps = []
|
||||||
|
for wid, (ax, wm, g) in walls.items():
|
||||||
|
wand_layers = wm.get("wand_layers") or []
|
||||||
|
if layer_idx >= len(wand_layers): continue
|
||||||
|
try: my_dicke = float(wm.get("dicke", 0))
|
||||||
|
except Exception: my_dicke = 0
|
||||||
|
referenz = wm.get("referenz", "mid")
|
||||||
|
start_off, _ = _wall_offsets_from_referenz(my_dicke, referenz)
|
||||||
|
cur = start_off
|
||||||
|
for prev_l in wand_layers[:layer_idx]:
|
||||||
|
try: cur -= float(prev_l.get("dicke", 0))
|
||||||
|
except Exception: pass
|
||||||
|
d_left = cur
|
||||||
|
try: d_right = cur - float(wand_layers[layer_idx].get("dicke", 0))
|
||||||
|
except Exception: d_right = cur
|
||||||
|
ext_start, ext_end = extensions.get(wid, (0.0, 0.0))
|
||||||
|
ext_g = _extend_axis_curve(g, ext_start, ext_end)
|
||||||
|
b = _make_wall_layer_brep(ext_g, d_left, d_right, uk, ok)
|
||||||
|
if b is not None:
|
||||||
|
layer_breps.append(b)
|
||||||
|
color = layer.get("color", "")
|
||||||
|
name = layer.get("name", "")
|
||||||
|
if not layer_breps:
|
||||||
|
out.append((None, color, name)); continue
|
||||||
|
if len(layer_breps) == 1:
|
||||||
|
try: layer_breps[0].MergeCoplanarFaces(tol)
|
||||||
|
except Exception: pass
|
||||||
|
out.append((layer_breps[0], color, name)); continue
|
||||||
|
try:
|
||||||
|
unioned = rg.Brep.CreateBooleanUnion(layer_breps, tol)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] cluster layered union exc layer={}:".format(
|
||||||
|
layer_idx), ex)
|
||||||
|
unioned = None
|
||||||
|
if not unioned or len(unioned) == 0:
|
||||||
|
print("[ELEMENTE] cluster layered union empty layer={}".format(
|
||||||
|
layer_idx))
|
||||||
|
out.append((None, color, name)); continue
|
||||||
|
if len(unioned) == 1:
|
||||||
|
result = unioned[0]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
joined = rg.Brep.JoinBreps(list(unioned), tol)
|
||||||
|
if joined and len(joined) > 0:
|
||||||
|
result = max(joined, key=lambda b: b.GetVolume() if b else 0)
|
||||||
|
else:
|
||||||
|
result = max(unioned, key=lambda b: b.GetVolume() if b else 0)
|
||||||
|
except Exception:
|
||||||
|
result = max(unioned, key=lambda b: b.GetVolume() if b else 0)
|
||||||
|
try: result.MergeCoplanarFaces(tol)
|
||||||
|
except Exception: pass
|
||||||
|
out.append((result, color, name))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
class _ClusterVolumeSelectHandler(Rhino.UI.MouseCallback):
|
class _ClusterVolumeSelectHandler(Rhino.UI.MouseCallback):
|
||||||
"""Mouse-Down auf Wand-Volume:
|
"""Mouse-Down auf Wand-Volume:
|
||||||
- Klick INNEN (nicht am Rand/Eck) → naechste Achse selektieren
|
- Klick INNEN (nicht am Rand/Eck) → naechste Achse selektieren
|
||||||
@@ -2804,22 +2908,37 @@ def install_cluster_select_handler():
|
|||||||
|
|
||||||
|
|
||||||
def _regen_cluster_anchor(doc, anchor_id, cluster_ids, anchor_meta):
|
def _regen_cluster_anchor(doc, anchor_id, cluster_ids, anchor_meta):
|
||||||
"""Anchor-Build fuer komplexe Multi-Wand-Cluster (same material, verzweigt).
|
"""Anchor-Build fuer komplexe Multi-Wand-Cluster.
|
||||||
Baut BooleanUnion-Brep aller Cluster-Member-Volumina, subtrahiert
|
SOLID-Wand: ein Boolean-Union-Brep ueber alle Cluster-Member.
|
||||||
Oeffnungs-Cutouts. Opening-Sub-Pieces (Rahmen/Sims/Glas/Schwung) bleiben
|
LAYERED-Wand: pro Layer-Index separater Union-Brep (alle Cluster-Member
|
||||||
pro-Wand zustaendig — die werden von ihrer eigenen Wand-Regen verwaltet.
|
haben kompatible Layer-Struktur, sichergestellt durch chain_compat).
|
||||||
|
|
||||||
Return True bei Erfolg, False bei Fallback-Bedarf (caller faellt dann auf
|
Opening-Cutouts pro Member abziehen. Opening-Sub-Pieces (Rahmen/Sims/Glas)
|
||||||
chain/solo zurueck)."""
|
bleiben pro-Wand zustaendig (separate Objekte mit oeff_parent).
|
||||||
|
|
||||||
|
Return True bei Erfolg, False bei Fallback-Bedarf."""
|
||||||
cluster_list = sorted(cluster_ids)
|
cluster_list = sorted(cluster_ids)
|
||||||
uk, ok = _resolve_uk_ok(doc, anchor_meta["geschoss"],
|
uk, ok = _resolve_uk_ok(doc, anchor_meta["geschoss"],
|
||||||
anchor_meta["uk_override"],
|
anchor_meta["uk_override"],
|
||||||
anchor_meta["ok_override"])
|
anchor_meta["ok_override"])
|
||||||
cluster_brep = _build_cluster_union_brep(doc, cluster_list, uk, ok)
|
is_layered = bool(anchor_meta.get("wand_layered", False))
|
||||||
if cluster_brep is None:
|
layers_def = anchor_meta.get("wand_layers") or []
|
||||||
return False
|
if is_layered and not layers_def:
|
||||||
|
is_layered = False # Fallback wenn Layer-Def fehlt
|
||||||
|
|
||||||
# Openings: cutouts pro Member-Wand sammeln + abziehen
|
if is_layered:
|
||||||
|
# Per-Layer-Build via Boolean-Union der per-Wand Schicht-Breps
|
||||||
|
layer_results = _build_cluster_layered_breps(
|
||||||
|
doc, cluster_list, layers_def, uk, ok)
|
||||||
|
if not layer_results or all(b is None for (b, _c, _n) in layer_results):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
single = _build_cluster_union_brep(doc, cluster_list, uk, ok)
|
||||||
|
if single is None:
|
||||||
|
return False
|
||||||
|
layer_results = [(single, "", "")]
|
||||||
|
|
||||||
|
# Openings: cutouts pro Member-Wand sammeln (gleich fuer alle Layer)
|
||||||
tol = 0.001
|
tol = 0.001
|
||||||
cutouts = []
|
cutouts = []
|
||||||
for c_wid in cluster_list:
|
for c_wid in cluster_list:
|
||||||
@@ -2844,21 +2963,26 @@ def _regen_cluster_anchor(doc, anchor_id, cluster_ids, anchor_meta):
|
|||||||
if co: cutouts.append(co)
|
if co: cutouts.append(co)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] cluster cutout:", ex)
|
print("[ELEMENTE] cluster cutout:", ex)
|
||||||
|
# Cutouts pro Layer-Result abziehen
|
||||||
if cutouts:
|
if cutouts:
|
||||||
|
new_results = []
|
||||||
|
for (b, col, nm) in layer_results:
|
||||||
|
if b is None:
|
||||||
|
new_results.append((None, col, nm)); continue
|
||||||
try:
|
try:
|
||||||
diff = rg.Brep.CreateBooleanDifference(
|
diff = rg.Brep.CreateBooleanDifference([b], cutouts, tol)
|
||||||
[cluster_brep], cutouts, tol)
|
|
||||||
if diff and len(diff) > 0:
|
if diff and len(diff) > 0:
|
||||||
cluster_brep = diff[0]
|
b = diff[0]
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] cluster bool-diff openings:", ex)
|
print("[ELEMENTE] cluster bool-diff openings:", ex)
|
||||||
|
new_results.append((b, col, nm))
|
||||||
|
layer_results = new_results
|
||||||
|
|
||||||
# Alle stale wand_volume-Objekte der Cluster-Members raeumen
|
# Alle stale wand_volume-Objekte der Cluster-Members raeumen
|
||||||
for c_wid in cluster_list:
|
for c_wid in cluster_list:
|
||||||
for o, _m in _find_objects_by_wall_id(doc, c_wid, "wand_volume"):
|
for o, _m in _find_objects_by_wall_id(doc, c_wid, "wand_volume"):
|
||||||
try: doc.Objects.Delete(o.Id, True)
|
try: doc.Objects.Delete(o.Id, True)
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
# Auch alte Anchor-Volumes mit chain_members die unsere Member enthalten
|
|
||||||
cluster_set = set(cluster_list)
|
cluster_set = set(cluster_list)
|
||||||
for _vobj in list(doc.Objects):
|
for _vobj in list(doc.Objects):
|
||||||
_vm = _read_meta(_vobj)
|
_vm = _read_meta(_vobj)
|
||||||
@@ -2868,48 +2992,64 @@ def _regen_cluster_anchor(doc, anchor_id, cluster_ids, anchor_meta):
|
|||||||
try: doc.Objects.Delete(_vobj.Id, True)
|
try: doc.Objects.Delete(_vobj.Id, True)
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
# Layer + Material aus Anchor's Style
|
# AddBrep pro Layer (oder einmal bei solid)
|
||||||
g = _geschoss_by_id(doc, anchor_meta["geschoss"])
|
g = _geschoss_by_id(doc, anchor_meta["geschoss"])
|
||||||
geschoss_name = g.get("name", "EG") if g else "EG"
|
geschoss_name = g.get("name", "EG") if g else "EG"
|
||||||
layer_idx = _ensure_layer(doc, _layer_path_volume(doc, geschoss_name))
|
base_layer_idx = _ensure_layer(doc, _layer_path_volume(doc, geschoss_name))
|
||||||
mat_color = "#9a9a9a"
|
|
||||||
try:
|
|
||||||
sm = _wand_solid_material(doc, anchor_meta)
|
|
||||||
if sm:
|
|
||||||
all_mats = _get_all_materials(doc)
|
all_mats = _get_all_materials(doc)
|
||||||
if sm in all_mats:
|
import json as _json
|
||||||
mat_color = all_mats[sm].get("color", mat_color)
|
layers_json = (_json.dumps(layers_def, ensure_ascii=False)
|
||||||
ms = _ensure_material_sublayer(doc, geschoss_name, sm)
|
if is_layered else "")
|
||||||
if ms >= 0: layer_idx = ms
|
for idx, (lbrep, color, lname) in enumerate(layer_results):
|
||||||
except Exception as ex:
|
if lbrep is None: continue
|
||||||
print("[ELEMENTE] cluster material lookup:", ex)
|
# Material-Lookup: bei layered pro Schicht via layers_def[idx].material,
|
||||||
|
# bei solid via Style.
|
||||||
|
mat_name = ""
|
||||||
|
effective_color = color
|
||||||
|
if is_layered and idx < len(layers_def):
|
||||||
|
mat_name = layers_def[idx].get("material", "") or ""
|
||||||
|
elif not is_layered:
|
||||||
|
try: mat_name = _wand_solid_material(doc, anchor_meta) or ""
|
||||||
|
except Exception: mat_name = ""
|
||||||
|
target_layer = base_layer_idx
|
||||||
|
full_mat_dict = None
|
||||||
|
if mat_name and mat_name in all_mats:
|
||||||
|
full_mat_dict = all_mats[mat_name]
|
||||||
|
effective_color = full_mat_dict.get("color", effective_color)
|
||||||
|
ms = _ensure_material_sublayer(doc, geschoss_name, mat_name)
|
||||||
|
if ms >= 0: target_layer = ms
|
||||||
|
if not effective_color:
|
||||||
|
effective_color = "#9a9a9a"
|
||||||
|
|
||||||
attrs = Rhino.DocObjects.ObjectAttributes()
|
attrs = Rhino.DocObjects.ObjectAttributes()
|
||||||
attrs.LayerIndex = layer_idx
|
attrs.LayerIndex = target_layer
|
||||||
try:
|
try:
|
||||||
import System.Drawing as SD
|
import System.Drawing as SD
|
||||||
attrs.ColorSource = (
|
attrs.ColorSource = (
|
||||||
Rhino.DocObjects.ObjectColorSource.ColorFromObject)
|
Rhino.DocObjects.ObjectColorSource.ColorFromObject)
|
||||||
attrs.ObjectColor = SD.Color.FromArgb(255, 0, 0, 0)
|
attrs.ObjectColor = SD.Color.FromArgb(255, 0, 0, 0)
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
mat_idx = _ensure_material(doc, mat_color)
|
mat_idx_attr = -1
|
||||||
if mat_idx >= 0:
|
if full_mat_dict is not None:
|
||||||
attrs.MaterialIndex = mat_idx
|
mat_idx_attr = _ensure_pbr_material(doc, full_mat_dict)
|
||||||
|
if mat_idx_attr < 0 and effective_color:
|
||||||
|
mat_idx_attr = _ensure_material(doc, effective_color)
|
||||||
|
if mat_idx_attr >= 0:
|
||||||
|
attrs.MaterialIndex = mat_idx_attr
|
||||||
attrs.MaterialSource = (
|
attrs.MaterialSource = (
|
||||||
Rhino.DocObjects.ObjectMaterialSource.MaterialFromObject)
|
Rhino.DocObjects.ObjectMaterialSource.MaterialFromObject)
|
||||||
_attach_meta(attrs, anchor_id, "wand_volume",
|
_attach_meta(attrs, anchor_id, "wand_volume",
|
||||||
anchor_meta["geschoss"], anchor_meta["dicke"],
|
anchor_meta["geschoss"], anchor_meta["dicke"],
|
||||||
anchor_meta["uk_override"], anchor_meta["ok_override"],
|
anchor_meta["uk_override"], anchor_meta["ok_override"],
|
||||||
anchor_meta.get("referenz", "mid"),
|
anchor_meta.get("referenz", "mid"),
|
||||||
|
wand_layered=is_layered,
|
||||||
|
wand_layers=layers_json,
|
||||||
|
wand_layer_idx=(idx if is_layered else None),
|
||||||
wand_chain_members=cluster_list)
|
wand_chain_members=cluster_list)
|
||||||
# Cluster-Volume bewusst NICHT in Anchor-Group: sonst wuerde Click auf
|
|
||||||
# Anchor-Achse das ganze gemerged Brep mit-selektieren. Stattdessen:
|
|
||||||
# - Click auf Achse → nur die Achse
|
|
||||||
# - Click auf Volume → Select-Handler swappt auf naechste Cluster-Achse
|
|
||||||
try:
|
try:
|
||||||
doc.Objects.AddBrep(cluster_brep, attrs)
|
doc.Objects.AddBrep(lbrep, attrs)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] AddBrep cluster:", ex)
|
print("[ELEMENTE] AddBrep cluster layer {}:".format(idx), ex)
|
||||||
return False
|
return False
|
||||||
# Stale Auto-Groups + Centerlines fuer alle Cluster-Member regen
|
# Stale Auto-Groups + Centerlines fuer alle Cluster-Member regen
|
||||||
try:
|
try:
|
||||||
@@ -2918,8 +3058,9 @@ def _regen_cluster_anchor(doc, anchor_id, cluster_ids, anchor_meta):
|
|||||||
_regen_wall_lines(doc, _wid, in_cluster=True)
|
_regen_wall_lines(doc, _wid, in_cluster=True)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] strip/lines (cluster):", ex)
|
print("[ELEMENTE] strip/lines (cluster):", ex)
|
||||||
print("[ELEMENTE] cluster-union anchor={} members={} cutouts={}".format(
|
print("[ELEMENTE] cluster-union anchor={} members={} layers={} cutouts={}".format(
|
||||||
anchor_id, len(cluster_list), len(cutouts)))
|
anchor_id, len(cluster_list),
|
||||||
|
len(layer_results) if is_layered else 1, len(cutouts)))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -8084,8 +8225,7 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
|||||||
cluster_ids = _find_wall_cluster(doc, element_id)
|
cluster_ids = _find_wall_cluster(doc, element_id)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] cluster detect:", ex)
|
print("[ELEMENTE] cluster detect:", ex)
|
||||||
is_layered_meta = bool(meta.get("wand_layered", False))
|
if (len(cluster_ids) > 1
|
||||||
if (len(cluster_ids) > 1 and not is_layered_meta
|
|
||||||
and not _is_linear_chain(doc, cluster_ids)):
|
and not _is_linear_chain(doc, cluster_ids)):
|
||||||
anchor = sorted(cluster_ids)[0]
|
anchor = sorted(cluster_ids)[0]
|
||||||
if anchor != element_id:
|
if anchor != element_id:
|
||||||
@@ -15285,6 +15425,18 @@ def _on_object_deleted(sender, e):
|
|||||||
try:
|
try:
|
||||||
import time
|
import time
|
||||||
vol_ids = [v.Id for v in _find_all_volumes(doc, meta["id"])]
|
vol_ids = [v.Id for v in _find_all_volumes(doc, meta["id"])]
|
||||||
|
# Bei wand_axis-Delete auch wand_centerline + wand_outline
|
||||||
|
# Curves mit-cascaden — sonst bleiben sie als Orphan-Linien
|
||||||
|
# nach dem Loeschen einer Wand stehen.
|
||||||
|
if meta.get("type") == "wand_axis":
|
||||||
|
for _o in doc.Objects:
|
||||||
|
try:
|
||||||
|
_t = _o.Attributes.GetUserString(_KEY_TYPE) or ""
|
||||||
|
_wid = _o.Attributes.GetUserString(_KEY_ID) or ""
|
||||||
|
if (_t in ("wand_centerline", "wand_outline")
|
||||||
|
and _wid == meta["id"]):
|
||||||
|
vol_ids.append(_o.Id)
|
||||||
|
except Exception: pass
|
||||||
if vol_ids:
|
if vol_ids:
|
||||||
pending = sc.sticky.get("_elemente_pending_source_cascade")
|
pending = sc.sticky.get("_elemente_pending_source_cascade")
|
||||||
if not isinstance(pending, dict):
|
if not isinstance(pending, dict):
|
||||||
|
|||||||
Reference in New Issue
Block a user