import bpy
def clear_scene():
"""Removes all objects from the current scene to start fresh."""
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
def create_base_body_part(name, radius, scale, location):
"""Creates a basic body part sphere with specific dimensions and applies scale."""
bpy.ops.mesh.primitive_uv_sphere_add(segments=64, ring_count=32, radius=radius, location=location)
obj = bpy.context.active_object
obj.name = name
obj.scale = scale
bpy.ops.object.transform_apply(scale=True)
return obj
def add_named_part(name, location, scale, color, roughness=0.5):
"""Creates a basic primitive part with individual realistic PBR material settings."""
bpy.ops.mesh.primitive_uv_sphere_add(segments=32, ring_count=16, radius=1, location=location)
obj = bpy.context.active_object
obj.name = name
obj.scale = scale
mat = bpy.data.materials.new(name=f"Mat_{name}")
mat.use_nodes = True
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs['Base Color'].default_value = color
bsdf.inputs['Roughness'].default_value = roughness
obj.data.materials.append(mat)
return obj
def create_realistic_quill_material():
"""Generates a sharp, realistic three-tone constant gradient material for the quills."""
mat = bpy.data.materials.new(name="QuillMaterial")
mat.use_nodes = True
nodes, links = mat.node_tree.nodes, mat.node_tree.links
nodes.clear()
out_node = nodes.new('ShaderNodeOutputMaterial')
bsdf_node = nodes.new('ShaderNodeBsdfPrincipled')
ramp_node = nodes.new('ShaderNodeValToRGB')
hair_node = nodes.new('ShaderNodeHairInfo')
bsdf_node.inputs['Roughness'].default_value = 0.45
color_ramp = ramp_node.color_ramp
color_ramp.interpolation = 'CONSTANT'
color_ramp.elements[0].position, color_ramp.elements[0].color = 0.0, (0.01, 0.01, 0.01, 1) # Base: Black
color_ramp.elements[1].position, color_ramp.elements[1].color = 0.45, (0.18, 0.09, 0.04, 1) # Mid: Dark Brown
new_element = color_ramp.elements.new(0.85)
new_element.color = (0.95, 0.95, 0.95, 1) # Tip: White
links.new(hair_node.outputs['Intercept'], ramp_node.inputs['Fac'])
links.new(ramp_node.outputs['Color'], bsdf_node.inputs['Base Color'])
links.new(bsdf_node.outputs['BSDF'], out_node.inputs['Surface'])
return mat
def configure_realistic_quill_system(obj, vertex_group_name):
"""Sets up highly organic hair particle dynamics for wild, sharp quills."""
modifier = obj.modifiers.new(name="Quills", type='PARTICLE_SYSTEM')
settings = modifier.particle_system.settings
settings.type = 'HAIR'
settings.count = 5500
settings.hair_length = 0.35
settings.use_advanced_hair = True
settings.normal_factor = 0.3
settings.object_align_factor = (-0.4, 0, 0.1)
settings.length_random = 0.5
settings.root_radius, settings.tip_radius = 0.08, 0.005
settings.material = 2
settings.brownian_factor = 0.005
obj.particle_systems["Quills"].vertex_group_density = vertex_group_name
def configure_realistic_mane_system(obj, vertex_group_name):
"""Sets up the particle hair system for the dynamic head mane spikes."""
modifier = obj.modifiers.new(name="Mane", type='PARTICLE_SYSTEM')
settings = modifier.particle_system.settings
settings.type = 'HAIR'
settings.count = 1200
settings.hair_length = 0.9
settings.use_advanced_hair = True
settings.jitter_factor = 0.03
settings.length_random = 0.6
settings.root_radius, settings.tip_radius = 0.005, 0.001
settings.material = 2
settings.brownian_factor = 0.02
obj.particle_systems["Mane"].vertex_group_density = vertex_group_name
def create_perfect_hybrid_porcupine():
clear_scene()
# 1. Base Mesh Construction
torso = create_base_body_part("Torso", radius=1.5, scale=(1.4, 0.9, 0.8), location=(0, 0, 0))
head = create_base_body_part("Head", radius=0.9, scale=(1.1, 0.8, 0.8), location=(1.8, 0, 0.1))
muzzle = create_base_body_part("Muzzle", radius=0.5, scale=(1.4, 0.8, 0.8), location=(2.3, 0, 0.05))
# 2. Facial Features & Limbs
features = [
{"name": "Eye_L", "loc": (2.4, 0.42, 0.4), "scale": (0.09, 0.09, 0.09), "color": (0, 0, 0, 1), "roughness": 0.05},
{"name": "Eye_R", "loc": (2.4, -0.42, 0.4), "scale": (0.09, 0.09, 0.09), "color": (0, 0, 0, 1), "roughness": 0.05},
{"name": "Nose", "loc": (2.95, 0, 0.12), "scale": (0.12, 0.16, 0.14), "color": (0.08, 0.04, 0.04, 1), "roughness": 0.6},
{"name": "Ear_L", "loc": (1.85, 0.7, 0.55), "scale": (0.05, 0.15, 0.2), "color": (0.1, 0.05, 0.03, 1), "roughness": 0.7},
{"name": "Ear_R", "loc": (1.85, -0.7, 0.55), "scale": (0.05, 0.15, 0.2), "color": (0.1, 0.05, 0.03, 1), "roughness": 0.7}
]
for f in features:
add_named_part(f["name"], f["loc"], f["scale"], f["color"], f["roughness"])
legs_locations = [(1, -0.6, -0.8), (1, 0.6, -0.8), (-0.8, -0.7, -0.8), (-0.8, 0.7, -0.8)]
for i, loc in enumerate(legs_locations):
add_named_part(f"Leg_{i}", loc, (0.15, 0.15, 0.4), (0.05, 0.03, 0.02, 1), roughness=0.75)
# 3. Core Materials Assignment
body_mat = bpy.data.materials.new(name="Material_Body")
body_mat.use_nodes = True
body_mat.node_tree.nodes["Principled BSDF"].inputs['Base Color'].default_value = (0.05, 0.02, 0.01, 1)
body_mat.node_tree.nodes["Principled BSDF"].inputs['Roughness'].default_value = 0.85
quill_mat = create_realistic_quill_material()
for target in [torso, head, muzzle]:
target.data.materials.append(body_mat)
torso.data.materials.append(quill_mat)
head.data.materials.append(quill_mat)
# 4. Masking Distribution Areas via Vertex Groups
vg_quill = torso.vertex_groups.new(name="QuillArea")
for v in torso.data.vertices:
if v.co.x < 1.4 and v.co.z > -0.3:
vg_quill.add([v.index], 1.0, 'REPLACE')
vg_mane = head.vertex_groups.new(name="ManeArea")
for v in head.data.vertices:
if v.co.x < 1.7 and v.co.z > 0.4:
vg_mane.add([v.index], 1.0, 'REPLACE')
# 5. Populate Hair Particle Systems
configure_realistic_quill_system(torso, "QuillArea")
configure_realistic_mane_system(head, "ManeArea")
# 6. Global Smooth Shading Viewport Polish
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.shade_smooth()
bpy.ops.object.select_all(action='DESELECT')
if __name__ == "__main__":
create_perfect_hybrid_porcupine()
Comments
Post a Comment