diff --git a/launcher/latest.json b/launcher/latest.json index 778b231..498034c 100644 --- a/launcher/latest.json +++ b/launcher/latest.json @@ -1,10 +1,10 @@ { "version": "0.6.3", - "notes": "Silent Plugin Auto-Load via _-RunPythonScript + korrigierter Shebang #! python 3 — kein ScriptEditor mehr", - "pub_date": "2026-05-17T14:26:39Z", + "notes": "Dossier 0.6.3", + "pub_date": "2026-05-19T10:11:20Z", "platforms": { "darwin-aarch64": { - "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVRNFYzbUN3TE44QnNuNmhvcmJZcTdFbklSYllMcmQvYTI0NDhVRFlPWGN0YXFmSWxXSW9XSE1zbmJTWEpRdTZSc0xlcFg1VVZ5bGUvaEhSTGlhL2NjNi96NzVQZ0R0bWc4PQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc5MDI3OTk5CWZpbGU6RG9zc2llci5hcHAudGFyLmd6CjkzQXZKMDlBbndqeTlHa2hBY2NwUGJNckJXZXNCbWhNRkZ2bW9VeDlSZ0JiK1lVRzBVTHdqK0V5T0NXNFlBMWdJRXRHRStKWnVtNG1WcWx2T1pkMUNnPT0K", + "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVRNFYzbUN3TE44QnR0RlRXY3c2MGloMTY4L0hja3ZaOGVYbHc4VHY5MHU3V0NqQ2o5aTlyWFJwaWc0RHV4OXNYRGsrZWN4eGdnSzhpTENQRy95NzZONGFiV2cwNWVpdnd3PQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc5MTg1NDgwCWZpbGU6RG9zc2llci5hcHAudGFyLmd6CmRKZlBWWTZMSUI5L1VmUmh4QnZoUkxXTU5zQ0pMWUpyUk4yQUZ2SGtzaUF2WTl2ZU8xM21saElqT1ppeldxTmpGUUhGZkJtN0t3RFIyakwvMFByK0F3PT0K", "url": "https://git.kgva.ch/karim/DOSSIER/releases/download/0.6.3/Dossier.app.tar.gz" } } diff --git a/launcher/src/styles.css b/launcher/src/styles.css index 34f28e9..42ca540 100644 --- a/launcher/src/styles.css +++ b/launcher/src/styles.css @@ -1,9 +1,9 @@ :root { - /* Paper-Theme — warm-weisser Hintergrund, schwarze Schrift, Petrol-Akzent */ - --bg: #f4f0e6; /* warmes Papier */ + /* Light-Theme — neutrales Weiss, schwarze Schrift, Petrol-Akzent */ + --bg: #ffffff; /* pures Weiss */ --surface: #ffffff; /* Cards */ - --surface2: #faf6ec; /* leichter Hover / zweite Ebene */ - --dark: #ebe5d4; /* "vertieft" — Inputs, Code, dropdowns */ + --surface2: #f5f5f5; /* leichter Hover / zweite Ebene */ + --dark: #f0f0f0; /* "vertieft" — Inputs, Code, dropdowns */ --accent: #5fa896; --accent-soft: #4a8a7c; @@ -11,11 +11,11 @@ --text: #1a1a1a; /* fast schwarz, Lesefluss */ --text2: #404040; /* sekundaer */ - --text3: #6e6a62; /* tertiaer / meta */ - --text4: #a09a8e; /* Hint / Placeholder */ + --text3: #6e6e6e; /* tertiaer / meta */ + --text4: #a0a0a0; /* Hint / Placeholder */ - --border: #d8d0bc; /* warme Papier-Linie */ - --border2: #b9b09a; /* staerker fuer Hover */ + --border: #e2e2e2; /* neutrale, kuehle Linie */ + --border2: #c8c8c8; /* staerker fuer Hover */ --danger: #b54e30; @@ -24,10 +24,10 @@ --font-mono: 'DM Mono', 'Menlo', monospace; --font-ui: 'DM Mono', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif; - /* Sanftere Schatten auf hellem Grund — warm-braeunlicher Ton statt schwarz */ - --sh-sm: 0 1px 3px rgba(70,55,30,0.08), 0 1px 2px rgba(70,55,30,0.04); - --sh-md: 0 6px 16px rgba(70,55,30,0.10), 0 2px 4px rgba(70,55,30,0.06); - --sh-lg: 0 16px 40px rgba(70,55,30,0.14), 0 4px 12px rgba(70,55,30,0.08); + /* Neutrale, sanfte Schatten — kein warmer Stich mehr */ + --sh-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04); + --sh-md: 0 6px 16px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.05); + --sh-lg: 0 16px 40px rgba(0,0,0,0.12), 0 4px 12px rgba(0,0,0,0.06); } * { box-sizing: border-box; } @@ -129,10 +129,7 @@ input::placeholder { color: var(--text4); } display: flex; flex-direction: column; height: 100vh; - /* Subtiler Petrol-Schein oben — Papier mit leichtem Sonnen-Touch */ - background: - radial-gradient(ellipse at top, rgba(95,168,150,0.08) 0%, transparent 55%), - var(--bg); + background: var(--bg); } /* --- Topbar ----------------------------------------------------------- */ diff --git a/rhino/elemente.py b/rhino/elemente.py index 30c547c..8ad7d5e 100644 --- a/rhino/elemente.py +++ b/rhino/elemente.py @@ -503,13 +503,19 @@ def _make_circle_preview(center): return handler -def _make_oeffnung_preview(axis_curve, breite, hoehe, brueest, base_z): - """Preview fuer Fenster/Tuer-Platzierung. Zeigt das vertikale Rechteck - der Oeffnungsflaeche auf Hoehe der Achse (entlang Tangente x Hoehe), - zusaetzlich eine Marker-Linie auf der Achse die die Breite andeutet.""" +def _make_oeffnung_preview(axis_curve, wall_dicke, breite, hoehe, brueest, base_z, typ): + """Preview fuer Fenster/Tuer-Platzierung. Zeigt: + - Voller 3D-Quader des Oeffnungs-Cutouts (mit Wand-Dicke) + - Glas-Diagonalen auf Vorder- und Hinterflaeche + - Brueestungs-Markierung (gepunktet) bei Fenster mit Brueest > 0 + - Achs-Marker (Strich auf der Wand-Achse) + - Mass-Label oberhalb des Sturzes (B x H, ggf. Brueest) + Aktualisiert sich live wenn User Optionen aendert.""" import System.Drawing as SD - color = SD.Color.FromArgb(255, 95, 200, 180) # Accent gruen - color_axis = SD.Color.FromArgb(180, 95, 200, 180) # halbtransparent + color_main = SD.Color.FromArgb(255, 95, 200, 180) # Accent gruen, voll + color_soft = SD.Color.FromArgb(160, 95, 200, 180) # halbtransparent + color_dotted = SD.Color.FromArgb(210, 95, 200, 180) # Brueest-/Achs-Marker + hd = wall_dicke * 0.5 def handler(sender, e): try: cur = e.CurrentPoint @@ -520,25 +526,70 @@ def _make_oeffnung_preview(axis_curve, breite, hoehe, brueest, base_z): tlen = (tan.X * tan.X + tan.Y * tan.Y) ** 0.5 if tlen < 1e-9: return tx = tan.X / tlen; ty = tan.Y / tlen + # Normale zur Tangente in XY (90deg gegen Uhrzeigersinn) + nx, ny = -ty, tx hb = breite * 0.5 z_bot = base_z + brueest z_top = z_bot + hoehe cx, cy = on_axis.X, on_axis.Y - # Breiten-Marker auf der Achse - left_xy = rg.Point3d(cx - hb * tx, cy - hb * ty, on_axis.Z) - right_xy = rg.Point3d(cx + hb * tx, cy + hb * ty, on_axis.Z) - e.Display.DrawLine(left_xy, right_xy, color_axis, 1) - # 4 Kanten der vertikalen Oeffnungs-Flaeche - p_lb = rg.Point3d(cx - hb * tx, cy - hb * ty, z_bot) - p_rb = rg.Point3d(cx + hb * tx, cy + hb * ty, z_bot) - p_rt = rg.Point3d(cx + hb * tx, cy + hb * ty, z_top) - p_lt = rg.Point3d(cx - hb * tx, cy - hb * ty, z_top) - for a, b in ((p_lb, p_rb), (p_rb, p_rt), - (p_rt, p_lt), (p_lt, p_lb)): - e.Display.DrawLine(a, b, color, 2) - # Diagonalen — Andeutung der Glasflaeche - e.Display.DrawLine(p_lb, p_rt, color_axis, 1) - e.Display.DrawLine(p_lt, p_rb, color_axis, 1) + + # 8 Ecken des Quaders. side_t = ±1 (links/rechts entlang Tangente), + # side_n = ±1 (vorne/hinten entlang Normale) + def pt(side_t, side_n, z): + return rg.Point3d( + cx + side_t * hb * tx + side_n * hd * nx, + cy + side_t * hb * ty + side_n * hd * ny, + z) + + lbf = pt(-1, +1, z_bot); rbf = pt(+1, +1, z_bot) + rtf = pt(+1, +1, z_top); ltf = pt(-1, +1, z_top) + lbb = pt(-1, -1, z_bot); rbb = pt(+1, -1, z_bot) + rtb = pt(+1, -1, z_top); ltb = pt(-1, -1, z_top) + + # 12 Quader-Kanten + for a, b in ( + (lbf, rbf), (rbf, rtf), (rtf, ltf), (ltf, lbf), # vorne + (lbb, rbb), (rbb, rtb), (rtb, ltb), (ltb, lbb), # hinten + (lbf, lbb), (rbf, rbb), (rtf, rtb), (ltf, ltb), # Tiefen-Kanten + ): + e.Display.DrawLine(a, b, color_main, 2) + + # Glas-Diagonalen (vorne + hinten) + e.Display.DrawLine(lbf, rtf, color_soft, 1) + e.Display.DrawLine(ltf, rbf, color_soft, 1) + e.Display.DrawLine(lbb, rtb, color_soft, 1) + e.Display.DrawLine(ltb, rbb, color_soft, 1) + + # Achs-Marker (durchgestrichen wo das Loch sitzt) + ax_l = rg.Point3d(cx - hb * tx, cy - hb * ty, on_axis.Z) + ax_r = rg.Point3d(cx + hb * tx, cy + hb * ty, on_axis.Z) + try: e.Display.DrawDottedLine(ax_l, ax_r, color_dotted) + except Exception: e.Display.DrawLine(ax_l, ax_r, color_dotted, 1) + + # Brueestungs-Linie (gepunktet, nur Fenster mit Brueest > 0) + if typ == "fenster" and brueest > 1e-4: + # quer ueber die Vorderflaeche + bL = rg.Point3d(cx - hb * tx + hd * nx, cy - hb * ty + hd * ny, z_bot) + bR = rg.Point3d(cx + hb * tx + hd * nx, cy + hb * ty + hd * ny, z_bot) + try: e.Display.DrawDottedLine(bL, bR, color_dotted) + except Exception: pass + + # Mass-Label ueberm Sturz + try: + if typ == "fenster": + label = "{:.2f} x {:.2f} Br {:.2f}".format(breite, hoehe, brueest) + else: + label = "{:.2f} x {:.2f}".format(breite, hoehe) + anchor = rg.Point3d(cx, cy, z_top + 0.12) + # Plane: X = Tangente (horizontal an Wand), Y = vertikal + text_plane = rg.Plane(anchor, + rg.Vector3d(tx, ty, 0), + rg.Vector3d(0, 0, 1)) + t3d = Rhino.Display.Text3d(label, text_plane, 0.10) + try: t3d.HorizontalAlignment = Rhino.DocObjects.TextHorizontalAlignment.Center + except Exception: pass + e.Display.Draw3dText(t3d, color_main) + except Exception: pass except Exception: pass return handler @@ -5578,10 +5629,16 @@ class ElementeBridge(panel_base.BaseBridge): gp.SetCommandPrompt(prompt) try: gp.Constrain(axis_curve, False) except Exception: pass - # Live-Preview: gruenes Oeffnungs-Rechteck auf der Wand + # Live-Preview: gruener Oeffnungs-Quader mit Glas-Diagonalen, + # Brueest-Marker und Mass-Label oberhalb des Sturzes + try: + wall_dicke = float(wall_meta.get("dicke", 0.15)) + except Exception: + wall_dicke = 0.15 try: gp.DynamicDraw += _make_oeffnung_preview( - axis_curve, breite, hoehe, brueest, preview_base_z) + axis_curve, wall_dicke, breite, hoehe, brueest, + preview_base_z, typ) except Exception: pass opt_b = gp.AddOption("Breite") opt_h = gp.AddOption("Hoehe") diff --git a/rhino/layer_builder.py b/rhino/layer_builder.py index fa71bf2..8db8385 100644 --- a/rhino/layer_builder.py +++ b/rhino/layer_builder.py @@ -99,117 +99,192 @@ def _find_linetype_index(doc, name): return -1 +def _try_set(obj, prop_names, value): + """Versucht den Wert auf das erste vorhandene Property zu setzen. + Liefert den Property-Namen bei Erfolg, sonst None.""" + if isinstance(prop_names, str): + prop_names = (prop_names,) + for prop in prop_names: + if hasattr(obj, prop): + try: + setattr(obj, prop, value) + return prop + except Exception as ex: + # Property da, aber Wert nicht akzeptiert (z.B. enum-conversion) + # — anderen Namen probieren statt aufgeben + continue + return None + + +def _enum_int(*candidates): + """Liefert den ersten Enum-Wert aus den Kandidaten der existiert. + candidates = list of (module-path-list, value-name). Bei keiner Match + liefert None.""" + for path, val_name in candidates: + try: + obj = Rhino + for p in path: + obj = getattr(obj, p) + return getattr(obj, val_name) + except Exception: + continue + return None + + def _apply_section_style(doc, layer, section_cfg, layer_color): """Setzt einen Custom-SectionStyle auf den Layer aus dem Dossier-section-dict. Nutzt Rhino-8's Python-3-API (Rhino.DocObjects.SectionStyle + Layer.SetCustomSectionStyle / RemoveCustomSectionStyle). In IPy 2.7 - sind diese Methoden nicht exponiert — dort no-op (mit Print-Warnung). + sind diese Methoden nicht exponiert — dort no-op. + + Wichtig: viele Farb-/Linetype-Properties greifen nur wenn der + zugehoerige "*Source"-Wert auf ColorFromObject / LinetypeFromObject + steht. Das setzen wir explizit. """ if not section_cfg or not isinstance(section_cfg, dict): return has_setter = hasattr(layer, "SetCustomSectionStyle") has_remover = hasattr(layer, "RemoveCustomSectionStyle") if not has_setter: - # IPy-2.7-Pfad: API nicht da, leise raus. - return + return # IPy-2.7 — keine API try: SS = Rhino.DocObjects.SectionStyle except Exception as ex: print("[EBENEN] SectionStyle-Klasse nicht da:", ex); return - # Wenn alles "leer/Default": Custom-Style abschalten pat = (section_cfg.get("hatchPattern") or "None").strip() - show = section_cfg.get("boundaryShow", True) - if pat == "None" and not show and has_remover: - try: layer.RemoveCustomSectionStyle() - except Exception: pass + show = bool(section_cfg.get("boundaryShow", True)) + diag = "[SS:{}]".format(layer.Name if layer else "?") + + # Wenn weder Hatch noch Boundary → Custom-Style entfernen + if pat == "None" and not show: + if has_remover: + try: + layer.RemoveCustomSectionStyle() + print(diag, "removed (kein Hatch + kein Boundary)") + except Exception as ex: + print(diag, "remove fehlgeschlagen:", ex) return style = SS() + + # Property-Inventar einmal beim ersten Aufruf loggen — hilft bei + # API-Verschiebungen zwischen Rhino-Versionen sofort den Mismatch zu sehen. + if not getattr(_apply_section_style, "_props_logged", False): + props = [n for n in dir(style) + if not n.startswith("_") and not callable(getattr(style, n, None))] + print("[SS] verfuegbare Properties:", ", ".join(sorted(props))) + _apply_section_style._props_logged = True + # --- Hatch --- if pat and pat != "None": hp_idx = _find_hatch_pattern_index(doc, pat) if hp_idx >= 0: - # Property-Name probieren — Rhino-8 hat HatchIndex - for prop in ("HatchIndex", "HatchPatternIndex"): - if hasattr(style, prop): - try: setattr(style, prop, hp_idx); break - except Exception: pass - # Hatch-Scale - for prop in ("HatchScale", "HatchPatternScale"): - if hasattr(style, prop): - try: setattr(style, prop, float(section_cfg.get("hatchScale") or 1.0)); break - except Exception: pass - # Hatch-Rotation (Rhino erwartet Radians — wir bekommen Grad) + set_to = _try_set(style, ("HatchIndex", "HatchPatternIndex"), hp_idx) + print(diag, "HatchIndex={} via {}".format(hp_idx, set_to)) + else: + print(diag, "Hatch '{}' nicht in HatchPatterns gefunden".format(pat)) + + scale_v = float(section_cfg.get("hatchScale") or 1.0) + _try_set(style, ("HatchScale", "HatchPatternScale"), scale_v) + import math rot_deg = float(section_cfg.get("hatchRotation") or 0) - for prop in ("HatchRotation", "HatchAngle"): - if hasattr(style, prop): - try: - setattr(style, prop, math.radians(rot_deg)) - break - except Exception: pass - # Hatch-Color (null = ByObject = nicht setzen) + _try_set(style, ("HatchRotation", "HatchAngle"), math.radians(rot_deg)) + + # Hatch-Color: explizit ColorFromObject setzen damit der eigene Wert greift hatch_color = section_cfg.get("hatchColor") if hatch_color: - for prop in ("HatchColor", "FillColor"): - if hasattr(style, prop): - try: setattr(style, prop, _color(hatch_color)); break - except Exception: pass - # Background + col = _color(hatch_color) + set_color = _try_set(style, ("HatchColor", "FillColor"), col) + # Source auf "FromObject" — sonst nutzt Rhino den Layer-Color + src_from_object = _enum_int( + (("DocObjects", "ObjectColorSource"), "ColorFromObject")) + if src_from_object is not None: + _try_set(style, ("HatchColorSource", "FillColorSource"), src_from_object) + print(diag, "HatchColor via {}".format(set_color)) + + # Background (viewport=0/transparent vs object=1) bg = section_cfg.get("background") if bg in ("object", "byObject"): - for prop in ("BackgroundColorUsage", "FillBackground"): - if hasattr(style, prop): - # Enum-Werte sind versioniert; wir versuchen via int - try: setattr(style, prop, 1); break - except Exception: pass + # Versuche Enum-Konstanten zu finden + for prop_names, en_paths in ( + (("BackgroundFillMode", "BackgroundColorUsage", "FillBackground"), + (("DocObjects", "SectionBackgroundFillMode"), "SolidColor")), + ): + en_val = _enum_int(en_paths) + if en_val is not None and _try_set(style, prop_names, en_val): + break + # Fallback: bool/int = 1 + else: + _try_set(style, ("FillBackground",), True) # --- Boundary --- - if hasattr(style, "BoundaryVisible"): - try: style.BoundaryVisible = bool(show) - except Exception: pass - elif hasattr(style, "ShowBoundary"): - try: style.ShowBoundary = bool(show) - except Exception: pass + set_show = _try_set(style, ("BoundaryVisible", "ShowBoundary"), show) + print(diag, "BoundaryVisible={} via {}".format(show, set_show)) if show: - # Boundary color + # Boundary-Color: setze Color + Source auf FromObject bc = section_cfg.get("boundaryColor") if bc: - for prop in ("BoundaryColor", "OutlineColor", "EdgeColor"): - if hasattr(style, prop): - try: setattr(style, prop, _color(bc)); break - except Exception: pass - # Boundary width scale + col = _color(bc) + set_to = _try_set(style, + ("BoundaryColor", "OutlineColor", "EdgeColor"), col) + src_from_object = _enum_int( + (("DocObjects", "ObjectColorSource"), "ColorFromObject")) + if src_from_object is not None: + _try_set(style, + ("BoundaryColorSource", "OutlineColorSource", + "EdgeColorSource"), + src_from_object) + print(diag, "BoundaryColor={} via {}".format(bc, set_to)) + + # Width-Scale auf PlotWeight uebertragen (RW8 hat keine WidthScale direkt; + # alternative Property-Namen probieren) ws = float(section_cfg.get("boundaryWidthScale") or 1.0) - for prop in ("BoundaryWidthScale", "EdgeWidthScale", "OutlineWidthScale"): - if hasattr(style, prop): - try: setattr(style, prop, ws); break - except Exception: pass - # Linetype + set_to = _try_set(style, + ("BoundaryWidthScale", "EdgeWidthScale", "OutlineWidthScale", + "PlotWeightScale"), ws) + if not set_to: + # Direkte PlotWeight setzen wenn Layer-PlotWeight bekannt + try: + base_lw = float(getattr(layer, "PlotWeight", 0.25) or 0.25) + except Exception: + base_lw = 0.25 + _try_set(style, ("BoundaryPlotWeight", "PlotWeight"), base_lw * ws) + + # Linetype: Index + Source auf LinetypeFromObject lt = section_cfg.get("boundaryLinetype") if lt and lt not in ("byLayer", "ByLayer"): lt_idx = _find_linetype_index(doc, lt) - for prop in ("BoundaryLinetypeIndex", "EdgeLinetypeIndex"): - if hasattr(style, prop): - try: setattr(style, prop, lt_idx); break - except Exception: pass + set_to = _try_set(style, + ("BoundaryLinetypeIndex", "EdgeLinetypeIndex"), lt_idx) + lt_src = _enum_int( + (("DocObjects", "ObjectLinetypeSource"), "LinetypeFromObject")) + if lt_src is not None: + _try_set(style, + ("BoundaryLinetypeSource", "EdgeLinetypeSource"), + lt_src) + print(diag, "BoundaryLinetype={} idx={} via {}".format(lt, lt_idx, set_to)) - # Section open objects + # SectionOpenObjects: bei nicht-geschlossener Geometrie auch schneiden soo = bool(section_cfg.get("sectionOpenObjects", True)) - for prop in ("SectionOpenObjects", "ClipOpenObjects"): - if hasattr(style, prop): - try: setattr(style, prop, soo); break - except Exception: pass + _try_set(style, ("SectionOpenObjects", "ClipOpenObjects", + "SectionCutsOpenObjects"), soo) - # Style auf Layer setzen + # Style auf Layer setzen + explizit Modify damit Mac-Rhino den Layer + # persistiert (sonst greift's nicht immer) try: layer.SetCustomSectionStyle(style) except Exception as ex: - print("[EBENEN] SetCustomSectionStyle({}): {}".format(layer.Name, ex)) + print(diag, "SetCustomSectionStyle FAIL:", ex) + return + try: + doc.Layers.Modify(layer, layer.LayerIndex, True) + except Exception: pass + print(diag, "OK applied") def build_layers(doc, zeichnungsebenen, ebenen):