Neu: Stylized Asset Utils 1.0.0 (viertes Addon)
Fuer handgebaute Assets, die nicht durch einen Generator laufen (Pilze, Baumstumpf, Erzfels, Props). Panel in fuenf einklappbaren Unter-Panels statt einer langen Liste: 1 Vorbereiten - Rotation/Scale anwenden, Ursprung unten-mittig, Normalen 2 Vertex-Farben - R=Cavity / G=Teil-oder-Moos-Maske / B=Hoehe / A=1 3 Benennung - <Basis>_<NN>, Mesh-Daten ziehen mit 4 Pruefen - Asset-Check auf alles, was in UE Aerger macht 5 Export - FBX je Mesh mit colors_type='SRGB' Zwei Bugs beim Bauen gefunden und gefixt: - v.link_faces gibt es nur in bmesh, nicht auf Mesh.vertices -> lose Vertices ueber die tatsaechlich benutzten Indizes zaehlen. - bpy.ops.paint.vertex_color_set braucht den Vertex-Paint-Modus und scheitert im Object-Mode mit "poll() failed". Zusammen mit vertex_color_dirt im selben try wurde dadurch der GANZE Bake stillschweigend uebersprungen (R/G/B blieben 1.0). Initialisierung laeuft jetzt direkt ueber Python. Verifiziert am nachgebauten Pilz: Check meldet vorher Scale/Rotation/Ursprung, nach "Vorbereiten" Scale 1/1/1, Rot 0/0/0, min_z 0.0000 und "ok". Teil-Maske G=1 im Hut, 0 im Stiel. Nach FBX-Roundtrip G-Werte [0.0, 1.0] erhalten. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import bpy, sys, importlib.util, os, tempfile, math
|
||||
spec=importlib.util.spec_from_file_location("sau", r"A:\eco\stylized_rock_generator\stylized_asset_utils.py")
|
||||
u=importlib.util.module_from_spec(spec); sys.modules["sau"]=u; spec.loader.exec_module(u)
|
||||
try: u.unregister()
|
||||
except Exception: pass
|
||||
u.register()
|
||||
fails=[]
|
||||
for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True)
|
||||
|
||||
# Pilz wie beim User: Hut + Stiel getrennt, dann Join. Mit ABSICHT unangewendeter
|
||||
# Transform und Ursprung nicht unten - das soll der Check finden.
|
||||
bpy.ops.mesh.primitive_cylinder_add(vertices=10, radius=0.028, depth=0.126, location=(0,0,0.5))
|
||||
st=bpy.context.active_object
|
||||
bpy.ops.mesh.primitive_uv_sphere_add(segments=14, ring_count=7, radius=0.042, location=(0,0,0.567))
|
||||
hu=bpy.context.active_object; hu.scale=(1,1,0.5); bpy.ops.object.transform_apply(scale=True)
|
||||
for o in bpy.context.selected_objects: o.select_set(False)
|
||||
st.select_set(True); hu.select_set(True); bpy.context.view_layer.objects.active=st
|
||||
bpy.ops.object.join()
|
||||
p=bpy.context.active_object; p.name="Pilz_roh"
|
||||
p.rotation_euler=(0.16,0.11,-2.26); p.scale=(0.6,0.6,0.6)
|
||||
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.05, location=(1,0,0)) # UV vorhanden
|
||||
p.select_set(True); bpy.context.view_layer.objects.active=p
|
||||
for o in bpy.context.selected_objects:
|
||||
if o is not p: o.select_set(False)
|
||||
|
||||
s=bpy.context.scene.asset_utils_settings
|
||||
s.tri_budget=400; s.g_mode='SHELLS'
|
||||
|
||||
print("--- Check VOR dem Vorbereiten (soll meckern) ---")
|
||||
bpy.ops.assetutils.check()
|
||||
print("Scale vorher: %s Rot: %s" % ([round(v,2) for v in p.scale],
|
||||
[round(math.degrees(v),1) for v in p.rotation_euler]))
|
||||
|
||||
bpy.ops.assetutils.prepare()
|
||||
zs=[(p.matrix_world @ v.co).z - p.location.z for v in p.data.vertices]
|
||||
print("NACH prepare: Scale %s Rot %s min_z %.4f"
|
||||
% ([round(v,3) for v in p.scale],
|
||||
[round(math.degrees(v),1) for v in p.rotation_euler], min(zs)))
|
||||
if any(abs(v-1)>1e-4 for v in p.scale): fails.append("scale_nicht_angewendet")
|
||||
if any(abs(v)>1e-4 for v in p.rotation_euler): fails.append("rot_nicht_angewendet")
|
||||
if abs(min(zs))>0.01: fails.append("ursprung_nicht_unten")
|
||||
|
||||
bpy.ops.assetutils.bake_vcol()
|
||||
ca=p.data.color_attributes.get("Cavity")
|
||||
if ca is None: fails.append("keine_vcol")
|
||||
else:
|
||||
shells=u._mesh_shells(p.data)
|
||||
infos=[]
|
||||
for sh in shells:
|
||||
vs={vi for pi in sh for vi in p.data.polygons[pi].vertices}
|
||||
infos.append((sh, max(p.data.vertices[v].co.z for v in vs)))
|
||||
infos.sort(key=lambda t:-t[1])
|
||||
g_top={round(ca.data[li].color[1],3) for pi in infos[0][0] for li in p.data.polygons[pi].loop_indices}
|
||||
g_rest={round(ca.data[li].color[1],3) for sh,_ in infos[1:] for pi in sh for li in p.data.polygons[pi].loop_indices}
|
||||
R=[d.color[0] for d in ca.data]; B=[d.color[2] for d in ca.data]; A=[d.color[3] for d in ca.data]
|
||||
print("VCol: G_Hut=%s G_Stiel=%s | R %.2f..%.2f | B %.2f..%.2f | A %.2f"
|
||||
% (sorted(g_top), sorted(g_rest), min(R),max(R),min(B),max(B),min(A)))
|
||||
if g_top!={1.0}: fails.append("G_hut")
|
||||
if g_rest!={0.0}: fails.append("G_stiel")
|
||||
if max(B)-min(B)<0.8: fails.append("B_flach")
|
||||
if min(A)<0.99: fails.append("A")
|
||||
|
||||
s.rename_base="Pilz_01"; s.rename_numbered=False
|
||||
bpy.ops.assetutils.rename()
|
||||
print("Umbenannt zu: %s (Mesh: %s)" % (p.name, p.data.name))
|
||||
if p.name!="Pilz_01": fails.append("rename")
|
||||
|
||||
print("--- Check NACH dem Vorbereiten (soll sauber sein) ---")
|
||||
bpy.ops.assetutils.check()
|
||||
|
||||
out=tempfile.mkdtemp(); s.export_dir=out
|
||||
bpy.ops.assetutils.export()
|
||||
fbx=[f for f in os.listdir(out) if f.endswith(".fbx")]
|
||||
print("Export:", fbx)
|
||||
if not fbx: fails.append("export")
|
||||
else:
|
||||
for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True)
|
||||
bpy.ops.import_scene.fbx(filepath=os.path.join(out,fbx[0]))
|
||||
imp=[o for o in bpy.data.objects if o.type=='MESH'][0]
|
||||
ic=[a.name for a in imp.data.color_attributes]
|
||||
ig={round(d.color[1],2) for d in imp.data.color_attributes[0].data} if ic else set()
|
||||
print("Nach FBX: VCol=%s G-Werte=%s" % (ic, sorted(ig)))
|
||||
if not ic: fails.append("fbx_vcol_verloren")
|
||||
u.unregister()
|
||||
print("ERGEBNIS:", "ALLE CHECKS OK" if not fails else "FEHLER: "+", ".join(fails))
|
||||
Reference in New Issue
Block a user