From 9d1660560cd2a4592b8935e299daf79022da0505 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Fri, 31 Jul 2026 10:08:38 +0200 Subject: [PATCH] Rock Generator 2.9.0: Vertex-Farb-Layout v2 (kanal-getrennt) Nach Wunschliste des Users. Attribut "Cavity" traegt jetzt drei UNABHAENGIGE Masken statt einer eingebackenen Mischung: R = Cavity pur 1 = offene Flaeche, 0 = tiefe Fuge - OHNE Hoehen-Verlauf G = Moos/Schmutz aus Oberseiten-Anteil + Fugennaehe + Noise, mit Reglern fuer Menge, Aufbruch und Seed B = Hoehen-Verlauf 0 = Unterseite, 1 = oben, separat A = 1 (Reserve) Der Punkt: Boden-Saum und Moosfarbe entscheidet jetzt das MATERIAL pro Instanz, nicht mehr das Mesh. In v1 steckte der Verlauf fest in R und liess sich nicht mehr trennen. Gemessen: R ohne Hoehenabhaengigkeit (unten 0.62 / oben 0.59 - vorher war das der eingebackene Verlauf), B steigt 0.08 -> 0.92, G oben 0.57 vs unten 0.20, A konstant 1.00. Moos-Seed aendert die Verteilung. Alle drei Kanaele ueberleben den FBX-Roundtrip. tests/test_vertex_cavity.py prueft jeden Kanal einzeln inkl. Roundtrip. Co-Authored-By: Claude Opus 4.8 --- README.md | 17 +++++ blender_manifest.toml | 2 +- dist/index.json | 8 +-- dist/stylized_rock_generator-2.8.1.zip | Bin 19215 -> 0 bytes dist/stylized_rock_generator-2.9.0.zip | Bin 0 -> 19688 bytes stylized_rock_generator.py | 96 ++++++++++++++++--------- tests/test_vertex_cavity.py | 76 +++++++++++++------- 7 files changed, 136 insertions(+), 63 deletions(-) delete mode 100644 dist/stylized_rock_generator-2.8.1.zip create mode 100644 dist/stylized_rock_generator-2.9.0.zip diff --git a/README.md b/README.md index 1e771ee..992efb7 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,23 @@ Abgestimmt auf `A:\eco\EcoGame` (UE 5.8). Kurzfassung: Platten), `Fugenbreite` (klein = harte Fuge, flache Platte), `Platten flach` (1 = Flaeche nahezu einfarbig). Ein dichtes Zellmuster ueber die ganze Oberflaeche erschlaegt die Form und passt NICHT zu diesem Look. +- **Vertex-Farb-Layout v2 (kanal-getrennt), Attribut `Cavity`:** + + | Kanal | Inhalt | + |---|---| + | **R** | Cavity pur — 1 = offene Flaeche, 0 = tiefe Fuge (**ohne** Hoehen-Verlauf) | + | **G** | Moos-/Schmutzmaske — 1 = hier waechst was (Oberseite + Fugennaehe + Noise) | + | **B** | Hoehen-Verlauf — 0 = Unterseite, 1 = oben | + | **A** | 1 (Reserve) | + + Getrennt statt eingebacken: der erdige **Boden-Saum** am Stein-Fuss und die + **Moosfarbe** werden pro Material-Instanz geregelt, nicht im Mesh festgelegt. + Regler: `Cavity-Staerke`, `Weichzeichnen`, `Moos-Menge`, `Moos-Aufbruch`, + `Moos-Seed`. + Verifiziert: R ohne Hoehenabhaengigkeit (unten 0.62 / oben 0.59), B steigt + sauber (0.08 -> 0.92), G oben staerker (0.20 -> 0.57), A konstant 1, + Seed aendert die Verteilung, und alle Kanaele ueberleben den FBX-Roundtrip. + - **Texturfreie Alternative — Vertex-Cavity:** Statt zu baken laesst sich die Cavity in die **Vertex-Farben** schreiben (Attribut `Cavity`, ueberlebt den FBX-Roundtrip verlustfrei — gemessen 0.250..0.947 vor und nach dem Import). diff --git a/blender_manifest.toml b/blender_manifest.toml index f7be3bd..81c5c10 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "stylized_rock_generator" -version = "2.8.1" +version = "2.9.0" name = "Stylized Rock Generator" tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export" maintainer = "D4rkst3r" diff --git a/dist/index.json b/dist/index.json index 187393f..a9eee63 100644 --- a/dist/index.json +++ b/dist/index.json @@ -7,7 +7,7 @@ "id": "stylized_rock_generator", "name": "Stylized Rock Generator", "tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export", - "version": "2.8.1", + "version": "2.9.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -23,9 +23,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_rock_generator-2.8.1.zip", - "archive_size": 19215, - "archive_hash": "sha256:c548846fa03033b4aa2cf792ecd046118527926c5e09246c07095f77617dded1" + "archive_url": "./stylized_rock_generator-2.9.0.zip", + "archive_size": 19688, + "archive_hash": "sha256:78452434a3f175ae1a415b4d4b2abdb5cb803415499c114c0452fb0a4524959e" }, { "schema_version": "1.0.0", diff --git a/dist/stylized_rock_generator-2.8.1.zip b/dist/stylized_rock_generator-2.8.1.zip deleted file mode 100644 index 2ad8f459ef4569194a5f9770715523643a325163..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19215 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 NtAl^3vG(87zX0@P!}$OJ literal 0 HcmV?d00001 diff --git a/stylized_rock_generator.py b/stylized_rock_generator.py index fd1b5dd..2cedf4c 100644 --- a/stylized_rock_generator.py +++ b/stylized_rock_generator.py @@ -1,7 +1,7 @@ bl_info = { "name": "Stylized Rock Generator", "author": "D4rkst3r", - "version": (2, 8, 1), + "version": (2, 9, 0), "blender": (4, 2, 0), "location": "View3D > Sidebar > Rock Gen", "description": ( @@ -60,7 +60,8 @@ PRESET_KEYS = ( "shading_mode", "autosmooth_angle", "randomize_shape", "noise_scale_max", "displace_strength_max", "nonuniform_scale", "flatten_bottom", "flatten_ratio", "set_origin_bottom", "keep_size", - "bake_vertex_cavity", "cavity_strength", "cavity_blur", "cavity_gradient", + "bake_vertex_cavity", "cavity_strength", "cavity_blur", + "moss_amount", "moss_noise", "moss_seed", "use_class_size", "base_size", ) @@ -455,23 +456,27 @@ def _apply_random_transform(context, obj, settings, rng): def _bake_vertex_cavity(context, obj, settings): - """Cavity + Hoehen-Verlauf in die VERTEX-FARBEN backen. + """Vertex-Farb-Layout v2 - KANAL-GETRENNT im Attribut "Cavity". - Warum: das Material braucht damit KEINE Textur. Base Color = Farbton x - Vertex-Farbe, dazu Kantenlicht und Schattentint im Shader - genau der - Genshin-Aufbau. Ausserdem sitzen die dunklen Fugen auf den ECHTEN Kanten - des Meshes, statt per Triplanar darueberzuschweben. + R = Cavity pur 1 = offene Flaeche, 0 = tiefe Fuge (ohne Verlauf!) + G = Moos/Schmutz 1 = hier waechst was (Oberseite + Fugennaehe + Noise) + B = Hoehen-Verlauf 0 = Unterseite, 1 = oben + A = 1 (Reserve) - Aufloesung haengt an der Vertex-Dichte; fuer flaechige Platten reicht das, - fuer feine Struktur nicht (dann Textur nehmen). + Getrennt statt eingebacken, damit das Material selbst entscheidet: Boden-Saum + und Moosfarbe werden pro Material-Instanz geregelt, nicht im Mesh festgelegt. """ + import random as _rnd me = obj.data layer = me.color_attributes.get("Cavity") if layer is None: layer = me.color_attributes.new(name="Cavity", type='BYTE_COLOR', domain='CORNER') me.color_attributes.active_color = layer - me.color_attributes.render_color_index = me.color_attributes.find("Cavity") + idx = me.color_attributes.find("Cavity") + if idx >= 0: + me.color_attributes.render_color_index = idx + # --- R: Cavity ueber Blenders Dirty-Vertex-Colors --- _deselect_all(context) context.view_layer.objects.active = obj obj.select_set(True) @@ -481,36 +486,51 @@ def _bake_vertex_cavity(context, obj, settings): pass try: bpy.ops.paint.vertex_color_dirt( - blur_strength=1.0, - blur_iterations=settings.cavity_blur, - clean_angle=3.14159, - dirt_angle=0.0, - dirt_only=False, - normalize=True, + blur_strength=1.0, blur_iterations=settings.cavity_blur, + clean_angle=3.14159, dirt_angle=0.0, dirt_only=False, normalize=True, ) except RuntimeError as exc: raise RockGenError("Vertex-Cavity fehlgeschlagen: %s" % exc) - # Staerke regeln + optionaler Hoehen-Verlauf (unten dunkler, wie in der - # Referenz). Beides direkt auf den Farbwerten, das ist schnell genug. + data = layer.data + cav_raw = [data[i].color[0] for i in range(len(data))] + zs = [v.co.z for v in me.vertices] if not zs: return zmin, zmax = min(zs), max(zs) span = max(zmax - zmin, 1e-6) + + # Normalen je Loop fuer den Oberseiten-Anteil + me.calc_normals_split() if hasattr(me, "calc_normals_split") else None + rng = _rnd.Random(settings.moss_seed) + # Noise-Wert je VERTEX (nicht je Loop), damit er nicht flackert + vnoise = [rng.random() for _ in range(len(me.vertices))] + amt = settings.cavity_strength - grad = settings.cavity_gradient - data = layer.data + moss = settings.moss_amount + mnoise = settings.moss_noise + for poly in me.polygons: + nz = poly.normal.z # 1 = zeigt nach oben for li in poly.loop_indices: vi = me.loops[li].vertex_index - c = data[li].color - val = 1.0 - (1.0 - c[0]) * amt # Cavity abschwaechen - if grad > 0.0: - t = (me.vertices[vi].co.z - zmin) / span - val *= (1.0 - grad) + grad * (0.35 + 0.65 * t) - val = max(0.0, min(1.0, val)) - data[li].color = (val, val, val, 1.0) + c_raw = cav_raw[li] + + # R: reine Cavity, Staerke geregelt, KEIN Hoehen-Verlauf + r = max(0.0, min(1.0, 1.0 - (1.0 - c_raw) * amt)) + + # B: Hoehen-Verlauf 0..1 + b = max(0.0, min(1.0, (me.vertices[vi].co.z - zmin) / span)) + + # G: Moos - Oberseite UND Fugennaehe, mit Noise aufgebrochen + up = max(0.0, nz) # 0 unten, 1 oben + crev = 1.0 - c_raw # 1 in der Fuge + base = up * 0.7 + crev * 0.5 + n = 1.0 - mnoise + mnoise * vnoise[vi] + g = max(0.0, min(1.0, base * n * moss * 1.6)) + + data[li].color = (r, g, b, 1.0) def _apply_shading(context, obj, settings): @@ -807,10 +827,18 @@ class RockGenSettings(PropertyGroup): name="Weichzeichnen", default=1, min=0, max=5, description="Glaettet den Cavity-Verlauf ueber benachbarte Vertices", ) - cavity_gradient: FloatProperty( - name="Hoehen-Verlauf", default=0.35, min=0.0, max=1.0, - description=("Dunkelt den Stein nach unten hin ab (wie in der Referenz). " - "0 = aus"), + moss_amount: FloatProperty( + name="Moos-Menge", default=0.55, min=0.0, max=1.0, + description=("Wie viel der Oberflaeche die Moos-/Schmutzmaske (Kanal G) " + "markiert. Die FARBE bestimmt das Material, nicht das Mesh"), + ) + moss_noise: FloatProperty( + name="Moos-Aufbruch", default=0.55, min=0.0, max=1.0, + description="Bricht die Maske mit Rauschen auf, damit sie nicht flaechig wirkt", + ) + moss_seed: IntProperty( + name="Moos-Seed", default=0, min=0, max=9999, + description="Anderer Wert = andere Moos-Verteilung bei gleicher Form", ) # --- Material --- @@ -1658,8 +1686,12 @@ class VIEW3D_PT_rock_generator(Panel): sub = box.column(align=True) sub.enabled = settings.bake_vertex_cavity sub.prop(settings, "cavity_strength") - sub.prop(settings, "cavity_gradient") sub.prop(settings, "cavity_blur") + sub.separator() + sub.prop(settings, "moss_amount") + sub.prop(settings, "moss_noise") + sub.prop(settings, "moss_seed") + sub.label(text="R=Cavity G=Moos B=Hoehe", icon='INFO') box = layout.box() box.label(text="Material") diff --git a/tests/test_vertex_cavity.py b/tests/test_vertex_cavity.py index 91f0a94..a0e43e8 100644 --- a/tests/test_vertex_cavity.py +++ b/tests/test_vertex_cavity.py @@ -8,38 +8,62 @@ fails=[] for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True) s=bpy.context.scene.rock_gen_settings s.batch_count=1; s.subsurf_levels=4; s.use_modal=False -s.shading_mode='FLAT'; s.bake_vertex_cavity=True -s.name_prefix="Fels"; s.name_form="A"; s.name_class='M' -res=bpy.ops.object.generate_rocks_batch() -obs=[o for o in bpy.context.scene.objects if o.type=='MESH'] -ob=obs[0] +s.shading_mode='FLAT'; s.bake_vertex_cavity=True; s.set_origin_bottom=True +bpy.ops.object.generate_rocks_batch() +ob=[o for o in bpy.context.scene.objects if o.type=='MESH'][0] me=ob.data -names=[a.name for a in me.color_attributes] -print("FARB-ATTRIBUTE:", names) -if "Cavity" not in names: fails.append("kein_attribut") -else: - vals=[d.color[0] for d in me.color_attributes["Cavity"].data] - vals.sort() - print("CAVITY min=%.3f p10=%.3f median=%.3f max=%.3f (n=%d)" - % (vals[0], vals[len(vals)//10], vals[len(vals)//2], vals[-1], len(vals))) - if vals[-1]-vals[0] < 0.05: fails.append("kein_kontrast") -# Faceted? -sm=sum(1 for p in me.polygons if p.use_smooth) -print("Shading: smooth=%d / flat=%d" % (sm, len(me.polygons)-sm)) -# FBX-Roundtrip: ueberleben die Vertex-Farben? -tmp=tempfile.mkdtemp(); fp=os.path.join(tmp,"cav.fbx") +d=me.color_attributes["Cavity"].data +R=[x.color[0] for x in d]; G=[x.color[1] for x in d] +B=[x.color[2] for x in d]; A=[x.color[3] for x in d] +def rng_(v): return (min(v),max(v)) +print("R (Cavity) %.2f..%.2f | G (Moos) %.2f..%.2f | B (Hoehe) %.2f..%.2f | A %.2f..%.2f" + % (*rng_(R),*rng_(G),*rng_(B),*rng_(A))) +if max(R)-min(R)<0.05: fails.append("R_ohne_kontrast") +if max(G)-min(G)<0.05: fails.append("G_ohne_kontrast") +if max(A)!=1.0 or min(A)!=1.0: fails.append("A_nicht_1") + +# B muss mit der Hoehe korrelieren +zs=[me.vertices[me.loops[i].vertex_index].co.z for i in range(len(d))] +zmin,zmax=min(zs),max(zs) +lo=[B[i] for i in range(len(d)) if zs[i] < zmin+(zmax-zmin)*0.15] +hi=[B[i] for i in range(len(d)) if zs[i] > zmin+(zmax-zmin)*0.85] +print("B unten=%.2f oben=%.2f (muss steigen)" % (sum(lo)/len(lo), sum(hi)/len(hi))) +if sum(lo)/len(lo) >= sum(hi)/len(hi): fails.append("B_falsch_herum") + +# R darf KEINEN Hoehen-Verlauf mehr haben (das war v1) +rlo=sum(R[i] for i in range(len(d)) if zs[i]zmin+(zmax-zmin)*0.85)/max(1,len(hi)) +print("R unten=%.2f oben=%.2f (Differenz soll klein sein)" % (rlo,rhi)) +if abs(rlo-rhi) > 0.25: fails.append("R_hat_noch_verlauf") + +# G soll oben mehr sein als unten +glo=sum(G[i] for i in range(len(d)) if zs[i]zmin+(zmax-zmin)*0.85)/max(1,len(hi)) +print("G unten=%.2f oben=%.2f (oben soll mehr sein)" % (glo,ghi)) +if ghi <= glo: fails.append("G_nicht_oben") + +# Moos-Seed aendert die Verteilung? +s.moss_seed=42 +for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True) +bpy.ops.object.generate_rocks_batch() +ob2=[o for o in bpy.context.scene.objects if o.type=='MESH'][0] +G2=[x.color[1] for x in ob2.data.color_attributes["Cavity"].data] +same = len(G)==len(G2) and all(abs(a-b)<1e-4 for a,b in zip(G,G2)) +print("Moos-Seed 0 vs 42 identisch:", same) +if same: fails.append("seed_wirkungslos") + +# FBX-Roundtrip +tmp=tempfile.mkdtemp(); fp=os.path.join(tmp,"v2.fbx") for o in bpy.context.selected_objects: o.select_set(False) -ob.select_set(True); bpy.context.view_layer.objects.active=ob +ob2.select_set(True); bpy.context.view_layer.objects.active=ob2 bpy.ops.export_scene.fbx(filepath=fp, use_selection=True, colors_type='SRGB', mesh_smooth_type='FACE', use_triangles=True) for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True) bpy.ops.import_scene.fbx(filepath=fp) imp=[o for o in bpy.data.objects if o.type=='MESH'][0] -inames=[a.name for a in imp.data.color_attributes] -print("NACH FBX-ROUNDTRIP:", inames, "Tris=%d" % sum(len(p.vertices)-2 for p in imp.data.polygons)) -if not inames: fails.append("fbx_verliert_farben") -else: - iv=[d.color[0] for d in imp.data.color_attributes[0].data] - print(" Wertebereich nach Import: %.3f .. %.3f" % (min(iv), max(iv))) +id_=imp.data.color_attributes[0].data +iR=[x.color[0] for x in id_]; iG=[x.color[1] for x in id_]; iB=[x.color[2] for x in id_] +print("NACH FBX: R %.2f..%.2f G %.2f..%.2f B %.2f..%.2f" % (*rng_(iR),*rng_(iG),*rng_(iB))) +if max(iG)-min(iG)<0.05 or max(iB)-min(iB)<0.05: fails.append("fbx_kanaele_verloren") r.unregister() print("ERGEBNIS:", "ALLE CHECKS OK" if not fails else "FEHLER: "+", ".join(fails))