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:
+149
-16
@@ -2208,10 +2208,19 @@ _MATERIAL_LIBRARY = {
|
|||||||
|
|
||||||
def _get_all_materials(doc):
|
def _get_all_materials(doc):
|
||||||
"""Builtin _MATERIAL_LIBRARY + Projekt-Settings-Materialien gemerged.
|
"""Builtin _MATERIAL_LIBRARY + Projekt-Settings-Materialien gemerged.
|
||||||
Returns dict[name] -> {color, hatch, scale}. Projekt-Settings (inkl.
|
Returns dict[name] -> full material dict (color/hatch/scale + PBR +
|
||||||
Library-Imports) ueberschreibt builtin bei Namensgleichheit, sodass
|
textures + uvScaleM). Projekt-Settings ueberschreibt builtin bei
|
||||||
der User builtin-Defaults pro Projekt anpassen kann."""
|
Namensgleichheit. Builtin-Materialien bekommen leere PBR-Defaults."""
|
||||||
merged = {n: dict(m) for n, m in _MATERIAL_LIBRARY.items()}
|
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:
|
try:
|
||||||
import rhinopanel
|
import rhinopanel
|
||||||
ps = rhinopanel.load_project_settings(doc) if doc else None
|
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", []):
|
for m in ps.get("materials", []):
|
||||||
n = m.get("name")
|
n = m.get("name")
|
||||||
if not n: continue
|
if not n: continue
|
||||||
merged[n] = {
|
# Komplettes dict uebernehmen — _normalize_material hat
|
||||||
"color": m.get("color", "#888888"),
|
# bereits alle Felder validiert.
|
||||||
"hatch": m.get("hatch", "Solid"),
|
merged[n] = dict(m)
|
||||||
"scale": float(m.get("scale", 1.0) or 1.0),
|
|
||||||
}
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] _get_all_materials:", ex)
|
print("[ELEMENTE] _get_all_materials:", ex)
|
||||||
return merged
|
return merged
|
||||||
@@ -2330,6 +2337,126 @@ def _ensure_material(doc, hex_color):
|
|||||||
return -1
|
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,
|
def _make_wand_layer_breps(axis_curve, layers, dicke, referenz, uk, ok,
|
||||||
miter_start=None, miter_end=None):
|
miter_start=None, miter_end=None):
|
||||||
"""Baut eine Liste (brep, color_hex, name) pro Schicht. Schicht-Reihen-
|
"""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
|
effective_color = color
|
||||||
target_layer = layer
|
target_layer = layer
|
||||||
all_mats = _get_all_materials(doc)
|
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:
|
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,
|
target_layer = _ensure_material_sublayer(doc, geschoss_name,
|
||||||
mat_name)
|
mat_name)
|
||||||
if target_layer < 0: target_layer = layer
|
if target_layer < 0: target_layer = layer
|
||||||
@@ -5467,13 +5596,17 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
|||||||
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
|
||||||
# Faces via Material (DiffuseColor) — getrennt vom ObjectColor.
|
# Faces via Material — wenn voll-dict mit PBR vorhanden, PBR-
|
||||||
if effective_color:
|
# 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)
|
mat_idx = _ensure_material(doc, effective_color)
|
||||||
if mat_idx >= 0:
|
if mat_idx >= 0:
|
||||||
attrs.MaterialIndex = mat_idx
|
attrs.MaterialIndex = mat_idx
|
||||||
attrs.MaterialSource = (
|
attrs.MaterialSource = (
|
||||||
Rhino.DocObjects.ObjectMaterialSource.MaterialFromObject)
|
Rhino.DocObjects.ObjectMaterialSource.MaterialFromObject)
|
||||||
_attach_meta(attrs, element_id, "wand_volume",
|
_attach_meta(attrs, element_id, "wand_volume",
|
||||||
meta["geschoss"], meta["dicke"],
|
meta["geschoss"], meta["dicke"],
|
||||||
meta["uk_override"], meta["ok_override"],
|
meta["uk_override"], meta["ok_override"],
|
||||||
|
|||||||
@@ -706,12 +706,49 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
}
|
}
|
||||||
save_project_settings(doc2, new_settings)
|
save_project_settings(doc2, new_settings)
|
||||||
_broadcast_state(doc2)
|
_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:
|
try:
|
||||||
import scriptcontext as sc
|
import scriptcontext as sc
|
||||||
eb = sc.sticky.get("elemente_bridge")
|
eb = sc.sticky.get("elemente_bridge")
|
||||||
if eb is not None: eb._send_state()
|
if eb is not None: eb._send_state()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[PROJECT-SETTINGS] elemente refresh:", 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(
|
panel_base.open_satellite_window(
|
||||||
"project_settings",
|
"project_settings",
|
||||||
params=params,
|
params=params,
|
||||||
|
|||||||
Reference in New Issue
Block a user