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,545 @@
|
||||
bl_info = {
|
||||
"name": "Stylized Asset Utils",
|
||||
"author": "D4rkst3r",
|
||||
"version": (1, 0, 0),
|
||||
"blender": (4, 2, 0),
|
||||
"location": "View3D > Sidebar > Asset Utils",
|
||||
"description": (
|
||||
"Werkzeuge fuer HANDGEBAUTE Assets: Transform/Ursprung, Vertex-Farben, "
|
||||
"Benennung, Asset-Check und FBX-Export nach EcoGame-Konvention."
|
||||
),
|
||||
"category": "Object",
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Wofuer dieses Addon da ist
|
||||
#
|
||||
# Rock-, Tree- und Grass-Generator erzeugen Assets nach Konvention. Was von HAND
|
||||
# gebaut wird (Pilze, Baumstumpf, Erzfels, Props) laeuft daran vorbei. Dieses
|
||||
# Addon bringt beliebige Meshes auf denselben Stand:
|
||||
# - Transform anwenden + Ursprung unten-mittig
|
||||
# - Vertex-Farben R=Cavity / G=Teil-oder-Moos / B=Hoehe / A=1
|
||||
# - Benennung nach Schema
|
||||
# - Asset-Check: was wuerde in UE Aerger machen?
|
||||
# - FBX-Export mit den richtigen Schaltern (Vertex-Farben!)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
import bpy
|
||||
import os
|
||||
import math
|
||||
import random
|
||||
|
||||
from bpy.props import (
|
||||
IntProperty, FloatProperty, EnumProperty, BoolProperty,
|
||||
StringProperty, PointerProperty,
|
||||
)
|
||||
from bpy.types import Operator, Panel, PropertyGroup
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helfer
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _deselect_all(context):
|
||||
for o in list(context.view_layer.objects):
|
||||
try:
|
||||
o.select_set(False)
|
||||
except (ReferenceError, AttributeError, RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _ensure_object_mode(context):
|
||||
if context.mode != 'OBJECT':
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
|
||||
def _activate(context, ob):
|
||||
_deselect_all(context)
|
||||
context.view_layer.objects.active = ob
|
||||
ob.select_set(True)
|
||||
|
||||
|
||||
def _tris(me):
|
||||
return sum(len(p.vertices) - 2 for p in me.polygons)
|
||||
|
||||
|
||||
def _mesh_shells(me):
|
||||
"""Zusammenhaengende Teile als Mengen von Polygon-Indizes.
|
||||
|
||||
Assets, die aus mehreren Objekten mit Strg+J verbunden wurden (Pilz =
|
||||
Hut + Stiel), bleiben geometrisch getrennt - so laesst sich die Teil-Maske
|
||||
EXAKT bilden statt sie ueber Normalen zu schaetzen.
|
||||
"""
|
||||
adj = {}
|
||||
for poly in me.polygons:
|
||||
for ek in poly.edge_keys:
|
||||
adj.setdefault(ek, []).append(poly.index)
|
||||
seen, shells = set(), []
|
||||
for poly in me.polygons:
|
||||
if poly.index in seen:
|
||||
continue
|
||||
stack, comp = [poly.index], set()
|
||||
while stack:
|
||||
pi = stack.pop()
|
||||
if pi in comp:
|
||||
continue
|
||||
comp.add(pi)
|
||||
for ek in me.polygons[pi].edge_keys:
|
||||
for nb in adj.get(ek, ()):
|
||||
if nb not in comp:
|
||||
stack.append(nb)
|
||||
seen |= comp
|
||||
shells.append(comp)
|
||||
return shells
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Operatoren: Vorbereiten
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class ASSETUTILS_OT_prepare(Operator):
|
||||
bl_idname = "assetutils.prepare"
|
||||
bl_label = "Transform anwenden + Ursprung unten"
|
||||
bl_description = ("Wendet Rotation und Skalierung an und setzt den Ursprung "
|
||||
"unten-mittig - so steht das Asset in UE bei Scale 1 auf dem Boden")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
targets = [o for o in context.selected_objects if o.type == 'MESH']
|
||||
if not targets:
|
||||
self.report({'ERROR'}, "Kein Mesh ausgewaehlt.")
|
||||
return {'CANCELLED'}
|
||||
_ensure_object_mode(context)
|
||||
scene = context.scene
|
||||
prev_cursor = scene.cursor.location.copy()
|
||||
for ob in targets:
|
||||
_activate(context, ob)
|
||||
if s.apply_transforms:
|
||||
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
||||
if s.origin_bottom:
|
||||
mw = ob.matrix_world
|
||||
co = [mw @ v.co for v in ob.data.vertices]
|
||||
if co:
|
||||
cx = sum(c.x for c in co) / len(co)
|
||||
cy = sum(c.y for c in co) / len(co)
|
||||
zmin = min(c.z for c in co)
|
||||
scene.cursor.location = (cx, cy, zmin)
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
|
||||
if s.recalc_normals:
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.normals_make_consistent(inside=False)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
scene.cursor.location = prev_cursor
|
||||
self.report({'INFO'}, "%d Asset(s) vorbereitet." % len(targets))
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Operatoren: Vertex-Farben
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class ASSETUTILS_OT_bake_vcol(Operator):
|
||||
bl_idname = "assetutils.bake_vcol"
|
||||
bl_label = "Vertex-Farben backen"
|
||||
bl_description = ("Backt R=Cavity, G=Teil-/Moos-Maske, B=Hoehe, A=1 in das "
|
||||
"Farbattribut 'Cavity'")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
targets = [o for o in context.selected_objects if o.type == 'MESH']
|
||||
if not targets:
|
||||
self.report({'ERROR'}, "Kein Mesh ausgewaehlt.")
|
||||
return {'CANCELLED'}
|
||||
_ensure_object_mode(context)
|
||||
notes = []
|
||||
for ob in targets:
|
||||
me = ob.data
|
||||
if not me.polygons:
|
||||
continue
|
||||
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
|
||||
idx = me.color_attributes.find("Cavity")
|
||||
if idx >= 0:
|
||||
me.color_attributes.render_color_index = idx
|
||||
|
||||
_activate(context, ob)
|
||||
# FALLE: bpy.ops.paint.vertex_color_set braucht den VERTEX-PAINT-Modus
|
||||
# und schlaegt im Object-Mode mit "poll() failed" fehl. Steht es mit
|
||||
# vertex_color_dirt im selben try-Block, wird der ganze Bake
|
||||
# uebersprungen. Deshalb hier direkt in Python auf Weiss setzen -
|
||||
# das geht immer und ist schneller.
|
||||
for i in range(len(layer.data)):
|
||||
layer.data[i].color = (1.0, 1.0, 1.0, 1.0)
|
||||
try:
|
||||
bpy.ops.paint.vertex_color_dirt(
|
||||
blur_strength=1.0, blur_iterations=s.cavity_blur,
|
||||
clean_angle=math.pi, dirt_angle=0.0,
|
||||
dirt_only=False, normalize=True,
|
||||
)
|
||||
except RuntimeError as exc:
|
||||
self.report({'WARNING'}, "%s: Cavity uebersprungen (%s)" % (ob.name, exc))
|
||||
|
||||
data = layer.data
|
||||
cav_raw = [data[i].color[0] for i in range(len(data))]
|
||||
zs = [v.co.z for v in me.vertices]
|
||||
zmin, zmax = min(zs), max(zs)
|
||||
span = max(zmax - zmin, 1e-6)
|
||||
|
||||
part = {}
|
||||
if s.g_mode == 'SHELLS':
|
||||
shells = _mesh_shells(me)
|
||||
if len(shells) >= 2:
|
||||
infos = []
|
||||
for sh in shells:
|
||||
vs = {vi for pi in sh for vi in me.polygons[pi].vertices}
|
||||
zt = max(me.vertices[v].co.z for v in vs)
|
||||
xs = [me.vertices[v].co.x for v in vs]
|
||||
ys = [me.vertices[v].co.y for v in vs]
|
||||
infos.append((sh, zt, max(max(xs) - min(xs), max(ys) - min(ys))))
|
||||
top = max(infos, key=lambda t: (t[1], t[2]))
|
||||
for sh, _, _ in infos:
|
||||
for pi in sh:
|
||||
part[pi] = 1.0 if sh is top[0] else 0.0
|
||||
notes.append("%s: %d Teile" % (ob.name, len(shells)))
|
||||
else:
|
||||
# Nur ein Teil -> nichts markieren, statt etwas zu behaupten
|
||||
part = {p.index: 0.0 for p in me.polygons}
|
||||
notes.append("%s: 1 Teil (G=0)" % ob.name)
|
||||
|
||||
rng = random.Random(s.moss_seed)
|
||||
vnoise = [rng.random() for _ in range(len(me.vertices))]
|
||||
|
||||
for poly in me.polygons:
|
||||
nz = poly.normal.z
|
||||
for li in poly.loop_indices:
|
||||
vi = me.loops[li].vertex_index
|
||||
c_raw = cav_raw[li]
|
||||
r = max(0.0, min(1.0, 1.0 - (1.0 - c_raw) * s.cavity_strength))
|
||||
b = max(0.0, min(1.0, (me.vertices[vi].co.z - zmin) / span))
|
||||
if s.g_mode == 'SHELLS':
|
||||
g = part.get(poly.index, 0.0)
|
||||
else:
|
||||
up = max(0.0, nz)
|
||||
crev = 1.0 - c_raw
|
||||
n = 1.0 - s.moss_noise + s.moss_noise * vnoise[vi]
|
||||
g = max(0.0, min(1.0, (up * 0.7 + crev * 0.5) * n * s.moss_amount * 1.6))
|
||||
data[li].color = (r, g, b, 1.0)
|
||||
|
||||
msg = "Vertex-Farben auf %d Mesh(es)." % len(targets)
|
||||
if notes:
|
||||
msg += " " + ", ".join(notes[:4])
|
||||
self.report({'INFO'}, msg)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Operatoren: Benennung
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class ASSETUTILS_OT_rename(Operator):
|
||||
bl_idname = "assetutils.rename"
|
||||
bl_label = "Umbenennen"
|
||||
bl_description = "Benennt die Auswahl nach <Basis>_<NN> und zieht die Mesh-Daten mit"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
base = s.rename_base.strip()
|
||||
if not base:
|
||||
self.report({'ERROR'}, "Bitte einen Basisnamen eingeben.")
|
||||
return {'CANCELLED'}
|
||||
targets = sorted([o for o in context.selected_objects if o.type == 'MESH'],
|
||||
key=lambda o: o.name)
|
||||
if not targets:
|
||||
self.report({'ERROR'}, "Kein Mesh ausgewaehlt.")
|
||||
return {'CANCELLED'}
|
||||
for i, ob in enumerate(targets):
|
||||
name = base if (len(targets) == 1 and not s.rename_numbered) \
|
||||
else "%s_%02d" % (base, s.rename_start + i)
|
||||
ob.name = name
|
||||
ob.data.name = name
|
||||
self.report({'INFO'}, "%d Objekt(e) umbenannt." % len(targets))
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Operatoren: Asset-Check
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class ASSETUTILS_OT_check(Operator):
|
||||
bl_idname = "assetutils.check"
|
||||
bl_label = "Asset-Check"
|
||||
bl_description = ("Prueft die Auswahl auf die Dinge, die in UE Aerger machen: "
|
||||
"Transform, Ursprung, Tri-Budget, UV, Vertex-Farben, Normalen")
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
targets = [o for o in context.selected_objects if o.type == 'MESH']
|
||||
if not targets:
|
||||
self.report({'ERROR'}, "Kein Mesh ausgewaehlt.")
|
||||
return {'CANCELLED'}
|
||||
problems = 0
|
||||
print("\n=== Asset-Check ===")
|
||||
for ob in targets:
|
||||
me = ob.data
|
||||
issues = []
|
||||
if any(abs(v - 1.0) > 1e-4 for v in ob.scale):
|
||||
issues.append("Scale nicht angewendet %s"
|
||||
% [round(v, 3) for v in ob.scale])
|
||||
if any(abs(v) > 1e-4 for v in ob.rotation_euler):
|
||||
issues.append("Rotation nicht angewendet")
|
||||
zs = [(ob.matrix_world @ v.co).z - ob.location.z for v in me.vertices]
|
||||
if zs and abs(min(zs)) > s.origin_tol:
|
||||
issues.append("Ursprung nicht unten (min_z %.3f)" % min(zs))
|
||||
t = _tris(me)
|
||||
if s.tri_budget and t > s.tri_budget:
|
||||
issues.append("%d Tris > Budget %d" % (t, s.tri_budget))
|
||||
if not me.uv_layers:
|
||||
issues.append("kein UV")
|
||||
if not me.color_attributes:
|
||||
issues.append("keine Vertex-Farben")
|
||||
# link_faces gibt es nur in bmesh - auf einem Mesh ueber die
|
||||
# tatsaechlich benutzten Vertex-Indizes gehen.
|
||||
used = {vi for poly in me.polygons for vi in poly.vertices}
|
||||
n_loose = len(me.vertices) - len(used)
|
||||
if n_loose:
|
||||
issues.append("%d lose Vertices" % n_loose)
|
||||
if me.has_custom_normals:
|
||||
issues.append("Custom Split Normals (UE ggf. 'Import Normals' noetig)")
|
||||
shells = len(_mesh_shells(me))
|
||||
info = "%-24s %5d Tris %d Teil(e)" % (ob.name, t, shells)
|
||||
if issues:
|
||||
problems += 1
|
||||
print(" %s\n -> %s" % (info, "; ".join(issues)))
|
||||
else:
|
||||
print(" %s ok" % info)
|
||||
print("=== %d von %d Asset(s) mit Hinweisen ===\n" % (problems, len(targets)))
|
||||
if problems:
|
||||
self.report({'WARNING'},
|
||||
"%d von %d Asset(s) mit Hinweisen - Details in der System-Konsole."
|
||||
% (problems, len(targets)))
|
||||
else:
|
||||
self.report({'INFO'}, "Alle %d Asset(s) in Ordnung." % len(targets))
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Operatoren: Export
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class ASSETUTILS_OT_export(Operator):
|
||||
bl_idname = "assetutils.export"
|
||||
bl_label = "FBX exportieren"
|
||||
bl_description = "Exportiert jedes ausgewaehlte Mesh als eigene FBX (EcoGame-Settings)"
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
folder = bpy.path.abspath(s.export_dir) if s.export_dir else ""
|
||||
if not folder or not os.path.isdir(folder):
|
||||
self.report({'ERROR'}, "Bitte einen gueltigen Export-Ordner waehlen.")
|
||||
return {'CANCELLED'}
|
||||
targets = [o for o in context.selected_objects if o.type == 'MESH']
|
||||
if not targets:
|
||||
self.report({'ERROR'}, "Kein Mesh ausgewaehlt.")
|
||||
return {'CANCELLED'}
|
||||
_ensure_object_mode(context)
|
||||
done = 0
|
||||
for ob in targets:
|
||||
_activate(context, ob)
|
||||
fp = os.path.join(folder, ob.name + ".fbx")
|
||||
try:
|
||||
bpy.ops.export_scene.fbx(
|
||||
filepath=fp, use_selection=True, object_types={'MESH'},
|
||||
use_mesh_modifiers=True,
|
||||
mesh_smooth_type='FACE' if s.smooth_type == 'FACE' else 'EDGE',
|
||||
use_triangles=True,
|
||||
colors_type='SRGB', # Vertex-Farben mitnehmen
|
||||
add_leaf_bones=False, bake_anim=False, path_mode='AUTO',
|
||||
)
|
||||
done += 1
|
||||
except RuntimeError as exc:
|
||||
self.report({'WARNING'}, "%s: %s" % (ob.name, exc))
|
||||
self.report({'INFO'}, "%d FBX nach %s" % (done, folder))
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Properties
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class AssetUtilsSettings(PropertyGroup):
|
||||
apply_transforms: BoolProperty(name="Rotation + Scale anwenden", default=True)
|
||||
origin_bottom: BoolProperty(name="Ursprung unten-mittig", default=True)
|
||||
recalc_normals: BoolProperty(name="Normalen neu berechnen", default=False)
|
||||
|
||||
g_mode: EnumProperty(
|
||||
name="G-Kanal",
|
||||
items=[
|
||||
('SHELLS', "Teil-Maske", "Oberster getrennter Teil = 1 (z.B. Pilzhut)"),
|
||||
('MOSS', "Moos/Schmutz", "Oberseite + Ritzen + Rauschen"),
|
||||
],
|
||||
default='SHELLS',
|
||||
)
|
||||
cavity_strength: FloatProperty(name="Cavity-Staerke", default=0.7, min=0.0, max=1.0)
|
||||
cavity_blur: IntProperty(name="Weichzeichnen", default=1, min=0, max=5)
|
||||
moss_amount: FloatProperty(name="Moos-Menge", default=0.55, min=0.0, max=1.0)
|
||||
moss_noise: FloatProperty(name="Moos-Aufbruch", default=0.55, min=0.0, max=1.0)
|
||||
moss_seed: IntProperty(name="Moos-Seed", default=0, min=0, max=9999)
|
||||
|
||||
rename_base: StringProperty(name="Basisname", default="")
|
||||
rename_start: IntProperty(name="Startnummer", default=1, min=0, max=99)
|
||||
rename_numbered: BoolProperty(name="Immer nummerieren", default=False)
|
||||
|
||||
tri_budget: IntProperty(
|
||||
name="Tri-Budget", default=2500, min=0, max=100000,
|
||||
description="0 = nicht pruefen. ASSETS.md: Gras 400, Busch 800, Baum 2500",
|
||||
)
|
||||
origin_tol: FloatProperty(name="Ursprung-Toleranz (m)", default=0.01,
|
||||
min=0.0, max=0.5)
|
||||
|
||||
export_dir: StringProperty(name="Export-Ordner", default="", subtype='DIR_PATH')
|
||||
smooth_type: EnumProperty(
|
||||
name="Smoothing",
|
||||
items=[('FACE', "Faceted", "Harte Facetten"),
|
||||
('EDGE', "Edge", "Weiche Kanten mit Sharp-Edges")],
|
||||
default='FACE',
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Panels (Haupt-Panel + einklappbare Unter-Panels)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class VIEW3D_PT_asset_utils(Panel):
|
||||
bl_label = "Asset Utils"
|
||||
bl_idname = "VIEW3D_PT_asset_utils"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Asset Utils"
|
||||
|
||||
def draw(self, context):
|
||||
n = len([o for o in context.selected_objects if o.type == 'MESH'])
|
||||
self.layout.label(text="%d Mesh(es) ausgewaehlt" % n,
|
||||
icon='OUTLINER_OB_MESH' if n else 'INFO')
|
||||
|
||||
|
||||
class _Sub(Panel):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Asset Utils"
|
||||
bl_parent_id = "VIEW3D_PT_asset_utils"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
|
||||
class VIEW3D_PT_au_prepare(_Sub):
|
||||
bl_label = "1 - Vorbereiten"
|
||||
bl_options = set() # offen lassen
|
||||
|
||||
def draw(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
col = self.layout.column(align=True)
|
||||
col.prop(s, "apply_transforms")
|
||||
col.prop(s, "origin_bottom")
|
||||
col.prop(s, "recalc_normals")
|
||||
self.layout.operator("assetutils.prepare", icon='OBJECT_ORIGIN')
|
||||
|
||||
|
||||
class VIEW3D_PT_au_vcol(_Sub):
|
||||
bl_label = "2 - Vertex-Farben"
|
||||
bl_options = set()
|
||||
|
||||
def draw(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
lay = self.layout
|
||||
lay.prop(s, "g_mode", expand=True)
|
||||
col = lay.column(align=True)
|
||||
col.prop(s, "cavity_strength")
|
||||
col.prop(s, "cavity_blur")
|
||||
if s.g_mode == 'MOSS':
|
||||
col.separator()
|
||||
col.prop(s, "moss_amount")
|
||||
col.prop(s, "moss_noise")
|
||||
col.prop(s, "moss_seed")
|
||||
lay.label(text="R=Cavity G=%s B=Hoehe"
|
||||
% ("Teil" if s.g_mode == 'SHELLS' else "Moos"), icon='INFO')
|
||||
lay.operator("assetutils.bake_vcol", icon='BRUSH_DATA')
|
||||
|
||||
|
||||
class VIEW3D_PT_au_name(_Sub):
|
||||
bl_label = "3 - Benennung"
|
||||
|
||||
def draw(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
col = self.layout.column(align=True)
|
||||
col.prop(s, "rename_base")
|
||||
row = col.row(align=True)
|
||||
row.prop(s, "rename_start")
|
||||
row.prop(s, "rename_numbered", text="", icon='LINENUMBERS_ON')
|
||||
self.layout.operator("assetutils.rename", icon='SORTALPHA')
|
||||
|
||||
|
||||
class VIEW3D_PT_au_check(_Sub):
|
||||
bl_label = "4 - Pruefen"
|
||||
bl_options = set()
|
||||
|
||||
def draw(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
col = self.layout.column(align=True)
|
||||
col.prop(s, "tri_budget")
|
||||
col.prop(s, "origin_tol")
|
||||
self.layout.operator("assetutils.check", icon='CHECKMARK')
|
||||
|
||||
|
||||
class VIEW3D_PT_au_export(_Sub):
|
||||
bl_label = "5 - Export"
|
||||
|
||||
def draw(self, context):
|
||||
s = context.scene.asset_utils_settings
|
||||
col = self.layout.column(align=True)
|
||||
col.prop(s, "export_dir")
|
||||
col.prop(s, "smooth_type", expand=True)
|
||||
self.layout.operator("assetutils.export", icon='EXPORT')
|
||||
self.layout.label(text="UE: Vertex Color = Replace", icon='INFO')
|
||||
|
||||
|
||||
classes = (
|
||||
AssetUtilsSettings,
|
||||
ASSETUTILS_OT_prepare,
|
||||
ASSETUTILS_OT_bake_vcol,
|
||||
ASSETUTILS_OT_rename,
|
||||
ASSETUTILS_OT_check,
|
||||
ASSETUTILS_OT_export,
|
||||
VIEW3D_PT_asset_utils,
|
||||
VIEW3D_PT_au_prepare,
|
||||
VIEW3D_PT_au_vcol,
|
||||
VIEW3D_PT_au_name,
|
||||
VIEW3D_PT_au_check,
|
||||
VIEW3D_PT_au_export,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
bpy.types.Scene.asset_utils_settings = PointerProperty(type=AssetUtilsSettings)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.asset_utils_settings
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
Reference in New Issue
Block a user