Rock Generator 2.10.0: Quader als Grundform (Platten und Riegel)
Auf Wunsch des Users neben Wuerfel und Ico-Sphere eine Quader-Grundform mit frei einstellbaren Proportionen (X/Y/Z) plus Streuung je Stein. Damit entstehen flache Platten und langgestreckte Riegel - passt besonders zum Plattenlook der Genshin-Referenz, wo Felsen aus flachen Schichten bestehen. Wichtiges Detail: die Proportionen werden auf die MESH-DATEN angewendet (transform_apply), nicht auf das Objekt. Der Displace arbeitet mit globalen Texturkoordinaten - eine Objekt-Skalierung wuerde das Rauschen mitverzerren statt die Form zu aendern. Gemessen bei Proportion (2.0, 1.5, 0.35): 3.43 / 2.56 / 0.79 m (Z = 23 % von X), bei (2.2, 0.8, 0.9): 3.81 / 1.40 / 1.60 m. Der Zufalls-Button kennt die neue Form ebenfalls. Neuer Test tests/test_base_shapes.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -173,7 +173,14 @@ damit die Raw-URL sie ausliefert.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Basis-Formen:** Cube oder Ico-Sphere, einstellbare Subsurf-Stufen.
|
- **Basis-Formen:** Wuerfel, **Quader** oder Ico-Sphere, einstellbare Subsurf-Stufen.
|
||||||
|
Der Quader hat frei einstellbare `Quader-Proportionen` (X/Y/Z) plus
|
||||||
|
`Proportions-Streuung` — damit entstehen flache **Platten** und langgestreckte
|
||||||
|
**Riegel** statt nur runder Brocken. Gemessen bei Proportion (2.0, 1.5, 0.35):
|
||||||
|
3.43 / 2.56 / 0.79 m — Z also 23 % von X.
|
||||||
|
Die Proportionen werden auf die MESH-DATEN angewendet, nicht auf das Objekt:
|
||||||
|
der Displace nutzt globale Texturkoordinaten, eine Objekt-Skalierung wuerde
|
||||||
|
das Rauschen mitverzerren statt die Form zu aendern.
|
||||||
- **Noise/Displace:** Voronoi / Clouds / Distorted Noise, Scale + Staerke, Seed.
|
- **Noise/Displace:** Voronoi / Clouds / Distorted Noise, Scale + Staerke, Seed.
|
||||||
Seed-Variation ueber einen moderaten Welt-Offset (±25) vor dem Displace-Apply –
|
Seed-Variation ueber einen moderaten Welt-Offset (±25) vor dem Displace-Apply –
|
||||||
Blenders prozedurale Texturen haben keinen echten Seed-Parameter.
|
Blenders prozedurale Texturen haben keinen echten Seed-Parameter.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "stylized_rock_generator"
|
id = "stylized_rock_generator"
|
||||||
version = "2.9.0"
|
version = "2.10.0"
|
||||||
name = "Stylized Rock Generator"
|
name = "Stylized Rock Generator"
|
||||||
tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export"
|
tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export"
|
||||||
maintainer = "D4rkst3r"
|
maintainer = "D4rkst3r"
|
||||||
|
|||||||
Vendored
+4
-4
@@ -7,7 +7,7 @@
|
|||||||
"id": "stylized_rock_generator",
|
"id": "stylized_rock_generator",
|
||||||
"name": "Stylized Rock Generator",
|
"name": "Stylized Rock Generator",
|
||||||
"tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export",
|
"tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export",
|
||||||
"version": "2.9.0",
|
"version": "2.10.0",
|
||||||
"type": "add-on",
|
"type": "add-on",
|
||||||
"maintainer": "D4rkst3r",
|
"maintainer": "D4rkst3r",
|
||||||
"license": [
|
"license": [
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
"Mesh",
|
"Mesh",
|
||||||
"Modeling"
|
"Modeling"
|
||||||
],
|
],
|
||||||
"archive_url": "./stylized_rock_generator-2.9.0.zip",
|
"archive_url": "./stylized_rock_generator-2.10.0.zip",
|
||||||
"archive_size": 19688,
|
"archive_size": 20241,
|
||||||
"archive_hash": "sha256:78452434a3f175ae1a415b4d4b2abdb5cb803415499c114c0452fb0a4524959e"
|
"archive_hash": "sha256:d092ae0a1cfefed9bdd4fbe1ffcd8160f4fffb00033cec346bbde3d14c755254"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"schema_version": "1.0.0",
|
"schema_version": "1.0.0",
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Stylized Rock Generator",
|
"name": "Stylized Rock Generator",
|
||||||
"author": "D4rkst3r",
|
"author": "D4rkst3r",
|
||||||
"version": (2, 9, 0),
|
"version": (2, 10, 0),
|
||||||
"blender": (4, 2, 0),
|
"blender": (4, 2, 0),
|
||||||
"location": "View3D > Sidebar > Rock Gen",
|
"location": "View3D > Sidebar > Rock Gen",
|
||||||
"description": (
|
"description": (
|
||||||
@@ -50,7 +50,7 @@ CLASS_SIZES = {'S': 0.45, 'M': 1.0, 'L': 1.9, 'XL': 3.2}
|
|||||||
|
|
||||||
# Property-Keys, die von Presets gespeichert/geladen werden.
|
# Property-Keys, die von Presets gespeichert/geladen werden.
|
||||||
PRESET_KEYS = (
|
PRESET_KEYS = (
|
||||||
"base_shape", "subsurf_levels", "texture_type", "noise_scale",
|
"base_shape", "box_proportions", "box_variation", "subsurf_levels", "texture_type", "noise_scale",
|
||||||
"displace_strength", "seed", "decimate_ratio_1", "decimate_ratio_2",
|
"displace_strength", "seed", "decimate_ratio_1", "decimate_ratio_2",
|
||||||
"dissolve_angle", "generate_uv0", "generate_lightmap_uv",
|
"dissolve_angle", "generate_uv0", "generate_lightmap_uv",
|
||||||
"randomize_transform", "scale_min", "scale_max",
|
"randomize_transform", "scale_min", "scale_max",
|
||||||
@@ -303,8 +303,24 @@ def _make_lightmap_uv(context, obj):
|
|||||||
def _build_rock_mesh(context, settings, index, location, rng, name=None):
|
def _build_rock_mesh(context, settings, index, location, rng, name=None):
|
||||||
"""Baut das reine Rock-Mesh (ohne Transform/Material). Kann RockGenError werfen."""
|
"""Baut das reine Rock-Mesh (ohne Transform/Material). Kann RockGenError werfen."""
|
||||||
# 1. Basis-Mesh
|
# 1. Basis-Mesh
|
||||||
if settings.base_shape == 'CUBE':
|
if settings.base_shape in ('CUBE', 'BOX'):
|
||||||
bpy.ops.mesh.primitive_cube_add(size=2, location=location)
|
bpy.ops.mesh.primitive_cube_add(size=2, location=location)
|
||||||
|
if settings.base_shape == 'BOX':
|
||||||
|
# Proportionen auf die MESH-DATEN anwenden, nicht auf das Objekt:
|
||||||
|
# der Displace nutzt globale Texturkoordinaten, eine Objekt-Skalierung
|
||||||
|
# wuerde das Rauschen mitverzerren statt die Form zu aendern.
|
||||||
|
px, py, pz = settings.box_proportions
|
||||||
|
v = settings.box_variation
|
||||||
|
if v > 0.0:
|
||||||
|
px *= 1.0 + rng.uniform(-v, v) * 0.5
|
||||||
|
py *= 1.0 + rng.uniform(-v, v) * 0.5
|
||||||
|
pz *= 1.0 + rng.uniform(-v, v) * 0.5
|
||||||
|
ob_tmp = context.active_object
|
||||||
|
ob_tmp.scale = (px, py, pz)
|
||||||
|
_deselect_all(context)
|
||||||
|
context.view_layer.objects.active = ob_tmp
|
||||||
|
ob_tmp.select_set(True)
|
||||||
|
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
||||||
else:
|
else:
|
||||||
bpy.ops.mesh.primitive_ico_sphere_add(
|
bpy.ops.mesh.primitive_ico_sphere_add(
|
||||||
subdivisions=1, radius=1.0, location=location
|
subdivisions=1, radius=1.0, location=location
|
||||||
@@ -727,9 +743,24 @@ class RockGenSettings(PropertyGroup):
|
|||||||
# --- Basis ---
|
# --- Basis ---
|
||||||
base_shape: EnumProperty(
|
base_shape: EnumProperty(
|
||||||
name="Basis-Form",
|
name="Basis-Form",
|
||||||
items=[('CUBE', "Cube", ""), ('ICOSPHERE', "Ico-Sphere", "")],
|
items=[
|
||||||
|
('CUBE', "Wuerfel", "Gleichmaessiger Brocken"),
|
||||||
|
('BOX', "Quader", "Laengliche oder flache Grundform - Proportionen unten"),
|
||||||
|
('ICOSPHERE', "Ico-Sphere", "Rundlicher Brocken"),
|
||||||
|
],
|
||||||
default='CUBE',
|
default='CUBE',
|
||||||
)
|
)
|
||||||
|
box_proportions: FloatVectorProperty(
|
||||||
|
name="Quader-Proportionen", size=3, default=(1.6, 1.1, 0.55),
|
||||||
|
min=0.15, max=4.0, subtype='XYZ',
|
||||||
|
description=("Seitenverhaeltnis der Grundform bei 'Quader'. Flach (kleines Z) "
|
||||||
|
"ergibt Platten und Findlinge, langgestreckt ergibt Brocken. "
|
||||||
|
"Wird VOR dem Displace angewendet, damit die Verformung mitgeht"),
|
||||||
|
)
|
||||||
|
box_variation: FloatProperty(
|
||||||
|
name="Proportions-Streuung", default=0.25, min=0.0, max=1.0,
|
||||||
|
description="Wuerfelt die Proportionen je Stein etwas anders (0 = alle gleich)",
|
||||||
|
)
|
||||||
subsurf_levels: IntProperty(name="Subsurf Levels", default=5, min=1, max=6)
|
subsurf_levels: IntProperty(name="Subsurf Levels", default=5, min=1, max=6)
|
||||||
|
|
||||||
# --- Noise / Displace ---
|
# --- Noise / Displace ---
|
||||||
@@ -1501,7 +1532,7 @@ class ROCKGEN_OT_randomize(Operator):
|
|||||||
jitter("noise_scale_max", 0.6, 4.0)
|
jitter("noise_scale_max", 0.6, 4.0)
|
||||||
jitter("displace_strength_max", 0.2, 0.9)
|
jitter("displace_strength_max", 0.2, 0.9)
|
||||||
if s.random_shape:
|
if s.random_shape:
|
||||||
s.base_shape = rng.choice(['CUBE', 'ICOSPHERE'])
|
s.base_shape = rng.choice(['CUBE', 'BOX', 'ICOSPHERE'])
|
||||||
s.texture_type = rng.choice(['VORONOI', 'CLOUDS', 'DISTORTED_NOISE'])
|
s.texture_type = rng.choice(['VORONOI', 'CLOUDS', 'DISTORTED_NOISE'])
|
||||||
self.report({'INFO'}, "Neue Zufallswerte (Seed %d)." % s.seed)
|
self.report({'INFO'}, "Neue Zufallswerte (Seed %d)." % s.seed)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
@@ -1626,6 +1657,9 @@ class VIEW3D_PT_rock_generator(Panel):
|
|||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text="Basis")
|
box.label(text="Basis")
|
||||||
box.prop(settings, "base_shape")
|
box.prop(settings, "base_shape")
|
||||||
|
if settings.base_shape == 'BOX':
|
||||||
|
box.prop(settings, "box_proportions")
|
||||||
|
box.prop(settings, "box_variation")
|
||||||
box.prop(settings, "subsurf_levels")
|
box.prop(settings, "subsurf_levels")
|
||||||
|
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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:
|
||||||
|
for o in list(bpy.data.objects): 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
|
||||||
|
bpy.ops.object.generate_rocks_batch()
|
||||||
|
ob=[o for o in bpy.context.scene.objects if o.type=='MESH'][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(); keep.append((cp,x))
|
||||||
|
x+=2.4
|
||||||
|
for cp,px in keep:
|
||||||
|
bpy.context.collection.objects.link(cp); 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))
|
||||||
Reference in New Issue
Block a user