Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
197 lines
10 KiB
Python
197 lines
10 KiB
Python
"""Mechanical silhouettes matched to the Phase-3 unit icons."""
|
|
from math import sin, cos, pi
|
|
from common import box, ellipsoid, cylinder, beam, cone, ring
|
|
from reference_geometry import r_plate, r_block, r_sweep, r_lathe, r_band, r_disc, r_inset, r_rock
|
|
|
|
|
|
def m_joint(name,loc,radius,m):
|
|
r_disc(name,loc,radius,.065,m['secondary'])
|
|
r_disc(name+'_Pin',(loc[0],loc[1]-.04,loc[2]),radius*.55,.026,m['accent'])
|
|
|
|
|
|
def m_legs(m,tall=False,wide=False,four=False):
|
|
height=.73 if tall else .43
|
|
spread=.27 if wide else .20
|
|
for k in range(4 if four else 2):
|
|
s=-1 if k%2==0 else 1
|
|
y=.29 if k>1 else -.045
|
|
x=s*spread
|
|
hip=(x*.68,y+.045,height)
|
|
knee=(x*1.22,y+.09,height*.58)
|
|
ankle=(x,y,.11)
|
|
side=str(k)
|
|
r_band('Leg'+side+'_Piston',hip,knee,.055 if tall else .085,m['accent'],6)
|
|
r_band('Leg'+side+'_Shin',knee,ankle,.048 if tall else .07,m['accent'],6)
|
|
m_joint('Leg'+side+'_Knee',(knee[0],knee[1]-.02,knee[2]),.065 if tall else .09,m)
|
|
r_plate('Leg'+side+'_ThighArmor',(hip[0],y-.022,height*.82),
|
|
[(-.07,.12),(.07,.10),(.035,-.12),(-.045,-.09)],.08,m['light'])
|
|
r_plate('Leg'+side+'_Greave',(x,y-.045,height*.35),
|
|
[(-.055,.12),(.07,.15),(.035,-.16),(-.045,-.09)],.07,m['primary'])
|
|
for j in range(2):
|
|
toe=(x+(j-.5)*.095,y-.16,.02)
|
|
r_sweep('Leg'+side+'_Toe'+str(j),[(x+(j-.5)*.04,y,.095),toe],[.065,0],m['accent'],5)
|
|
r_band('Leg'+side+'_AnkleGold',(x,y,.10),(x,y,.15),.067,m['secondary'],8)
|
|
|
|
|
|
def m_face(name,loc,m,round_face=False,tall=False):
|
|
x,y,z=loc
|
|
if round_face:
|
|
r_disc(name+'_BrassFrame',loc,.145,.055,m['secondary'])
|
|
r_disc(name+'_Ceramic',(x,y-.038,z),.118,.035,m['light'])
|
|
for s in [-1,1]:
|
|
r_disc(name+'_Eye'+str(s),(x+s*.051,y-.065,z+.027),.027,.015,m['accent'])
|
|
box(name+'_Mouth',(x,y-.068,z-.048),(.068,.012,.013),m['accent'])
|
|
else:
|
|
r_plate(name+'_Face',loc,[(-.09,.10),(.085,.10),(.074,-.03),(0,-.15),(-.076,-.025)],.09,m['light'])
|
|
for s in [-1,1]:
|
|
r_plate(name+'_Eye'+str(s),(x+s*.04,y-.075,z+.016),[(-.023,.01),(.023,.01),(0,-.013)],.012,m['accent'])
|
|
if tall:
|
|
r_plate(name+'_Crest',(x,y+.035,z+.12),[(-.11,-.05),(.10,-.04),(-.04,.24)],.10,m['primary'])
|
|
|
|
|
|
def m_torso(m,up=False,z=.98):
|
|
outline=[(-.22,.20),(.22,.20),(.14,-.04),(0,-.30),(-.14,-.04)]
|
|
r_inset('Body_Breastplate',(0,.025,z),outline,.25,m['secondary'],m['primary'])
|
|
r_band('Body_Waist',(0,.03,z-.30),(0,.03,z-.12),.085,m['accent'],8)
|
|
for s,side in [(-1,'L'),(1,'R')]:
|
|
m_joint('Arm'+side+'_Shoulder',(s*.22,.015,z+.11),.078,m)
|
|
r_plate('Body_HipPlate'+side,(s*.16,.04,z-.26),[(s*-.07,.13),(s*.08,.07),(s*.12,-.14),(0,-.10)],.09,m['light'])
|
|
|
|
|
|
def m_boiler(up,m):
|
|
m_legs(m,False,True)
|
|
if not up:
|
|
ellipsoid('Body_RoundKettle',(0,.04,.83),(.40,.31,.43),m['primary'],subdivisions=2)
|
|
ring('Body_Belt',(0,.04,.64),.335,.036,m['secondary'],segments=12)
|
|
m_face('Head',(0,-.27,.99),m,True)
|
|
r_disc('Body_PressureGauge',(-.22,-.24,.86),.095,.035,m['secondary'])
|
|
r_disc('Body_GaugeFace',(-.22,-.266,.86),.071,.018,m['light'])
|
|
r_band('Body_GaugeNeedle',(-.22,-.28,.86),(-.19,-.28,.90),.009,m['accent'],4)
|
|
r_lathe('Body_Chimney',(-.15,.09,1.14),[(.075,0),(.075,.23),(.053,.23),(.053,.025)],m['accent'],segments=8)
|
|
ring('Body_ChimneyLip',(-.15,.09,1.37),.076,.018,m['secondary'],segments=8)
|
|
else:
|
|
r_block('Body_HeavyChest',(0,.04,.89),(.65,.45,.60),m['primary'])
|
|
r_inset('Body_CeramicChest',(0,-.22,.89),[(-.13,.27),(.13,.27),(.11,-.20),(0,-.25),(-.11,-.20)],.09,m['secondary'],m['light'])
|
|
m_face('Head',(0,-.005,1.28),m)
|
|
for s,side in [(-1,'L'),(1,'R')]:
|
|
cylinder('Body_PressureTank'+side,(s*.30,.16,1.20),.14,.47,m['light'],vertices=10)
|
|
for z in [1.00,1.40]:
|
|
cylinder('Body_TankBand'+side+str(z),(s*.30,.16,z),.155,.05,m['accent'],vertices=10)
|
|
cylinder('Body_ValveStem'+side,(s*.30,.16,1.50),.025,.13,m['secondary'],vertices=6)
|
|
ring('Body_ValveWheel'+side,(s*.30,.16,1.56),.07,.016,m['secondary'],segments=8)
|
|
for s,side in [(-1,'L'),(1,'R')]:
|
|
sh=(s*.40,.04,.98); elbow=(s*.48,-.035,.73); hand=(s*.46,-.14,.57)
|
|
r_band('Arm'+side+'_Upper',sh,elbow,.075,m['accent'],8)
|
|
r_band('Arm'+side+'_Forearm',elbow,hand,.10,m['accent'],8)
|
|
m_joint('Arm'+side+'_Joint',(s*.40,-.075,.98),.13,m)
|
|
r_block('Arm'+side+'_Fist',hand,(.23,.23,.23),m['accent'])
|
|
r_band('Arm'+side+'_Cuff',(s*.46,-.11,.64),(s*.46,-.12,.59),.13,m['secondary'],8)
|
|
if up:
|
|
r_block('Arm'+side+'_Pauldron',(s*.40,.04,1.07),(.29,.38,.25),m['primary'])
|
|
r_inset('Arm'+side+'_Shield',(s*.49,-.16,.79),[(-.14,.23),(.14,.23),(.15,-.13),(0,-.23),(-.15,-.13)],.13,m['secondary'],m['primary'] if s>0 else m['light'])
|
|
|
|
|
|
def m_shear(up,m):
|
|
m_legs(m,True,up)
|
|
m_torso(m,up,1.02)
|
|
m_face('Head',(0,-.035,1.35),m,False,not up)
|
|
levels=[(1.20,1.57),(1.09,1.11),(.96,.71)] if up else [(1.16,1.00)]
|
|
for s,side in [(-1,'L'),(1,'R')]:
|
|
for k,(root_z,end_z) in enumerate(levels):
|
|
shoulder=(s*.18,.04,root_z)
|
|
elbow=(s*.39,.035,(root_z+end_z)/2)
|
|
wrist=(s*.58,-.06,end_z)
|
|
r_band('Arm'+side+str(k)+'_Rod',shoulder,elbow,.047,m['accent'],6)
|
|
r_band('Arm'+side+str(k)+'_Housing',elbow,wrist,.065,m['primary'],6)
|
|
m_joint('Arm'+side+str(k)+'_Pivot',(wrist[0],wrist[1]-.025,wrist[2]),.065,m)
|
|
directions=[.18] if up else [-.20,.20]
|
|
for j,dz in enumerate(directions):
|
|
points=[wrist,(s*.76,-.08,end_z+dz*.65),(s*.97,-.06,end_z+dz)]
|
|
r_sweep('Weapon'+side+str(k)+'_Blade'+str(j),points,[.040,.115,0],m['accent'],4,ribbon=True)
|
|
|
|
|
|
def m_crossbow(name,loc,width,length,m):
|
|
x,y,z=loc
|
|
r_block(name+'_Rail',loc,(.14,length,.11),m['accent'])
|
|
for s in [-1,1]:
|
|
points=[(x+s*.07,y-length*.27,z),(x+s*width*.33,y-length*.34,z),
|
|
(x+s*width*.50,y,z+.035)]
|
|
r_sweep(name+'_Bow'+str(s),points,[.065,.065,.025],m['accent'],5)
|
|
r_band(name+'_String'+str(s),points[-1],(x,y+length*.42,z+.025),.012,m['secondary'],4)
|
|
r_band(name+'_Bolt',(x,y+length*.35,z+.071),(x,y-length*.70,z+.071),.024,m['light'],5)
|
|
r_sweep(name+'_BoltTip',[(x,y-length*.60,z+.07),(x,y-length*.85,z+.07)],[.06,0],m['secondary'],4)
|
|
for j,yoff in enumerate([-.2,.15]):
|
|
r_block(name+'_Binding'+str(j),(x,y+yoff*length,z),(.19,.04,.15),m['secondary'])
|
|
|
|
|
|
def m_ranged(up,m):
|
|
m_legs(m,True,False,up)
|
|
m_torso(m,up,1.01)
|
|
m_face('Head',(0,-.025,1.37),m,False,True)
|
|
if not up:
|
|
r_disc('Head_Scope',(.073,-.094,1.39),.044,.055,m['secondary'])
|
|
for k in range(3):
|
|
r_band('Body_QuiverBolt'+str(k),(-.14+k*.055,.23,1.0),(-.14+k*.055,.23,1.43),.017,m['secondary'],5)
|
|
for s,side in [(-1,'L'),(1,'R')]:
|
|
hand=(s*.23,-.23,1.07 if up else .88)
|
|
r_band('Arm'+side+'_Upper',(s*.23,.02,1.16),(s*.32,-.025,.97),.048,m['accent'],6)
|
|
r_band('Arm'+side+'_Lower',(s*.32,-.025,.97),hand,.063,m['primary'],6)
|
|
m_joint('Arm'+side+'_Wrist',hand,.048,m)
|
|
m_crossbow('Weapon_Crossbow',(0 if up else .27,-.26,1.11 if up else .89),1.25 if up else .48,.84 if up else .53,m)
|
|
|
|
|
|
def m_resonator(up,m):
|
|
m_legs(m,False,True,up)
|
|
r_block('Body_SiegeChassis',(0,.10,.62),(.64,.50,.31),m['primary'])
|
|
m_face('Head',(0,-.015,1.26 if up else 1.22),m,False,False)
|
|
centres=[-.31,.31] if up else [0]
|
|
for j,x in enumerate(centres):
|
|
# Body is the horizontal resonator, not a thin man carrying a trumpet.
|
|
axis=(0,-1,0)
|
|
r_lathe('Body_ResonatorHousing'+str(j),(x,.30,.91),[(0,0),(.26,0),(.29,.18),(.27,.46),(0,.46)],m['primary'],axis,12)
|
|
if up:
|
|
r_lathe('Weapon_ImpactDisc'+str(j),(x,-.17,.91),[(0,0),(.30,0),(.36,.08),(.34,.13),(.12,.16),(0,.16)],m['secondary'],axis,12)
|
|
r_band('Weapon_DiscPiston'+str(j),(x,-.28,.91),(x,-.48,.91),.075,m['accent'],8)
|
|
else:
|
|
r_lathe('Weapon_ResonatorBell',(x,-.09,.91),[(.13,0),(.17,.15),(.35,.42),(.40,.47),(.34,.47),(.12,.14),(0,.12),(0,0)],m['secondary'],axis,12)
|
|
r_band('Weapon_ResonatorPiston',(x,-.21,.91),(x,-.63,.91),.078,m['accent'],8)
|
|
for s in [-1,1]:
|
|
r_block('Body_HousingLatch'+str(j)+'_'+str(s),(x+s*.265,-.02,.91),(.065,.10,.19),m['secondary'])
|
|
|
|
|
|
def m_conductor(up,m):
|
|
m_legs(m,not up,False)
|
|
if up:
|
|
# Broad floor-length open coat, cream stole and large exposed pendulum.
|
|
r_lathe('Body_Coat',(0,.075,.12),[(0,0),(.50,0),(.20,.94),(.12,1.11),(0,1.11)],m['primary'],segments=8)
|
|
for s,side in [(-1,'L'),(1,'R')]:
|
|
r_plate('Body_CreamStole'+side,(s*.20,-.205,.84),[(-.065,.38),(.065,.38),(.11,-.40),(0,-.48),(-.055,-.37)],.035,m['light'])
|
|
r_band('Body_CoatGold'+side,(s*.10,-.26,1.17),(s*.45,-.26,.19),.02,m['secondary'],4)
|
|
r_plate('Body_PendulumOpening',(0,-.28,.76),[(-.09,.34),(.09,.34),(.13,-.34),(-.13,-.34)],.025,m['accent'])
|
|
r_band('Body_PendulumStem',(0,-.309,1.08),(0,-.309,.55),.022,m['secondary'],5)
|
|
r_disc('Body_PendulumBob',(0,-.325,.53),.13,.035,m['secondary'])
|
|
m_face('Head',(0,-.04,1.40),m)
|
|
for j,x in enumerate([-.12,0,.12]):
|
|
r_sweep('Head_Crown'+str(j),[(x,.025,1.48),(x*1.25,.025,1.78 if j==1 else 1.68)],
|
|
[.035,0],m['secondary'],4)
|
|
else:
|
|
m_torso(m,False,1.01)
|
|
m_face('Head',(0,-.025,1.37),m,True)
|
|
r_band('Body_PendulumStem',(0,-.17,1.06),(0,-.17,.80),.017,m['secondary'],5)
|
|
r_disc('Body_PendulumBob',(0,-.19,.78),.06,.028,m['secondary'])
|
|
for s,side in [(-1,'L'),(1,'R')]:
|
|
hand=(s*.53,-.11,1.52 if s<0 else 1.12)
|
|
elbow=(s*.41,.015,1.20 if s<0 else 1.02)
|
|
r_band('Arm'+side+'_Upper',(s*.22,.03,1.20),elbow,.045,m['accent'],6)
|
|
r_band('Arm'+side+'_Forearm',elbow,hand,.046,m['accent'],6)
|
|
m_joint('Arm'+side+'_Elbow',elbow,.065,m)
|
|
r_rock('Arm'+side+'_Palm',hand,(.055,.04,.07),m['accent'])
|
|
for j in range(3):
|
|
end=(hand[0]+s*.10,hand[1]-.02,hand[2]+(j-1)*.046)
|
|
r_band('Arm'+side+'_Finger'+str(j),hand,end,.012,m['accent'],4)
|
|
if s<0:
|
|
r_band('Weapon_Baton',hand,(hand[0]+.20,hand[1],hand[2]+.33),.012,m['secondary'],4)
|
|
|
|
|
|
def build_nietenkonvent(spec,m):
|
|
{'tank':m_boiler,'melee':m_shear,'ranged':m_ranged,'artillery':m_resonator,'support':m_conductor}[spec['role']](spec['upgraded'],m)
|