Material Phase B Stufe 4: PBR auf Rhino-Material applied + Auto-Regen
_ensure_pbr_material baut ein vollständiges Rhino-Material aus dem Project-Settings-dict: - ToPhysicallyBased() + BaseColor/Roughness/Metallic/Opacity/OpacityIor - Diffuse-/Bump-/Transparency-Texture via SetBitmapTexture etc. - UV-Repeat = 1/uvScaleM - Cache per Signature (Color+PBR+Texture-Pfade) _get_all_materials liefert jetzt full-dicts (nicht mehr nur color/hatch/ scale) damit Wand-Regen Zugriff auf PBR + Texturen hat. Wand-Regen: wenn voll-dict aus Project-Settings vorliegt → PBR-Material, sonst Fallback auf legacy _ensure_material(hex). Auto-Regen on Save: - PBR-Material-Cache + Legacy-Material-Cache invalidieren - Alle wand_axis im Doc regenerieren (in EINEM Undo-Record) - User aendert Material-Properties -> existierende Waende updaten sofort Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+145
-12
@@ -2208,10 +2208,19 @@ _MATERIAL_LIBRARY = {
|
||||
|
||||
def _get_all_materials(doc):
|
||||
"""Builtin _MATERIAL_LIBRARY + Projekt-Settings-Materialien gemerged.
|
||||
Returns dict[name] -> {color, hatch, scale}. Projekt-Settings (inkl.
|
||||
Library-Imports) ueberschreibt builtin bei Namensgleichheit, sodass
|
||||
der User builtin-Defaults pro Projekt anpassen kann."""
|
||||
merged = {n: dict(m) for n, m in _MATERIAL_LIBRARY.items()}
|
||||
Returns dict[name] -> full material dict (color/hatch/scale + PBR +
|
||||
textures + uvScaleM). Projekt-Settings ueberschreibt builtin bei
|
||||
Namensgleichheit. Builtin-Materialien bekommen leere PBR-Defaults."""
|
||||
merged = {}
|
||||
for n, m in _MATERIAL_LIBRARY.items():
|
||||
merged[n] = dict(m)
|
||||
# Builtin: PBR-Defaults wenn nicht gesetzt
|
||||
merged[n].setdefault("roughness", 0.7)
|
||||
merged[n].setdefault("reflection", 0.1)
|
||||
merged[n].setdefault("transparency", 0.0)
|
||||
merged[n].setdefault("iorN", 1.0)
|
||||
merged[n].setdefault("uvScaleM", 1.0)
|
||||
merged[n].setdefault("textures", {})
|
||||
try:
|
||||
import rhinopanel
|
||||
ps = rhinopanel.load_project_settings(doc) if doc else None
|
||||
@@ -2219,11 +2228,9 @@ def _get_all_materials(doc):
|
||||
for m in ps.get("materials", []):
|
||||
n = m.get("name")
|
||||
if not n: continue
|
||||
merged[n] = {
|
||||
"color": m.get("color", "#888888"),
|
||||
"hatch": m.get("hatch", "Solid"),
|
||||
"scale": float(m.get("scale", 1.0) or 1.0),
|
||||
}
|
||||
# Komplettes dict uebernehmen — _normalize_material hat
|
||||
# bereits alle Felder validiert.
|
||||
merged[n] = dict(m)
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] _get_all_materials:", ex)
|
||||
return merged
|
||||
@@ -2330,6 +2337,126 @@ def _ensure_material(doc, hex_color):
|
||||
return -1
|
||||
|
||||
|
||||
def _ensure_pbr_material(doc, mat_dict):
|
||||
"""Erstellt oder findet ein Rhino-PBR-Material aus dem Project-Settings-
|
||||
dict (color + roughness + reflection + transparency + iorN + textures +
|
||||
uvScaleM). Cached pro signature in sc.sticky. Liefert Material-Index
|
||||
oder -1.
|
||||
Setzt:
|
||||
- PhysicallyBased.BaseColor (aus color)
|
||||
- PhysicallyBased.Roughness, Opacity (= 1-transparency), OpacityIor
|
||||
- PhysicallyBased.Metallic = reflection (Naeherung)
|
||||
- SetBitmapTexture / SetBumpTexture / SetTransparencyTexture wenn
|
||||
Pfade gesetzt
|
||||
- Texture.Repeat = (1/uvScaleM, 1/uvScaleM)"""
|
||||
if not isinstance(mat_dict, dict): return -1
|
||||
color = mat_dict.get("color") or "#888888"
|
||||
if not isinstance(color, str) or not color.startswith("#") or len(color) < 7:
|
||||
return -1
|
||||
# Cache-Key: alle PBR-Felder + Texture-Pfade
|
||||
texs = mat_dict.get("textures") or {}
|
||||
def _p(slot):
|
||||
t = texs.get(slot) if isinstance(texs, dict) else None
|
||||
return (t or {}).get("path", "") if isinstance(t, dict) else ""
|
||||
sig = "|".join([
|
||||
color.lower(),
|
||||
"r{:.3f}".format(float(mat_dict.get("roughness", 0.7))),
|
||||
"x{:.3f}".format(float(mat_dict.get("reflection", 0.1))),
|
||||
"t{:.3f}".format(float(mat_dict.get("transparency", 0.0))),
|
||||
"i{:.3f}".format(float(mat_dict.get("iorN", 1.0))),
|
||||
"u{:.3f}".format(float(mat_dict.get("uvScaleM", 1.0))),
|
||||
"d=" + _p("diffuse"),
|
||||
"b=" + _p("bump"),
|
||||
"g=" + _p("roughness"),
|
||||
"p=" + _p("transparency"),
|
||||
])
|
||||
cache = sc.sticky.get("_dossier_pbr_material_cache")
|
||||
if not isinstance(cache, dict):
|
||||
cache = {}
|
||||
sc.sticky["_dossier_pbr_material_cache"] = cache
|
||||
cached = cache.get(sig)
|
||||
if cached is not None:
|
||||
try:
|
||||
if 0 <= cached < doc.Materials.Count:
|
||||
m = doc.Materials[cached]
|
||||
if m is not None and not m.IsDeleted:
|
||||
return cached
|
||||
except Exception: pass
|
||||
del cache[sig]
|
||||
# Material bauen
|
||||
try:
|
||||
import System.Drawing as SD
|
||||
h = color.lstrip("#")
|
||||
r = int(h[0:2], 16); g = int(h[2:4], 16); b = int(h[4:6], 16)
|
||||
mat = Rhino.DocObjects.Material()
|
||||
mat.Name = "Dossier_PBR_" + (mat_dict.get("name") or "Mat")[:20]
|
||||
mat.DiffuseColor = SD.Color.FromArgb(255, r, g, b)
|
||||
# PBR-Properties
|
||||
try:
|
||||
mat.ToPhysicallyBased()
|
||||
pbr = mat.PhysicallyBased
|
||||
if pbr is not None:
|
||||
# Color4f (linear) — RGB normalisieren
|
||||
try:
|
||||
from Rhino.Display import Color4f
|
||||
pbr.BaseColor = Color4f(r/255.0, g/255.0, b/255.0, 1.0)
|
||||
except Exception: pass
|
||||
try: pbr.Roughness = float(mat_dict.get("roughness", 0.7))
|
||||
except Exception: pass
|
||||
# reflection ~ metallic (Naeherung — bei nicht-Metallen
|
||||
# bleibt Roughness/IoR das Hauptmerkmal). User kann das
|
||||
# spaeter via separates Metallic-Feld feiner steuern.
|
||||
try: pbr.Metallic = float(mat_dict.get("reflection", 0.0))
|
||||
except Exception: pass
|
||||
trans = float(mat_dict.get("transparency", 0.0))
|
||||
try: pbr.Opacity = max(0.0, 1.0 - trans)
|
||||
except Exception: pass
|
||||
try: pbr.OpacityIor = float(mat_dict.get("iorN", 1.0))
|
||||
except Exception: pass
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] PBR setup:", ex)
|
||||
# Texturen + UV-Repeat
|
||||
try:
|
||||
uv = float(mat_dict.get("uvScaleM", 1.0)) or 1.0
|
||||
uv_repeat = 1.0 / uv # uvScaleM = 1m → 1x; 0.5m → 2x Repeat
|
||||
except Exception: uv_repeat = 1.0
|
||||
def _apply_tex(slot, setter_name):
|
||||
p = _p(slot)
|
||||
if not p: return
|
||||
try:
|
||||
setter = getattr(mat, setter_name, None)
|
||||
if setter is None: return
|
||||
ok = setter(p)
|
||||
if not ok: return
|
||||
# Repeat auf den frisch gesetzten Texture-Slot — alle
|
||||
# Slot-Indices durchgehen und matchen.
|
||||
try:
|
||||
import Rhino.Geometry as rgg
|
||||
for i in range(mat.GetTextures().Count if False else 100):
|
||||
try: t = mat.GetTexture(i)
|
||||
except Exception: break
|
||||
if t is None: continue
|
||||
try: t.Repeat = rgg.Vector2d(uv_repeat, uv_repeat)
|
||||
except Exception: pass
|
||||
try: mat.SetTexture(t, t.TextureType)
|
||||
except Exception: pass
|
||||
except Exception: pass
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] apply tex {}:".format(slot), ex)
|
||||
_apply_tex("diffuse", "SetBitmapTexture")
|
||||
_apply_tex("bump", "SetBumpTexture")
|
||||
_apply_tex("transparency", "SetTransparencyTexture")
|
||||
# Roughness-Texture: kein direkter Setter im Legacy-Material —
|
||||
# PhysicallyBased haette eine, ueberspringen wir hier (Phase B+)
|
||||
idx = doc.Materials.Add(mat)
|
||||
if idx >= 0:
|
||||
cache[sig] = idx
|
||||
return idx
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] _ensure_pbr_material:", ex)
|
||||
return -1
|
||||
|
||||
|
||||
def _make_wand_layer_breps(axis_curve, layers, dicke, referenz, uk, ok,
|
||||
miter_start=None, miter_end=None):
|
||||
"""Baut eine Liste (brep, color_hex, name) pro Schicht. Schicht-Reihen-
|
||||
@@ -5451,8 +5578,10 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
effective_color = color
|
||||
target_layer = layer
|
||||
all_mats = _get_all_materials(doc)
|
||||
full_mat_dict = None # PBR + Texturen wenn aus Project-Settings
|
||||
if mat_name and mat_name in all_mats:
|
||||
effective_color = all_mats[mat_name]["color"]
|
||||
full_mat_dict = all_mats[mat_name]
|
||||
effective_color = full_mat_dict.get("color", color)
|
||||
target_layer = _ensure_material_sublayer(doc, geschoss_name,
|
||||
mat_name)
|
||||
if target_layer < 0: target_layer = layer
|
||||
@@ -5467,8 +5596,12 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
Rhino.DocObjects.ObjectColorSource.ColorFromObject)
|
||||
attrs.ObjectColor = SD.Color.FromArgb(255, 0, 0, 0)
|
||||
except Exception: pass
|
||||
# Faces via Material (DiffuseColor) — getrennt vom ObjectColor.
|
||||
if effective_color:
|
||||
# Faces via Material — wenn voll-dict mit PBR vorhanden, PBR-
|
||||
# Material mit Texturen bauen, sonst einfaches Diffuse-Material.
|
||||
mat_idx = -1
|
||||
if full_mat_dict is not None:
|
||||
mat_idx = _ensure_pbr_material(doc, full_mat_dict)
|
||||
if mat_idx < 0 and effective_color:
|
||||
mat_idx = _ensure_material(doc, effective_color)
|
||||
if mat_idx >= 0:
|
||||
attrs.MaterialIndex = mat_idx
|
||||
|
||||
@@ -706,12 +706,49 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
}
|
||||
save_project_settings(doc2, new_settings)
|
||||
_broadcast_state(doc2)
|
||||
# Material-Cache invalidieren (PBR-Cache hashed Color+Texturen+
|
||||
# PBR-Werte — wenn der User ein Material aendert, muss der
|
||||
# Cache leer sein, sonst kriegen Waende stale Material-Indizes).
|
||||
try:
|
||||
import scriptcontext as sc
|
||||
sc.sticky["_dossier_pbr_material_cache"] = {}
|
||||
sc.sticky["_dossier_material_cache"] = {}
|
||||
except Exception: pass
|
||||
try:
|
||||
import scriptcontext as sc
|
||||
eb = sc.sticky.get("elemente_bridge")
|
||||
if eb is not None: eb._send_state()
|
||||
except Exception as ex:
|
||||
print("[PROJECT-SETTINGS] elemente refresh:", ex)
|
||||
# Alle wand_axis im Doc regenen damit Material-Aenderungen
|
||||
# (PBR/Texturen/Hatch) auf existing Waende durchschlagen.
|
||||
try:
|
||||
import elemente
|
||||
undo_serial = doc2.BeginUndoRecord("Material-Update Regen")
|
||||
prev_redraw = doc2.Views.RedrawEnabled
|
||||
doc2.Views.RedrawEnabled = False
|
||||
wall_ids = []
|
||||
for obj in doc2.Objects:
|
||||
m = elemente._read_meta(obj)
|
||||
if m and m.get("type") == "wand_axis":
|
||||
wall_ids.append(m["id"])
|
||||
# Chain-Anchor regent automatisch alle members — wir koennen
|
||||
# trotzdem alle einzeln triggern, _REGEN_BUSY-Guard verhindert
|
||||
# Doppel-Arbeit. Einfacher als Anchor-Election hier.
|
||||
try:
|
||||
for wid in wall_ids:
|
||||
try: elemente._regenerate_element(doc2, wid)
|
||||
except Exception as ex:
|
||||
print("[PROJECT-SETTINGS] regen", wid, ex)
|
||||
finally:
|
||||
doc2.Views.RedrawEnabled = prev_redraw
|
||||
try: doc2.EndUndoRecord(undo_serial)
|
||||
except Exception: pass
|
||||
try: doc2.Views.Redraw()
|
||||
except Exception: pass
|
||||
print("[PROJECT-SETTINGS] {} Waende regenert".format(len(wall_ids)))
|
||||
except Exception as ex:
|
||||
print("[PROJECT-SETTINGS] wall regen:", ex)
|
||||
panel_base.open_satellite_window(
|
||||
"project_settings",
|
||||
params=params,
|
||||
|
||||
Reference in New Issue
Block a user