Added F3D/skeleton/animation importing for OOT.

This commit is contained in:
kurethedead
2021-03-04 21:28:25 -08:00
parent 19fa4d9833
commit 3f05935e52
24 changed files with 3394 additions and 816 deletions
+28 -6
View File
@@ -40,9 +40,7 @@ class ArmatureApplyWithMesh(bpy.types.Operator):
# Can also be called from operator search menu (Spacebar)
def execute(self, context):
try:
if context.mode != 'OBJECT' and context.mode != 'POSE':
raise PluginError("Operator can only be used in object or pose mode.")
elif context.mode == 'POSE':
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = "OBJECT")
if len(context.selected_objects) == 0:
@@ -72,7 +70,11 @@ class ArmatureApplyWithMesh(bpy.types.Operator):
context.view_layer.objects.active = armatureObj
bpy.ops.object.mode_set(mode = "POSE")
bpy.ops.pose.armature_apply()
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = "OBJECT")
except Exception as e:
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = "OBJECT")
raisePluginError(self, e)
return {"CANCELLED"}
@@ -125,9 +127,7 @@ class CreateMetarig(bpy.types.Operator):
# Can also be called from operator search menu (Spacebar)
def execute(self, context):
try:
if context.mode != 'OBJECT' and context.mode != 'POSE':
raise PluginError("Operator can only be used in object or pose mode.")
elif context.mode == 'POSE':
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = "OBJECT")
if len(context.selected_objects) == 0:
@@ -231,6 +231,23 @@ class Fast64_GlobalSettingsPanel(bpy.types.Panel):
prop_split(col, context.scene, 'gameEditorMode', "Game")
col.prop(context.scene, 'exportHiddenGeometry')
col.prop(context.scene, 'fullTraceback')
class Fast64_GlobalToolsPanel(bpy.types.Panel):
bl_idname = "FAST64_PT_global_tools"
bl_label = "Fast64 Tools"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Fast64'
@classmethod
def poll(cls, context):
return True
# called every frame
def draw(self, context):
col = self.layout.column()
col.operator(ArmatureApplyWithMesh.bl_idname)
#col.operator(CreateMetarig.bl_idname)
#def updateGameEditor(scene, context):
@@ -260,6 +277,7 @@ classes = (
F3D_GlobalSettingsPanel,
Fast64_GlobalSettingsPanel,
SM64_ArmatureToolsPanel,
Fast64_GlobalToolsPanel,
)
# called on add-on enabling
@@ -278,6 +296,7 @@ def register():
register_class(cls)
f3d_writer_register()
f3d_parser_register()
# ROM
@@ -293,10 +312,12 @@ def register():
name = 'Save Textures As PNGs (Breaks CI Textures)')
bpy.types.Scene.generateF3DNodeGraph = bpy.props.BoolProperty(name = "Generate F3D Node Graph", default = True)
bpy.types.Scene.exportHiddenGeometry = bpy.props.BoolProperty(name = "Export Hidden Geometry", default = True)
bpy.types.Scene.blenderF3DScale = bpy.props.FloatProperty(name = "F3D Blender Scale", default = 100)
# called on add-on disabling
def unregister():
f3d_writer_unregister()
f3d_parser_unregister()
sm64_unregister(True)
oot_unregister(True)
mat_unregister()
@@ -311,6 +332,7 @@ def unregister():
del bpy.types.Scene.gameEditorMode
del bpy.types.Scene.generateF3DNodeGraph
del bpy.types.Scene.exportHiddenGeometry
del bpy.types.Scene.blenderF3DScale
for cls in classes:
unregister_class(cls)
+2
View File
@@ -294,6 +294,7 @@ enumRenderModesCycle1 = [
('G_RM_ZB_XLU_SURF', "Transparent (No AA)", 'G_RM_ZB_XLU_SURF'),
('G_RM_ZB_XLU_DECAL', "Transparent Decal (No AA)", 'G_RM_ZB_XLU_DECAL'),
('G_RM_OPA_SURF', "Opaque (No AA, No ZBuf)", 'G_RM_OPA_SURF'),
('G_RM_ZB_CLD_SURF', "Cloud (No AA)", 'G_RM_ZB_CLD_SURF'),
]
enumRenderModesCycle2 = [
@@ -312,6 +313,7 @@ enumRenderModesCycle2 = [
('G_RM_ZB_OPA_DECAL2', "Opaque Decal (No AA)", 'G_RM_ZB_OPA_DECAL2'),
('G_RM_ZB_XLU_SURF2', "Transparent (No AA)", 'G_RM_ZB_XLU_SURF2'),
('G_RM_ZB_XLU_DECAL2', "Transparent Decal (No AA)", 'G_RM_ZB_XLU_DECAL2'),
('G_RM_ZB_CLD_SURF2', "Cloud (No AA)", 'G_RM_ZB_CLD_SURF2'),
]
enumTexFormat = [
+148 -73
View File
@@ -475,81 +475,103 @@ class F3D:
self.G_ACMUX_0 = 7
# typical CC cycle 1 modes
self.G_CC_PRIMITIVE = '0', '0', '0', 'PRIMITIVE', '0', '0', '0', 'PRIMITIVE'
self.G_CC_SHADE = '0', '0', '0', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_MODULATEI = 'TEXEL0', '0', 'SHADE', '0', '0', '0', '0', 'SHADE'
self.G_CC_MODULATEIA = 'TEXEL0', '0', 'SHADE', '0', 'TEXEL0', '0', 'SHADE', '0'
self.G_CC_MODULATEIDECALA = 'TEXEL0', '0', 'SHADE', '0', '0', '0', '0', 'TEXEL0'
self.G_CC_MODULATERGB = self.G_CC_MODULATEI
self.G_CC_MODULATERGBA = self.G_CC_MODULATEIA
self.G_CC_MODULATERGBDECALA = self.G_CC_MODULATEIDECALA
self.G_CC_MODULATEI_PRIM = 'TEXEL0', '0', 'PRIMITIVE', '0', '0', '0', '0', 'PRIMITIVE'
self.G_CC_MODULATEIA_PRIM = 'TEXEL0', '0', 'PRIMITIVE', '0', 'TEXEL0', '0', 'PRIMITIVE', '0'
self.G_CC_MODULATEIDECALA_PRIM = 'TEXEL0', '0', 'PRIMITIVE', '0', '0', '0', '0', 'TEXEL0'
self.G_CC_MODULATERGB_PRIM = self.G_CC_MODULATEI_PRIM
self.G_CC_MODULATERGBA_PRIM = self.G_CC_MODULATEIA_PRIM
self.G_CC_MODULATERGBDECALA_PRIM = self.G_CC_MODULATEIDECALA_PRIM
self.G_CC_DECALRGB = '0', '0', '0', 'TEXEL0', '0', '0', '0', 'SHADE'
self.G_CC_DECALRGBA = '0', '0', '0', 'TEXEL0', '0', '0', '0', 'TEXEL0'
self.G_CC_BLENDI = 'ENVIRONMENT', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_BLENDIA = 'ENVIRONMENT', 'SHADE', 'TEXEL0', 'SHADE', 'TEXEL0', '0', 'SHADE', '0'
self.G_CC_BLENDIDECALA = 'ENVIRONMENT', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_BLENDRGBA = 'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_BLENDRGBDECALA = 'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_ADDRGB = '1', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_ADDRGBDECALA = '1', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_REFLECTRGB = 'ENVIRONMENT', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_REFLECTRGBDECALA = 'ENVIRONMENT', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_HILITERGB = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_HILITERGBA = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE'
self.G_CC_HILITERGBDECALA = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_SHADEDECALA = '0', '0', '0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_BLENDPE = 'PRIMITIVE', 'ENVIRONMENT', 'TEXEL0', 'ENVIRONMENT', 'TEXEL0', '0', 'SHADE', '0'
self.G_CC_BLENDPEDECALA = 'PRIMITIVE', 'ENVIRONMENT', 'TEXEL0', 'ENVIRONMENT', '0', '0', '0', 'TEXEL0'
self.G_CC_PRIMITIVE = '0', '0', '0', 'PRIMITIVE', '0', '0', '0', 'PRIMITIVE'
self.G_CC_SHADE = '0', '0', '0', 'SHADE', '0', '0', '0', 'SHADE'
# oddball 'modes'
self._G_CC_BLENDPE = 'ENVIRONMENT', 'PRIMITIVE', 'TEXEL0', 'PRIMITIVE', 'TEXEL0', '0', 'SHADE', '0'
self._G_CC_BLENDPEDECALA = 'ENVIRONMENT', 'PRIMITIVE', 'TEXEL0', 'PRIMITIVE', '0', '0', '0', 'TEXEL0'
self._G_CC_TWOCOLORTEX = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
# used for 1-cycle sparse 'mip-maps', primitive color has color of lowest 'LOD'
self._G_CC_SPARSEST = 'PRIMITIVE', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0', 'PRIMITIVE', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0'
self.G_CC_TEMPLERP = 'TEXEL1', 'TEXEL0', 'PRIM_LOD_FRAC', 'TEXEL0', 'TEXEL1', 'TEXEL0', 'PRIM_LOD_FRAC', 'TEXEL0'
self.G_CC_MODULATEI = 'TEXEL0', '0', 'SHADE', '0', '0', '0', '0', 'SHADE'
self.G_CC_MODULATEIDECALA = 'TEXEL0', '0', 'SHADE', '0', '0', '0', '0', 'TEXEL0'
self.G_CC_MODULATEIFADE = 'TEXEL0', '0', 'SHADE', '0', '0', '0', '0', 'ENVIRONMENT'
# typical CC cycle 1 'modes', usually followed by other cycle 2 'modes'
self.G_CC_TRILERP = 'TEXEL1', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0', 'TEXEL1', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0'
self.G_CC_INTERFERENCE = 'TEXEL0', '0', 'TEXEL1', '0', 'TEXEL0', '0', 'TEXEL1', '0'
self.G_CC_MODULATERGB = self.G_CC_MODULATEI
self.G_CC_MODULATERGBDECALA = self.G_CC_MODULATEIDECALA
self.G_CC_MODULATERGBFADE = self.G_CC_MODULATEIFADE
# One-cycle color convert 'operation'
self.G_CC_1CYUV2RGB = 'TEXEL0', 'K4', 'K5', 'TEXEL0', '0', '0', '0', 'SHADE'
self.G_CC_MODULATEIA = 'TEXEL0', '0', 'SHADE', '0', 'TEXEL0', '0', 'SHADE', '0'
self.G_CC_MODULATEIFADEA = 'TEXEL0', '0', 'SHADE', '0', 'TEXEL0', '0', 'ENVIRONMENT', '0'
'''
* NOTE: YUV2RGB expects TF step1 color conversion to occur in 2nd clock.
* 'Therefore', CC looks for step1 results in TEXEL1
'''
self.G_CC_YUV2RGB = 'TEXEL1', 'K4', 'K5', 'TEXEL1', '0', '0', '0', 0
self.G_CC_MODULATEFADE = 'TEXEL0', '0', 'SHADE', '0', 'ENVIRONMENT', '0', 'TEXEL0', '0'
# typical CC cycle 2 modes
self.G_CC_PASS2 = '0', '0', '0', 'COMBINED', '0', '0', '0', 'COMBINED'
self.G_CC_MODULATEI2 = 'COMBINED', '0', 'SHADE', '0', '0', '0', '0', 'SHADE'
self.G_CC_MODULATEIA2 = 'COMBINED', '0', 'SHADE', '0', 'COMBINED', '0', 'SHADE', '0'
self.G_CC_MODULATERGB2 = self.G_CC_MODULATEI2
self.G_CC_MODULATERGBA2 = self.G_CC_MODULATEIA2
self.G_CC_MODULATEI_PRIM2 = 'COMBINED', '0', 'PRIMITIVE', '0', '0', '0', '0', 'PRIMITIVE'
self.G_CC_MODULATEIA_PRIM2 = 'COMBINED', '0', 'PRIMITIVE', '0', 'COMBINED', '0', 'PRIMITIVE', '0'
self.G_CC_MODULATERGB_PRIM2 = self.G_CC_MODULATEI_PRIM2
self.G_CC_MODULATERGBA_PRIM2 = self.G_CC_MODULATEIA_PRIM2
self.G_CC_DECALRGB2 = '0', '0', '0', 'COMBINED', '0', '0', '0', 'SHADE'
'''
* ?
self.G_CC_DECALRGBA2 = 'COMBINED', 'SHADE', 'COMBINED_ALPHA', 'SHADE', '0', '0', '0', 'SHADE'
'''
self.G_CC_BLENDI2 = 'ENVIRONMENT', 'SHADE', 'COMBINED', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_BLENDIA2 = 'ENVIRONMENT', 'SHADE', 'COMBINED', 'SHADE', 'COMBINED', '0', 'SHADE', '0'
self.G_CC_CHROMA_KEY2 = 'TEXEL0', 'CENTER', 'SCALE', '0', '0', '0', '0', '0'
self.G_CC_HILITERGB2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', '0', '0', '0', 'SHADE'
self.G_CC_HILITERGBA2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED'
self.G_CC_HILITERGBDECALA2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', '0', '0', '0', 'TEXEL0'
self.G_CC_HILITERGBPASSA2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', '0', '0', '0', 'COMBINED'
self.G_CC_MODULATERGBA = self.G_CC_MODULATEIA
self.G_CC_MODULATERGBFADEA = self.G_CC_MODULATEIFADEA
self.G_CC_MODULATEI_PRIM = 'TEXEL0', '0', 'PRIMITIVE', '0', '0', '0', '0', 'PRIMITIVE'
self.G_CC_MODULATEIA_PRIM = 'TEXEL0', '0', 'PRIMITIVE', '0', 'TEXEL0', '0', 'PRIMITIVE', '0'
self.G_CC_MODULATEIDECALA_PRIM = 'TEXEL0', '0', 'PRIMITIVE', '0', '0', '0', '0', 'TEXEL0'
self.G_CC_MODULATERGB_PRIM = self.G_CC_MODULATEI_PRIM
self.G_CC_MODULATERGBA_PRIM = self.G_CC_MODULATEIA_PRIM
self.G_CC_MODULATERGBDECALA_PRIM = self.G_CC_MODULATEIDECALA_PRIM
self.G_CC_FADE = 'SHADE', '0', 'ENVIRONMENT', '0', 'SHADE', '0', 'ENVIRONMENT', '0'
self.G_CC_FADEA = 'TEXEL0', '0', 'ENVIRONMENT', '0', 'TEXEL0', '0', 'ENVIRONMENT', '0'
self.G_CC_DECALRGB = '0', '0', '0', 'TEXEL0', '0', '0', '0', 'SHADE'
self.G_CC_DECALRGBA = '0', '0', '0', 'TEXEL0', '0', '0', '0', 'TEXEL0'
self.G_CC_DECALFADE = '0', '0', '0', 'TEXEL0', '0', '0', '0', 'ENVIRONMENT'
self.G_CC_DECALFADEA = '0', '0', '0', 'TEXEL0', 'TEXEL0', '0', 'ENVIRONMENT', '0'
self.G_CC_BLENDI = 'ENVIRONMENT', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_BLENDIA = 'ENVIRONMENT', 'SHADE', 'TEXEL0', 'SHADE', 'TEXEL0', '0', 'SHADE', '0'
self.G_CC_BLENDIDECALA = 'ENVIRONMENT', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_BLENDRGBA = 'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_BLENDRGBDECALA = 'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_BLENDRGBFADEA = 'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', '0', '0', '0', 'ENVIRONMENT'
self.G_CC_ADDRGB = 'TEXEL0', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_ADDRGBDECALA = 'TEXEL0', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_ADDRGBFADE = 'TEXEL0', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'ENVIRONMENT'
self.G_CC_REFLECTRGB = 'ENVIRONMENT', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_REFLECTRGBDECALA = 'ENVIRONMENT', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_HILITERGB = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_HILITERGBA = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE'
self.G_CC_HILITERGBDECALA = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_SHADEDECALA = '0', '0', '0', 'SHADE', '0', '0', '0', 'TEXEL0'
self.G_CC_SHADEFADEA = '0', '0', '0', 'SHADE', '0', '0', '0', 'ENVIRONMENT'
self.G_CC_BLENDPE = 'PRIMITIVE', 'ENVIRONMENT', 'TEXEL0', 'ENVIRONMENT', 'TEXEL0', '0', 'SHADE', '0'
self.G_CC_BLENDPEDECALA = 'PRIMITIVE', 'ENVIRONMENT', 'TEXEL0', 'ENVIRONMENT', '0', '0', '0', 'TEXEL0'
# oddball modes
self._G_CC_BLENDPE = 'ENVIRONMENT', 'PRIMITIVE', 'TEXEL0', 'PRIMITIVE', 'TEXEL0', '0', 'SHADE', '0'
self._G_CC_BLENDPEDECALA = 'ENVIRONMENT', 'PRIMITIVE', 'TEXEL0', 'PRIMITIVE', '0', '0', '0', 'TEXEL0'
self._G_CC_TWOCOLORTEX = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE'
# used for 1-cycle sparse mip-maps, primitive color has color of lowest LOD
self._G_CC_SPARSEST = 'PRIMITIVE', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0', 'PRIMITIVE', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0'
self.G_CC_TEMPLERP = 'TEXEL1', 'TEXEL0', 'PRIM_LOD_FRAC', 'TEXEL0', 'TEXEL1', 'TEXEL0', 'PRIM_LOD_FRAC', 'TEXEL0'
# typical CC cycle 1 modes, usually followed by other cycle 2 modes
self.G_CC_TRILERP = 'TEXEL1', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0', 'TEXEL1', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0'
self.G_CC_INTERFERENCE = 'TEXEL0', '0', 'TEXEL1', '0', 'TEXEL0', '0', 'TEXEL1', '0'
self.G_CC_1CYUV2RGB = 'TEXEL0', 'K4', 'K5', 'TEXEL0', '0', '0', '0', 'SHADE'
self.G_CC_YUV2RGB = 'TEXEL1', 'K4', 'K5', 'TEXEL1', '0', '0', '0', '0'
#typical CC cycle 2 modes
self.G_CC_PASS2 = '0', '0', '0', 'COMBINED', '0', '0', '0', 'COMBINED'
self.G_CC_MODULATEI2 = 'COMBINED', '0', 'SHADE', '0', '0', '0', '0', 'SHADE'
self.G_CC_MODULATEIA2 = 'COMBINED', '0', 'SHADE', '0', 'COMBINED', '0', 'SHADE', '0'
self.G_CC_MODULATERGB2 = self.G_CC_MODULATEI2
self.G_CC_MODULATERGBA2 = self.G_CC_MODULATEIA2
self.G_CC_MODULATEI_PRIM2 = 'COMBINED', '0', 'PRIMITIVE', '0', '0', '0', '0', 'PRIMITIVE'
self.G_CC_MODULATEIA_PRIM2 = 'COMBINED', '0', 'PRIMITIVE', '0', 'COMBINED', '0', 'PRIMITIVE', '0'
self.G_CC_MODULATERGB_PRIM2 = self.G_CC_MODULATEI_PRIM2
self.G_CC_MODULATERGBA_PRIM2 = self.G_CC_MODULATEIA_PRIM2
self.G_CC_DECALRGB2 = '0', '0', '0', 'COMBINED', '0', '0', '0', 'SHADE'
self.G_CC_DECALRGBA2 = 'COMBINED', 'SHADE', 'COMBINED_ALPHA', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_BLENDI2 = 'ENVIRONMENT', 'SHADE', 'COMBINED', 'SHADE', '0', '0', '0', 'SHADE'
self.G_CC_BLENDIA2 = 'ENVIRONMENT', 'SHADE', 'COMBINED', 'SHADE', 'COMBINED', '0', 'SHADE', '0'
self.G_CC_CHROMA_KEY2 = 'TEXEL0', 'CENTER', 'SCALE', '0', '0', '0', '0', '0'
self.G_CC_HILITERGB2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', '0', '0', '0', 'SHADE'
self.G_CC_HILITERGBA2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED'
self.G_CC_HILITERGBDECALA2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', '0', '0', '0', 'TEXEL0'
self.G_CC_HILITERGBPASSA2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', '0', '0', '0', 'COMBINED'
# G_SETOTHERMODE_L sft: shift count
@@ -676,6 +698,41 @@ class F3D:
self.G_BL_1 = G_BL_1 = 2
self.G_BL_0 = G_BL_0 = 3
self.cvgDstDict = {
CVG_DST_CLAMP : "CVG_DST_CLAMP",
CVG_DST_WRAP : "CVG_DST_WRAP",
CVG_DST_FULL : "CVG_DST_FULL",
CVG_DST_SAVE : "CVG_DST_SAVE",
}
self.zmodeDict = {
ZMODE_OPA : "ZMODE_OPA",
ZMODE_INTER : "ZMODE_INTER",
ZMODE_XLU : "ZMODE_XLU",
ZMODE_DEC : "ZMODE_DEC",
}
self.blendColorDict = {
G_BL_CLR_IN : "G_BL_CLR_IN",
G_BL_CLR_MEM : "G_BL_CLR_MEM",
G_BL_CLR_BL : "G_BL_CLR_BL",
G_BL_CLR_FOG : "G_BL_CLR_FOG",
}
self.blendAlphaDict = {
G_BL_A_IN : 'G_BL_A_IN',
G_BL_A_FOG : 'G_BL_A_FOG',
G_BL_A_SHADE : 'G_BL_A_SHADE',
G_BL_0 : 'G_BL_0',
}
self.blendMixDict = {
G_BL_1MA : 'G_BL_1MA',
G_BL_A_MEM : 'G_BL_A_MEM',
G_BL_1 : 'G_BL_1',
G_BL_0 : 'G_BL_0',
}
def GBL_c1(m1a, m1b, m2a, m2b):
return (m1a) << 30 | (m1b) << 26 | (m2a) << 22 | (m2b) << 18
def GBL_c2(m1a, m1b, m2a, m2b):
@@ -1039,7 +1096,7 @@ class F3D:
self.G_RM_OPA_CI2 = RM_OPA_CI(2)
self.G_RM_FOG_SHADE_A = GBL_c1(G_BL_CLR_FOG, G_BL_A_SHADE, G_BL_CLR_IN, G_BL_1MA)
self.G_RM_FOG_SHADE_A = GBL_c1(G_BL_CLR_FOG, G_BL_A_SHADE, G_BL_CLR_IN, G_BL_1MA)
self.G_RM_FOG_PRIM_A = GBL_c1(G_BL_CLR_FOG, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA)
self.G_RM_PASS = GBL_c1(G_BL_CLR_IN, G_BL_0, G_BL_CLR_IN, G_BL_1)
@@ -1285,6 +1342,11 @@ class F3D:
'NUMLIGHTS_7' : 7,
}
def GBL_c1(self, m1a, m1b, m2a, m2b):
return (m1a) << 30 | (m1b) << 26 | (m2a) << 22 | (m2b) << 18
def GBL_c2(self, m1a, m1b, m2a, m2b):
return (m1a) << 28 | (m1b) << 24 | (m2a) << 20 | (m2b) << 16
# macros for command parsing
def GDMACMD(self, x): return (x)
def GIMMCMD(self, x): return (self.G_IMMFIRST-(x))
@@ -1749,11 +1811,24 @@ class FModel:
fMaterial.usedLights.append(key)
self.lights[key] = value
def addMesh(self, name, namePrefix, drawLayer, isSkinned):
def addMesh(self, name, namePrefix, drawLayer, isSkinned, contextObj):
meshName = getFMeshName(name, namePrefix, drawLayer, isSkinned)
checkUniqueBoneNames(self, meshName, name)
self.meshes[meshName] = FMesh(meshName, self.DLFormat)
self.onAddMesh(self.meshes[meshName], contextObj)
return self.meshes[meshName]
def onAddMesh(self, fMesh, contextObj):
return
def endDraw(self, fMesh, contextObj):
self.onEndDraw(fMesh, contextObj)
fMesh.draw.commands.append(SPEndDisplayList())
def onEndDraw(self, fMesh, contextObj):
return
def getTextureAndHandleShared(self, imageKey):
# Check if texture is in self
@@ -2022,7 +2097,7 @@ class FModel:
oldpath = image.filepath
try:
image.filepath = \
os.path.join(dirpath, imageFileName)
bpy.path.abspath(os.path.join(dirpath, imageFileName))
image.save()
texturesSaved += 1
if not isPacked:
+66 -26
View File
@@ -85,11 +85,11 @@ drawLayerOOTtoSM64 = {
"Overlay" : '1',
}
drawLayerOOTAlpha = {
"Opaque" : "CLIP",
"Transparent" : "BLEND",
"Overlay" : 'CLIP',
}
#drawLayerOOTAlpha = {
# "Opaque" : "OPAQUE",
# "Transparent" : "BLEND",
# "Overlay" : 'CLIP',
#}
drawLayerSM64Alpha = {
'0' : "CLIP",
@@ -166,7 +166,17 @@ def update_blend_method(material, context):
if material.mat_ver > 3:
drawLayer = material.f3d_mat.draw_layer
if context.scene.gameEditorMode == "OOT":
material.blend_method = drawLayerOOTAlpha[drawLayer.oot]
if drawLayer.oot == "Opaque" or drawLayer.oot == "Overlay":
f3dMat = material.f3d_mat
if not f3dMat.rdp_settings.rendermode_advanced_enabled and\
"TEX_EDGE" not in f3dMat.rdp_settings.rendermode_preset_cycle_1 and\
("TEX_EDGE" not in f3dMat.rdp_settings.rendermode_preset_cycle_2 or\
f3dMat.rdp_settings.g_mdsft_cycletype != 'G_CYC_2CYCLE'):
material.blend_method = "OPAQUE"
else:
material.blend_method = "CLIP"
else:
material.blend_method = "BLEND"
elif context.scene.gameEditorMode == "SM64":
material.blend_method = drawLayerSM64Alpha[drawLayer.sm64]
@@ -442,7 +452,7 @@ class F3DPanel(bpy.types.Panel):
inputGroup.prop(textureProp, 'menu', text = name + ' Properties',
icon = 'TRIA_DOWN' if textureProp.menu else 'TRIA_RIGHT')
if textureProp.menu:
tex = textureProp.tex
prop_input_name = inputGroup.column()
prop_input = inputGroup.column()
@@ -453,13 +463,22 @@ class F3DPanel(bpy.types.Panel):
#prop_input.template_image(textureProp, 'tex',
# nodes[name].image_user)
texIndex = name[-1]
prop_input.template_ID(textureProp, 'tex', new='image.new', open='image.open',
unlink='image.tex' + texIndex + "_unlink")
prop_input.enabled = textureProp.tex_set
tex = textureProp.tex
if tex is not None:
prop_input.label(text = "Size: " + str(tex.size[0]) + " x " + str(tex.size[1]))
prop_input.prop(textureProp, "use_tex_reference")
if textureProp.use_tex_reference:
prop_split(prop_input, textureProp, "tex_reference", "Texture Reference")
prop_split(prop_input, textureProp, "tex_reference_size", "Texture Size")
if textureProp.tex_format[:2] == 'CI':
prop_split(prop_input, textureProp, "pal_reference", "Palette Reference")
prop_split(prop_input, textureProp, "pal_reference_size", "Palette Size")
else:
prop_input.template_ID(textureProp, 'tex', new='image.new', open='image.open',
unlink='image.tex' + texIndex + "_unlink")
prop_input.enabled = textureProp.tex_set
if tex is not None:
prop_input.label(text = "Size: " + str(tex.size[0]) + " x " + str(tex.size[1]))
if material.mat_ver > 3 and material.f3d_mat.use_large_textures:
prop_input.label(text = "Large texture mode enabled.")
@@ -1165,7 +1184,7 @@ def update_node_values_without_preset(self, context):
update_node_values_of_material(material, context)
material.f3d_update_flag = False
else:
print('No material in context.')
#print('No material in context.')
pass
def update_node_values_directly(material, context):
@@ -1491,9 +1510,10 @@ def update_tex_values_field_v3(self, texProperty, tex_size,
(texProperty.T.high + 1) / tex_size[1], 0)
# Clamp
isTexGen = self.f3d_mat.rdp_settings.g_tex_gen or self.f3d_mat.rdp_settings.g_tex_gen_linear
nodes['Get UV ' + str(texIndex) + ' F3D v3'].inputs[5].default_value = (
1 if texProperty.S.clamp else 0,
1 if texProperty.T.clamp else 0, 0)
1 if texProperty.S.clamp and not isTexGen else 0,
1 if texProperty.T.clamp and not isTexGen else 0, 0)
# Normalized Mask
nodes['Get UV ' + str(texIndex) + ' F3D v3'].inputs[6].default_value = (
@@ -1525,8 +1545,11 @@ def update_tex_values_index(self, context, texProperty, texNodeName,
nodes = self.node_tree.nodes
nodes[texNodeName].image = texProperty.tex
if nodes[texNodeName].image is not None:
tex_size = nodes[texNodeName].image.size
if nodes[texNodeName].image is not None or texProperty.use_tex_reference:
if nodes[texNodeName].image is not None:
tex_size = nodes[texNodeName].image.size
else:
tex_size = texProperty.tex_reference_size
if tex_size[0] > 0 and tex_size[1] > 0:
if self.mat_ver == 1:
tex_x = nodes[uvNodeName].node_tree.nodes[\
@@ -1750,14 +1773,15 @@ def hasNodeGraph(material):
def createF3DMat(obj, preset = 'Shaded Solid', index = None):
material = bpy.data.materials.new('f3d_material')
if index is None:
obj.data.materials.append(material)
if bpy.context.object is not None:
bpy.context.object.active_material_index = len(obj.material_slots) - 1
else:
obj.material_slots[index].material = material
if bpy.context.object is not None:
bpy.context.object.active_material_index = index
if obj is not None:
if index is None:
obj.data.materials.append(material)
if bpy.context.object is not None:
bpy.context.object.active_material_index = len(obj.material_slots) - 1
else:
obj.material_slots[index].material = material
if bpy.context.object is not None:
bpy.context.object.active_material_index = index
material.is_f3d = True
material.mat_ver = 4
@@ -2001,6 +2025,12 @@ class TextureProperty(bpy.types.PropertyGroup):
S : bpy.props.PointerProperty(type = TextureFieldProperty)
T : bpy.props.PointerProperty(type = TextureFieldProperty)
use_tex_reference: bpy.props.BoolProperty(name = "Use Texture Reference", default = False, update = update_tex_values)
tex_reference: bpy.props.StringProperty(name = "Texture Reference", default = '0x08000000')
tex_reference_size: bpy.props.IntVectorProperty(name = "Texture Reference Size", min = 1, size = 2, default = (32,32), update = update_tex_values)
pal_reference: bpy.props.StringProperty(name = "Palette Reference", default = '0x08000000')
pal_reference_size : bpy.props.IntProperty(name = "Texture Reference Size", min = 1, default = 16)
menu : bpy.props.BoolProperty()
tex_set : bpy.props.BoolProperty(default = True, update = update_node_values)
autoprop : bpy.props.BoolProperty(name = 'Autoprop', update = update_tex_values, default = True)
@@ -2383,6 +2413,11 @@ class AddPresetF3D(AddPresetBase, Operator):
"f3d_mat.tex0.tex",
"f3d_mat.tex0.tex_format",
"f3d_mat.tex0.ci_format",
"f3d_mat.tex0.use_tex_reference",
"f3d_mat.tex0.tex_reference",
"f3d_mat.tex0.tex_reference_size",
"f3d_mat.tex0.pal_reference",
"f3d_mat.tex0.pal_reference_size",
"f3d_mat.tex0.S",
"f3d_mat.tex0.T",
"f3d_mat.tex0.menu",
@@ -2391,6 +2426,11 @@ class AddPresetF3D(AddPresetBase, Operator):
"f3d_mat.tex1.tex",
"f3d_mat.tex1.tex_format",
"f3d_mat.tex1.ci_format",
"f3d_mat.tex1.use_tex_reference",
"f3d_mat.tex1.tex_reference",
"f3d_mat.tex1.tex_reference_size",
"f3d_mat.tex1.pal_reference",
"f3d_mat.tex1.pal_reference_size",
"f3d_mat.tex1.S",
"f3d_mat.tex1.T",
"f3d_mat.tex1.menu",
+9 -9
View File
@@ -1156,7 +1156,7 @@ class GetAlphaFromColor(ShaderNode):
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
pass #print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
@@ -1214,7 +1214,7 @@ class F3DNodeA(ShaderNode):
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
pass #print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
@@ -1257,7 +1257,7 @@ class F3DNodeB(ShaderNode):
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
pass #print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
@@ -1300,7 +1300,7 @@ class F3DNodeC(ShaderNode):
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
pass #print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
@@ -1343,7 +1343,7 @@ class F3DNodeD(ShaderNode):
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
pass #print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
@@ -1387,7 +1387,7 @@ class F3DNodeA_alpha(ShaderNode):
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
pass #print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
@@ -1430,7 +1430,7 @@ class F3DNodeB_alpha(ShaderNode):
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
pass #print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
@@ -1473,7 +1473,7 @@ class F3DNodeC_alpha(ShaderNode):
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
pass #print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
@@ -1516,7 +1516,7 @@ class F3DNodeD_alpha(ShaderNode):
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
pass #print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
+2 -2
View File
@@ -508,7 +508,7 @@ f3d_mat.set_combiner = True
f3d_mat.use_default_lighting = True
f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0)
f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0)
f3d_mat.env_color = (0.0, 0.6405822038650513, 0.06569604575634003, 1.0)
f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0)
f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0)
f3d_mat.key_scale = (0.0, 0.0, 0.0)
f3d_mat.key_width = (0.0, 0.0, 0.0)
@@ -626,7 +626,7 @@ f3d_mat.set_combiner = True
f3d_mat.use_default_lighting = True
f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0)
f3d_mat.prim_color = (1.0, 1.0, 1.0, 0.5)
f3d_mat.env_color = (0.0, 0.6405822038650513, 0.06569604575634003, 1.0)
f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0)
f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0)
f3d_mat.key_scale = (0.0, 0.0, 0.0)
f3d_mat.key_width = (0.0, 0.0, 0.0)
File diff suppressed because it is too large Load Diff
+85 -30
View File
@@ -153,6 +153,8 @@ def fixLargeUVs(obj):
2 if f3dMat.tex0.T.mirror or f3dMat.tex1.T.mirror else 1]
size = texSizeDict[material]
if size[0] == 0 or size[1] == 0:
continue
cellSize = [1024 / size[0], 1024 / size[1]]
minUV, maxUV = findUVBounds(polygon, uv_data)
uvOffset = [0,0]
@@ -390,7 +392,7 @@ def saveStaticModel(triConverterInfo, fModel, obj, transformMatrix, ownerName, c
drawLayerName = None
if drawLayer not in fMeshes:
fMesh = fModel.addMesh(obj.original_name, ownerName, drawLayerName, False)
fMesh = fModel.addMesh(obj.original_name, ownerName, drawLayerName, False, obj)
fMeshes[drawLayer] = fMesh
if obj.use_f3d_culling and (fModel.f3d.F3DEX_GBI or fModel.f3d.F3DEX_GBI_2):
@@ -409,9 +411,10 @@ def saveStaticModel(triConverterInfo, fModel, obj, transformMatrix, ownerName, c
for drawLayer, fMesh in fMeshes.items():
if revertMatAtEnd:
fModel.onEndDraw(fMesh, obj)
revertMatAndEndDraw(fMesh.draw, [])
else:
fMesh.draw.commands.append(SPEndDisplayList())
fModel.endDraw(fMesh, obj)
return fMeshes
def addCullCommand(obj, fMesh, transformMatrix, matWriteMethod):
@@ -1106,13 +1109,19 @@ def getTexDimensions(material):
texDimensions1 = None
useDict = all_combiner_uses(f3dMat)
if useDict['Texture 0'] and f3dMat.tex0.tex_set:
if f3dMat.tex0.tex is None:
raise PluginError('In material \"' + material.name + '\", a texture has not been set.')
texDimensions0 = f3dMat.tex0.tex.size[0], f3dMat.tex0.tex.size[1]
if f3dMat.tex0.use_tex_reference:
texDimensions0 = f3dMat.tex0.tex_reference_size
else:
if f3dMat.tex0.tex is None:
raise PluginError('In material \"' + material.name + '\", a texture has not been set.')
texDimensions0 = f3dMat.tex0.tex.size[0], f3dMat.tex0.tex.size[1]
if useDict['Texture 1'] and f3dMat.tex1.tex_set:
if f3dMat.tex1.tex is None:
raise PluginError('In material \"' + material.name + '\", a texture has not been set.')
texDimensions1 = f3dMat.tex1.tex.size[0], f3dMat.tex1.tex.size[1]
if f3dMat.tex1.use_tex_reference:
texDimensions1 = f3dMat.tex1.tex_reference_size
else:
if f3dMat.tex1.tex is None:
raise PluginError('In material \"' + material.name + '\", a texture has not been set.')
texDimensions1 = f3dMat.tex1.tex.size[0], f3dMat.tex1.tex.size[1]
if texDimensions0 is not None and texDimensions1 is not None:
texDimensions = texDimensions0 if f3dMat.uv_basis == 'TEXEL0' \
@@ -1243,7 +1252,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData):
nextTmem = 0
loadTextures = not (material.mat_ver > 3 and f3dMat.use_large_textures)
if useDict['Texture 0'] and f3dMat.tex0.tex_set:
if f3dMat.tex0.tex is None:
if f3dMat.tex0.tex is None and not f3dMat.tex0.use_tex_reference:
raise PluginError('In material \"' + material.name + '\", a texture has not been set.')
fMaterial.useLargeTextures = not loadTextures
@@ -1253,7 +1262,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData):
fMaterial, fMaterial.material, fMaterial.revert, f3dMat.tex0, 0, nextTmem, None, convertTextureData,
None, loadTextures, True)
if useDict['Texture 1'] and f3dMat.tex1.tex_set:
if f3dMat.tex1.tex is None:
if f3dMat.tex1.tex is None and not f3dMat.tex1.use_tex_reference:
raise PluginError('In material \"' + material.name + '\", a texture has not been set.')
fMaterial.useLargeTextures = not loadTextures
@@ -1377,18 +1386,27 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData):
def saveTextureIndex(propName, fModel, fMaterial, loadTexGfx, revertTexGfx, texProp, index, tmem,
overrideName, convertTextureData, tileSettingsOverride, loadTextures, loadPalettes):
tex = texProp.tex
if tex is None:
raise PluginError('In ' + propName + ", no texture is selected.")
elif len(tex.pixels) == 0:
raise PluginError("Could not load missing texture: " + tex.name + ". Make sure this texture has not been deleted or moved on disk.")
if tex is not None and (tex.size[0] == 0 or tex.size[1] == 0):
raise PluginError("Image " + tex.name + " has either a 0 width or height; image may have been removed from original location.")
if not texProp.use_tex_reference:
if tex is None:
raise PluginError('In ' + propName + ", no texture is selected.")
elif len(tex.pixels) == 0:
raise PluginError("Could not load missing texture: " + tex.name + ". Make sure this texture has not been deleted or moved on disk.")
texFormat = texProp.tex_format
isCITexture = texFormat[:2] == 'CI'
palFormat = texProp.ci_format if isCITexture else ''
if tex.filepath == "":
name = tex.name
if not texProp.use_tex_reference:
if tex.filepath == "":
name = tex.name
else:
name = tex.filepath
else:
name = tex.filepath
name = texProp.tex_reference
texName = fModel.name + '_' + \
(getNameFromPath(name, True) + '_' + texFormat.lower() if overrideName is None else overrideName)
@@ -1399,8 +1417,10 @@ def saveTextureIndex(propName, fModel, fMaterial, loadTexGfx, revertTexGfx, texP
setTLUTMode = False
else:
tileSettings = None
width = tex.size[0]
height = tex.size[1]
if texProp.use_tex_reference:
width, height = texProp.tex_reference_size
else:
width, height = tex.size
setTLUTMode = fModel.matWriteMethod == GfxMatWriteMethod.WriteAll
nextTmem = tmem + getTmemWordUsage(texFormat, width, height)
@@ -1409,7 +1429,7 @@ def saveTextureIndex(propName, fModel, fMaterial, loadTexGfx, revertTexGfx, texP
if nextTmem > (512 if texFormat[:2] != 'CI' else 256):
raise PluginError("Error in \"" + propName + "\": Textures are too big. Max TMEM size is 4k " + \
"bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries.")
if tex.size[0] > 1024 or tex.size[1] > 1024:
if width > 1024 or height > 1024:
raise PluginError("Error in \"" + propName + "\": Any side of an image cannot be greater " +\
"than 1024.")
@@ -1445,14 +1465,22 @@ def saveTextureIndex(propName, fModel, fMaterial, loadTexGfx, revertTexGfx, texP
convertTextureData = convertTextureData and not (fMaterial.useLargeTextures and fMaterial.saveLargeTextures[index])
if isCITexture:
fImage, fPalette = saveOrGetPaletteDefinition(
fMaterial, fModel, tex, texName, texFormat, palFormat, convertTextureData)
if texProp.use_tex_reference:
fImage = FImage(texProp.tex_reference, None, None, width, height, None, False)
fPalette = FImage(texProp.pal_reference, None, None, 1, texProp.pal_reference_size, None, False)
else:
fImage, fPalette = saveOrGetPaletteDefinition(
fMaterial, fModel, tex, texName, texFormat, palFormat, convertTextureData)
if loadPalettes:
savePaletteLoading(loadTexGfx, revertTexGfx, fPalette,
palFormat, 0, fPalette.height, fModel.f3d, fModel.matWriteMethod)
else:
fImage = saveOrGetTextureDefinition(fMaterial, fModel, tex, texName,
texFormat, convertTextureData)
if texProp.use_tex_reference:
fImage = FImage(texProp.tex_reference, None, None, width, height, None, False)
else:
fImage = saveOrGetTextureDefinition(fMaterial, fModel, tex, texName,
texFormat, convertTextureData)
if setTLUTMode and not isCITexture:
loadTexGfx.commands.append(DPSetTextureLUT('G_TT_NONE'))
@@ -1716,7 +1744,7 @@ def saveOrGetTextureDefinition(fMaterial, fModel, image, imageName, texFormat, c
((((int(round(image.pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F)) & 0x1F) << 3) |
((int(round(image.pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x1F) >> 2)),
(((int(round(image.pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x02) << 6) | \
(((int(round(image.pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x03) << 6) | \
((int(round(image.pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F)) & 0x1F) << 1) | \
(1 if image.pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0)))
@@ -2092,7 +2120,10 @@ def saveOtherModeLDefinitionAll(fMaterial, settings, defaults, defaultRenderMode
flagList, blendList = getRenderModeFlagList(settings, fMaterial)
cmd.flagList.extend(flagList)
if blendList is not None:
cmd.flagList.extend(blendList)
cmd.flagList.extend([
"GBL_c1(" + blendList[0] + ", " + blendList[1] + ", " + blendList[2] + ", " + blendList[3] + ")",
"GBL_c2(" + blendList[4] + ", " + blendList[5] + ", " + blendList[6] + ", " + blendList[7] + ")",
])
else:
cmd.flagList.extend(defaultRenderMode)
@@ -2128,9 +2159,11 @@ def getRenderModeFlagList(settings, fMaterial):
settings.rendermode_preset_cycle_1,
settings.rendermode_preset_cycle_2]
else:
cycle2 = settings.rendermode_preset_cycle_1 + '2'
if cycle2 not in [value[0] for value in enumRenderModesCycle2]:
cycle2 = "G_RM_NOOP"
flagList = [
settings.rendermode_preset_cycle_1,
settings.rendermode_preset_cycle_1 + '2']
settings.rendermode_preset_cycle_1, cycle2]
else:
if settings.g_mdsft_cycletype == 'G_CYC_2CYCLE':
blendList = \
@@ -2233,6 +2266,27 @@ def exportF3DtoC(dirPath, obj, DLFormat, transformMatrix,
writeCData(staticData, os.path.join(modelDirPath, 'header.h'),
os.path.join(modelDirPath, 'model.inc.c'))
def removeDL(sourcePath, headerPath, DLName):
DLDataC = readFile(sourcePath)
originalDataC = DLDataC
DLDataH = readFile(headerPath)
originalDataH = DLDataH
matchResult = re.search("Gfx\s*" + re.escape(DLName) + "\s*\[\s*[0-9x]*\s*\]\s*=\s*\{([^}]*)}\s*;\s*", DLDataC, re.DOTALL)
if matchResult is not None:
DLDataC = DLDataC[:matchResult.start(0)] + DLDataC[matchResult.end(0):]
headerMatch = getDeclaration(DLDataH, DLName)
if headerMatch is not None:
DLDataH = DLDataH[:headerMatch.start(0)] + DLDataH[headerMatch.end(0):]
if DLDataC != originalDataC:
writeFile(sourcePath, DLDataC)
if DLDataH != originalDataH:
writeFile(headerPath, DLDataH)
class F3D_ExportDL(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.f3d_export_dl'
@@ -2252,7 +2306,7 @@ class F3D_ExportDL(bpy.types.Operator):
if not isinstance(obj.data, bpy.types.Mesh):
raise PluginError("Object is not a mesh.")
scaleValue = bpy.context.scene.blenderToSM64Scale
scaleValue = bpy.context.scene.blenderF3DScale
finalTransform = mathutils.Matrix.Diagonal(mathutils.Vector((
scaleValue, scaleValue, scaleValue))).to_4x4()
@@ -2305,8 +2359,9 @@ class F3D_ExportDLPanel(bpy.types.Panel):
col = self.layout.column()
col.operator(F3D_ExportDL.bl_idname)
col.prop(context.scene, 'DLExportPath')
prop_split(col, context.scene, 'DLName', 'Name')
prop_split(col, context.scene, 'DLExportPath', "Export Path")
prop_split(col, context.scene, "blenderF3DScale", "Scale")
prop_split(col, context.scene, 'matWriteMethod', "Material Write Method")
col.prop(context.scene, 'DLExportisStatic')
+23 -1
View File
@@ -44,4 +44,26 @@ Additionally, for functions like Gfx_TexScroll(), the x,y inputs are pre-shifted
Collision properties are found underneath the material properties. Water boxes and collision properties will have references the properties "Camera", "Lighting", and "Exit", which reference the indices of the scene cameras, scene light list, and scene exit list respectively. If you want separate visual/collision geometry, you can set per mesh object "Ignore Collision" or "Ignore Render" in object inspector window.
### Skeletons And Animations
To export a skeletal mesh, select an armature and then click "Export" for the armature exporter. The root bone of the armature must be named "root". To export an animation, the process is the same. The active animation on the armature will be exported.
For bones, there are 3 bone types: Default, Custom DL, and Ignore. This can be set in the bone properties window.
Default is a regular deformation bone. Ignore will not be handled by the exporter/importer. Custom DL lets you define the name of a DL that you want a bone to draw instead of drawing geometry from the armature. There is also an option for billboarding the specific geometry associated with the bone.
The armature properties window also has the option to set a LOD armature. This armature must have the same bone structure as your current armature.
To export a skeletal mesh, select an armature and then click "Export" for the armature exporter. Make sure there is only one bone without a parent (the root bone), as the exporter will choose the first parentless bone as the start bone of the armature.
To import a skeletal mesh, just click "Import" for the armature importer. You may encounter a couple issues:
![](https://bitbucket.org/kurethedead/fast64/raw/master/images/oot_imported_gerudo_textured.png)
![](https://bitbucket.org/kurethedead/fast64/raw/master/images/oot_imported_gerudo_solid.png)
1. Eye/face textures are black: Texture pointers which are set dynamically will not be imported. Instead, the name of the pointer will be used instead of the actual data.
2. Certain colors are white/different: Some graphical effects are achieved through dynamic Gfx commands, such as tinting white textures. These effects will not be imported.
3. Strange imported normals: Due to the behaviour of rotating vertices on a skinned triangle that differs between Blender and the N64, normals may look strange. Note that these normals will look correct if re exported back into the game (assuming the rest pose is not changed).
Note that rest pose rotations are zeroed out on export, so you can modify the rest pose of imported armature while still preserving its structure. You can do this by using the "Apply As Rest Pose" operator under the Fast64 tab. Note that imported animations however still require the imported rest pose to work correctly.
There may also be an issue where some meshes import completely black due to the assumption that the F3D cycle mode is set to 2-Cycle, when it should really be 1-Cycle. Try changing the cycle type to 1-Cycle in cases where a dynamic texture pointer is not expected.
To import an animation, select the armature the animation belongs to then click "Import" on the animation importer.
To export an animation, select an armature and click "Export", which will export the active animation of the armature.
-1
View File
@@ -1,4 +1,3 @@
from .oot_collision_c import *
from .oot_level_c import *
from .oot_dma_manager_c import *
from .oot_scene_id_c import *
@@ -1,151 +0,0 @@
from ..oot_collision import *
def ootCollisionVertexToC(vertex):
return "{ " + str(vertex.position[0]) + ", " +\
str(vertex.position[1]) + ", " +\
str(vertex.position[2]) + " },\n"
def ootCollisionPolygonToC(polygon, ignoreCamera, ignoreActor, ignoreProjectile, enableConveyor, polygonTypeIndex):
return "{ " + format(polygonTypeIndex, "#06x") + ', ' +\
format(polygon.convertShort02(ignoreCamera, ignoreActor, ignoreProjectile), "#06x") + ', ' +\
format(polygon.convertShort04(enableConveyor), "#06x") + ', ' +\
format(polygon.convertShort06(), "#06x") + ', ' +\
format(polygon.normal[0], "#06x") + ', ' +\
format(polygon.normal[1], "#06x") + ', ' +\
format(polygon.normal[2], "#06x") + ', ' +\
format(polygon.distance, "#06x") + ' },\n'
def ootPolygonTypeToC(polygonType):
return " " + format(polygonType.convertHigh(), "#010x") + ', ' +\
format(polygonType.convertLow(), "#010x") + ',\n'
def ootWaterBoxToC(waterBox):
return "{ " + str(waterBox.low[0]) + ", " +\
str(waterBox.height) + ", " +\
str(waterBox.low[1]) + ", " +\
str(waterBox.high[0] - waterBox.low[0]) + ", " +\
str(waterBox.high[1] - waterBox.low[1]) + ", " +\
format(waterBox.propertyData(), "#010x") + ' },\n'
def ootCameraDataToC(camData):
posC = CData()
camC = CData()
if len(camData.camPosDict) > 0:
camDataName = "CamData " + camData.camDataName() + "[" + str(len(camData.camPosDict)) + "]"
camC.source = camDataName + ' = {\n'
camC.header = "extern " + camDataName + ';\n'
camPosIndex = 0
for i in range(len(camData.camPosDict)):
camC.source += '\t' + ootCameraEntryToC(camData.camPosDict[i], camData, camPosIndex)
if camData.camPosDict[i].hasPositionData:
posC.source += ootCameraPosToC(camData.camPosDict[i])
camPosIndex += 3
posC.source += '};\n\n'
camC.source += '};\n\n'
posDataName = "Vec3s " + camData.camPositionsName() + '[' + str(camPosIndex) + ']'
posC.header = "extern " + posDataName + ';\n'
posC.source = posDataName + " = {\n" + posC.source
posC.append(camC)
return posC
def ootCameraPosToC(camPos):
return "\t{ " +\
str(camPos.position[0]) + ', ' +\
str(camPos.position[1]) + ', ' +\
str(camPos.position[2]) + ' },\n\t{ ' +\
str(camPos.rotation[0]) + ', ' +\
str(camPos.rotation[1]) + ', ' +\
str(camPos.rotation[2]) + ' },\n\t{ ' +\
str(camPos.fov) + ', ' +\
str(camPos.jfifID) + ', ' +\
str(camPos.unknown) + ' },\n'
def ootCameraEntryToC(camPos, camData, camPosIndex):
return "{ " +\
str(camPos.camSType) + ', ' +\
('3' if camPos.hasPositionData else '0') + ', ' +\
(("&" + camData.camPositionsName() + '[' + str(camPosIndex) + ']')
if camPos.hasPositionData else "0") + ' },\n'
def ootCollisionToC(collision):
data = CData()
data.append(ootCameraDataToC(collision.cameraData))
if len(collision.polygonGroups) > 0:
data.header += "extern u32 " + collision.polygonTypesName() + "[];\n"
data.header += "extern CollisionPoly " + collision.polygonsName() + "[];\n"
polygonTypeC = "u32 " + collision.polygonTypesName() + "[] = {\n"
polygonC = "CollisionPoly " + collision.polygonsName() + "[] = {\n"
polygonIndex = 0
for polygonType, polygons in collision.polygonGroups.items():
polygonTypeC += '\t' + ootPolygonTypeToC(polygonType)
for polygon in polygons:
polygonC += '\t' + ootCollisionPolygonToC(polygon,
polygonType.ignoreCameraCollision,
polygonType.ignoreActorCollision,
polygonType.ignoreProjectileCollision,
polygonType.enableConveyor,
polygonIndex)
polygonIndex += 1
polygonTypeC += '};\n\n'
polygonC += '};\n\n'
data.source += polygonTypeC + polygonC
polygonTypesName = collision.polygonTypesName()
polygonsName = collision.polygonsName()
else:
polygonTypesName = '0'
polygonsName = '0'
if len(collision.vertices) > 0:
data.header += "extern Vec3s " + collision.verticesName() + "[" + str(len(collision.vertices)) + "];\n"
data.source += "Vec3s " + collision.verticesName() + "[" + str(len(collision.vertices)) + "] = {\n"
for vertex in collision.vertices:
data.source += '\t' + ootCollisionVertexToC(vertex)
data.source += '};\n\n'
collisionVerticesName = collision.verticesName()
else:
collisionVerticesName = '0'
if len(collision.waterBoxes) > 0:
data.header += "extern WaterBox " + collision.waterBoxesName() + "[];\n"
data.source += "WaterBox " + collision.waterBoxesName() + "[] = {\n"
for waterBox in collision.waterBoxes:
data.source += '\t' + ootWaterBoxToC(waterBox)
data.source += '};\n\n'
waterBoxesName = collision.waterBoxesName()
else:
waterBoxesName = '0'
if len(collision.cameraData.camPosDict) > 0:
camDataName = "&" + collision.camDataName()
else:
camDataName = '0'
data.header += "extern CollisionHeader " + collision.headerName() + ';\n'
data.source += "CollisionHeader " + collision.headerName() + ' = { '
if len(collision.bounds) == 2:
for bound in range(2): # min, max bound
for field in range(3): # x, y, z
data.source += str(collision.bounds[bound][field]) + ', '
else:
data.source += '0, 0, 0, 0, 0, 0, '
data.source += \
str(len(collision.vertices)) + ', ' +\
collisionVerticesName + ', ' +\
str(collision.polygonCount()) + ", " +\
polygonsName + ', ' +\
polygonTypesName + ', ' +\
camDataName + ', ' +\
str(len(collision.waterBoxes)) + ", " +\
waterBoxesName + ' };\n\n'
return data
+1 -1
View File
@@ -1,7 +1,7 @@
import math
from ..oot_f3d_writer import *
from ..oot_level_writer import *
from .oot_collision_c import *
from ..oot_collision import *
def cmdName(name, header, index):
return name + "_header" + format(header, '02') + "_cmd" + format(index, '02')
+173 -16
View File
@@ -1,4 +1,4 @@
import shutil, copy, math, mathutils, bpy, os
import shutil, copy, math, mathutils, bpy, os, re
from bpy.utils import register_class, unregister_class
from .oot_constants import *
@@ -178,6 +178,96 @@ def exportAnimationC(armatureObj, exportPath, isCustomExport, folderName, skelet
if not isCustomExport:
addIncludeFiles(folderName, path, ootAnim.name)
def getNextBone(boneStack, armatureObj):
if len(boneStack) == 0:
raise PluginError("More bones in animation than on armature.")
bone = armatureObj.data.bones[boneStack[0]]
boneStack = boneStack[1:]
boneStack = getSortedChildren(armatureObj, bone) + boneStack
return bone, boneStack
def ootImportAnimationC(armatureObj, filepath, animName, actorScale):
animData = readFile(filepath)
matchResult = re.search(re.escape(animName) + "\s*=\s*\{\s*\{\s*([^,\s]*)\s*\}*\s*,\s*([^,\s]*)\s*,\s*([^,\s]*)\s*,\s*([^,\s]*)\s*\}\s*;", animData)
if matchResult is None:
raise PluginError("Cannot find animation named " + animName + " in " + filepath)
frameCount = hexOrDecInt(matchResult.group(1).strip())
frameDataName = matchResult.group(2).strip()
jointIndicesName = matchResult.group(3).strip()
staticIndexMax = hexOrDecInt(matchResult.group(4).strip())
frameData = getFrameData(filepath, animData, frameDataName)
jointIndices = getJointIndices(filepath, animData, jointIndicesName)
#print(frameDataName + " " + jointIndicesName)
#print(str(frameData) + "\n" + str(jointIndices))
bpy.context.scene.frame_end = frameCount
anim = bpy.data.actions.new(animName)
startBoneName = getStartBone(armatureObj)
boneStack = [startBoneName]
isRootTranslation = True
# boneFrameData = [[x keyframes], [y keyframes], [z keyframes]]
# len(armatureFrameData) should be = number of bones
# property index = 0,1,2 (aka x,y,z)
for jointIndex in jointIndices:
if isRootTranslation:
for propertyIndex in range(3):
fcurve = anim.fcurves.new(
data_path = 'pose.bones["' + startBoneName + '"].location',
index = propertyIndex,
action_group = startBoneName)
if jointIndex[propertyIndex] < staticIndexMax:
value = frameData[jointIndex[propertyIndex]] / actorScale
fcurve.keyframe_points.insert(0, value)
else:
for frame in range(frameCount):
value = frameData[jointIndex[propertyIndex] + frame] / actorScale
fcurve.keyframe_points.insert(frame, value)
isRootTranslation = False
else:
# WARNING: This assumes the order bones are processed are in alphabetical order.
# If this changes in the future, then this won't work.
bone, boneStack = getNextBone(boneStack, armatureObj)
for propertyIndex in range(3):
fcurve = anim.fcurves.new(
data_path = 'pose.bones["' + bone.name + '"].rotation_euler',
index = propertyIndex,
action_group = bone.name)
if jointIndex[propertyIndex] < staticIndexMax:
value = math.radians(frameData[jointIndex[propertyIndex]] * 360 / (2**16))
fcurve.keyframe_points.insert(0, value)
else:
for frame in range(frameCount):
value = math.radians(frameData[jointIndex[propertyIndex] + frame] * 360 / (2**16))
fcurve.keyframe_points.insert(frame, value)
if armatureObj.animation_data is None:
armatureObj.animation_data_create()
armatureObj.animation_data.action = anim
def getFrameData(filepath, animData, frameDataName):
matchResult = re.search(re.escape(frameDataName) + "\s*\[\s*[0-9]*\s*\]\s*=\s*\{([^\}]*)\}", animData, re.DOTALL)
if matchResult is None:
raise PluginError("Cannot find animation frame data named " + frameDataName + " in " + filepath)
data = matchResult.group(1)
frameData = [int.from_bytes([int(value.strip()[2:4], 16), int(value.strip()[4:6], 16)],
'big', signed = True) for value in data.split(",") if value.strip() != ""]
return frameData
def getJointIndices(filepath, animData, jointIndicesName):
matchResult = re.search(re.escape(jointIndicesName) + "\s*\[\s*[0-9]*\s*\]\s*=\s*\{([^;]*);", animData, re.DOTALL)
if matchResult is None:
raise PluginError("Cannot find animation joint indices data named " + jointIndicesName + " in " + filepath)
data = matchResult.group(1)
jointIndicesData = [[hexOrDecInt(match.group(i)) for i in range(1,4)] for match in re.finditer("\{([^,\}]*),([^,\}]*),([^,\}]*)\}", data, re.DOTALL)]
return jointIndicesData
class OOT_ExportAnim(bpy.types.Operator):
bl_idname = 'object.oot_export_anim'
bl_label = "Export Animation"
@@ -201,10 +291,52 @@ class OOT_ExportAnim(bpy.types.Operator):
try:
isCustomExport = context.scene.ootAnimIsCustomExport
exportPath = context.scene.ootAnimCustomPath
folderName = context.scene.ootAnimFolderName
skeletonName = context.scene.ootAnimName
exportAnimationC(armatureObj, exportPath, isCustomExport, folderName, skeletonName)
exportPath = bpy.path.abspath(context.scene.ootAnimExportCustomPath)
folderName = context.scene.ootAnimExportFolderName
skeletonName = context.scene.ootAnimSkeletonName
path = ootGetObjectPath(isCustomExport, exportPath, folderName)
exportAnimationC(armatureObj, path, isCustomExport, folderName, skeletonName)
self.report({'INFO'}, 'Success!')
except Exception as e:
raisePluginError(self, e)
return {'CANCELLED'} # must return a set
return {'FINISHED'} # must return a set
class OOT_ImportAnim(bpy.types.Operator):
bl_idname = 'object.oot_import_anim'
bl_label = "Import Animation"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
# Called on demand (i.e. button press, menu item)
# Can also be called from operator search menu (Spacebar)
def execute(self, context):
try:
if len(context.selected_objects) == 0 or not \
isinstance(context.selected_objects[0].data, bpy.types.Armature):
raise PluginError("Armature not selected.")
if len(context.selected_objects) > 1 :
raise PluginError("Multiple objects selected, make sure to select only one.")
armatureObj = context.selected_objects[0]
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = "OBJECT")
except Exception as e:
raisePluginError(self, e)
return {"CANCELLED"}
try:
isCustomImport = context.scene.ootAnimIsCustomImport
folderName = context.scene.ootAnimImportFolderName
importPath = bpy.path.abspath(context.scene.ootAnimImportCustomPath)
animName = context.scene.ootAnimName
actorScale = context.scene.ootActorBlenderScale
path = ootGetObjectPath(isCustomImport, importPath, folderName)
ootImportAnimationC(armatureObj, path, animName, actorScale)
self.report({'INFO'}, 'Success!')
except Exception as e:
@@ -227,18 +359,30 @@ class OOT_ExportAnimPanel(bpy.types.Panel):
# called every frame
def draw(self, context):
col = self.layout.column()
col.operator(OOT_ExportAnim.bl_idname)
col.prop(context.scene, 'ootAnimIsCustomExport')
prop_split(col, context.scene, 'ootAnimName', 'Skeleton')
prop_split(col, context.scene, 'ootAnimSkeletonName', 'Skeleton Name')
if context.scene.ootAnimIsCustomExport:
col.prop(context.scene, 'ootAnimCustomPath')
prop_split(col, context.scene, 'ootAnimExportCustomPath', "Folder")
else:
prop_split(col, context.scene, 'ootAnimFolderName', 'Object')
prop_split(col, context.scene, 'ootAnimExportFolderName', 'Object')
col.prop(context.scene, 'ootAnimIsCustomExport')
col.operator(OOT_ImportAnim.bl_idname)
prop_split(col, context.scene, 'ootAnimName', 'Anim Name')
if context.scene.ootAnimIsCustomImport:
prop_split(col, context.scene, 'ootAnimImportCustomPath', "File")
else:
prop_split(col, context.scene, 'ootAnimImportFolderName', 'Object')
col.prop(context.scene, 'ootAnimIsCustomImport')
oot_anim_classes = (
OOT_ExportAnim,
OOT_ImportAnim,
)
oot_anim_panels = (
@@ -255,17 +399,30 @@ def oot_anim_panel_unregister():
def oot_anim_register():
bpy.types.Scene.ootAnimIsCustomExport = bpy.props.BoolProperty(name = "Use Custom Path")
bpy.types.Scene.ootAnimCustomPath = bpy.props.StringProperty(
bpy.types.Scene.ootAnimExportCustomPath = bpy.props.StringProperty(
name ='Folder', subtype = 'FILE_PATH')
bpy.types.Scene.ootAnimFolderName = bpy.props.StringProperty(name = "Animation Folder", default = "gameplay_keep")
bpy.types.Scene.ootAnimName = bpy.props.StringProperty(name = "Skeleton Name", default = "skeleton")
bpy.types.Scene.ootAnimExportFolderName = bpy.props.StringProperty(name = "Animation Folder", default = "object_geldb")
bpy.types.Scene.ootAnimIsCustomImport = bpy.props.BoolProperty(name = "Use Custom Path")
bpy.types.Scene.ootAnimImportCustomPath = bpy.props.StringProperty(
name ='Folder', subtype = 'FILE_PATH')
bpy.types.Scene.ootAnimImportFolderName = bpy.props.StringProperty(name = "Animation Folder", default = "object_geldb")
bpy.types.Scene.ootAnimSkeletonName = bpy.props.StringProperty(name = "Skeleton Name", default = "gGerudoRedSkel")
bpy.types.Scene.ootAnimName = bpy.props.StringProperty(name = "Anim Name", default = "gGerudoRedSpinAttackAnim")
for cls in oot_anim_classes:
register_class(cls)
def oot_anim_unregister():
del bpy.types.Scene.ootAnimIsCustomExport
del bpy.types.Scene.ootAnimCustomPath
del bpy.types.Scene.ootAnimFolderName
del bpy.types.Scene.ootAnimExportCustomPath
del bpy.types.Scene.ootAnimExportFolderName
del bpy.types.Scene.ootAnimIsCustomImport
del bpy.types.Scene.ootAnimImportCustomPath
del bpy.types.Scene.ootAnimImportFolderName
del bpy.types.Scene.ootAnimSkeletonName
del bpy.types.Scene.ootAnimName
for cls in reversed(oot_anim_classes):
unregister_class(cls)
+215 -108
View File
@@ -466,14 +466,14 @@ class OOT_CollisionPanel(bpy.types.Panel):
box.prop(collisionProp, "conveyorKeepMomentum", text = "Keep Momentum")
# water boxes handled by level writer
def exportCollisionCommon(collision, sceneObj, allObjs, transformMatrix, includeChildren, name):
def exportCollisionCommon(collision, obj, transformMatrix, includeChildren, name):
bpy.ops.object.select_all(action = 'DESELECT')
sceneObj.select_set(True)
obj.select_set(True)
# dict of collisionType : faces
collisionDict = {}
addCollisionTriangles(sceneObj, collisionDict, includeChildren, transformMatrix, collision.bounds)
addCollisionTriangles(obj, collisionDict, includeChildren, transformMatrix, collision.bounds)
for material, faces in collisionDict.items():
polygonType = getPolygonType(material.ootCollisionProperty)
collision.polygonGroups[polygonType] = []
@@ -488,6 +488,30 @@ def exportCollisionCommon(collision, sceneObj, allObjs, transformMatrix, include
indices.append(index)
collision.polygonGroups[polygonType].append(OOTCollisionPolygon(indices, normal, distance))
def exportCollisionToC(obj, transformMatrix, includeChildren, name, isCustomExport, folderName,
exportPath):
collision = OOTCollision(name)
collision.cameraData = OOTCameraData(name)
exportCollisionCommon(collision, obj, transformMatrix, includeChildren, name)
collisionC = ootCollisionToC(collision)
data = CData()
data.source += '#include "ultra64.h"\n#include "z64.h"\n#include "macros.h"\n'
if not isCustomExport:
data.source += '#include "' + folderName + '.h"\n\n'
else:
data.source += '\n'
data.append(collisionC)
path = ootGetPath(exportPath, isCustomExport, 'assets/objects/', folderName, False, False)
writeCData(data,
os.path.join(path, name + '.h'),
os.path.join(path, name + '.c'))
if not isCustomExport:
addIncludeFiles(folderName, path, name)
def updateBounds(position, bounds):
if len(bounds) == 0:
bounds.append([position[0], position[1], position[2]])
@@ -561,6 +585,160 @@ def collisionVertIndex(vert, vertArray):
return i
return None
def ootCollisionVertexToC(vertex):
return "{ " + str(vertex.position[0]) + ", " +\
str(vertex.position[1]) + ", " +\
str(vertex.position[2]) + " },\n"
def ootCollisionPolygonToC(polygon, ignoreCamera, ignoreActor, ignoreProjectile, enableConveyor, polygonTypeIndex):
return "{ " + format(polygonTypeIndex, "#06x") + ', ' +\
format(polygon.convertShort02(ignoreCamera, ignoreActor, ignoreProjectile), "#06x") + ', ' +\
format(polygon.convertShort04(enableConveyor), "#06x") + ', ' +\
format(polygon.convertShort06(), "#06x") + ', ' +\
format(polygon.normal[0], "#06x") + ', ' +\
format(polygon.normal[1], "#06x") + ', ' +\
format(polygon.normal[2], "#06x") + ', ' +\
format(polygon.distance, "#06x") + ' },\n'
def ootPolygonTypeToC(polygonType):
return " " + format(polygonType.convertHigh(), "#010x") + ', ' +\
format(polygonType.convertLow(), "#010x") + ',\n'
def ootWaterBoxToC(waterBox):
return "{ " + str(waterBox.low[0]) + ", " +\
str(waterBox.height) + ", " +\
str(waterBox.low[1]) + ", " +\
str(waterBox.high[0] - waterBox.low[0]) + ", " +\
str(waterBox.high[1] - waterBox.low[1]) + ", " +\
format(waterBox.propertyData(), "#010x") + ' },\n'
def ootCameraDataToC(camData):
posC = CData()
camC = CData()
if len(camData.camPosDict) > 0:
camDataName = "CamData " + camData.camDataName() + "[" + str(len(camData.camPosDict)) + "]"
camC.source = camDataName + ' = {\n'
camC.header = "extern " + camDataName + ';\n'
camPosIndex = 0
for i in range(len(camData.camPosDict)):
camC.source += '\t' + ootCameraEntryToC(camData.camPosDict[i], camData, camPosIndex)
if camData.camPosDict[i].hasPositionData:
posC.source += ootCameraPosToC(camData.camPosDict[i])
camPosIndex += 3
posC.source += '};\n\n'
camC.source += '};\n\n'
posDataName = "Vec3s " + camData.camPositionsName() + '[' + str(camPosIndex) + ']'
posC.header = "extern " + posDataName + ';\n'
posC.source = posDataName + " = {\n" + posC.source
posC.append(camC)
return posC
def ootCameraPosToC(camPos):
return "\t{ " +\
str(camPos.position[0]) + ', ' +\
str(camPos.position[1]) + ', ' +\
str(camPos.position[2]) + ' },\n\t{ ' +\
str(camPos.rotation[0]) + ', ' +\
str(camPos.rotation[1]) + ', ' +\
str(camPos.rotation[2]) + ' },\n\t{ ' +\
str(camPos.fov) + ', ' +\
str(camPos.jfifID) + ', ' +\
str(camPos.unknown) + ' },\n'
def ootCameraEntryToC(camPos, camData, camPosIndex):
return "{ " +\
str(camPos.camSType) + ', ' +\
('3' if camPos.hasPositionData else '0') + ', ' +\
(("&" + camData.camPositionsName() + '[' + str(camPosIndex) + ']')
if camPos.hasPositionData else "0") + ' },\n'
def ootCollisionToC(collision):
data = CData()
data.append(ootCameraDataToC(collision.cameraData))
if len(collision.polygonGroups) > 0:
data.header += "extern u32 " + collision.polygonTypesName() + "[];\n"
data.header += "extern CollisionPoly " + collision.polygonsName() + "[];\n"
polygonTypeC = "u32 " + collision.polygonTypesName() + "[] = {\n"
polygonC = "CollisionPoly " + collision.polygonsName() + "[] = {\n"
polygonIndex = 0
for polygonType, polygons in collision.polygonGroups.items():
polygonTypeC += '\t' + ootPolygonTypeToC(polygonType)
for polygon in polygons:
polygonC += '\t' + ootCollisionPolygonToC(polygon,
polygonType.ignoreCameraCollision,
polygonType.ignoreActorCollision,
polygonType.ignoreProjectileCollision,
polygonType.enableConveyor,
polygonIndex)
polygonIndex += 1
polygonTypeC += '};\n\n'
polygonC += '};\n\n'
data.source += polygonTypeC + polygonC
polygonTypesName = collision.polygonTypesName()
polygonsName = collision.polygonsName()
else:
polygonTypesName = '0'
polygonsName = '0'
if len(collision.vertices) > 0:
data.header += "extern Vec3s " + collision.verticesName() + "[" + str(len(collision.vertices)) + "];\n"
data.source += "Vec3s " + collision.verticesName() + "[" + str(len(collision.vertices)) + "] = {\n"
for vertex in collision.vertices:
data.source += '\t' + ootCollisionVertexToC(vertex)
data.source += '};\n\n'
collisionVerticesName = collision.verticesName()
else:
collisionVerticesName = '0'
if len(collision.waterBoxes) > 0:
data.header += "extern WaterBox " + collision.waterBoxesName() + "[];\n"
data.source += "WaterBox " + collision.waterBoxesName() + "[] = {\n"
for waterBox in collision.waterBoxes:
data.source += '\t' + ootWaterBoxToC(waterBox)
data.source += '};\n\n'
waterBoxesName = collision.waterBoxesName()
else:
waterBoxesName = '0'
if len(collision.cameraData.camPosDict) > 0:
camDataName = "&" + collision.camDataName()
else:
camDataName = '0'
data.header += "extern CollisionHeader " + collision.headerName() + ';\n'
data.source += "CollisionHeader " + collision.headerName() + ' = { '
if len(collision.bounds) == 2:
for bound in range(2): # min, max bound
for field in range(3): # x, y, z
data.source += str(collision.bounds[bound][field]) + ', '
else:
data.source += '0, 0, 0, 0, 0, 0, '
data.source += \
str(len(collision.vertices)) + ', ' +\
collisionVerticesName + ', ' +\
str(collision.polygonCount()) + ", " +\
polygonsName + ', ' +\
polygonTypesName + ', ' +\
camDataName + ', ' +\
str(len(collision.waterBoxes)) + ", " +\
waterBoxesName + ' };\n\n'
return data
class OOT_ExportCollision(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.oot_export_collision'
@@ -568,109 +746,38 @@ class OOT_ExportCollision(bpy.types.Operator):
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
def execute(self, context):
romfileOutput = None
tempROM = None
try:
obj = None
if context.mode != 'OBJECT':
raise PluginError("Operator can only be used in object mode.")
if len(context.selected_objects) == 0:
raise PluginError("Object not selected.")
obj = context.active_object
#if type(obj.data) is not bpy.types.Mesh:
# raise PluginError("Mesh not selected.")
#T, R, S = obj.matrix_world.decompose()
#objTransform = R.to_matrix().to_4x4() @ \
# mathutils.Matrix.Diagonal(S).to_4x4()
#finalTransform = (blenderToSM64Rotation * \
# (bpy.context.scene.blenderToSM64Scale)).to_4x4()
#finalTransform = mathutils.Matrix.Identity(4)
obj = None
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = "OBJECT")
if len(context.selected_objects) == 0:
raise PluginError("No object selected.")
obj = context.active_object
if type(obj.data) is not bpy.types.Mesh:
raise PluginError("No mesh object selected.")
scaleValue = bpy.context.scene.blenderToSM64Scale
finalTransform = mathutils.Matrix.Scale(context.scene.ootActorBlenderScale, 4)
try:
scaleValue = bpy.context.scene.ootBlenderScale
finalTransform = mathutils.Matrix.Diagonal(mathutils.Vector((
scaleValue, scaleValue, scaleValue))).to_4x4()
except Exception as e:
raisePluginError(self, e)
return {"CANCELLED"}
try:
applyRotation([obj], math.radians(90), 'X')
if context.scene.colExportType == 'C':
exportPath, levelName = getPathAndLevel(context.scene.colCustomExport,
context.scene.colExportPath, context.scene.colLevelName,
context.scene.colLevelOption)
if not context.scene.colCustomExport:
applyBasicTweaks(exportPath)
exportCollisionC(obj, finalTransform,
exportPath, False,
context.scene.colIncludeChildren,
bpy.context.scene.colName, context.scene.colCustomExport, context.scene.colExportRooms,
context.scene.colExportHeaderType, context.scene.colGroupName, levelName)
self.report({'INFO'}, 'Success!')
elif context.scene.colExportType == 'Insertable Binary':
exportCollisionInsertableBinary(obj, finalTransform,
bpy.path.abspath(context.scene.colInsertableBinaryPath),
False, context.scene.colIncludeChildren)
self.report({'INFO'}, 'Success! Collision at ' + \
context.scene.colInsertableBinaryPath)
else:
tempROM = tempName(context.scene.outputRom)
checkExpanded(bpy.path.abspath(context.scene.exportRom))
romfileExport = \
open(bpy.path.abspath(context.scene.exportRom), 'rb')
shutil.copy(bpy.path.abspath(context.scene.exportRom),
bpy.path.abspath(tempROM))
romfileExport.close()
romfileOutput = open(bpy.path.abspath(tempROM), 'rb+')
levelParsed = parseLevelAtPointer(romfileOutput,
level_pointers[context.scene.colExportLevel])
segmentData = levelParsed.segmentData
includeChildren = context.scene.ootColIncludeChildren
name = context.scene.ootColName
isCustomExport = context.scene.ootColCustomExport
folderName = context.scene.ootColFolder
exportPath = bpy.path.abspath(context.scene.ootColExportPath)
if context.scene.extendBank4:
ExtendBank0x04(romfileOutput, segmentData,
defaultExtendSegment4)
filepath = ootGetObjectPath(isCustomExport, exportPath, folderName)
exportCollisionToC(obj, finalTransform, includeChildren, name, isCustomExport, folderName,
filepath)
addrRange = \
exportCollisionBinary(obj, finalTransform, romfileOutput,
int(context.scene.colStartAddr, 16),
int(context.scene.colEndAddr, 16),
False, context.scene.colIncludeChildren)
segAddress = encodeSegmentedAddr(addrRange[0], segmentData)
if context.scene.set_addr_0x2A:
romfileOutput.seek(int(context.scene.addr_0x2A, 16) + 4)
romfileOutput.write(segAddress)
segPointer = bytesToHex(segAddress)
romfileOutput.close()
if os.path.exists(bpy.path.abspath(context.scene.outputRom)):
os.remove(bpy.path.abspath(context.scene.outputRom))
os.rename(bpy.path.abspath(tempROM),
bpy.path.abspath(context.scene.outputRom))
self.report({'INFO'}, 'Success! Collision at (' + \
hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \
') (Seg. ' + segPointer + ').')
applyRotation([obj], math.radians(-90), 'X')
return {'FINISHED'} # must return a set
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
except Exception as e:
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = 'OBJECT')
applyRotation([obj], math.radians(-90), 'X')
if context.scene.colExportType == 'Binary':
if romfileOutput is not None:
romfileOutput.close()
if tempROM is not None and os.path.exists(bpy.path.abspath(tempROM)):
os.remove(bpy.path.abspath(tempROM))
obj.select_set(True)
context.view_layer.objects.active = obj
raisePluginError(self, e)
return {'CANCELLED'} # must return a set
@@ -688,18 +795,15 @@ class OOT_ExportCollisionPanel(bpy.types.Panel):
# called every frame
def draw(self, context):
col = self.layout.column()
propsColE = col.operator(OOT_ExportCollision.bl_idname)
col.operator(OOT_ExportCollision.bl_idname)
col.prop(context.scene, 'colIncludeChildren')
col.prop(context.scene, 'colCustomExport')
if context.scene.colCustomExport:
col.prop(context.scene, 'colExportPath')
prop_split(col, context.scene, 'colName', 'Name')
customExportWarning(col)
prop_split(col, context.scene, 'ootColName', 'Name')
if context.scene.ootColCustomExport:
prop_split(col, context.scene, 'ootColExportPath', "Custom Folder")
else:
prop_split(col, context.scene, 'colExportLevel', 'Level')
if context.scene.colExportLevel == 'Custom':
prop_split(col, context.scene, 'colLevelName', 'Level Name')
prop_split(col, context.scene, 'ootColFolder', 'Object')
col.prop(context.scene, 'ootColCustomExport')
col.prop(context.scene, 'ootColIncludeChildren')
oot_col_classes = (
@@ -713,7 +817,7 @@ oot_col_classes = (
oot_col_panel_classes = (
OOT_CollisionPanel,
OOT_CameraPosPanel,
#OOT_ExportCollisionPanel,
OOT_ExportCollisionPanel,
)
def oot_col_panel_register():
@@ -736,11 +840,13 @@ def oot_col_register():
bpy.types.Scene.ootColIncludeChildren = bpy.props.BoolProperty(
name = 'Include child objects', default = True)
bpy.types.Scene.ootColName = bpy.props.StringProperty(
name = 'Name', default = 'link')
name = 'Name', default = 'collision')
bpy.types.Scene.ootColLevelName = bpy.props.StringProperty(
name = 'Name', default = 'SCENE_YDAN')
bpy.types.Scene.ootColCustomExport = bpy.props.BoolProperty(
name = 'Custom Export Path')
bpy.types.Scene.ootColFolder = bpy.props.StringProperty(
name = 'Object Name', default = "gameplay_keep")
bpy.types.Object.ootCameraPositionProperty = bpy.props.PointerProperty(type = OOTCameraPositionProperty)
bpy.types.Material.ootCollisionProperty = bpy.props.PointerProperty(type = OOTMaterialCollisionProperty)
@@ -753,6 +859,7 @@ def oot_col_unregister():
del bpy.types.Scene.ootColLevelName
del bpy.types.Scene.ootColIncludeChildren
del bpy.types.Scene.ootColCustomExport
del bpy.types.Scene.ootColFolder
for cls in reversed(oot_col_classes):
unregister_class(cls)
+408 -230
View File
@@ -1,4 +1,4 @@
import shutil, copy, bpy
import shutil, copy, bpy, os
from ..f3d.f3d_writer import *
from ..f3d.f3d_material import *
@@ -6,18 +6,24 @@ from bpy.utils import register_class, unregister_class
from .oot_constants import *
from .oot_utility import *
from .oot_scene_room import *
from ..f3d.f3d_parser import *
class OOTModel(FModel):
def __init__(self, f3dType, isHWv1, name, DLFormat):
def __init__(self, f3dType, isHWv1, name, DLFormat, drawLayerOverride):
self.drawLayerOverride = drawLayerOverride
FModel.__init__(self, f3dType, isHWv1, name, DLFormat, GfxMatWriteMethod.WriteAll)
def getDrawLayerV3(self, obj):
return obj.ootDrawLayer
def getRenderMode(self, drawLayer):
if self.drawLayerOverride:
drawLayerUsed = self.drawLayerOverride
else:
drawLayerUsed = drawLayer
defaultRenderModes = bpy.context.scene.world.ootDefaultRenderModes
cycle1 = getattr(defaultRenderModes, drawLayer.lower() + "Cycle1")
cycle2 = getattr(defaultRenderModes, drawLayer.lower() + "Cycle2")
cycle1 = getattr(defaultRenderModes, drawLayerUsed.lower() + "Cycle1")
cycle2 = getattr(defaultRenderModes, drawLayerUsed.lower() + "Cycle2")
return [cycle1, cycle2]
def getTextureSuffixFromFormat(self, texFmt):
@@ -34,6 +40,11 @@ class OOTModel(FModel):
GfxList("0x" + format(i,'X') + '000000', GfxListTag.Material, DLFormat.Static)))
return
def onAddMesh(self, fMesh, contextObj):
if contextObj is not None and hasattr(contextObj, 'ootDynamicTransform'):
if contextObj.ootDynamicTransform.billboard:
fMesh.draw.commands.append(SPMatrix("0x01000000", "G_MTX_MODELVIEW | G_MTX_NOPUSH | G_MTX_MUL"))
class OOTDynamicMaterialDrawLayer:
def __init__(self, opaque, transparent):
self.opaque = opaque
@@ -89,6 +100,176 @@ class OOTVertexGroupInfo(VertexGroupInfo):
self.vertexGroupToMatrixIndex = {}
VertexGroupInfo.__init__(self)
#class OOTBox:
# def __init__(self):
# self.minBounds = [-2**8, -2**8]
# self.maxBounds = [2**8 - 1, 2**8 - 1]
class OOTObjectCategorizer:
def __init__(self):
self.sceneObj = None
self.roomObjs = []
self.actors = []
self.transitionActors = []
self.meshes = []
self.entrances = []
self.waterBoxes = []
def sortObjects(self, allObjs):
for obj in allObjs:
if obj.data is None:
if obj.ootEmptyType == "Actor":
self.actors.append(obj)
elif obj.ootEmptyType == "Transition Actor":
self.transitionActors.append(obj)
elif obj.ootEmptyType == "Entrance":
self.entrances.append(obj)
elif obj.ootEmptyType == "Water Box":
self.waterBoxes.append(obj)
elif obj.ootEmptyType == "Room":
self.roomObjs.append(obj)
elif obj.ootEmptyType == "Scene":
self.sceneObj = obj
elif isinstance(obj.data, bpy.types.Mesh):
self.meshes.append(obj)
# This also sets all origins relative to the scene object.
def ootDuplicateHierarchy(obj, ignoreAttr, includeEmpties, objectCategorizer):
# Duplicate objects to apply scale / modifiers / linked data
bpy.ops.object.select_all(action = 'DESELECT')
ootSelectMeshChildrenOnly(obj, includeEmpties)
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.duplicate()
try:
tempObj = bpy.context.view_layer.objects.active
allObjs = bpy.context.selected_objects
bpy.ops.object.make_single_user(obdata = True)
objectCategorizer.sortObjects(allObjs)
meshObjs = objectCategorizer.meshes
bpy.ops.object.select_all(action = 'DESELECT')
for selectedObj in meshObjs:
selectedObj.select_set(True)
bpy.ops.object.transform_apply(location = False,
rotation = True, scale = True, properties = False)
for selectedObj in meshObjs:
bpy.ops.object.select_all(action = 'DESELECT')
selectedObj.select_set(True)
bpy.context.view_layer.objects.active = selectedObj
for modifier in selectedObj.modifiers:
attemptModifierApply(modifier)
for selectedObj in meshObjs:
setOrigin(obj, selectedObj)
if ignoreAttr is not None:
for selectedObj in meshObjs:
if getattr(selectedObj, ignoreAttr):
for child in selectedObj.children:
bpy.ops.object.select_all(action = 'DESELECT')
child.select_set(True)
bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
selectedObj.parent.select_set(True)
bpy.ops.object.parent_set(keep_transform = True)
selectedObj.parent = None
applyRotation([tempObj], math.radians(90), 'X')
return tempObj, allObjs
except Exception as e:
cleanupDuplicatedObjects(allObjs)
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
raise Exception(str(e))
def ootSelectMeshChildrenOnly(obj, includeEmpties):
isMesh = isinstance(obj.data, bpy.types.Mesh)
isEmpty = (obj.data is None or \
isinstance(obj.data, bpy.types.Camera) or\
isinstance(obj.data, bpy.types.Curve)) and includeEmpties
if (isMesh or isEmpty):
obj.select_set(True)
obj.original_name = obj.name
for child in obj.children:
ootSelectMeshChildrenOnly(child, includeEmpties)
def ootCleanupScene(originalSceneObj, allObjs):
cleanupDuplicatedObjects(allObjs)
originalSceneObj.select_set(True)
bpy.context.view_layer.objects.active = originalSceneObj
class OOTF3DContext(F3DContext):
def __init__(self, f3d, limbList, basePath):
self.limbList = limbList
self.dlList = [] # in the order they are rendered
self.isBillboard = False
materialContext = createF3DMat(None, preset = 'oot_shaded_solid')
#materialContext.f3d_mat.rdp_settings.g_mdsft_cycletype = "G_CYC_1CYCLE"
F3DContext.__init__(self, f3d, basePath, materialContext)
def getLimbName(self, index):
return self.limbList[index]
def getBoneName(self, index):
return "bone" + format(index, "03") + "_" + self.getLimbName(index)
def vertexFormatPatterns(self, data):
# position, uv, color/normal
if "VTX" in data:
return ["VTX\s*\(([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*)\)"]
else:
return F3DContext.vertexFormatPatterns(self, data)
# For game specific instance, override this to be able to identify which verts belong to which bone.
def setCurrentTransform(self, name):
if name[:4].lower() == "0x0d":
self.currentTransformName = self.getLimbName(self.dlList[int(int(name[4:], 16) / MTX_SIZE)].limbIndex)
else:
try:
pointer = hexOrDecInt(name)
except:
self.currentTransformName = name
else:
if pointer >> 24 == 0x01:
self.isBillboard = True
else:
print("Unhandled matrix: " + name)
def processDLName(self, name):
# Commands loaded to 0x0C are material related only.
try:
pointer = hexOrDecInt(name)
except:
return name
else:
segment = pointer >> 24
print("POINTER")
if segment >= 0x08 and segment <= 0x0D:
print("SETTING " + str(segment))
setattr(self.materialContext.ootMaterial.opaque, "segment" + format(segment, "1X"), True)
setattr(self.materialContext.ootMaterial.transparent, "segment" + format(segment, "1X"), True)
return None
return name
def processTextureName(self, textureName):
try:
pointer = hexOrDecInt(textureName)
except:
return textureName
else:
return textureName
#if (pointer >> 24) == 0x08:
# print("Unhandled OOT pointer: " + textureName)
def clearMaterial(self):
self.isBillboard = False
clearOOTMaterialDrawLayerProperty(self.materialContext.ootMaterial.opaque)
clearOOTMaterialDrawLayerProperty(self.materialContext.ootMaterial.transparent)
F3DContext.clearMaterial(self)
def postMaterialChanged(self):
clearOOTMaterialDrawLayerProperty(self.materialContext.ootMaterial.opaque)
clearOOTMaterialDrawLayerProperty(self.materialContext.ootMaterial.transparent)
# returns:
# mesh,
# anySkinnedFaces (to determine if skeleton should be flex)
@@ -164,8 +345,8 @@ def ootProcessVertexGroup(fModel, meshObj, vertexGroup, convertTransformMatrix,
# This doesn't handle case where vertices belong to a limb, but not triangles.
# Therefore we create a dummy DL
if anyConnectedToUnhandledBone:
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayerOverride, False)
fMesh.draw.commands.append(SPEndDisplayList())
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayerOverride, False, bone)
fModel.endDraw(fMesh, bone)
meshInfo.vertexGroupInfo.vertexGroupToMatrixIndex[currentGroupIndex] = nextDLIndex
return fMesh, False
else:
@@ -189,7 +370,7 @@ def ootProcessVertexGroup(fModel, meshObj, vertexGroup, convertTransformMatrix,
drawLayer = drawLayerOverride
if drawLayerKey not in fMeshes:
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayer, False)
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayer, False, bone)
fMeshes[drawLayerKey] = fMesh
checkForF3dMaterialInFaces(meshObj, material)
@@ -214,7 +395,7 @@ def ootProcessVertexGroup(fModel, meshObj, vertexGroup, convertTransformMatrix,
if drawLayerKey not in fMeshes:
# technically skinned, but for oot we don't have separate skinned/unskinned meshes.
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayer, False)
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayer, False, bone)
fMeshes[drawLayerKey] = fMesh
checkForF3dMaterialInFaces(meshObj, material)
@@ -228,7 +409,7 @@ def ootProcessVertexGroup(fModel, meshObj, vertexGroup, convertTransformMatrix,
meshObj, drawLayer, convertTextureData, currentGroupIndex, triConverterInfo, None, None)
for drawLayer, fMesh in fMeshes.items():
fMesh.draw.commands.append(SPEndDisplayList())
fModel.endDraw(fMesh, bone)
return fMeshes[drawLayerKey], len(skinnedFaces) > 0
@@ -237,6 +418,54 @@ ootEnumObjectMenu = [
("Room", "Parent Room Settings", "Room"),
]
def ootConvertMeshToC(originalObj, finalTransform, f3dType, isHWv1, name, folderName, DLFormat, saveTextures,
exportPath, isCustomExport, drawLayer, removeVanillaData):
name = toAlnum(name)
try:
obj, allObjs = ootDuplicateHierarchy(originalObj, None, False, OOTObjectCategorizer())
fModel = OOTModel(f3dType, isHWv1, name, DLFormat, drawLayer)
triConverterInfo = TriangleConverterInfo(obj, None, fModel.f3d, finalTransform, getInfoDict(obj))
fMeshes = saveStaticModel(triConverterInfo, fModel, obj, finalTransform, fModel.name,
not saveTextures, False, 'oot')
# Since we provide a draw layer override, there should only be one fMesh.
for drawLayer, fMesh in fMeshes.items():
fMesh.draw.name = name
ootCleanupScene(originalObj, allObjs)
except Exception as e:
ootCleanupScene(originalObj, allObjs)
raise Exception(str(e))
data = CData()
data.source += '#include "ultra64.h"\n#include "global.h"\n'
if not isCustomExport:
data.source += '#include "' + folderName + '.h"\n\n'
else:
data.source += '\n'
exportData = fModel.to_c(
TextureExportSettings(False, saveTextures, "test"), OOTGfxFormatter(ScrollMethod.Vertex))
data.append(exportData.all())
path = ootGetPath(exportPath, isCustomExport, 'assets/objects/', folderName, False, False)
writeCData(data,
os.path.join(path, name + '.h'),
os.path.join(path, name + '.c'))
if not isCustomExport:
addIncludeFiles(folderName, path, name)
if removeVanillaData:
headerPath = os.path.join(path, folderName + '.h')
sourcePath = os.path.join(path, folderName + '.c')
removeDL(sourcePath, headerPath, name)
class OOT_DisplayListPanel(bpy.types.Panel):
bl_label = "Display List Inspector"
bl_idname = "OBJECT_PT_OOT_DL_Inspector"
@@ -259,165 +488,97 @@ class OOT_DisplayListPanel(bpy.types.Panel):
box.prop(obj, "ignore_render")
box.prop(obj, "ignore_collision")
# Doesn't work since all static meshes are pre-transformed
#box.prop(obj.ootDynamicTransform, "billboard")
#drawParentSceneRoom(box, obj)
def ootExportF3DtoC(basePath, obj, DLFormat, transformMatrix,
f3dType, isHWv1, texDir, savePNG, texSeparate, includeChildren, name, levelName, groupName, customExport, headerType):
dirPath, texDir = getExportDir(customExport, basePath, headerType,
levelName, texDir, name)
fModel = OOTModel(f3dType, isHWv1, name, DLFormat, 0)
fMesh = exportF3DCommon(obj, fModel, transformMatrix,
includeChildren, name, DLFormat, not savePNG)
modelDirPath = os.path.join(dirPath, toAlnum(name))
if not os.path.exists(modelDirPath):
os.mkdir(modelDirPath)
if headerType == 'Actor':
scrollName = 'actor_dl_' + name
elif headerType == 'Level':
scrollName = levelName + '_level_dl_' + name
gfxFormatter = SM64GfxFormatter(ScrollMethod.Vertex)
exportData = fModel.to_c(
TextureExportSettings(texSeparate, savePNG, texDir), gfxFormatter)
staticData = exportData.staticData
dynamicData = exportData.dynamicData
texC = exportData.textureData
scrollData, hasScrolling = fModel.to_c_vertex_scroll(scrollName, gfxFormatter)
scroll_data = scrollData.source
cDefineScroll = scrollData.header
modifyTexScrollFiles(basePath, modelDirPath, cDefineScroll, scroll_data, hasScrolling)
if DLFormat == DLFormat.Static:
staticData.append(dynamicData)
else:
geoString = writeMaterialFiles(basePath, modelDirPath,
'#include "actors/' + toAlnum(name) + '/header.h"',
'#include "actors/' + toAlnum(name) + '/material.inc.h"',
dynamicData.header, dynamicData.source, '', customExport)
#fModel.save_textures(modelDirPath, not savePNG)
#fModel.freePalettes()
if texSeparate:
texCFile = open(os.path.join(modelDirPath, 'texture.inc.c'), 'w', newline='\n')
texCFile.write(texC.source)
texCFile.close()
modelPath = os.path.join(modelDirPath, 'model.inc.c')
outFile = open(modelPath, 'w', newline='\n')
outFile.write(staticData.source)
outFile.close()
headerPath = os.path.join(modelDirPath, 'header.h')
cDefFile = open(headerPath, 'w', newline='\n')
cDefFile.write(staticData.header)
cDefFile.close()
if not customExport:
if headerType == 'Actor':
# Write to group files
if groupName == '' or groupName is None:
raise PluginError("Actor header type chosen but group name not provided.")
groupPathC = os.path.join(dirPath, groupName + ".c")
groupPathH = os.path.join(dirPath, groupName + ".h")
writeIfNotFound(groupPathC, '\n#include "' + toAlnum(name) + '/model.inc.c"', '')
writeIfNotFound(groupPathH, '\n#include "' + toAlnum(name) + '/header.h"', '\n#endif')
if DLFormat != DLFormat.Static: # Change this
writeMaterialHeaders(basePath,
'#include "actors/' + toAlnum(name) + '/material.inc.c"',
'#include "actors/' + toAlnum(name) + '/material.inc.h"')
texscrollIncludeC = '#include "actors/' + name + '/texscroll.inc.c"'
texscrollIncludeH = '#include "actors/' + name + '/texscroll.inc.h"'
texscrollGroup = groupName
texscrollGroupInclude = '#include "actors/' + groupName + '.h"'
elif headerType == 'Level':
groupPathC = os.path.join(dirPath, "leveldata.c")
groupPathH = os.path.join(dirPath, "header.h")
writeIfNotFound(groupPathC, '\n#include "levels/' + levelName + '/' + \
toAlnum(name) + '/model.inc.c"', '')
writeIfNotFound(groupPathH, '\n#include "levels/' + levelName + '/' + \
toAlnum(name) + '/header.h"', '\n#endif')
if DLFormat != DLFormat.Static: # Change this
writeMaterialHeaders(basePath,
'#include "levels/' + levelName + '/' + toAlnum(name) + '/material.inc.c"',
'#include "levels/' + levelName + '/' + toAlnum(name) + '/material.inc.h"')
texscrollIncludeC = '#include "levels/' + levelName + '/' + name + '/texscroll.inc.c"'
texscrollIncludeH = '#include "levels/' + levelName + '/' + name + '/texscroll.inc.h"'
texscrollGroup = levelName
texscrollGroupInclude = '#include "levels/' + levelName + '/header.h"'
modifyTexScrollHeadersGroup(basePath, texscrollIncludeC, texscrollIncludeH,
texscrollGroup, cDefineScroll, texscrollGroupInclude, hasScrolling)
if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = 'OBJECT')
class OOT_ExportDL(bpy.types.Operator):
class OOT_ImportDL(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.oot_export_dl'
bl_label = "Export Display List"
bl_idname = 'object.oot_import_dl'
bl_label = "Import DL"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
# Called on demand (i.e. button press, menu item)
# Can also be called from operator search menu (Spacebar)
def execute(self, context):
obj = None
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = "OBJECT")
try:
if context.mode != 'OBJECT':
raise PluginError("Operator can only be used in object mode.")
allObjs = context.selected_objects
if len(allObjs) == 0:
raise PluginError("No objects selected.")
obj = context.selected_objects[0]
if not isinstance(obj.data, bpy.types.Mesh):
raise PluginError("Object is not a mesh.")
name = context.scene.ootDLImportName
folderName = context.scene.ootDLImportFolderName
importPath = bpy.path.abspath(context.scene.ootDLImportCustomPath)
isCustomImport = context.scene.ootDLImportUseCustomPath
scale = context.scene.ootActorBlenderScale
basePath = bpy.path.abspath(context.scene.ootDecompPath)
removeDoubles = context.scene.ootDLRemoveDoubles
importNormals = context.scene.ootDLImportNormals
drawLayer = bpy.context.scene.ootDLImportDrawLayer
scaleValue = bpy.context.scene.ootBlenderScale
finalTransform = mathutils.Matrix.Diagonal(mathutils.Vector((
scaleValue, scaleValue, scaleValue))).to_4x4()
filepaths = [ootGetObjectPath(isCustomImport, importPath, folderName)]
if not isCustomImport:
filepaths.append(os.path.join(bpy.context.scene.ootDecompPath, "assets/objects/gameplay_keep/gameplay_keep.c"))
except Exception as e:
raisePluginError(self, e)
return {"CANCELLED"}
try:
applyRotation([obj], math.radians(90), 'X')
importMeshC(filepaths, name, scale, removeDoubles, importNormals, drawLayer,
OOTF3DContext(F3D("F3DEX2/LX2", False), [name], basePath))
exportPath, levelName = getPathAndLevel(context.scene.ootDLCustomExport,
context.scene.ootExportPath, context.scene.ootDLLevelName,
context.scene.ootDLLevelOption)
#if not context.scene.ootDLCustomExport:
# applyBasicTweaks(exportPath)
ootExportF3DtoC(exportPath, obj,
DLFormat.Static if context.scene.ootDLExportisStatic else DLFormat.Dynamic, finalTransform,
context.scene.f3d_type, context.scene.isHWv1,
bpy.context.scene.ootDLTexDir,
bpy.context.scene.ootDLSaveTextures or bpy.context.scene.ignoreTextureRestrictions,
bpy.context.scene.ootDLSeparateTextureDef,
bpy.context.scene.ootDLincludeChildren, bpy.context.scene.ootDLName, levelName, context.scene.ootDLGroupName,
context.scene.ootDLCustomExport,
context.scene.ootDLExportHeaderType)
self.report({'INFO'}, 'Success!')
applyRotation([obj], math.radians(-90), 'X')
return {'FINISHED'} # must return a set
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
except Exception as e:
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = 'OBJECT')
raisePluginError(self, e)
return {'CANCELLED'} # must return a set
class OOT_ExportDL(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.oot_export_dl'
bl_label = "Export DL"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
# Called on demand (i.e. button press, menu item)
# Can also be called from operator search menu (Spacebar)
def execute(self, context):
obj = None
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = "OBJECT")
if len(context.selected_objects) == 0:
raise PluginError("Mesh not selected.")
obj = context.active_object
if type(obj.data) is not bpy.types.Mesh:
raise PluginError("Mesh not selected.")
finalTransform = mathutils.Matrix.Scale(context.scene.ootActorBlenderScale, 4)
try:
#exportPath, levelName = getPathAndLevel(context.scene.geoCustomExport,
# context.scene.geoExportPath, context.scene.geoLevelName,
# context.scene.geoLevelOption)
saveTextures = bpy.context.scene.saveTextures or bpy.context.scene.ignoreTextureRestrictions
isHWv1 = context.scene.isHWv1
f3dType = context.scene.f3d_type
name = context.scene.ootDLExportName
folderName = context.scene.ootDLExportFolderName
exportPath = bpy.path.abspath(context.scene.ootDLExportCustomPath)
isCustomExport = context.scene.ootDLExportUseCustomPath
drawLayer = context.scene.ootDLExportDrawLayer
removeVanillaData = context.scene.ootDLRemoveVanillaData
ootConvertMeshToC(obj, finalTransform,
f3dType, isHWv1, name, folderName, DLFormat.Static, saveTextures,
exportPath, isCustomExport, drawLayer, removeVanillaData)
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
except Exception as e:
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = 'OBJECT')
applyRotation([obj], math.radians(-90), 'X')
raisePluginError(self, e)
return {'CANCELLED'} # must return a set
@@ -435,41 +596,29 @@ class OOT_ExportDLPanel(bpy.types.Panel):
# called every frame
def draw(self, context):
col = self.layout.column()
propsDLE = col.operator(OOT_ExportDL.bl_idname)
col.prop(context.scene, 'ootDLExportisStatic')
col.operator(OOT_ExportDL.bl_idname)
col.prop(context.scene, 'ootDLCustomExport')
if context.scene.ootDLCustomExport:
col.prop(context.scene, 'ootDLExportPath')
prop_split(col, context.scene, 'ootDLName', 'Name')
if not bpy.context.scene.ignoreTextureRestrictions:
col.prop(context.scene, 'saveTextures')
if context.scene.saveTextures:
prop_split(col, context.scene, 'ootDLTexDir',
'Texture Include Path')
col.prop(context.scene, 'ootDLSeparateTextureDef')
customExportWarning(col)
else:
prop_split(col, context.scene, 'ootDLExportHeaderType', 'Export Type')
prop_split(col, context.scene, 'ootDLName', 'Name')
if context.scene.ootDLExportHeaderType == 'Actor':
prop_split(col, context.scene, 'ootDLGroupName', 'Group Name')
elif context.scene.ootDLExportHeaderType == 'Level':
prop_split(col, context.scene, 'ootDLLevelOption', 'Level')
if context.scene.ootDLLevelOption == 'custom':
prop_split(col, context.scene, 'ootDLLevelName', 'Level Name')
if not bpy.context.scene.ignoreTextureRestrictions:
col.prop(context.scene, 'saveTextures')
if context.scene.saveTextures:
col.prop(context.scene, 'ootDLSeparateTextureDef')
#decompFolderMessage(col)
#writeBox = makeWriteInfoBox(col)
#writeBoxExportType(writeBox, context.scene.ootDLExportHeaderType,
# context.scene.ootDLName, context.scene.ootDLLevelName, context.scene.ootDLLevelOption)
col.prop(context.scene, 'ootDLincludeChildren')
prop_split(col, context.scene, 'ootDLExportName', "DL")
if context.scene.ootDLExportUseCustomPath:
prop_split(col, context.scene, 'ootDLExportCustomPath', "Folder")
else:
prop_split(col, context.scene, 'ootDLExportFolderName', "Object")
prop_split(col, context.scene, "ootDLExportDrawLayer", "Export Draw Layer")
col.prop(context.scene, "ootDLExportUseCustomPath")
col.prop(context.scene, "ootDLRemoveVanillaData")
col.operator(OOT_ImportDL.bl_idname)
prop_split(col, context.scene, 'ootDLImportName', "DL")
if context.scene.ootDLImportUseCustomPath:
prop_split(col, context.scene, 'ootDLImportCustomPath', "File")
else:
prop_split(col, context.scene, 'ootDLImportFolderName', "Object")
prop_split(col, context.scene, "ootDLImportDrawLayer", "Import Draw Layer")
col.prop(context.scene, "ootDLImportUseCustomPath")
col.prop(context.scene, "ootDLRemoveDoubles")
col.prop(context.scene, "ootDLImportNormals")
class OOTDefaultRenderModesProperty(bpy.types.PropertyGroup):
expandTab : bpy.props.BoolProperty()
@@ -524,7 +673,16 @@ class OOT_MaterialPanel(bpy.types.Panel):
layout = self.layout
mat = context.material
col = layout.column()
drawOOTMaterialProperty(col.box().column(), mat.ootMaterial, mat.f3d_mat.draw_layer.oot)
if hasattr(context, "object") and context.object is not None and \
context.object.parent is not None and isinstance(context.object.parent.data, bpy.types.Armature):
drawLayer = context.object.parent.ootDrawLayer
if drawLayer != mat.f3d_mat.draw_layer.oot:
col.label(text = "Draw layer is being overriden by skeleton.", icon = "ERROR")
else:
drawLayer = mat.f3d_mat.draw_layer.oot
drawOOTMaterialProperty(col.box().column(), mat.ootMaterial, drawLayer)
def drawOOTMaterialDrawLayerProperty(layout, matDrawLayerProp, suffix):
#layout.box().row().label(text = title)
@@ -536,6 +694,11 @@ def drawOOTMaterialDrawLayerProperty(layout, matDrawLayerProp, suffix):
name = "Segment " + format(i, 'X') + " " + suffix
col.prop(matDrawLayerProp, "segment" + format(i, 'X'), text = name)
def clearOOTMaterialDrawLayerProperty(matDrawLayerProp):
for i in range(0x08, 0x0E):
setattr(matDrawLayerProp, "segment" + format(i, 'X'), False)
drawLayerSuffix = {
"Opaque" : "OPA",
"Transparent" : 'XLU',
@@ -543,6 +706,8 @@ drawLayerSuffix = {
}
def drawOOTMaterialProperty(layout, matProp, drawLayer):
if drawLayer == "Overlay":
return
suffix = "(" + drawLayerSuffix[drawLayer] + ")"
layout.box().column().label(text = "OOT Dynamic Material Properties " + suffix)
layout.label(text = "See gSPSegment calls in z_scene_table.c.")
@@ -562,11 +727,16 @@ class OOTDynamicMaterialProperty(bpy.types.PropertyGroup):
opaque : bpy.props.PointerProperty(type = OOTDynamicMaterialDrawLayerProperty)
transparent : bpy.props.PointerProperty(type = OOTDynamicMaterialDrawLayerProperty)
class OOTDynamicTransformProperty(bpy.types.PropertyGroup):
billboard : bpy.props.BoolProperty(name = "Billboard")
oot_dl_writer_classes = (
OOTDefaultRenderModesProperty,
OOTDynamicMaterialDrawLayerProperty,
OOTDynamicMaterialProperty,
#OOT_ExportDL,
OOTDynamicTransformProperty,
OOT_ExportDL,
OOT_ImportDL,
)
oot_dl_writer_panel_classes = (
@@ -574,6 +744,7 @@ oot_dl_writer_panel_classes = (
OOT_DisplayListPanel,
OOT_DrawLayersPanel,
OOT_MaterialPanel,
OOT_ExportDLPanel,
)
def oot_dl_writer_panel_register():
@@ -589,34 +760,36 @@ def oot_dl_writer_register():
register_class(cls)
bpy.types.Object.ootDrawLayer = bpy.props.EnumProperty(items = ootEnumDrawLayers, default = 'Opaque')
# Doesn't work since all static meshes are pre-transformed
#bpy.types.Object.ootDynamicTransform = bpy.props.PointerProperty(type = OOTDynamicTransformProperty)
bpy.types.World.ootDefaultRenderModes = bpy.props.PointerProperty(type = OOTDefaultRenderModesProperty)
bpy.types.Scene.ootLevelDLExport = bpy.props.EnumProperty(items = ootEnumSceneID,
name = 'Level', default = 'SCENE_YDAN')
bpy.types.Scene.ootDLExportPath = bpy.props.StringProperty(
name = 'Directory', subtype = 'FILE_PATH')
bpy.types.Scene.ootDLExportisStatic = bpy.props.BoolProperty(
name = 'Static DL', default = True)
bpy.types.Scene.ootDLDefinePath = bpy.props.StringProperty(
name = 'Definitions Filepath', subtype = 'FILE_PATH')
bpy.types.Scene.ootDLTexDir = bpy.props.StringProperty(
name ='Include Path', default = 'levels/bob')
bpy.types.Scene.ootDLSeparateTextureDef = bpy.props.BoolProperty(
name = 'Save texture.inc.c separately')
bpy.types.Scene.ootDLincludeChildren = bpy.props.BoolProperty(
name = 'Include Children')
bpy.types.Scene.ootDLName = bpy.props.StringProperty(
name = 'Name', default = 'mario')
bpy.types.Scene.ootDLCustomExport = bpy.props.BoolProperty(
name = 'Custom Export Path')
bpy.types.Scene.ootDLExportHeaderType = bpy.props.EnumProperty(
items = enumExportHeaderType, name = 'Header Export', default = 'Actor')
bpy.types.Scene.ootDLGroupName = bpy.props.StringProperty(name = 'Group Name',
default = 'group0')
bpy.types.Scene.ootDLLevelName = bpy.props.StringProperty(name = 'Level',
default = 'bob')
bpy.types.Scene.ootDLLevelOption = bpy.props.EnumProperty(
items = ootEnumSceneID, name = 'Level', default = 'SCENE_YDAN')
bpy.types.Scene.ootDLExportName = bpy.props.StringProperty(
name = "DL Name", default = "gBoulderFragmentsDL")
bpy.types.Scene.ootDLExportFolderName = bpy.props.StringProperty(
name = "DL Folder", default = "gameplay_keep")
bpy.types.Scene.ootDLExportCustomPath = bpy.props.StringProperty(
name ='Custom DL Path', subtype = 'FILE_PATH')
bpy.types.Scene.ootDLExportUseCustomPath = bpy.props.BoolProperty(
name = "Use Custom Path")
bpy.types.Scene.ootDLRemoveVanillaData = bpy.props.BoolProperty(
name = "Replace Vanilla DLs")
bpy.types.Scene.ootDLImportName = bpy.props.StringProperty(
name = "DL Name", default = "gBoulderFragmentsDL")
bpy.types.Scene.ootDLImportFolderName = bpy.props.StringProperty(
name = "DL Folder", default = "gameplay_keep")
bpy.types.Scene.ootDLImportCustomPath = bpy.props.StringProperty(
name ='Custom DL Path', subtype = 'FILE_PATH')
bpy.types.Scene.ootDLImportUseCustomPath = bpy.props.BoolProperty(
name = "Use Custom Path")
bpy.types.Scene.ootDLRemoveDoubles = bpy.props.BoolProperty(name = "Remove Doubles", default = True)
bpy.types.Scene.ootDLImportNormals = bpy.props.BoolProperty(name = "Import Normals", default = True)
bpy.types.Scene.ootDLImportDrawLayer = bpy.props.EnumProperty(name = "Draw Layer", items = ootEnumDrawLayers)
bpy.types.Scene.ootDLExportDrawLayer = bpy.props.EnumProperty(name = "Draw Layer", items = ootEnumDrawLayers)
bpy.types.Material.ootMaterial = bpy.props.PointerProperty(type = OOTDynamicMaterialProperty)
bpy.types.Object.ootObjectMenu = bpy.props.EnumProperty(items = ootEnumObjectMenu)
@@ -625,17 +798,22 @@ def oot_dl_writer_unregister():
for cls in reversed(oot_dl_writer_classes):
unregister_class(cls)
del bpy.types.Scene.ootLevelDLExport
del bpy.types.Scene.ootDLExportPath
del bpy.types.Scene.ootDLExportisStatic
del bpy.types.Scene.ootDLDefinePath
del bpy.types.Scene.ootDLTexDir
del bpy.types.Scene.ootDLSeparateTextureDef
del bpy.types.Scene.ootDLincludeChildren
del bpy.types.Scene.ootDLName
del bpy.types.Scene.ootDLCustomExport
del bpy.types.Scene.ootDLExportHeaderType
del bpy.types.Scene.ootDLGroupName
del bpy.types.Scene.ootDLLevelName
del bpy.types.Scene.ootDLLevelOption
del bpy.types.Material.ootMaterial
del bpy.types.Scene.ootDLExportName
del bpy.types.Scene.ootDLExportFolderName
del bpy.types.Scene.ootDLExportCustomPath
del bpy.types.Scene.ootDLExportUseCustomPath
del bpy.types.Scene.ootDLRemoveVanillaData
del bpy.types.Scene.ootDLImportName
del bpy.types.Scene.ootDLImportFolderName
del bpy.types.Scene.ootDLImportCustomPath
del bpy.types.Scene.ootDLImportUseCustomPath
del bpy.types.Scene.ootDLRemoveDoubles
del bpy.types.Scene.ootDLImportNormals
del bpy.types.Scene.ootDLImportDrawLayer
del bpy.types.Scene.ootDLExportDrawLayer
del bpy.types.Material.ootMaterial
del bpy.types.Object.ootObjectMenu
+1 -1
View File
@@ -167,7 +167,7 @@ class OOTScene:
def addRoom(self, roomIndex, roomName, meshType):
roomModel = self.model.addSubModel(
OOTModel(self.model.f3d.F3D_VER, self.model.f3d._HW_VERSION_1, roomName + '_dl', self.model.DLFormat))
OOTModel(self.model.f3d.F3D_VER, self.model.f3d._HW_VERSION_1, roomName + '_dl', self.model.DLFormat, None))
room = OOTRoom(roomIndex, roomName, roomModel, meshType)
if roomIndex in self.rooms:
raise PluginError("Repeat room index " + str(roomIndex) + " for " + str(roomName))
+2 -99
View File
@@ -17,103 +17,6 @@ from io import BytesIO
import bpy, bmesh, os, math, re, shutil, mathutils
#class OOTBox:
# def __init__(self):
# self.minBounds = [-2**8, -2**8]
# self.maxBounds = [2**8 - 1, 2**8 - 1]
class OOTObjectCategorizer:
def __init__(self):
self.sceneObj = None
self.roomObjs = []
self.actors = []
self.transitionActors = []
self.meshes = []
self.entrances = []
self.waterBoxes = []
def sortObjects(self, allObjs):
for obj in allObjs:
if obj.data is None:
if obj.ootEmptyType == "Actor":
self.actors.append(obj)
elif obj.ootEmptyType == "Transition Actor":
self.transitionActors.append(obj)
elif obj.ootEmptyType == "Entrance":
self.entrances.append(obj)
elif obj.ootEmptyType == "Water Box":
self.waterBoxes.append(obj)
elif obj.ootEmptyType == "Room":
self.roomObjs.append(obj)
elif obj.ootEmptyType == "Scene":
self.sceneObj = obj
elif isinstance(obj.data, bpy.types.Mesh):
self.meshes.append(obj)
# This also sets all origins relative to the scene object.
def ootDuplicateHierarchy(obj, ignoreAttr, includeEmpties, objectCategorizer):
# Duplicate objects to apply scale / modifiers / linked data
bpy.ops.object.select_all(action = 'DESELECT')
ootSelectMeshChildrenOnly(obj, includeEmpties)
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.duplicate()
try:
tempObj = bpy.context.view_layer.objects.active
allObjs = bpy.context.selected_objects
bpy.ops.object.make_single_user(obdata = True)
objectCategorizer.sortObjects(allObjs)
meshObjs = objectCategorizer.meshes
bpy.ops.object.select_all(action = 'DESELECT')
for selectedObj in meshObjs:
selectedObj.select_set(True)
bpy.ops.object.transform_apply(location = False,
rotation = True, scale = True, properties = False)
for selectedObj in meshObjs:
bpy.ops.object.select_all(action = 'DESELECT')
selectedObj.select_set(True)
bpy.context.view_layer.objects.active = selectedObj
for modifier in selectedObj.modifiers:
attemptModifierApply(modifier)
for selectedObj in meshObjs:
setOrigin(obj, selectedObj)
if ignoreAttr is not None:
for selectedObj in meshObjs:
if getattr(selectedObj, ignoreAttr):
for child in selectedObj.children:
bpy.ops.object.select_all(action = 'DESELECT')
child.select_set(True)
bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
selectedObj.parent.select_set(True)
bpy.ops.object.parent_set(keep_transform = True)
selectedObj.parent = None
applyRotation([tempObj], math.radians(90), 'X')
return tempObj, allObjs
except Exception as e:
cleanupDuplicatedObjects(allObjs)
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
raise Exception(str(e))
def ootSelectMeshChildrenOnly(obj, includeEmpties):
isMesh = isinstance(obj.data, bpy.types.Mesh)
isEmpty = (obj.data is None or \
isinstance(obj.data, bpy.types.Camera) or\
isinstance(obj.data, bpy.types.Curve)) and includeEmpties
if (isMesh or isEmpty):
obj.select_set(True)
obj.original_name = obj.name
for child in obj.children:
ootSelectMeshChildrenOnly(child, includeEmpties)
def ootCleanupScene(originalSceneObj, allObjs):
cleanupDuplicatedObjects(allObjs)
originalSceneObj.select_set(True)
bpy.context.view_layer.objects.active = originalSceneObj
def sceneNameFromID(sceneID):
if sceneID in ootSceneIDToName:
return ootSceneIDToName[sceneID]
@@ -339,7 +242,7 @@ def ootConvertScene(originalSceneObj, transformMatrix,
raise PluginError("The scene has no child empties with the 'Room' empty type.")
try:
scene = OOTScene(sceneName, OOTModel(f3dType, isHWv1, sceneName + '_dl', DLFormat))
scene = OOTScene(sceneName, OOTModel(f3dType, isHWv1, sceneName + '_dl', DLFormat, None))
readSceneData(scene, sceneObj.ootSceneHeader, sceneObj.ootAlternateSceneHeaders)
processedRooms = set()
@@ -372,7 +275,7 @@ def ootConvertScene(originalSceneObj, transformMatrix,
scene.pathList.append(ootConvertPath(sceneName, len(scene.pathList), obj, relativeTransform))
scene.validateIndices()
exportCollisionCommon(scene.collision, sceneObj, allObjs, transformMatrix, True, sceneName)
exportCollisionCommon(scene.collision, sceneObj, transformMatrix, True, sceneName)
ootCleanupScene(originalSceneObj, allObjs)
+486 -41
View File
@@ -1,5 +1,6 @@
import shutil, copy, mathutils, bpy, math, os
import shutil, copy, mathutils, bpy, math, os, re
from ..f3d.f3d_parser import *
from ..f3d.f3d_writer import *
from ..f3d.f3d_material import TextureProperty, tmemUsageUI
from bpy.utils import register_class, unregister_class
@@ -8,11 +9,18 @@ from .oot_utility import *
from .oot_f3d_writer import *
from ..utility import *
ootEnumBoneType = [
("Default", "Default", "Default"),
("Custom DL", "Custom DL", "Custom DL"),
("Ignore", "Ignore", "Ignore"),
]
class OOTSkeleton():
def __init__(self, name):
self.name = name + "Skel"
self.name = name
self.segmentID = None
self.limbRoot = None
self.hasLOD = False
def createLimbList(self):
if self.limbRoot is None:
@@ -56,7 +64,7 @@ class OOTSkeleton():
data.source += "void* " + self.limbsName() + "[" + str(self.getNumLimbs()) + "] = {\n"
for limb in limbList:
limbData.source += limb.toC()
limbData.source += limb.toC(self.hasLOD)
data.source += '\t&' + limb.name() + ',\n'
limbData.source += '\n'
data.source += "};\n\n"
@@ -88,17 +96,31 @@ class OOTLimb():
self.children = []
self.inverseRotation = None
def toC(self):
return "StandardLimb " + self.name() + " = { " +\
def toC(self, isLOD):
if not isLOD:
data = "StandardLimb "
else:
data = "LodLimb "
data += self.name() + " = { " +\
"{ " + str(int(round(self.translation[0]))) + ", " + \
str(int(round(self.translation[1]))) + ", " + \
str(int(round(self.translation[2]))) + " }, " + \
str(self.firstChildIndex) + ", " +\
str(self.nextSiblingIndex) + ", " + \
(self.DL.name if self.DL is not None else "NULL") + " };\n"
str(self.nextSiblingIndex) + ", "
if not isLOD:
data += (self.DL.name if self.DL is not None else "NULL")
else:
data += "{ " + (self.DL.name if self.DL is not None else "NULL") + ", " +\
(self.lodDL.name if self.lodDL is not None else "NULL") + " }"
data += " };\n"
return data
def name(self):
return self.skeletonName + "Limb_" + str(self.index)
return self.skeletonName + "Limb_" + format(self.index, '03')
def getNumLimbs(self):
numLimbs = 1
@@ -108,9 +130,7 @@ class OOTLimb():
def getNumDLs(self):
numDLs = 0
if self.DL is not None:
numDLs += 1
if self.lodDL is not None:
if self.DL is not None or self.lodDL is not None:
numDLs += 1
for child in self.children:
@@ -156,6 +176,7 @@ def setBoneNonRotated(armatureObj, boneName, restPoseRotations):
poseBone.constraints.remove(poseBone.constraints[0])
rotation = bone.matrix_local.inverted().decompose()[1]
armatureObj.pose.bones[boneName].rotation_mode = "QUATERNION"
armatureObj.pose.bones[boneName].rotation_quaternion = rotation
restPoseRotations[boneName] = rotation
@@ -259,9 +280,8 @@ def ootConvertArmatureToSkeletonWithoutMesh(originalArmatureObj, convertTransfor
None, name, False, True, "Opaque")
return skeleton, restPoseRotations
def ootConvertArmatureToSkeletonWithMesh(originalArmatureObj, convertTransformMatrix,
f3dType, isHWv1, name, DLFormat, convertTextureData, drawLayer):
fModel = OOTModel(f3dType, isHWv1, name, DLFormat)
def ootConvertArmatureToSkeletonWithMesh(originalArmatureObj, convertTransformMatrix, fModel, name, convertTextureData, drawLayer):
skeleton, fModel, restPoseRotations = ootConvertArmatureToSkeleton(originalArmatureObj, convertTransformMatrix,
fModel, name, convertTextureData, False, drawLayer)
return skeleton, fModel
@@ -328,6 +348,13 @@ def ootProcessBone(fModel, boneName, parentLimb, nextIndex, meshObj, armatureObj
convertTransformMatrix, armatureObj, namePrefix,
meshInfo, drawLayer, convertTextureData)
if bone.ootBoneType == "Custom DL":
if mesh is not None:
raise PluginError(bone.name + " is set to use a custom DL but still has geometry assigned to it. Remove this geometry from this bone.")
else:
# Dummy data, only used so that name is set correctly
mesh = FMesh(bone.ootCustomDLName, DLFormat.Static)
DL = None
if mesh is not None:
if not bone.use_deform:
@@ -353,14 +380,40 @@ def ootProcessBone(fModel, boneName, parentLimb, nextIndex, meshObj, armatureObj
return nextIndex
def ootConvertArmatureToC(originalArmatureObj, convertTransformMatrix,
f3dType, isHWv1, skeletonName, folderName, DLFormat, savePNG, exportPath, isCustomExport, drawLayer):
f3dType, isHWv1, skeletonName, folderName, DLFormat, savePNG, exportPath, isCustomExport, drawLayer, removeVanillaData):
skeletonName = toAlnum(skeletonName)
fModel = OOTModel(f3dType, isHWv1, skeletonName, DLFormat, drawLayer)
skeleton, fModel = ootConvertArmatureToSkeletonWithMesh(originalArmatureObj, convertTransformMatrix,
f3dType, isHWv1, skeletonName, DLFormat, not savePNG, drawLayer)
fModel, skeletonName, not savePNG, drawLayer)
if originalArmatureObj.ootFarLOD is not None:
lodSkeleton, fModel = ootConvertArmatureToSkeletonWithMesh(originalArmatureObj.ootFarLOD, convertTransformMatrix,
fModel, skeletonName + "_lod", not savePNG, drawLayer)
else:
lodSkeleton = None
if lodSkeleton is not None:
skeleton.hasLOD = True
limbList = skeleton.createLimbList()
lodLimbList = lodSkeleton.createLimbList()
if len(limbList) != len(lodLimbList):
raise PluginError(originalArmatureObj.name + " cannot use " + originalArmatureObj.ootFarLOD.name + \
"as LOD because they do not have the same bone structure.")
for i in range(len(limbList)):
limbList[i].lodDL = lodLimbList[i].DL
limbList[i].isFlex |= lodLimbList[i].isFlex
data = CData()
data.source += '#include "ultra64.h"\n#include "global.h"\n\n'
data.source += '#include "ultra64.h"\n#include "global.h"\n'
if not isCustomExport:
data.source += '#include "' + folderName + '.h"\n\n'
else:
data.source += '\n'
exportData = fModel.to_c(
TextureExportSettings(False, savePNG, "test"), OOTGfxFormatter(ScrollMethod.Vertex))
skeletonC = skeleton.toC()
@@ -375,6 +428,295 @@ def ootConvertArmatureToC(originalArmatureObj, convertTransformMatrix,
if not isCustomExport:
addIncludeFiles(folderName, path, skeletonName)
if removeVanillaData:
ootRemoveSkeleton(path, folderName, skeletonName)
class OOTDLEntry:
def __init__(self, dlName, limbIndex):
self.dlName = dlName
self.limbIndex = limbIndex
def ootGetSkeleton(skeletonData, skeletonName, continueOnError):
# TODO: Does this handle non flex skeleton?
matchResult = re.search("(Flex)?SkeletonHeader\s*" + re.escape(skeletonName) + \
"\s*=\s*\{\s*([^,\s]*)\s*,\s*([^,\s]*)\s*(,\s*([^,\s]*))?\s*\}\s*;\s*", skeletonData)
if matchResult is None:
if continueOnError:
return None
else:
raise PluginError("Cannot find skeleton named " + skeletonName)
return matchResult
def ootGetLimbs(skeletonData, limbsName, continueOnError):
matchResult = re.search("(static\s*)?void\s*\*\s*" + re.escape(limbsName) + \
"\s*\[\s*[0-9]*\s*\]\s*=\s*\{([^\}]*)\}\s*;\s*", skeletonData, re.DOTALL)
if matchResult is None:
if continueOnError:
return None
else:
raise PluginError("Cannot find skeleton limbs named " + limbsName)
return matchResult
def ootGetLimb(skeletonData, limbName, continueOnError):
matchResult = re.search("([A-Za-z0-9\_]*)Limb\s*" + re.escape(limbName), skeletonData)
if matchResult is None:
if continueOnError:
return None
else:
raise PluginError("Cannot find skeleton limb named " + limbName)
limbType = matchResult.group(1)
if limbType == "Lod":
dlRegex = "\{\s*([^,\s]*)\s*,\s*([^,\s]*)\s*\}"
else:
dlRegex = "([^,\s]*)"
matchResult = re.search("[A-Za-z0-9\_]*Limb\s*" + re.escape(limbName) + \
"\s*=\s*\{\s*\{\s*([^,\s]*)\s*,\s*([^,\s]*)\s*,\s*([^,\s]*)\s*\},\s*([^, ]*)\s*,\s*([^, ]*)\s*,\s*" +\
dlRegex + "\s*\}\s*;\s*", skeletonData, re.DOTALL)
if matchResult is None:
if continueOnError:
return None
else:
raise PluginError("Cannot handle skeleton limb named " + limbName + " of type " + limbType)
return matchResult
def ootImportSkeletonC(filepaths, skeletonName, actorScale, removeDoubles, importNormals, basePath, drawLayer):
skeletonData = getImportData(filepaths)
matchResult = ootGetSkeleton(skeletonData, skeletonName, False)
limbsName = matchResult.group(2)
matchResult = ootGetLimbs(skeletonData, limbsName, False)
limbsData = matchResult.group(2)
limbList = [entry.strip()[1:] for entry in limbsData.split(',')]
#print(limbList)
isLOD, armatureObj = ootBuildSkeleton(skeletonName, skeletonData, limbList,
actorScale, removeDoubles, importNormals, False, basePath, drawLayer)
if isLOD:
isLOD, LODArmatureObj = ootBuildSkeleton(skeletonName, skeletonData, limbList,
actorScale, removeDoubles, importNormals, True, basePath, drawLayer)
armatureObj.ootFarLOD = LODArmatureObj
def ootBuildSkeleton(skeletonName, skeletonData, limbList, actorScale, removeDoubles,
importNormals, useFarLOD, basePath, drawLayer):
lodString = "_lod" if useFarLOD else ""
# Create new skinned mesh
mesh = bpy.data.meshes.new(skeletonName + '_mesh' + lodString)
obj = bpy.data.objects.new(skeletonName + '_mesh' + lodString, mesh)
bpy.context.scene.collection.objects.link(obj)
# Create new armature
armature = bpy.data.armatures.new(skeletonName + lodString)
armatureObj = bpy.data.objects.new(skeletonName + lodString, armature)
armatureObj.show_in_front = True
armatureObj.ootDrawLayer = drawLayer
#armature.show_names = True
bpy.context.scene.collection.objects.link(armatureObj)
bpy.context.view_layer.objects.active = armatureObj
#bpy.ops.object.mode_set(mode = 'EDIT')
f3dContext = OOTF3DContext(F3D("F3DEX2/LX2", False), limbList, basePath)
f3dContext.mat().draw_layer.oot = armatureObj.ootDrawLayer
transformMatrix = mathutils.Matrix.Scale(1 / actorScale, 4)
isLOD = ootAddLimbRecursively(0, skeletonData, obj, armatureObj, transformMatrix, None, f3dContext, useFarLOD)
for dlEntry in f3dContext.dlList:
limbName = f3dContext.getLimbName(dlEntry.limbIndex)
boneName = f3dContext.getBoneName(dlEntry.limbIndex)
parseF3D(skeletonData, dlEntry.dlName, obj, f3dContext.matrixData[limbName],
limbName, boneName, "oot", drawLayer, f3dContext)
if f3dContext.isBillboard:
armatureObj.data.bones[boneName].ootDynamicTransform.billboard = True
f3dContext.clearMaterial() # THIS IS IMPORTANT
f3dContext.createMesh(obj, removeDoubles, importNormals)
armatureObj.location = bpy.context.scene.cursor.location
# Set bone rotation mode.
bpy.ops.object.select_all(action = "DESELECT")
armatureObj.select_set(True)
bpy.context.view_layer.objects.active = armatureObj
bpy.ops.object.mode_set(mode = 'POSE')
for bone in armatureObj.pose.bones:
bone.rotation_mode = 'XYZ'
# Apply mesh to armature.
if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = 'OBJECT')
bpy.ops.object.select_all(action = 'DESELECT')
obj.select_set(True)
armatureObj.select_set(True)
bpy.context.view_layer.objects.active = armatureObj
bpy.ops.object.parent_set(type = "ARMATURE")
applyRotation([armatureObj], math.radians(-90), 'X')
return isLOD, armatureObj
def ootAddBone(armatureObj, boneName, parentBoneName, currentTransform, loadDL):
if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action = 'DESELECT')
bpy.context.view_layer.objects.active = armatureObj
bpy.ops.object.mode_set(mode="EDIT")
bone = armatureObj.data.edit_bones.new(boneName)
bone.use_connect = False
if parentBoneName is not None:
bone.parent = armatureObj.data.edit_bones[parentBoneName]
bone.head = currentTransform @ mathutils.Vector((0,0,0))
bone.tail = bone.head + (currentTransform.to_quaternion() @ \
mathutils.Vector((0,0.3,0)))
# Connect bone to parent if it is possible without changing parent direction.
if parentBoneName is not None:
nodeOffsetVector = mathutils.Vector(bone.head - bone.parent.head)
# set fallback to nonzero to avoid creating zero length bones
if(nodeOffsetVector.angle(bone.parent.tail - bone.parent.head, 1) \
< 0.0001 and loadDL):
for child in bone.parent.children:
if child != bone:
child.use_connect = False
bone.parent.tail = bone.head
bone.use_connect = True
elif bone.head == bone.parent.head and bone.tail == bone.parent.tail:
bone.tail += currentTransform.to_quaternion() @ mathutils.Vector((0,0.2,0))
if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode="OBJECT")
def ootAddLimbRecursively(limbIndex, skeletonData, obj, armatureObj, parentTransform, parentBoneName, f3dContext, useFarLOD):
limbName = f3dContext.getLimbName(limbIndex)
boneName = f3dContext.getBoneName(limbIndex)
matchResult = ootGetLimb(skeletonData, limbName, False)
isLOD = matchResult.lastindex > 6
if isLOD and useFarLOD:
dlName = matchResult.group(7)
else:
dlName = matchResult.group(6)
# Animations override the root translation, so we just ignore importing them as well.
if limbIndex == 0:
translation = [0,0,0]
else:
translation = [int(matchResult.group(1)), int(matchResult.group(2)), int(matchResult.group(3))]
nextChildIndex = int(matchResult.group(4))
nextSiblingIndex = int(matchResult.group(5))
#str(limbIndex) + " " + str(translation) + " " + str(nextChildIndex) + " " + \
# str(nextSiblingIndex) + " " + str(dlName))
currentTransform = parentTransform @ mathutils.Matrix.Translation(mathutils.Vector(translation))
f3dContext.matrixData[limbName] = currentTransform
loadDL = dlName != "NULL"
ootAddBone(armatureObj, boneName, parentBoneName, currentTransform, loadDL)
# DLs can access bone transforms not yet processed.
# Therefore were delay F3D parsing until after skeleton is processed.
if loadDL:
f3dContext.dlList.append(OOTDLEntry(dlName, limbIndex))
#parseF3D(skeletonData, dlName, obj, transformMatrix, boneName, f3dContext)
if nextChildIndex != 255:
isLOD |= ootAddLimbRecursively(nextChildIndex, skeletonData, obj, armatureObj, currentTransform, boneName, f3dContext, useFarLOD)
if nextSiblingIndex != 255:
isLOD |= ootAddLimbRecursively(nextSiblingIndex, skeletonData, obj, armatureObj, parentTransform, parentBoneName, f3dContext, useFarLOD)
return isLOD
def ootRemoveSkeleton(filepath, objectName, skeletonName):
headerPath = os.path.join(filepath, objectName + '.h')
sourcePath = os.path.join(filepath, objectName + '.c')
skeletonDataC = readFile(sourcePath)
originalDataC = skeletonDataC
skeletonDataH = readFile(headerPath)
originalDataH = skeletonDataH
matchResult = ootGetSkeleton(skeletonDataC, skeletonName, True)
if matchResult is None:
return
skeletonDataC = skeletonDataC[:matchResult.start(0)] + skeletonDataC[matchResult.end(0):]
limbsName = matchResult.group(2)
headerMatch = getDeclaration(skeletonDataH, skeletonName)
if headerMatch is not None:
skeletonDataH = skeletonDataH[:headerMatch.start(0)] + skeletonDataH[headerMatch.end(0):]
matchResult = ootGetLimbs(skeletonDataC, limbsName, True)
if matchResult is None:
return
skeletonDataC = skeletonDataC[:matchResult.start(0)] + skeletonDataC[matchResult.end(0):]
limbsData = matchResult.group(2)
limbList = [entry.strip()[1:] for entry in limbsData.split(',')]
headerMatch = getDeclaration(skeletonDataH, limbsName)
if headerMatch is not None:
skeletonDataH = skeletonDataH[:headerMatch.start(0)] + skeletonDataH[headerMatch.end(0):]
for limb in limbList:
matchResult = ootGetLimb(skeletonDataC, limb, True)
if matchResult is not None:
skeletonDataC = skeletonDataC[:matchResult.start(0)] + skeletonDataC[matchResult.end(0):]
headerMatch = getDeclaration(skeletonDataH, limb)
if headerMatch is not None:
skeletonDataH = skeletonDataH[:headerMatch.start(0)] + skeletonDataH[headerMatch.end(0):]
if skeletonDataC != originalDataC:
writeFile(sourcePath, skeletonDataC)
if skeletonDataH != originalDataH:
writeFile(headerPath, skeletonDataH)
class OOT_ImportSkeleton(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.oot_import_skeleton'
bl_label = "Import Skeleton"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
# Called on demand (i.e. button press, menu item)
# Can also be called from operator search menu (Spacebar)
def execute(self, context):
armatureObj = None
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = "OBJECT")
try:
importPath = bpy.path.abspath(context.scene.ootSkeletonImportCustomPath)
isCustomImport = context.scene.ootSkeletonImportUseCustomPath
folderName = context.scene.ootSkeletonImportFolderName
skeletonName = context.scene.ootSkeletonImportName
scale = context.scene.ootActorBlenderScale
removeDoubles = context.scene.ootActorRemoveDoubles
importNormals = context.scene.ootActorImportNormals
decompPath = bpy.path.abspath(bpy.context.scene.ootDecompPath)
drawLayer = bpy.context.scene.ootActorImportDrawLayer
filepaths = [ootGetObjectPath(isCustomImport, importPath, folderName)]
if not isCustomImport:
filepaths.append(os.path.join(bpy.context.scene.ootDecompPath, "assets/objects/gameplay_keep/gameplay_keep.c"))
ootImportSkeletonC(filepaths, skeletonName, scale, removeDoubles, importNormals, decompPath, drawLayer)
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
except Exception as e:
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = 'OBJECT')
raisePluginError(self, e)
return {'CANCELLED'} # must return a set
class OOT_ExportSkeleton(bpy.types.Operator):
# set bl_ properties
@@ -410,15 +752,16 @@ class OOT_ExportSkeleton(bpy.types.Operator):
saveTextures = bpy.context.scene.saveTextures or bpy.context.scene.ignoreTextureRestrictions
isHWv1 = context.scene.isHWv1
f3dType = context.scene.f3d_type
skeletonName = context.scene.ootSkeletonName
folderName = context.scene.ootSkeletonFolderName
exportPath = context.scene.ootSkeletonCustomPath
isCustomExport = context.scene.ootSkeletonUseCustomPath
drawLayer = context.scene.ootSkeletonDrawLayer
skeletonName = context.scene.ootSkeletonExportName
folderName = context.scene.ootSkeletonExportFolderName
exportPath = bpy.path.abspath(context.scene.ootSkeletonExportCustomPath)
isCustomExport = context.scene.ootSkeletonExportUseCustomPath
drawLayer = armatureObj.ootDrawLayer
removeVanillaData = context.scene.ootSkeletonRemoveVanillaData
ootConvertArmatureToC(armatureObj, finalTransform,
f3dType, isHWv1, skeletonName, folderName, DLFormat.Static, saveTextures,
exportPath, isCustomExport, drawLayer)
exportPath, isCustomExport, drawLayer, removeVanillaData)
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
@@ -445,20 +788,88 @@ class OOT_ExportSkeletonPanel(bpy.types.Panel):
col = self.layout.column()
col.operator(OOT_ExportSkeleton.bl_idname)
col.prop(context.scene, "ootSkeletonUseCustomPath")
prop_split(col, context.scene, 'ootSkeletonName', "Skeleton")
if context.scene.ootSkeletonUseCustomPath:
prop_split(col, context.scene, 'ootSkeletonCustomPath', "Folder")
prop_split(col, context.scene, 'ootSkeletonExportName', "Skeleton")
if context.scene.ootSkeletonExportUseCustomPath:
prop_split(col, context.scene, 'ootSkeletonExportCustomPath', "Folder")
else:
prop_split(col, context.scene, 'ootSkeletonFolderName', "Object")
prop_split(col, context.scene, "ootSkeletonDrawLayer", "Draw Layer")
prop_split(col, context.scene, 'ootSkeletonExportFolderName', "Object")
col.prop(context.scene, "ootSkeletonExportUseCustomPath")
col.operator(OOT_ImportSkeleton.bl_idname)
prop_split(col, context.scene, 'ootSkeletonImportName', "Skeleton")
if context.scene.ootSkeletonImportUseCustomPath:
prop_split(col, context.scene, 'ootSkeletonImportCustomPath', "File")
else:
prop_split(col, context.scene, 'ootSkeletonImportFolderName', "Object")
prop_split(col, context.scene, "ootActorImportDrawLayer", "Import Draw Layer")
col.prop(context.scene, "ootSkeletonImportUseCustomPath")
col.prop(context.scene, "ootActorRemoveDoubles")
col.prop(context.scene, "ootActorImportNormals")
col.prop(context.scene, "ootSkeletonRemoveVanillaData")
class OOT_SkeletonPanel(bpy.types.Panel):
bl_idname = "OOT_PT_skeleton"
bl_label = "OOT Skeleton Properties"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
return context.scene.gameEditorMode == "OOT" and \
hasattr(context, "object") and context.object is not None and isinstance(context.object.data, bpy.types.Armature)
# called every frame
def draw(self, context):
col = self.layout.box().column()
col.box().label(text = "OOT Skeleton Inspector")
prop_split(col, context.object, "ootDrawLayer", "Draw Layer")
prop_split(col, context.object, "ootFarLOD", "LOD Skeleton")
if context.object.ootFarLOD is not None:
col.label(text = "Make sure LOD has same bone structure.", icon = "ERROR")
class OOT_BonePanel(bpy.types.Panel):
bl_idname = "OOT_PT_bone"
bl_label = "OOT Bone Properties"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "bone"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
return context.scene.gameEditorMode == "OOT" and context.bone is not None
# called every frame
def draw(self, context):
col = self.layout.box().column()
col.box().label(text = "OOT Bone Inspector")
prop_split(col, context.bone, "ootBoneType", "Bone Type")
if context.bone.ootBoneType == "Custom DL":
prop_split(col, context.bone, "ootCustomDLName", "DL Name")
if context.bone.ootBoneType == "Custom DL" or\
context.bone.ootBoneType == "Ignore":
col.label(text = "Make sure no geometry is skinned to this bone.", icon = "ERROR")
if context.bone.ootBoneType != "Ignore":
col.prop(context.bone.ootDynamicTransform, 'billboard')
def pollArmature(self, obj):
return isinstance(obj.data, bpy.types.Armature)
oot_skeleton_classes = (
OOT_ExportSkeleton,
OOT_ImportSkeleton,
)
oot_skeleton_panels = (
OOT_ExportSkeletonPanel,
OOT_SkeletonPanel,
OOT_BonePanel,
)
def oot_skeleton_panel_register():
@@ -470,25 +881,59 @@ def oot_skeleton_panel_unregister():
unregister_class(cls)
def oot_skeleton_register():
bpy.types.Scene.ootSkeletonName = bpy.props.StringProperty(
name = "Skeleton Name", default = "skeleton")
bpy.types.Scene.ootSkeletonFolderName = bpy.props.StringProperty(
name = "Skeleton Folder", default = "gameplay_keep")
bpy.types.Scene.ootSkeletonCustomPath = bpy.props.StringProperty(
bpy.types.Scene.ootSkeletonExportName = bpy.props.StringProperty(
name = "Skeleton Name", default = "gGerudoRedSkel")
bpy.types.Scene.ootSkeletonExportFolderName = bpy.props.StringProperty(
name = "Skeleton Folder", default = "object_geldb")
bpy.types.Scene.ootSkeletonExportCustomPath = bpy.props.StringProperty(
name ='Custom Skeleton Path', subtype = 'FILE_PATH')
bpy.types.Scene.ootSkeletonUseCustomPath = bpy.props.BoolProperty(
bpy.types.Scene.ootSkeletonExportUseCustomPath = bpy.props.BoolProperty(
name = "Use Custom Path")
bpy.types.Scene.ootSkeletonDrawLayer = bpy.props.EnumProperty(items = ootEnumDrawLayers)
bpy.types.Scene.ootSkeletonImportName = bpy.props.StringProperty(
name = "Skeleton Name", default = "gGerudoRedSkel")
bpy.types.Scene.ootSkeletonImportFolderName = bpy.props.StringProperty(
name = "Skeleton Folder", default = "object_geldb")
bpy.types.Scene.ootSkeletonImportCustomPath = bpy.props.StringProperty(
name ='Custom Skeleton Path', subtype = 'FILE_PATH')
bpy.types.Scene.ootSkeletonImportUseCustomPath = bpy.props.BoolProperty(
name = "Use Custom Path")
bpy.types.Scene.ootActorRemoveDoubles = bpy.props.BoolProperty(name = "Remove Doubles On Import", default = True)
bpy.types.Scene.ootActorImportNormals = bpy.props.BoolProperty(name = "Import Normals", default = True)
bpy.types.Scene.ootSkeletonRemoveVanillaData = bpy.props.BoolProperty(name = "Replace Vanilla Headers On Export", default = True)
bpy.types.Scene.ootActorImportDrawLayer = bpy.props.EnumProperty(name = "Import Draw Layer", items = ootEnumDrawLayers)
bpy.types.Object.ootFarLOD = bpy.props.PointerProperty(type = bpy.types.Object, poll = pollArmature)
bpy.types.Bone.ootBoneType = bpy.props.EnumProperty(name = 'Bone Type', items = ootEnumBoneType)
bpy.types.Bone.ootDynamicTransform = bpy.props.PointerProperty(type = OOTDynamicTransformProperty)
bpy.types.Bone.ootCustomDLName = bpy.props.StringProperty(name = 'Custom DL', default = "gEmptyDL")
for cls in oot_skeleton_classes:
register_class(cls)
def oot_skeleton_unregister():
del bpy.types.Scene.ootSkeletonCustomPath
del bpy.types.Scene.ootSkeletonFolderName
del bpy.types.Scene.ootSkeletonUseCustomPath
del bpy.types.Scene.ootSkeletonName
del bpy.types.Scene.ootSkeletonDrawLayer
del bpy.types.Scene.ootSkeletonExportName
del bpy.types.Scene.ootSkeletonExportFolderName
del bpy.types.Scene.ootSkeletonExportCustomPath
del bpy.types.Scene.ootSkeletonExportUseCustomPath
del bpy.types.Scene.ootSkeletonImportName
del bpy.types.Scene.ootSkeletonImportFolderName
del bpy.types.Scene.ootSkeletonImportCustomPath
del bpy.types.Scene.ootSkeletonImportUseCustomPath
del bpy.types.Scene.ootActorRemoveDoubles
del bpy.types.Scene.ootActorImportNormals
del bpy.types.Scene.ootSkeletonRemoveVanillaData
del bpy.types.Scene.ootActorImportDrawLayer
del bpy.types.Object.ootFarLOD
del bpy.types.Bone.ootBoneType
del bpy.types.Bone.ootDynamicTransform
del bpy.types.Bone.ootCustomDLName
for cls in reversed(oot_skeleton_classes):
unregister_class(cls)
+21 -5
View File
@@ -1,5 +1,5 @@
from ..utility import *
import bpy, math, mathutils, os
import bpy, math, mathutils, os, re
from bpy.utils import register_class, unregister_class
ootSceneDungeons = [
@@ -190,6 +190,15 @@ def checkEmptyName(name):
if name == "":
raise PluginError("No name entered for the exporter.")
def ootGetObjectPath(isCustomExport, exportPath, folderName):
if isCustomExport:
filepath = exportPath
else:
filepath = os.path.join(ootGetPath(exportPath, isCustomExport, 'assets/objects/',
folderName, False, False), folderName + '.c')
return filepath
def ootGetPath(exportPath, isCustomExport, subPath, folderName, makeIfNotExists, useFolderForCustom):
if isCustomExport:
path = bpy.path.abspath(os.path.join(exportPath, (folderName if useFolderForCustom else '')))
@@ -207,14 +216,21 @@ def ootGetPath(exportPath, isCustomExport, subPath, folderName, makeIfNotExists,
return path
def getSortedChildren(armatureObj, bone):
return sorted([child.name for child in bone.children])
return sorted([child.name for child in bone.children if child.ootBoneType != "Ignore"], key = lambda childName : childName.lower())
def getStartBone(armatureObj):
return 'root'
startBoneNames = [bone.name for bone in armatureObj.data.bones if \
bone.parent is None and bone.ootBoneType != "Ignore"]
if len(startBoneNames) == 0:
raise PluginError(armatureObj.name + " does not have any root bones that are not of the \"Ignore\" type.")
startBoneName = startBoneNames[0]
return startBoneName
#return 'root'
def checkForStartBone(armatureObj):
if "root" not in armatureObj.data.bones:
raise PluginError("Skeleton must have a bone named 'root' where the skeleton starts from.")
pass
#if "root" not in armatureObj.data.bones:
# raise PluginError("Skeleton must have a bone named 'root' where the skeleton starts from.")
class BoxEmpty:
def __init__(self, position, scale, emptyScale):
@@ -660,7 +660,7 @@ def parseDLWithOffset(romfile, currentAddress,
displayListStartAddress = decodeSegmentedAddr(segmentedAddr,
segmentData = segmentData)
#print(displayListStartAddress)
parseF3D(romfile, displayListStartAddress, bpy.context.scene,
parseF3DBinary(romfile, displayListStartAddress, bpy.context.scene,
bMesh, obj, finalTransform, boneName, segmentData, vertexBuffer)
# Handle child objects
@@ -721,7 +721,7 @@ def handleNodeCommon(romfile, armatureObj, parentBoneName, finalTransform,
hasMeshData = int.from_bytes(segmentedAddr, 'big') != 0
if hasMeshData:
startAddress = decodeSegmentedAddr(segmentedAddr, segmentData)
parseF3D(romfile, startAddress, bpy.context.scene, bMesh, obj,
parseF3DBinary(romfile, startAddress, bpy.context.scene, bMesh, obj,
finalTransform, boneName, segmentData, vertexBuffer)
elif armatureObj is not None:
armatureObj.data.bones[boneName].use_deform = False
@@ -1664,7 +1664,7 @@ def saveModelGivenVertexGroup(fModel, obj, vertexGroup,
# Save unskinned mesh
for drawLayer, materialFaces in groupFaces.items():
if drawLayer not in fMeshes:
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayer, False)
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayer, False, None)
fMeshes[drawLayer] = fMesh
for material_index, bFaces in materialFaces.items():
+80 -3
View File
@@ -36,6 +36,31 @@ enumCompressionFormat = [
('yay0', 'YAY0', 'YAY0'),
]
def getDeclaration(data, name):
matchResult = re.search("extern\s*[A-Za-z0-9\_]*\s*" + re.escape(name) + \
"\s*(\[[^;\]]*\])?;\s*", data, re.DOTALL)
return matchResult
def hexOrDecInt(value):
if isinstance(value, int):
return value
elif "<<" in value:
i = value.index("<<")
return hexOrDecInt(value[:i]) << hexOrDecInt(value[i + 2:])
elif ">>" in value:
i = value.index(">>")
return hexOrDecInt(value[:i]) >> hexOrDecInt(value[i + 2:])
elif 'x' in value:
return int(value, 16)
else:
return int(value)
def getOrMakeVertexGroup(obj, groupName):
for group in obj.vertex_groups:
if group.name == groupName:
return group
return obj.vertex_groups.new(name = groupName)
def unhideAllAndGetHiddenList(scene):
hiddenObjs = []
for obj in scene.objects:
@@ -133,6 +158,58 @@ def copyPropertyGroup(oldProp, newProp):
else:
setattr(newProp, sub_value_attr, sub_value)
def propertyCollectionEquals(oldProp, newProp):
if len(oldProp) != len(newProp):
print("Unequal size: " + str(oldProp) + " " + str(len(oldProp)) + ", " + str(newProp) + str(len(newProp)))
return False
equivalent = True
for i in range(len(oldProp)):
item = oldProp[i]
newItem = newProp[i]
if isinstance(item, bpy.types.PropertyGroup):
equivalent &= propertyGroupEquals(item, newItem)
elif type(item).__name__ == "bpy_prop_collection_idprop":
equivalent &= propertyCollectionEquals(item, newItem)
else:
try:
iterator = iter(item)
except TypeError:
isEquivalent = newItem == item
else:
isEquivalent = tuple([i for i in newItem]) == tuple([ i for i in item])
if not isEquivalent:
pass #print("Not equivalent: " + str(item) + " " + str(newItem))
equivalent &= isEquivalent
return equivalent
def propertyGroupEquals(oldProp, newProp):
equivalent = True
for sub_value_attr in oldProp.bl_rna.properties.keys():
if sub_value_attr == "rna_type":
continue
sub_value = getattr(oldProp, sub_value_attr)
if isinstance(sub_value, bpy.types.PropertyGroup):
equivalent &= propertyGroupEquals(sub_value, getattr(newProp, sub_value_attr))
elif type(sub_value).__name__ == "bpy_prop_collection_idprop":
newCollection = getattr(newProp, sub_value_attr)
copyPropertyCollection(sub_value, newCollection)
else:
newValue = getattr(newProp, sub_value_attr)
try:
iterator = iter(newValue)
except TypeError:
isEquivalent = newValue == sub_value
else:
isEquivalent = tuple([i for i in newValue]) == tuple([i for i in sub_value])
if not isEquivalent:
pass #print("Not equivalent: " + str(sub_value) + " " + str(newValue) + " " + str(sub_value_attr))
equivalent &= isEquivalent
return equivalent
def writeCData(data, headerPath, sourcePath):
sourceFile = open(sourcePath, 'w', newline = '\n', encoding = 'utf-8')
sourceFile.write(data.source)
@@ -285,9 +362,9 @@ def writeMaterialBase(baseDir):
matCFile = open(matCPath, 'w', newline = '\n')
matCFile.write(
'#include "types.h"\n' +\
'#include "rendering_graph_node.h"\n' +\
'#include "object_fields.h"\n' +\
'#include "materials.h"')
'#include "rendering_graph_node.h"\n' +\
'#include "object_fields.h"\n' +\
'#include "materials.h"')
# Write global texture load function here
# Write material.inc.c
Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB