Files
stylized-rock-generator/tests/test_grass.py
T
D4rkst3randClaude Opus 4.8 c7154ca690 Neu: Stylized Grass Generator 1.0.0 (drittes Addon im Repo)
Gras-, Blumen- und Farn-Bueschel. Kern ist die WIND-UV nach Vorgabe des Users:
UV0.V = 0 an der Wurzel, 1 an der Spitze - genau die Maske, die UEs
"SimpleGrassWind" als WindWeight braucht.

Am Zielmaterial geprueft (M_Grass.uasset): es nutzt SimpleGrassWind +
WorldPositionOffset + TextureCoordinate, aber KEINE VertexColor. Die Windmaske
kommt deshalb primaer ueber die UV - die Assets laufen damit ohne
Materialaenderung.

Vertex-Farben zusaetzlich, nicht als einzige Quelle:
  R = Kopie der Windmaske (Fallback)
  G = Variation je HALM - Ebene unter dem Biom-Tint, den die Engine bereits ueber
      PerInstanceCustomData 0/1 pro Instanz setzt; im Material multiplizieren
  B = AO (unten dunkel)

Presets gras / gras_hoch / steppengras / farn / blume, Zufalls-Button,
Tri-Budget-Warnung (400 laut ASSETS.md).

Gemessen: UV.V Wurzel 0.000 -> Spitze 1.000, 112 Tris bei 14 Halmen, G hat
exakt 14 verschiedene Werte bei 14 Halmen (also wirklich pro Halm, nicht pro
Vertex), min_z = 0.0000, UV und VCol ueberleben den FBX-Roundtrip.

build.ps1 baut jetzt drei Extensions - dieselbe URL liefert alle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-31 10:46:01 +02:00

67 lines
3.2 KiB
Python

import bpy, sys, importlib.util, os, tempfile
spec=importlib.util.spec_from_file_location("sgg", r"A:\eco\stylized_rock_generator\stylized_grass_generator.py")
g=importlib.util.module_from_spec(spec); sys.modules["sgg"]=g; spec.loader.exec_module(g)
try: g.unregister()
except Exception: pass
g.register()
fails=[]
for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True)
s=bpy.context.scene.grass_gen_settings
s.preset='gras'; bpy.ops.grassgen.apply_preset()
s.art_name="Gras"; s.variant=1; s.batch_count=1
bpy.ops.object.grassgen_create()
ob=[o for o in bpy.context.scene.objects if o.type=='MESH'][0]
me=ob.data
tris=sum(len(p.vertices)-2 for p in me.polygons)
print("OBJEKT %s Tris=%d (Budget %d)" % (ob.name, tris, s.tri_budget))
if tris > s.tri_budget: fails.append("budget")
# --- Kernpruefung: UV.V muss mit der HOEHE korrelieren ---
uv=me.uv_layers[0].data
zs=[]; vs=[]
for poly in me.polygons:
for li in poly.loop_indices:
zs.append(me.vertices[me.loops[li].vertex_index].co.z)
vs.append(uv[li].uv[1])
zmin,zmax=min(zs),max(zs)
wurzel=[vs[i] for i in range(len(vs)) if zs[i] < zmin + (zmax-zmin)*0.05]
spitze=[vs[i] for i in range(len(vs)) if zs[i] > zmin + (zmax-zmin)*0.95]
print("UV.V an der WURZEL: %.3f an der SPITZE: %.3f (Bereich %.2f..%.2f)"
% (sum(wurzel)/len(wurzel), sum(spitze)/len(spitze), min(vs), max(vs)))
if sum(wurzel)/len(wurzel) > 0.12: fails.append("V_wurzel_nicht_0")
if sum(spitze)/len(spitze) < 0.80: fails.append("V_spitze_nicht_1")
if min(vs) < -0.001 or max(vs) > 1.001: fails.append("V_ausserhalb_0_1")
# U ueber die Breite
us=[uv[li].uv[0] for poly in me.polygons for li in poly.loop_indices]
print("UV.U Bereich: %.2f..%.2f" % (min(us),max(us)))
# --- Vertex-Farben als Zusatzebene ---
ca=me.color_attributes[0] if me.color_attributes else None
if ca is None: fails.append("keine_vcol")
else:
R=[d.color[0] for d in ca.data]; G=[d.color[1] for d in ca.data]; B=[d.color[2] for d in ca.data]
print("VCol R %.2f..%.2f | G %.2f..%.2f | B %.2f..%.2f" % (min(R),max(R),min(G),max(G),min(B),max(B)))
if max(G)-min(G) < 0.2: fails.append("G_keine_halmvariation")
# G muss PRO HALM konstant sein -> Anzahl unterschiedlicher Werte ~ Halmzahl
uniq=len({round(x,4) for x in G})
print("G unterschiedliche Werte: %d (Halme: %d)" % (uniq, s.blades))
if uniq > s.blades*1.5: fails.append("G_nicht_pro_halm")
# Ursprung unten
print("min_z = %.4f (soll ~0)" % zmin)
if abs(zmin) > 0.01: fails.append("ursprung")
# FBX-Roundtrip
tmp=tempfile.mkdtemp(); fp=os.path.join(tmp,"g.fbx")
ob.select_set(True); bpy.context.view_layer.objects.active=ob
bpy.ops.export_scene.fbx(filepath=fp, use_selection=True, colors_type='SRGB', 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]
iuv=[d.uv[1] for d in imp.data.uv_layers[0].data]
ica=[a.name for a in imp.data.color_attributes]
print("NACH FBX: UV.V %.2f..%.2f VCol=%s" % (min(iuv),max(iuv), ica))
if max(iuv) < 0.9: fails.append("fbx_uv_verloren")
g.unregister()
print("ERGEBNIS:", "ALLE CHECKS OK" if not fails else "FEHLER: "+", ".join(fails))