"""Phase 7: authored high-detail basalt/metal figures with real layered geometry. Blender Z up, -Y forward. All pieces retain stable semantic names for Godot's rigid-joint animation. Bevels, armor overlaps, joints, fingers and ornaments are modeled geometry; no subdivision-only inflation of the former silhouettes. """ from math import pi, sin, cos import hashlib import random import bpy import bmesh from mathutils import Matrix, Vector from common import box, ellipsoid, cylinder, beam, ring, mesh_object, srgb_to_linear from reference_geometry import r_plate, r_block, r_rock, r_sweep, r_lathe, r_arch_shell def hd_finish(obj, width=.012, segments=3, smooth=False): """Bake scale before bevel so round-overs have consistent physical width.""" obj.data.transform(Matrix.Diagonal((*obj.scale, 1))) obj.scale = (1,1,1) bpy.context.view_layer.objects.active = obj if width: mod = obj.modifiers.new('Crafted_edge_radius','BEVEL') mod.width = width mod.segments = segments mod.limit_method = 'ANGLE' mod.angle_limit = .32 mod.use_clamp_overlap = True bpy.ops.object.modifier_apply(modifier=mod.name) for face in obj.data.polygons: face.use_smooth = smooth or len(face.vertices) == 4 if width or smooth: normal = obj.modifiers.new('Weighted_surface_normals','WEIGHTED_NORMAL') normal.keep_sharp = True normal.weight = 50 bpy.ops.object.modifier_apply(modifier=normal.name) return obj def hd_box(name, loc, size, mat, radius=.012, rotation=None): return hd_finish(box(name,loc,size,mat,rotation),radius) def hd_plate(name,loc,outline,depth,mat,radius=.01,bulge=.0): return hd_finish(r_plate(name,loc,outline,depth,mat,bulge),radius) def hd_rock(name,loc,size,mat,seed=0): obj=ellipsoid(name,loc,size,mat,subdivisions=2) rng=random.Random(name+str(seed)) # Separate broad chisel facets with chipped edges, not a smooth sphere. for v in obj.data.vertices: v.co *= rng.uniform(.94,1.045) obj.rotation_euler=(.07*seed,.12*seed,.17*seed) return hd_finish(obj,.009,2) def hd_rod(name,a,b,radius,mat,sides=20): return hd_finish(beam(name,a,b,radius,mat,sides),min(radius*.18,.007),2,True) def hd_ring(name,loc,radius,thickness,mat,rotation=None,segments=40): obj=ring(name,loc,radius,thickness,mat,rotation,segments) for face in obj.data.polygons: face.use_smooth=True return obj def hd_disc(name,loc,radius,depth,mat): return hd_finish(cylinder(name,loc,radius,depth,mat,32,(pi/2,0,0)),.006,3,True) def hd_curve(name,points,radii,mat,sides=12,steps=5): """Catmull-Rom centerline and tapered cross sections for claws and cables.""" pts=[Vector(p) for p in points] positions=[]; widths=[] for i in range(len(pts)-1): p0,p1,p2,p3=pts[max(0,i-1)],pts[i],pts[i+1],pts[min(len(pts)-1,i+2)] for k in range(steps): t=k/steps p=.5*((2*p1)+(-p0+p2)*t+(2*p0-5*p1+4*p2-p3)*t*t+(-p0+3*p1-3*p2+p3)*t*t*t) positions.append(tuple(p)); widths.append(radii[i]*(1-t)+radii[i+1]*t) positions.append(tuple(pts[-1]));widths.append(radii[-1]) obj=r_sweep(name,positions,widths,mat,sides) for face in obj.data.polygons:face.use_smooth=True return obj def hd_materials(m): """A small shared PBR palette; values are linear RGB for glTF.""" definitions={ 'basalt':('41434B',.05,.77,0), 'edge':('62636A',.10,.71,0), 'dark':('171A21',.15,.66,0), 'gold':('C99645',.78,.42,0), 'gold_light':('F1CA7C',.68,.39,0), 'ember':('FF5210',.05,.40,1.25), 'hot':('FFC65E',.0,.36,2.2), 'ceramic':('D8CDB2',.10,.37,0), 'bone':('BAC8CF',.16,.55,0), 'rift':('CE609B',.10,.35,2.0), } for key,(hexcolor,metal,rough,emission) in definitions.items(): name='SW_HD_'+key mat=bpy.data.materials.new(name) mat['material_name']=name c=tuple(srgb_to_linear(int(hexcolor[i:i+2],16)/255) for i in (0,2,4)) mat.diffuse_color=(*c,1);mat.use_nodes=True node=mat.node_tree.nodes.get('Principled BSDF') node.inputs['Base Color'].default_value=(*c,1) node.inputs['Metallic'].default_value=metal node.inputs['Roughness'].default_value=rough if emission: node.inputs['Emission Color'].default_value=(*c,1) node.inputs['Emission Strength'].default_value=emission m[key]=mat return m def hd_rivet(name,loc,m,r=.018): hd_disc(name+'_Seat',loc,r*1.5,.009,m['dark']) hd_disc(name+'_Head',(loc[0],loc[1]-.009,loc[2]),r,.018,m['gold_light']) def hd_trim(name,points,m,r=.012): return hd_curve(name,points,[r]*len(points),m['gold_light'],8,2) def hd_sun(name,loc,r,m): x,y,z=loc hd_disc(name+'_Base',(x,y,z),r,.024,m['gold']) hd_disc(name+'_Face',(x,y-.020,z),r*.68,.018,m['dark']) hd_disc(name+'_Heart',(x,y-.033,z),r*.43,.022,m['gold_light']) for i in range(12): a=i*pi/6 hd_plate(name+'_Ray'+str(i),(x,y-.032,z),[(r*.64*cos(a-.13),r*.64*sin(a-.13)), (r*.96*cos(a),r*.96*sin(a)),(r*.64*cos(a+.13),r*.64*sin(a+.13))],.016,m['gold_light'],.002) def hd_legs(m,spread=.30,height=.50,slim=False): radius=.070 if slim else .13 for s,side in [(-1,'L'),(1,'R')]: n='Leg'+side hip=(s*spread*.8,.055,height) knee=(s*spread,.06,height*.53) ankle=(s*spread,-.035,.13) hd_rod(n+'_InnerThigh',hip,knee,radius*.64,m['dark']) hd_rod(n+'_InnerShin',knee,ankle,radius*.64,m['dark']) hd_rock(n+'_ThighArmor',tuple((Vector(hip)+Vector(knee))*.5),(radius*1.24,radius*.90,height*.24),m['basalt'],s) hd_rock(n+'_ShinArmor',tuple((Vector(knee)+Vector(ankle))*.5),(radius*1.10,radius,height*.22),m['basalt'],2*s) for p,label in [(hip,'Hip'),(knee,'Knee'),(ankle,'Ankle')]: hd_disc(n+'_'+label+'Pivot',(p[0],p[1]-radius*.8,p[2]),radius*.72,.055,m['gold']) hd_disc(n+'_'+label+'Pin',(p[0],p[1]-radius*1.12,p[2]),radius*.36,.022,m['dark']) hd_disc(n+'_'+label+'Rim',(p[0],p[1]-radius*1.27,p[2]),radius*.16,.025,m['gold_light']) # Three individually modeled tapered foot/toe plates. for j in range(3): x=ankle[0]+(j-1)*radius*.70 toe=[(-radius*.40,-.045),(radius*.40,-.045),(radius*.30,.060),(0,.090),(-radius*.32,.060)] hd_plate(n+'_Toe'+str(j),(x,-.135,.065),toe,.26 if not slim else .20,m['basalt'],.013) hd_box(n+'_ToeCap'+str(j),(x,-.225,.075),(radius*.62,.045,.048),m['gold'],.008) hd_box(n+'_AnkleCuff',(ankle[0],-.05,.19),(radius*2.65,.22,.065),m['gold'],.012) if not slim: hd_plate(n+'_KneeGuard',(knee[0],-.103,knee[2]),[(-.10,.09),(.10,.09),(.085,-.08),(0,-.15),(-.085,-.08)],.075,m['edge'],.014) else: hd_plate(n+'_ShinGold',(knee[0],-.095,knee[2]-.10),[(-.057,.14),(.057,.14),(.026,-.11),(0,-.21)],.046,m['gold'],.005) hd_plate(n+'_ShinEmber',(knee[0],-.126,knee[2]-.11),[(-.018,.08),(.018,.08),(0,-.14)],.015,m['ember'],.002) def hd_arm(name,shoulder,elbow,hand,m,radius=.16,fingers=True): hd_rod(name+'_UpperCore',shoulder,elbow,radius*.56,m['dark']) hd_rod(name+'_ForearmCore',elbow,hand,radius*.64,m['dark']) hd_rock(name+'_Elbow',elbow,(radius*.90,)*3,m['basalt'],2) hd_rock(name+'_UpperArmor',tuple((Vector(shoulder)+Vector(elbow))*.5),(radius*1.08,radius*.88,Vector(shoulder).lerp(Vector(elbow),.5).z*.17),m['basalt'],1) hd_rock(name+'_ForearmArmor',tuple((Vector(elbow)+Vector(hand))*.5),(radius*1.20,radius,Vector(elbow).lerp(Vector(hand),.5).z*.28),m['basalt'],3) hd_rock(name+'_Shoulder',shoulder,(radius*1.48,radius*1.24,radius*1.15),m['basalt'],2) hd_disc(name+'_ShoulderJoint',(shoulder[0],shoulder[1]-radius*.85,shoulder[2]),radius*.66,.052,m['gold']) for t,label in [(.58,'UpperBand'),(.85,'WristBand')]: p=Vector(elbow).lerp(Vector(hand),t) d=(Vector(hand)-Vector(elbow)).normalized()*.040 hd_rod(name+'_'+label,tuple(p-d),tuple(p+d),radius*1.27,m['gold'],24) hd_rivet(name+'_'+label+'Rivet',(p.x,p.y-radius*1.25,p.z),m) hd_rock(name+'_Palm',hand,(radius*1.15,radius*.81,radius*1.06),m['basalt'],4) if fingers: for j in range(4): x=hand[0]+(j-1.5)*radius*.46 for k in range(2): hd_box(name+'_Finger%d_%d'%(j,k),(x,hand[1]-.09-k*.04,hand[2]-.045-k*.07), (radius*.40,radius*.53,radius*.55),m['edge'] if k==0 else m['basalt'],radius*.08) s=-1 if hand[0]<0 else 1 hd_rock(name+'_Thumb',(hand[0]-s*radius,hand[1]-.025,hand[2]-.04),(radius*.46,radius*.62,radius*.76),m['basalt'],1) def hd_tower(name,loc,size,m): x,y,z=loc;w,d,h=size hd_box(name+'_StoneKeep',(x,y,z),(w,d,h),m['basalt'],.018) hd_box(name+'_LowerCornice',(x,y,z-h*.31),(w*1.04,d*1.04,.045),m['gold'],.008) hd_box(name+'_UpperCornice',(x,y,z+h*.34),(w*1.055,d*1.055,.045),m['edge'],.008) for j in range(3): px=x+(j-1)*w*.26 hd_box(name+'_WindowRecess'+str(j),(px,y-d*.5-.014,z),(w*.12,.018,h*.42),m['dark'],.006) hd_box(name+'_WindowFire'+str(j),(px,y-d*.5-.026,z),(w*.052,.009,h*.30),m['ember'],.003) hd_box(name+'_WindowLintel'+str(j),(px,y-d*.5-.020,z+h*.25),(w*.19,.07,.028),m['edge'],.003) for k,(dx,dy) in enumerate([(-1,-1),(1,-1),(-1,1),(1,1)]): hd_box(name+'_Merlon'+str(k),(x+dx*w*.35,y+dy*d*.34,z+h*.55),(w*.29,d*.31,h*.29),m['basalt'],.010) hd_box(name+'_MerlonCap'+str(k),(x+dx*w*.35,y+dy*d*.34,z+h*.70),(w*.31,d*.33,.032),m['edge'],.004) def hd_tank(up,m): hd_legs(m,.295,.46) hd_rock('Body_ArmoredCore',(0,.04,.82),(.42,.31,.48),m['dark'],1) # Broad tapered chest formed from five slabs, with a narrow recessed furnace. for s,side in [(-1,'L'),(1,'R')]: outline=[(s*.045,.45),(s*.34,.40),(s*.41,.20),(s*.34,-.27),(s*.05,-.46)] hd_plate('Body_Chest'+side,(0,-.28,.84),outline,.22,m['basalt'],.020,.018) chest_pieces=[[(.078,.40),(.28,.365),(.335,.21),(.18,.075),(.083,.17)], [(.19,.045),(.337,.17),(.298,-.12),(.21,-.18),(.067,-.052),(.087,.125)], [(.070,-.094),(.215,-.217),(.286,-.162),(.222,-.30),(.068,-.385)]] for j,outline_piece in enumerate(chest_pieces): hd_plate('Body_ChestRelief'+side+str(j),(0,-.411-j*.006,.84),[(s*x,z) for x,z in outline_piece],.066,m['basalt'] if j%2 else m['edge'],.007,.021) hd_trim('Body_Border'+side,[(s*.05,-.437,.40),(s*.36,-.407,.62),(s*.40,-.385,1.17),(s*.27,-.394,1.28)],m,.021) # Chisel seams divide the armor into larger readable stone layers. hd_curve('Body_Seam'+side,[(s*.065,-.418,.72),(s*.18,-.410,.87),(s*.12,-.409,1.01),(s*.32,-.404,1.14)],[.009]*4,m['dark'],6,1) hd_arm('Arm'+side,(s*.49,.0,1.04),(s*.62,-.01,.76),(s*.65,-.14,.46),m,.155) hd_plate('Arm'+side+'_Pauldron',(s*.48,-.105,1.12),[(-.21,.05),(-.13,.17),(.13,.17),(.21,.04),(.15,-.15),(-.15,-.15)],.34,m['gold'],.016) hd_plate('Arm'+side+'_PauldronStone',(s*.48,-.139,1.16),[(-.17,.03),(-.10,.14),(.10,.14),(.17,.03),(.11,-.10),(-.11,-.10)],.33,m['basalt'],.014) if up: hd_tower('Arm'+side+'_Castle',(s*.48,.01,1.31),(.38,.40,.29),m) hd_tower('Arm'+side+'_Bastion',(s*.65,-.08,.68),(.29,.32,.24),m) for z in [.59,1.19]: hd_rivet('Body_'+side+'Fastener'+str(z),(s*.31,-.414,z),m,.023) hd_box('Head_FurnaceRecess',(0,-.402,1.09),(.095,.055,.32),m['dark'],.007) hd_box('Head_Fire',(0,-.432,1.10),(.039,.025,.258),m['ember'],.008) hd_box('Head_FireHeart',(0,-.446,1.10),(.012,.005,.19),m['hot'],.002) for s in [-1,1]: hd_box('Body_CenterGold'+str(s),(s*.061,-.44,1.02),(.023,.034,.51),m['gold'],.006) hd_plate('Body_BottomGold',(0,-.443,.70),[(-.072,.05),(.072,.05),(0,-.13)],.030,m['gold'],.005) if up: hd_tower('Head_Crown',(0,.01,1.46),(.28,.31,.31),m) hd_box('Body_SpineGold',(0,-.446,.61),(.025,.040,.26),m['gold'],.005) def hd_coal(up,m): hd_legs(m,.25,.39) hd_rock('Body_LavaCore',(0,.025,.76),(.375,.27,.39),m['ember'],2) # Interlocking shell plates leave true gaps above an orange inner body. for j,z in enumerate([.53,.81,1.07]): for k in range(7): a=2*pi*k/7+.20*(j%2) if sin(a)<-.42:continue pos=(.33*cos(a),.24*sin(a),z) hd_rock('Body_Shell%d_%d'%(j,k),pos,(.19,.135,.205),m['basalt'],j*3+k) hd_rock('Head_JawCavity',(0,-.245,.77),(.37,.15,.235),m['dark'],0) hd_plate('Head_MouthEmber',(0,-.345,.78),[(-.30,.07),(-.17,.10),(0,.02),(.17,.10),(.30,.07),(.24,-.08),(0,-.15),(-.24,-.08)],.028,m['ember'],.008) for s,side in [(-1,'L'),(1,'R')]: hd_rock('Head_Brow'+side,(s*.19,-.26,1.025),(.22,.17,.145),m['basalt'],s*2) hd_curve('Head_Eye'+side,[(s*.29,-.419,1.02),(s*.18,-.433,.99),(s*.09,-.413,.97)],[.012,.019,.007],m['hot'],8,3) hd_rock('Head_Cheek'+side,(s*.32,-.24,.82),(.15,.13,.22),m['basalt'],4*s) for j in range(4): x=s*(.035+j*.07) hd_curve('Head_UpperTooth'+side+str(j),[(x,-.32,.96-j*.016),(x,-.405,.85-j*.016),(x-s*.010,-.413,.80-j*.012)],[.055,.029,0],m['edge'],8,3) hd_curve('Head_LowerTooth'+side+str(j),[(x,-.28,.54+j*.02),(x,-.38,.67+j*.01),(x,-.40,.72+j*.01)],[.057,.027,0],m['basalt'],8,3) hd_arm('Arm'+side,(s*.44,.03,.98),(s*.54,-.015,.72),(s*.57,-.16,.52),m,.14,False) for j in range(3): x=s*.58+(j-1)*.14 hd_curve('Weapon'+side+'_Claw'+str(j),[(x,-.20,.60),(x+s*.035,-.37,.48),(x+s*.01,-.48,.26),(x-s*.070,-.41,.18)], [.10 if up else .075,.095 if up else .068,.045,0],m['gold'],16,6) hd_rivet('Arm'+side+'_Knuckle'+str(j),(x,-.27,.63),m,.021) hd_plate('Arm'+side+'_ShoulderTrim',(s*.42,-.125,1.085),[(-.10,.04),(.10,.04),(.08,-.08),(0,-.12),(-.08,-.08)],.04,m['gold'],.008) for k in range(5 if up else 3): x=(k-(2 if up else 1))*.15 hd_curve('Body_DorsalSpine'+str(k),[(x,.12,1.02),(x*1.12,.13,1.26+(.10 if k%2==0 else 0)),(x*.95,.10,1.43 if up else 1.32)], [.125,.085,0],m['basalt'],9,3) hd_rock('Head_Chin',(0,-.20,.53),(.24,.19,.12),m['basalt'],5) def hd_lancer(m): hd_legs(m,.23,.74,True) hd_rod('Body_Spine',(0,.045,.61),(0,.03,1.24),.085,m['dark']) for s,side in [(-1,'L'),(1,'R')]: hd_plate('Body_Cuirass'+side,(0,-.025,1.07),[(s*.025,.17),(s*.23,.13),(s*.16,-.13),(s*.025,-.22)],.20,m['gold'],.011) hd_plate('Body_ChestStone'+side,(0,-.071,1.08),[(s*.048,.13),(s*.18,.10),(s*.12,-.10),(s*.048,-.15)],.16,m['basalt'],.008) hd_arm('Arm'+side,(s*.24,.01,1.21),(s*.33,-.02,1.03),(s*.30,-.23,.96),m,.065) hd_plate('Arm'+side+'_ShoulderFin',(s*.28,0,1.23),[(s*-.07,-.07),(s*.20,-.035),(s*.03,.16)],.15,m['gold'],.007) hd_plate('Body_EmberHeart',(0,-.162,1.03),[(-.028,.10),(.028,.10),(0,-.13)],.019,m['ember'],.003) hd_rod('Head_Neck',(0,.03,1.21),(0,.03,1.37),.06,m['dark']) hd_plate('Head_Helmet',(0,-.01,1.47),[(-.19,.105),(0,-.005),(.19,.105),(.072,-.09),(0,-.21),(-.072,-.09)],.22,m['gold'],.008) hd_plate('Head_Visor',(0,-.135,1.40),[(-.052,.018),(.052,.018),(0,-.116)],.017,m['ember'],.003) hd_curve('Head_Crest',[(0,.07,1.49),(0,.12,1.64),(-.06,.11,1.76)],[.061,.034,0],m['gold_light'],10,4) a=Vector((-.71,-.265,1.01)); b=Vector((.70,-.29,.94)) hd_rod('Weapon_LanceShaft',a,b,.046,m['dark'],24) hd_rod('Weapon_LanceEmber',a+Vector((0,-.042,0)),b+Vector((0,-.042,0)),.011,m['ember'],10) for k in range(11): t=.05+k*.087;p=a.lerp(b,t) hd_rod('Weapon_LanceCollar'+str(k),p-Vector((.012,0,0)),p+Vector((.012,0,0)),.065,m['gold'],24) hd_curve('Weapon_SpearHead',[b,b+Vector((.14,0,-.005)),b+Vector((.46,0,-.02))],[.06,.092,0],m['ember'],8,3) for s in [-1,1]: hd_curve('Weapon_SpearEdge'+str(s),[b+Vector((0,0,s*.07)),b+Vector((.17,0,s*.085)),b+Vector((.47,0,-.02))],[.024,.020,0],m['gold_light'],8,3) def hd_ballista(m): hd_legs(m,.33,.62) hd_box('Body_Gearbox',(0,.02,.66),(.49,.43,.26),m['basalt'],.035) for s,side in [(-1,'L'),(1,'R')]: hd_disc('Body_Crank'+side,(s*.23,-.21,.73),.16,.055,m['gold']) hd_disc('Body_CrankInset'+side,(s*.23,-.247,.73),.10,.018,m['dark']) for k in range(8): a=k*pi/4 hd_box('Body_CrankTooth'+side+str(k),(s*.23+.137*cos(a),-.212,.73+.137*sin(a)),(.055,.067,.052),m['gold'],.006,rotation=(0,a,0)) hd_box('Weapon_Rail'+side,(s*.13,-.11,.99),(.095,1.15,.14),m['basalt'],.018) hd_box('Weapon_RailTrim'+side,(s*.13,-.11,1.068),(.052,1.17,.020),m['gold'],.005) bow=[(s*.11,-.46,1.00),(s*.39,-.47,.97),(s*.67,-.31,1.01),(s*.80,.045,1.12)] hd_curve('Weapon_GlassLimb'+side,bow,[.095,.12,.095,.051],m['ember'],12,7) hd_curve('Weapon_LimbEdge'+side,[(x,y,z+.068) for x,y,z in bow],[.022,.025,.022,.017],m['gold'],10,7) hd_curve('Weapon_DrawCable'+side,[bow[-1],(s*.37,.26,1.09),(0,.45,1.075)],[.011,.011,.011],m['dark'],10,3) hd_rod('Weapon_LimbClamp'+side,(s*.18,-.52,.95),(s*.18,-.39,1.055),.089,m['gold'],12) hd_box('Weapon_RearHousing',(0,.46,1.06),(.38,.25,.29),m['basalt'],.023) hd_box('Weapon_RearTrim',(0,.40,1.08),(.42,.055,.33),m['gold'],.012) hd_rod('Weapon_CentralBolt',(0,.38,1.085),(0,-.70,1.085),.035,m['ember'],16) hd_curve('Weapon_BoltHead',[(0,-.62,1.085),(0,-.76,1.085),(0,-1.00,1.085)],[.040,.085,0],m['ember'],8,3) for k in range(6): hd_box('Weapon_RailRivet'+str(k),(.17,-.54+k*.18,1.10),(.025,.025,.026),m['gold_light'],.005) def hd_mortar(up,m): hd_legs(m,.29,.42) hd_rock('Body_LowerCore',(0,.035,.60),(.38,.28,.25),m['basalt'],3) radius=.49 if up else .435 # Closed outer/inner profile: deep bore with individual radial barrel staves. profile=[(0,0),(.22,.025),(radius*.83,.24),(radius,.64),(radius*.85,.68),(radius*.74,.60),(radius*.48,.16),(0,.14)] obj=r_lathe('Body_MortarBore',(0,.07,.51),profile,m['dark'],segments=48) hd_finish(obj,.006,2,True) for k in range(12): a=2*pi*k/12 x=radius*.80*cos(a);y=.07+radius*.80*sin(a) o=hd_rock('Body_MortarStave'+str(k),(x,y,.86),(.15,.13,.29),m['basalt'],k) o.rotation_euler[2]=a p=(radius*.98*cos(a),.07+radius*.98*sin(a),1.125) hd_box('Body_RimClasp'+str(k),p,(.115,.13,.15),m['gold'],.014,rotation=(0,0,a-pi/2)) hd_ring('Body_MortarLip',(0,.07,1.175),radius*.91,.033,m['gold_light'],segments=64) hd_ring('Body_MortarBelt',(0,.07,.68),radius*.72,.027,m['gold'],segments=48) pool_z=1.015 if up else .79 hd_finish(cylinder('Weapon_LavaPool',(0,.07,pool_z),radius*.68,.038,m['ember'],48),.005,2,True) if not up: hd_rock('Weapon_EmberBoulder',(0,.02,.87),(.18,.18,.17),m['ember'],1) for k in range(7): a=k*2*pi/7 hd_rock('Weapon_BoulderCrust'+str(k),(.14*cos(a),.02+.14*sin(a),.89),(.074,.068,.10),m['basalt'],k) else: for k in range(3): hd_curve('Weapon_LavaEddy'+str(k),[(.26*cos(a+k),.07+.26*sin(a+k),pool_z+.026) for a in [0,.6,1.1,1.8,2.4]], [.009]*5,m['hot'],8,3) for s,side in [(-1,'L'),(1,'R')]: hd_arm('Arm'+side,(s*.46,.055,.99),(s*.59,-.04,.73),(s*.43,-.29,.52),m,.155) hd_plate('Body_FrontBadge',(0,-.355,.83),[(-.15,.17),(.15,.17),(.11,-.12),(0,-.25),(-.11,-.12)],.060,m['gold'],.009) hd_plate('Body_FrontBadgeStone',(0,-.397,.84),[(-.10,.13),(.10,.13),(.070,-.10),(0,-.18),(-.070,-.10)],.029,m['basalt'],.006) hd_plate('Body_FrontSigil',(0,-.42,.83),[(-.024,.09),(.025,.12),(.037,-.065),(0,-.14),(-.036,-.065)],.018,m['ember'],.003) def hd_hearth(up,m): hd_legs(m,.28,.45) hd_rock('Body_HearthBack',(0,.13,.88),(.43,.32,.44),m['basalt'],1) # Deep oven vault: an open front arch with a visible brick-lined interior. hd_plate('Body_OvenDark',(0,-.20,.99),[(-.30,-.25),(-.30,.10),(-.20,.27),(0,.35),(.20,.27),(.30,.10),(.30,-.25)],.085,m['dark'],.016) if not up: for j in range(3): for k in range(3): hd_box('Body_HearthBrick%d_%d'%(j,k),((k-1)*.15,-.251,.83+j*.10),(.136,.024,.083),m['basalt'],.009) for k in range(3): x=(k-1)*.12 hd_curve('Flame_Oven'+str(k),[(x,-.29,.79),(x+.04,-.30,.93),(x-.015,-.30,1.06+(k%2)*.15)], [.068,.058,0],m['ember'],12,6) for k in range(11): a=pi*k/10 hd_box('Body_ArchStone'+str(k),(.335*cos(a),-.16,1.03+.335*sin(a)),(.125,.25,.19),m['ceramic'],.018,rotation=(0,pi/2-a,0)) for s in [-1,1]: for j in range(2): hd_box('Body_Pillar%d_%d'%(s,j),(s*.335,-.16,.85+j*.14),(.15,.25,.13),m['ceramic'],.015) hd_box('Head_Chimney',(0,.045,1.42),(.35,.31,.25),m['ceramic'],.029) hd_box('Head_ChimneyRecess',(0,-.119,1.41),(.21,.015,.10),m['dark'],.012) hd_box('Head_ChimneyCap',(0,.04,1.56),(.40,.35,.07),m['ceramic'],.017) else: hd_disc('Body_SunSocket',(0,-.30,1.22),.25,.06,m['dark']) hd_ring('Body_SunFrame',(0,-.36,1.22),.248,.042,m['gold'],(pi/2,0,0),64) hd_rock('Body_SunHeart',(0,-.34,1.22),(.203,.092,.203),m['ember'],0) for j in range(3): points=[] for k in range(10): a=k*.30+j*2*pi/3 r=.018+k*.017 points.append((r*cos(a),-.443+.025*(r/.18)**2,1.22+r*sin(a))) hd_curve('Flame_SolarEddy'+str(j),points,[.006]*10,m['hot'],8,3) for k in range(9): a=pi*k/8 hd_curve('Body_HaloRay'+str(k),[(.30*cos(a),-.13,1.22+.30*sin(a)),(.44*cos(a),-.13,1.22+.44*sin(a))],[.045,0],m['gold'],10,3) for s,side in [(-1,'L'),(1,'R')]: hd_plate('Body_Mantle'+side,(s*.40,.07,.99),[(s*-.06,.40),(s*.15,.32),(s*.19,-.24),(s*.10,-.35),(s*-.13,-.20)],.27,m['ceramic'],.020) hd_sun('Body_MantleSun'+side,(s*.43,-.085,1.19),.082,m) hd_plate('Body_Tabard'+side,(s*.40,-.16,.46),[(-.10,.20),(.10,.20),(.105,-.22),(0,-.32),(-.105,-.22)],.045,m['ceramic'],.011) hd_sun('Body_TabardSun'+side,(s*.40,-.19,.42),.060,m) r=.36 if up else .30 obj=r_lathe('Weapon_HearthBowl',(0,-.40,.54),[(0,0),(.15,.02),(r,.16),(r*1.04,.22),(r*.88,.23),(r*.81,.18),(.16,.06),(0,.06)],m['gold'],segments=48) hd_finish(obj,.009,3,True) hd_ring('Weapon_BowlRim',(0,-.40,.762),r*.97,.022,m['gold_light'],segments=48) hd_finish(cylinder('Weapon_EmberBed',(0,-.40,.697),r*.79,.023,m['ember'],40),.005,2,True) for k in range(5): x=(k-2)*.085;h=.19+(.15 if k==2 else .06*(k%2)) hd_curve('Weapon_Flame'+str(k),[(x,-.40,.70),(x-.035,-.38,.70+h*.35),(x+.035,-.38,.70+h*.72),(x-.02,-.38,.70+h)], [.050,.049,.023,0],m['hot'] if k%2 else m['ember'],12,6) for s,side in [(-1,'L'),(1,'R')]: hd_arm('Arm'+side,(s*.42,.02,1.05),(s*.55,-.16,.80),(s*.34,-.41,.65),m,.15) hd_sun('Arm'+side+'_Seal',(s*.45,-.155,1.06),.095,m) hd_sun('Weapon_BowlSeal',(0,-.702,.65),.063,m) if up: hd_curve('Body_SolarStream',[(0,-.44,1.11),(.05,-.45,.98),(-.03,-.43,.83),(0,-.43,.72)],[.023,.024,.016,.013],m['hot'],12,6) def build_high_glutpakt(spec,m): hd_materials(m) role=spec['role'];up=spec['upgraded'] if role=='melee':hd_coal(up,m) elif role=='tank':hd_tank(up,m) elif role=='ranged':hd_ballista(m) if up else hd_lancer(m) elif role=='artillery':hd_mortar(up,m) elif role=='support':hd_hearth(up,m) def build_high_enemy(spec,m): hd_materials(m) id=spec['id'] if id in ('e05','e06','e09','e10'): build_expedition_enemy(id,m) return if id in ('e01','e02'): heavy=id=='e02' hd_rock('Body_Core',(0,0,.52),(.44,.55,.30),m['dark'],2) # Segmented back carapace, raised central ridge and six clawed legs. for j in range(4): y=-.34+j*.25 width=.46 if heavy else .40 hd_plate('Body_Carapace'+str(j),(0,y,.62),[(-width,0),(-width*.67,.23),(0,.35),(width*.67,.23),(width,0),(0,-.09)],.26,m['bone'] if j%2 else m['basalt'],.024) hd_curve('Body_SeamGlow'+str(j),[(-width*.69,y-.145,.83),(0,y-.147,.95),(width*.69,y-.145,.83)],[.012,.015,.012],m['rift'],8,2) if heavy: hd_curve('Body_Spine'+str(j),[(0,y,.94),(0,y+.06,1.15),(0,y+.13,1.28)],[.10,.055,0],m['bone'],12,5) hd_rock('Head_Skull',(0,-.65,.54),(.29,.20,.19),m['basalt'],3) for s,side in [(-1,'L'),(1,'R')]: for k in range(3): y=-.38+k*.35;n='Leg'+side+str(k) hip=(s*.30,y,.53);knee=(s*.61,y-.11,.36);foot=(s*.77,y-.27,.06) hd_rod(n+'_Upper',hip,knee,.070 if heavy else .054,m['basalt']) hd_rock(n+'_Joint',knee,(.085,)*3,m['bone'],k) hd_curve(n+'_Shin',[knee,(s*.75,y-.20,.20),foot],[.076,.045,0],m['bone'],12,4) hd_rod(n+'_Inset',tuple(Vector(hip)+Vector((0,-.04,.035))),tuple(Vector(knee)+Vector((0,-.04,.035))),.014,m['rift'],8) hd_rock('Head_Eye'+side,(s*.115,-.816,.60),(.055,.021,.036),m['rift'],0) hd_curve('Head_Mandible'+side,[(s*.19,-.74,.45),(s*.29,-.91,.40),(s*.17,-1.00,.44)],[.070,.046,0],m['bone'],12,6) elif id=='e07': hd_rock('Body_DeerCore',(0,.12,1.12),(.38,.62,.42),m['dark'],1) for j in range(5): y=-.38+j*.22 hd_rock('Body_BackArmor'+str(j),(0,y,1.37),(.40,.19,.21),m['basalt'],j) for s,side in [(-1,'L'),(1,'R')]: for j,y in enumerate([-.35,.57]): n='Leg'+side+str(j) hip=(s*.27,y,1.08); knee=(s*.36,y+.10,.60); foot=(s*.36,y-.02,.08) hd_rod(n+'_Upper',hip,knee,.082,m['dark']) hd_rock(n+'_Knee',knee,(.12,.13,.14),m['basalt'],s) hd_rod(n+'_Shin',knee,foot,.060,m['bone']) hd_box(n+'_Hoof',(foot[0],foot[1]-.04,.10),(.19,.27,.20),m['dark'],.025) hd_box(n+'_ClovenLine',(foot[0],foot[1]-.179,.105),(.018,.012,.16),m['rift'],.004) for j in range(4): hd_curve('Body_Rib'+side+str(j),[(s*.12,-.16+j*.16,1.43),(s*.35,-.14+j*.16,1.26),(s*.32,-.13+j*.16,.99)],[.065,.074,.037],m['bone'],10,3) hd_curve('Head_Antler'+side,[(s*.17,-.39,1.89),(s*.36,-.25,2.17),(s*.56,-.10,2.42),(s*.50,.05,2.61)],[.075,.065,.036,0],m['bone'],12,7) for k in range(3): z=2.05+k*.14;x=s*(.28+k*.13);y=-.30+k*.09 hd_curve('Head_AntlerProng'+side+str(k),[(x,y,z),(x+s*.19,y-.05,z+.15),(x+s*.20,y-.13,z+.28)],[.041,.028,0],m['bone'],10,5) hd_rock('Head_Eye'+side,(s*.155,-.77,1.82),(.060,.041,.039),m['rift'],0) hd_rock('Head_Neck',(0,-.36,1.54),(.23,.26,.37),m['basalt'],2) hd_rock('Head_Skull',(0,-.58,1.79),(.25,.24,.25),m['bone'],4) hd_rock('Head_Muzzle',(0,-.81,1.69),(.16,.24,.13),m['bone'],3) hd_box('Head_Nose',(0,-1.01,1.69),(.21,.055,.12),m['dark'],.022) hd_curve('Body_Tail',[(0,.70,1.20),(0,.89,1.05),(0,.95,.88)],[.075,.053,0],m['bone'],10,5) else: colossus=id=='e08'; ghost=id=='e03' if colossus: # A stone portcullis knight, with layered breastplates and battlements. mm=dict(m);mm['gold']=m['bone'];mm['gold_light']=m['bone'];mm['ember']=m['rift'];mm['hot']=m['rift'] hd_tank(True,mm) for s in [-1,1]: hd_curve('Body_RiftHorn'+str(s),[(s*.30,.06,1.48),(s*.40,.03,1.73),(s*.25,.04,1.86)],[.095,.066,0],m['bone'],12,5) return hd_rock('Body_Torso',(0,.025,1.08),(.24,.17,.34),m['dark'],2) for j in range(5): z=.93+j*.09 for s,side in [(-1,'L'),(1,'R')]: hd_curve('Body_Rib'+side+str(j),[(s*.04,-.12,z),(s*.20,-.12,z+.03),(s*.23,.025,z+.01)],[.025,.033,.022],m['bone'],10,3) hd_rock('Head_Skull',(0,-.005,1.63),(.20,.145,.24),m['bone'],1) hd_plate('Head_Face',(0,-.16,1.59),[(-.14,.08),(.14,.08),(.10,-.12),(0,-.18),(-.10,-.12)],.045,m['dark'],.012) for s,side in [(-1,'L'),(1,'R')]: hd_curve('Head_Eye'+side,[(s*.035,-.20,1.65),(s*.12,-.20,1.68)],[.020,.015],m['rift'],8,2) hd_arm('Arm'+side,(s*.24,.0,1.29),(s*.38,-.09,1.09),(s*.43,-.31,1.15),m,.065) if ghost: for k in range(7): a=2*pi*k/7 hd_curve('Body_Shroud'+str(k),[(.18*cos(a),.14*sin(a),1.0),(.25*cos(a+.3),.21*sin(a+.3),.59),(.14*cos(a+.6),.16*sin(a+.6),.18),(.30*cos(a+.8),.21*sin(a+.8),.04)],[.08,.09,.048,0],m['basalt'] if k%2 else m['dark'],12,6) hd_ring('Body_RiftHalo',(0,.08,1.71),.31,.019,m['rift'],(pi/2,0,0),48) else: hd_legs(m,.18,.85,True) for s in [-1,1]: hd_curve('Weapon_Bow'+str(s),[(0,-.44,1.11),(s*.37,-.44,1.11),(s*.64,-.25,1.15)],[.05,.044,.022],m['bone'],12,6) hd_curve('Weapon_String'+str(s),[(s*.64,-.25,1.15),(0,-.16,1.15)],[.009,.009],m['rift'],8,2) hd_rod('Weapon_Arrow',(0,-.15,1.15),(0,-.92,1.15),.02,m['rift'],12) def hd_mask(name,loc,r,m): x,y,z=loc outline=[(-r*.78,r*.58),(-r*.44,r),(.44*r,r),(.78*r,.58*r),(.61*r,-.62*r),(0,-r),(-.61*r,-.62*r)] hd_plate(name+'_Porcelain',loc,outline,r*.32,m['bone'],.015,.025) for s in [-1,1]: hd_plate(name+'_EyeSocket'+str(s),(x+s*r*.33,y-r*.195,z+r*.15),[(-r*.21,r*.08),(r*.21,r*.08),(r*.16,-r*.12),(-r*.12,-r*.16)],.025,m['dark'],.003) hd_curve(name+'_EyeLight'+str(s),[(x+s*r*.33-r*.13,y-r*.23,z+r*.12),(x+s*r*.33+r*.12,y-r*.23,z+r*.12)],[r*.027]*2,m['rift'],8,2) hd_plate(name+'_Nose',(x,y-r*.22,z-r*.10),[(-r*.12,r*.25),(r*.12,r*.25),(0,-r*.1)],.055,m['edge'],.004) for k in range(3): hd_box(name+'_MouthSlit'+str(k),(x+(k-1)*r*.16,y-r*.18,z-r*.54),(r*.055,.017,r*.18),m['dark'],.003) def build_expedition_enemy(id,m): """Four distinct expedition silhouettes; every part uses the same rigid rig contract.""" stone=dict(m); stone['gold']=m['bone'];stone['gold_light']=m['edge'] if id=='e05': # Broad hunchback; massive capped stone gauntlets distinguish this bruiser. hd_legs(stone,.32,.60) hd_rock('Body_BruteCore',(0,.06,1.03),(.43,.31,.48),m['dark'],2) for j in range(3): z=.75+j*.22 for s in [-1,1]: hd_plate('Body_Chest%d_%d'%(j,s),(s*.21,-.245,z),[(-.20,-.09),(.18,-.10),(.22,.11),(-.14,.14)],.15,m['basalt'],.024,.035) hd_curve('Body_RiftCrack',[(0,-.34,.66),(.025,-.36,.91),(-.03,-.36,1.14),(0,-.34,1.30)],[.018]*4,m['rift'],8,3) for j in range(4): hd_rock('Body_BackSlab'+str(j),(0,.23,.76+j*.21),(.45,.23,.17),m['basalt'],j) hd_mask('Head_Mask',(0,-.25,1.49),.20,m) for s,side in [(-1,'L'),(1,'R')]: hd_arm('Arm'+side,(s*.43,.045,1.32),(s*.65,-.02,1.00),(s*.65,-.15,.54),stone,.175) hd_box('Arm'+side+'_StoneHammer',(s*.65,-.17,.52),(.42,.48,.36),m['basalt'],.06) for k in range(3): hd_box('Arm'+side+'_Knuckle'+str(k),(s*.65+(k-1)*.12,-.419,.57),(.10,.07,.18),m['bone'],.022) hd_box('Arm'+side+'_Cuff',(s*.65,-.11,.72),(.44,.38,.075),m['edge'],.018) for k in range(3): hd_curve('Body_ShoulderSpike'+side+str(k),[(s*(.29+k*.13),.05,1.41),(s*(.36+k*.16),.065,1.70-k*.07)],[.10,0],m['bone'],12,5) elif id in ('e06','e09'): boss=id=='e09' # Floating knotted robe and staff. The judge has a wide suspended mask crown. hd_rock('Body_RobeCore',(0,.015,1.02),(.23,.19,.41),m['dark'],3) for k in range(9): a=k*2*pi/9 hd_curve('Body_RobeFold'+str(k),[(.19*cos(a),.14*sin(a),1.31),(.25*cos(a+.15),.22*sin(a+.15),.84),(.30*cos(a+.25),.27*sin(a+.25),.32),(.37*cos(a+.2),.30*sin(a+.2),.10)], [.065,.082,.054,.009],m['basalt'] if k%2 else m['dark'],12,7) for j in range(4): hd_ring('Body_KnotRing'+str(j),(0,0,.92+j*.085),.245-j*.012,.019,m['bone'],segments=40) hd_mask('Head_Face',(0,-.015,1.60),.235 if boss else .195,m) for s,side in [(-1,'L'),(1,'R')]: hd_arm('Arm'+side,(s*.26,0,1.29),(s*.38,-.11,1.04),(s*.43,-.26,1.22),stone,.081) hd_plate('Arm'+side+'_Pauldron',(s*.28,0,1.34),[(-.12,.02),(0,.16),(.16,.06),(.12,-.13),(-.10,-.13)],.19,m['bone'],.018) hd_curve('Weapon_Staff',[(.47,-.28,.12),(.46,-.28,.73),(.45,-.27,1.35),(.49,-.27,1.83)],[.028,.035,.035,.025],m['bone'],12,8) for k in range(5): hd_ring('Weapon_StaffCollar'+str(k),(.475,-.275,.68+k*.12),.045,.012,m['edge'],segments=24) hd_ring('Weapon_StaffKnot',(.49,-.27,1.82),.17,.03,m['bone'],(pi/2,0,0),48) hd_rock('Weapon_StaffHeart',(.49,-.27,1.82),(.105,.06,.105),m['rift'],0) for s in [-1,1]: hd_curve('Weapon_KnotBranch'+str(s),[(.48+s*.06,-.27,1.68),(.48+s*.20,-.27,1.84),(.48+s*.11,-.26,1.99)],[.028,.022,0],m['bone'],10,7) if boss: hd_ring('Body_JudgeHalo',(0,.12,1.53),.65,.034,m['bone'],(pi/2,0,0),72) hd_ring('Body_JudgeGlow',(0,.135,1.53),.60,.012,m['rift'],(pi/2,0,0),72) for k in range(5): a=.12*pi+k*.19*pi x=.65*cos(a);z=1.53+.65*sin(a) hd_mask('Body_CourtMask'+str(k),(x,.065,z),.16,m) hd_curve('Body_CrownSpike'+str(k),[(x,.14,z+.12),(x*1.07,.14,z+.32)],[.065,0],m['bone'],10,5) for s in [-1,1]: hd_curve('Body_Chains'+str(s),[(s*.54,.10,1.63),(s*.64,-.03,1.07),(s*.46,-.16,.47)],[.018]*3,m['bone'],10,9) else: for j in range(3): hd_ring('Body_HealingKnot'+str(j),((j-1)*.13,-.205,1.09),.084,.019,m['rift'],(pi/2,0,.4*j),40) else: # Final boss: four heavy legs, segmented carapace, actual open maw and a crown. hd_rock('Body_CrownCore',(0,.13,.98),(.54,.58,.52),m['dark'],1) for s,side in [(-1,'L'),(1,'R')]: for j,y in enumerate([-.10,.64]): n='Leg'+side+str(j);hip=(s*.40,y,.92);knee=(s*.66,y+.06,.51);foot=(s*.70,y-.10,.12) hd_rod(n+'_Inner',hip,knee,.10,m['dark']) hd_rock(n+'_Thigh',tuple((Vector(hip)+Vector(knee))*.5),(.18,.19,.30),m['basalt'],j) hd_rock(n+'_Shin',tuple((Vector(knee)+Vector(foot))*.5),(.16,.18,.24),m['basalt'],j+1) hd_disc(n+'_Joint',(knee[0],knee[1]-.18,knee[2]),.105,.045,m['bone']) for k in range(3): x=foot[0]+(k-1)*.09 hd_curve(n+'_Claw'+str(k),[(x,y-.05,.19),(x,y-.27,.10),(x,y-.39,.055)],[.064,.043,0],m['bone'],12,5) for j in range(4): hd_rock('Body_Shell'+side+str(j),(s*.37,.02+j*.20,1.20),(.25,.19,.28),m['basalt'],j) hd_curve('Body_Ridge'+side+str(j),[(s*.32,.02+j*.20,1.37),(s*.41,.06+j*.20,1.67),(s*.30,.14+j*.20,1.78)],[.105,.069,0],m['bone'],12,6) hd_disc('Head_MawVoid',(0,-.43,.99),.40,.15,m['dark']) hd_ring('Head_MawRim',(0,-.53,.99),.38,.065,m['basalt'],(pi/2,0,0),64) hd_disc('Head_RiftThroat',(0,-.522,.99),.22,.025,m['rift']) for k in range(14): a=k*2*pi/14 hd_curve('Head_Tooth'+str(k),[(.35*cos(a),-.58,.99+.35*sin(a)),(.29*cos(a),-.63,.99+.29*sin(a)),(.19*cos(a+.1),-.60,.99+.19*sin(a+.1))],[.058,.044,0],m['bone'],12,5) for s in [-1,1]: hd_rock('Head_Brow'+str(s),(s*.24,-.34,1.41),(.27,.17,.16),m['basalt'],s) hd_curve('Head_Eye'+str(s),[(s*.12,-.498,1.41),(s*.31,-.49,1.45)],[.023,.029],m['rift'],10,4) hd_ring('Head_CrownBand',(0,.02,1.63),.36,.052,m['bone'],segments=64) for k in range(7): a=k*2*pi/7 hd_curve('Head_CrownPoint'+str(k),[(.36*cos(a),.02+.36*sin(a),1.62),(.47*cos(a),.02+.47*sin(a),1.91),(.37*cos(a),.02+.37*sin(a),2.20 if k%2 else 2.06)],[.095,.065,0],m['bone'],12,7) hd_rock('Head_CrownGem'+str(k),(.38*cos(a),.02+.38*sin(a),1.69),(.055,)*3,m['rift'],k) hd_curve('Body_Tail',[(0,.61,.86),(0,.97,.66),(.18,1.13,.35),(.30,1.22,.25)],[.14,.11,.06,0],m['basalt'],16,8) def enhance_existing(spec,m): """Round existing neutral/environment geometry and add constructed details.""" for obj in list(bpy.context.scene.objects): if obj.type!='MESH':continue dimensions=obj.dimensions width=min(min(dimensions)*.09,.014) hd_finish(obj,max(.001,width),3) if spec['role']=='lane': hd_materials(m) # Detailed edge stones, anchored brass plates and inset light wells. for s in [-1,1]: for j in range(4): y=-1.5+j hd_box('Body_EdgeStone%d_%d'%(s,j),(s*6.13,y,.16),(.30,.91,.29),m['basalt'],.035) hd_box('Body_EdgeCap%d_%d'%(s,j),(s*6.13,y,.32),(.34,.92,.060),m['gold'],.014) for j,y in enumerate([-.95,.95]): hd_box('Body_LightRecess%d_%d'%(s,j),(s*6.13,y,.364),(.15,.22,.035),m['dark'],.009) hd_box('Body_LightGlass%d_%d'%(s,j),(s*6.13,y,.387),(.065,.14,.013),m['ember'],.005) elif spec['role']=='core': hd_materials(m) for k in range(8): a=k*pi/4 hd_box('Body_PlinthArmor'+str(k),(1.02*cos(a),1.02*sin(a),.18),(.37,.25,.29),m['basalt'],.029,rotation=(0,0,a+pi/2)) hd_ring('Body_PlinthGold'+str(k),(1.04*cos(a),1.04*sin(a),.35),.068,.012,m['gold'],segments=24) hd_ring('Body_OuterRim',(0,0,.36),1.19,.026,m['gold'],segments=72) def finalize_high_geometry(): """Resolve tiny bevel slivers and retain valid corner normals after triangulation. Thin inset discs can produce normals across their back edge when weighted normals are computed on n-gons. Triangulate first, then constrain only those corner normals that point below the actual triangle surface. """ for obj in bpy.context.scene.objects: if obj.type != 'MESH': continue mesh=obj.data bm=bmesh.new(); bm.from_mesh(mesh) bmesh.ops.remove_doubles(bm,verts=list(bm.verts),dist=.000001) bmesh.ops.dissolve_degenerate(bm,edges=list(bm.edges),dist=.000001) bmesh.ops.recalc_face_normals(bm,faces=list(bm.faces)) bmesh.ops.triangulate(bm,faces=list(bm.faces),quad_method='BEAUTY',ngon_method='EAR_CLIP') bm.to_mesh(mesh);bm.free();mesh.update() normals=[tuple(n.vector) for n in mesh.corner_normals] for face in mesh.polygons: for index in face.loop_indices: if Vector(normals[index]).dot(face.normal)<.01: normals[index]=tuple(face.normal) mesh.normals_split_custom_set(normals)