Files
D4rkst3randClaude Opus 4.8 71b9982441 Panels aufgeraeumt (alle vier Addons) + Test-Suite instandgesetzt
Panel-Umbau
-----------
Jedes Addon hatte seine Einstellungen als Kette dauerhaft offener Boxen - beim
Rock Generator 15 Stueck, man musste am ganzen Panel vorbeiscrollen, um an den
Erzeugen-Knopf zu kommen. Jetzt steht oben nur, was man bei jedem Asset anfasst
(Vorlage, Name, grosser Erzeugen-Knopf, Zufall), der Rest liegt in nummerierten,
eingeklappten Unter-Panels in der Reihenfolge des Arbeitsablaufs:

  Rock Gen    9 Unter-Panels (Benennung ... Textur-Bake)
  Tree Gen    4 (Wachstum, Blattwerk, Zusatz-Ebenen, Export)
  Grass Gen   4 (Form, Haltung, Batch, Kanal-Belegung)
  Asset Utils 5 (war schon so, dient als Vorlage)

Schalter, die ein ganzes Unter-Panel steuern (Vertex-Farben backen,
Wachstums-Stufen), sitzen jetzt als Haekchen im Panel-Kopf - Zustand sichtbar
ohne aufzuklappen.

Gemessen statt geglaubt: test_panels.py registriert alle vier Addons, laesst
jedes Panel zeichnen, prueft Tab-Zugehoerigkeit und Auf-/Zugeklappt-Absicht,
dass jeder angebotene Operator existiert - und vor allem, dass JEDE definierte
Property irgendwo erreichbar bleibt (Zweige wie base_shape='BOX' werden dabei
durchgeschaltet). Ergebnis: 0 von 122 Properties beim Umbau verloren.

Test-Suite
----------
Beim Gegenpruefen kam heraus, dass fuenf Tests still veraltet waren und eine
echte Regression nicht gemeldet haetten:

  test_rock_gen      suchte Objekte mit Praefix "Rock_" - die heissen seit dem
                     Benennungssystem "Fels_A_M_01". Fand 0 von 3.
  test_base_shapes   loeschte am Schleifenanfang ALLE Objekte, auch die eigenen
                     Kopien aus vorigen Durchlaeufen -> StructRNA removed.
  test_ecogame_tree  erwartete eine feste Namensliste ohne die spaeter
                     dazugekommenen _Leaf-Objekte.
  test_tree_addon    dito, ausserdem Zip-Version 1.22.0 fest verdrahtet.
  test_leaves        Zip-Version 1.23.0 fest verdrahtet.

Drei Muster vermieden statt nur die Symptome geflickt: neue Objekte ueber die
Differenz vorher/nachher finden statt ueber ein Namens-Praefix, Zips per glob
statt mit fester Version, Zusatz-Ebenen beim Zaehlen herausfiltern.

Neu: run_tests.ps1 faehrt die komplette Suite mit einem Befehl (-Filter fuer
einzelne). Genau das fehlte - deshalb war das Veralten nicht aufgefallen.
13 von 13 Tests bestehen.

Nebenbei
--------
build.ps1 ruft server-generate jetzt mit --html auf (browsbare index.html fuer
statisches Hosting). Nachgemessen und im README festgehalten: zwei Zips
derselben Extension-ID sind KEIN Versions-Archiv - server-generate warnt
("archive found with duplicates ... conflicting blender versions"), mehrere
Versionen einer ID gehen nur bei verschiedenen Blender-Bereichen.

Versionen: Rock 2.13.0, Tree 1.24.0, Grass 1.1.0, Asset Utils 1.1.0

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

70 lines
3.8 KiB
Python

import bpy, sys, importlib.util, mathutils
spec=importlib.util.spec_from_file_location("srg", r"A:\eco\stylized_rock_generator\stylized_rock_generator.py")
r=importlib.util.module_from_spec(spec); sys.modules["srg"]=r; spec.loader.exec_module(r)
try: r.unregister()
except Exception: pass
r.register()
fails=[]
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.set_origin_bottom=True
s.randomize_transform=False # Proportionen isoliert messen
mat=bpy.data.materials.new("P"); mat.use_nodes=True
nt=mat.node_tree; nt.nodes.clear()
o_=nt.nodes.new("ShaderNodeOutputMaterial"); b=nt.nodes.new("ShaderNodeBsdfPrincipled")
c=nt.nodes.new("ShaderNodeVertexColor"); c.layer_name="Cavity"
sep=nt.nodes.new("ShaderNodeSeparateColor"); nt.links.new(c.outputs["Color"], sep.inputs[0])
t=nt.nodes.new("ShaderNodeRGB"); t.outputs[0].default_value=(0.66,0.64,0.60,1)
mx=nt.nodes.new("ShaderNodeMixRGB"); mx.blend_type='MULTIPLY'; mx.inputs["Fac"].default_value=1.0
nt.links.new(t.outputs[0],mx.inputs["Color1"]); nt.links.new(sep.outputs[0],mx.inputs["Color2"])
nt.links.new(mx.outputs[0],b.inputs["Base Color"]); b.inputs["Roughness"].default_value=0.85
nt.links.new(b.outputs[0],o_.inputs["Surface"])
VARS=[("Wuerfel",'CUBE',None),
("Platte",'BOX',(2.0,1.5,0.35)),
("Riegel",'BOX',(2.2,0.8,0.9)),
("Ico",'ICOSPHERE',None)]
print("%-9s %-6s %s" % ("Variante","Form","Abmessungen X/Y/Z (m)"))
keep=[]; x=0.0
for name,shape,prop in VARS:
# FALLE: das hier loeschte frueher ALLE Objekte - auch die Kopien aus den
# vorigen Durchlaeufen. Die waren beim Rendern tot ("StructRNA ... removed").
kept = {k[0] for k in keep}
for o in list(bpy.data.objects):
if o not in kept: bpy.data.objects.remove(o,do_unlink=True)
s.base_shape=shape; s.box_variation=0.0
if prop: s.box_proportions=prop
s.seed=7
# Die behaltenen Kopien stehen weiter in der Szene - [0] wuerde eine davon
# greifen statt des frisch erzeugten Steins. Deshalb ueber die Differenz.
before=set(bpy.context.scene.objects)
bpy.ops.object.generate_rocks_batch()
ob=[o for o in bpy.context.scene.objects
if o.type=='MESH' and o not in before][0]
bb=ob.bound_box
d=[max(v[i] for v in bb)-min(v[i] for v in bb) for i in range(3)]
print("%-9s %-6s %.2f / %.2f / %.2f" % (name,shape,d[0],d[1],d[2]))
if name=="Platte" and not (d[2] < d[0]*0.45): fails.append("platte_nicht_flach")
if name=="Riegel" and not (d[0] > d[1]*1.5): fails.append("riegel_nicht_laenglich")
cp=ob.copy(); cp.data=ob.data.copy()
bpy.context.collection.objects.link(cp) # sofort verlinken, nicht spaeter
keep.append((cp,x))
x+=2.4
for cp,px in keep:
cp.location=(px,0,0)
cp.data.materials.clear(); cp.data.materials.append(mat)
for o in list(bpy.context.scene.objects):
if o.type=='MESH' and o not in [k[0] for k in keep]: bpy.data.objects.remove(o,do_unlink=True)
cam_d=bpy.data.cameras.new("C"); cam=bpy.data.objects.new("C",cam_d)
bpy.context.scene.collection.objects.link(cam); bpy.context.scene.camera=cam
cam.location=(3.6,-9.0,2.6)
d=mathutils.Vector((3.6,0,0.5))-cam.location
cam.rotation_euler=d.to_track_quat('-Z','Y').to_euler()
sun=bpy.data.objects.new("S", bpy.data.lights.new("S",'SUN'))
bpy.context.scene.collection.objects.link(sun); sun.rotation_euler=(0.9,0.2,0.7); sun.data.energy=3.5
sc=bpy.context.scene; sc.render.engine='CYCLES'; sc.cycles.samples=18
sc.render.resolution_x=1250; sc.render.resolution_y=470
sc.render.filepath=r"C:\Users\az319\AppData\Local\Temp\claude\A--eco-EcoGame--claude-worktrees-rock-generator-hardening-3d5f61\cf805d69-059c-4821-ac92-afae28022987\scratchpad\formen.png"
bpy.ops.render.render(write_still=True)
r.unregister()
print("ERGEBNIS:", "ALLE CHECKS OK" if not fails else "FEHLER: "+", ".join(fails))