Finished OOT support. Overhauled F3D UI. First child of room system is now global.

This commit is contained in:
kurethedead
2021-01-30 18:08:28 -08:00
parent 1a592dd0c0
commit 985ab94a2f
54 changed files with 5457 additions and 2253 deletions
Binary file not shown.
+14 -209
View File
@@ -5,238 +5,43 @@ This requires Blender 2.82+.
![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/mario_running.gif)
This is a Blender plugin that allows one to export display lists, geolayouts, and animations to Super Mario 64, either directly into a ROM or to C code. It supports custom color combiners / geometry modes, and different geolayout node types. It is also possible to use exported C code in homebrew applications.
This is a Blender plugin that allows one to export F3D display lists. It also has the ability to export assets for Super Mario 64 and Ocarina of Time decompilation projects. It supports custom color combiners / geometry modes / etc. It is also possible to use exported C code in homebrew applications.
Make sure to save often, as this plugin is prone to crashing when creating materials / undoing material creation. This is a Blender issue.
<https://developer.blender.org/T70574>
<https://developer.blender.org/T68406>
![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/mat_inspector.png)
![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/bone_inspector.png)
### Features (import means import into Blender)
- Geolayout import/export
- Display List import/export
- Animation import/export
- Collision export
- Level export (decomp only)
- Objects
- Trajectories
- Water Boxes
- Camera Triggers
- Skinned mesh support
- Custom normals
- Scrolling Textures (decomp only)
### Restrictions
- No YUV textures supported.
- When importing display lists / geolayouts from SM64 to Blender, only the mesh is imported (no material)
### Credits
Thanks to anonymous_moose, Cheezepin, Rovert, and especially InTheBeef for testing.
Thanks to InTheBeef for LowPolySkinnedMario.
# Instructions
### Table of Contents
2. [ Super Mario 64 ](sm64/REAMDE.md)
3. [ Ocarina Of Time ](oot/README.md)
### Installation
Unzip and drag the entire folder into your blender addons folder (usually at C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons). Note that you must drag the ENTIRE folder, not just the 'fast64_internal' folder. Then in Blender, go to Edit -> Preferences -> Add-Ons and find/check the 'Fast64' plugin. If it does not show up, go to Edit -> Preferences -> Save&Load and make sure 'Auto Run Python Scripts' is enabled.
Download the repository as a zip file. In Blender, go to Edit -> Preferences -> Add-Ons and click the "Install" button to install the plugin from the zip file. Find the Fast64 addon in the addon list and enable it. If it does not show up, go to Edit -> Preferences -> Save&Load and make sure 'Auto Run Python Scripts' is enabled.
### Tool Locations
The tools can be found in the properties sidebar under the 'Tools' tab (toggled by pressing N).
The tools can be found in the properties sidebar under the 'Fast64' tab (toggled by pressing N).
The F3D material inspector can be found in the properties editor under the material tab.
The armature geolayout inspector can be found in the properties editor under the bone tab in Pose Mode.
The object geolayout inspector can be found in the properties editor under the object tab.
The level camera settings can be found in the properties editor under the camera tab.
The RDP default settings can be found in the properties editor under the world tab.
### F3D Materials
Any exported mesh must use an F3D Material, which can be added by the 'Create F3D Material' button in the material inspector window. You CANNOT use regular blender materials. If you have a model with Principled BSDF materials, you can use the Principled BSDF to F3D conversion operator to automatically convert them. The image in the "Base Color" slot will be set as texture 0, while the image in the "Subsurface Color" slot will be set as texture 1. If you want the converter to use a specific material preset other than "Shaded Solid" or "Shaded Texture", add a custom property with the name "convert_preset" and set the preset name there.
### Converting To F3D v3 Materials
A new optimized shader graph was introduced to decrease processing times for material creation and exporting. If you have a project that still uses old materials, you may want to convert them to v3. To convert an old project, click the "Recreate F3D Materials As V3" operator near the top of the Fast64 tab in 3D view. This may take a while depending on the number of materials in the project. Then go to the outliner, change the display mode to "Orphan Data" (broken heart icon), then click "Purge" in the top right corner. Purge multiple times until all of the old node groups are gone.
### ROM File Settings
When importing from a ROM, the plugin will import from the ROM at filepath 'Import ROM', When exporting to a ROM, the plugin will make a copy of the file at 'Export ROM', modify it, and save the file to 'Output ROM'. The ROM must be expanded.
### Where Can I Export To (Binary)?
By default, when exporting to ROM, the plugin will copy the contents of bank 4 to the range (0x11A35B8 - 0x11FFF00). This will leave you with free space to export assets to range (0x11D8930 - 0x11FFF00).
Any exported mesh must use an F3D Material, which can be added by the 'Create F3D Material' button in the material inspector window. You CANNOT use regular blender materials. If you have a model with Principled BSDF materials, you can use the Principled BSDF to F3D conversion operator to automatically convert them. The image in the "Base Color" slot will be set as texture 0, while the image in the "Subsurface Color" slot will be set as texture 1.
### Vertex Colors
To use vertex colors, select the "Vertex Colored Texture" preset and add two vertex color layers to your mesh named 'Col' and 'Alpha'. The alpha layer will use the greyscale value of the vertex color to determine alpha.
To use vertex colors, select a vertex colored texture preset and add two vertex color layers to your mesh named 'Col' and 'Alpha'. The alpha layer will use the greyscale value of the vertex color to determine alpha.
### RDP State Optimization
A material will not set every single geometry mode / other mode option. Instead, each property will be checked against the default settings located in the world tab in the properties sidebar. If the values are the same, then the code to change it is not added.
### Geolayouts (Armature)
There are many different geolayout node types. In Blender, each node corresponds to a bone, whose type is defined in the armature geolayout inspector (properties editor under the bone tab in Pose Mode). Any imported bone is also added to a bone group, for the sake of color coding different types. These groups are for visuals only, and don't determine the node type. Imported geolayouts will already have these bone groups, but if you want to add them to your own armature use the 'Add Bone Groups' operator in the SM64 Armature Tools header. All regular bones (aka 0x13 commands) and 0x15 command bones are placed on armature layer 0. All other geolayout commands are placed on layer 1. Metarig generated bones are placed on layers 3 and 4 (see 'Animating Existing Geolayouts'). If you change a bone's geolayout command type and it dissapears, it might have been moved to a different layer. Make sure to go to the armature properties editor and set both layers 0 and 1 to be visible. Geolayout command types can be edited in the properties editor under the bone tab in pose mode.
### Geolayouts (Object)
Alternatively, for simple static geolayouts you can export object hierarchies. Geolayout properties such as draw layer can be located in the object properties tab.
### Exporting Geolayouts and Skinned Meshes
The N64 supports binary skinning, meaning each vertex will be influenced by one bone only. When skinning an exported geolayout, do NOT use automatic skinning, as this results in a smooth weight falloff. Instead, when weight painting set the weight to either 1 or 0, and set the brush Falloff to square. Skinning can only occur between an immediate parent and a child deform bone, not between siblings / across ancestors.
### Importing/Exporting SM64 Geolayouts
Download these documents by VL Tone:
[SM64MainLevelScripts.txt](http://qubedstudios.rustedlogic.net/SM64MainLevelScripts.txt)
[SM64GeoLayoutPtrsByLevels.txt](http://qubedstudios.rustedlogic.net/SM64GeoLayoutPtrsByLevels.txt)
For importing/exporting geolayouts:
- In SM64GeoLayoutPtrsByLevels.txt, search the name of the model.
- There you can get the modelID (Obj) and geolayout start (ROM Address start + offset)
- Note that these are decimal values, and must be converted to hex when used as inputs for fast64.
- In SM64MainLevelScripts, search '22 08 00 MM' where MM = modelID.
- There may be multiple instances, in which case you must use the offset field from before and check if it matches the last 3 bytes of the line.
- There you can get the level command, which is the first number before the slash on that line.
Plug these values into the SM64 Geolayout Exporter/Importer panels.
### Replacing Existing SM64 Geolayout Geometry
SM64 geolayouts are often in strange rest poses, which makes it hard to modify their geometry. It often helps to import an animation belonging to that geolayout to see what the idle pose of a geolayout should be. Once you know, you can rotate the bones of the armature in pose mode to a usable position and then use the 'Apply as Rest Pose' operator under the SM64 Armature Tools header. Skin your new mesh to that armature, then rotate the bones back to the original position and use 'Apply as Rest Pose' again. You can now export the geolayout to SM64 and it will be able to use existing animations.
For example, for Mario you would rotate the four limb joints around the Y-axis 180 degrees, then just the arms 90/-90 degrees as such:
![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/mario_t_pose.gif)
Then after applying the rest pose and skinning, you would apply those operations in reverse order then apply rest pose again.
### Importing/Exporting Binary SM64 Animations (Not Mario)
- Note: SM64 animations only allow for rotations, and translation only on the root bone.
- Download Quad64, open the desired level, and go to Misc -> Script Dumps.
- Go to the objects header, find the object you want, and view the Behaviour Script tab.
- For most models with animation, you can will see a 27 command, and optionally a 28 command.
For importing:
- The last 4 bytes of the 27 command will be the animation list pointer.
- Make sure 'Is DMA Animation' is unchecked, 'Is Anim List' is checked, and 'Is Segmented Pointer' is checked.
- Set the animation importer start address as those 4 bytes.
- If a 28 command exists, then the second byte will be the anim list index.
- Otherwise, the anim list index is usually 0.
For exporting:
- Make sure 'Set Anim List Entry' is checked.
- Copy the addresses of the 27 command, which is the first number before the slash on that line.
- Optionally do the same for the 28 command, which may not exist.
- If a 28 command exists, then the second byte will be the anim list index.
- Otherwise, the anim list index is usually 0.
Select an armature for the animation, and press 'Import/Export animation'.
### Importing/Exporting Binary Mario Animations
Mario animations use a DMA table, which contains 8 byte entries of (offset from table start, animation size). Documentation about this table is here:
https://dudaw.webs.com/sm64docs/sm64_marios_animation_table.txt
Basically, Mario's DMA table starts at 0x4EC000. There is an 8 byte header, and then the animation entries afterward. Thus the 'climb up ledge' DMA entry is at 0x4EC008. The first 4 bytes at that address indicate the offset from 0x4EC000 at which the actual animation exists. Thus the 'climb up ledge' animation entry address is at 0x4EC690. Using this table you can find animations you want to overwrite. Make sure the 'Is DMA Animation' option is checked and 'Is Segmented Pointer' is unchecked when importing/exporting. Check "Overwrite DMA Entry", set the start address to 4EC000 (for Mario), and set the entry address to the DMA entry obtained previously.
### Animating Existing Geolayouts
Often times it is hard to rig an existing SM64 geolayout, as there are many intermediate non-deform bones and bones don't point to their children. To make this easier you can use the 'Create Animatable Metarig' operator in the SM64 Armature Tools header. This will generate a metarig which can be used with IK. The metarig bones will be placed on armature layers 3 and 4.
## Decomp
To start, set your base decomp folder in SM64 File Settings. This allows the plugin to automatically add headers/includes to the correct locations. You can always choose to export to a custom location, although headers/includes won't be written.
### Decomp Export Types
Most exports will let you choose an export type.
"Actor" will export data to an actor folder. The headers modified will be:
- actors/my\_group.h
- actors/my\_group.c
- actors/my\_group\_geo.c (for geolayouts)
"Level" will export data to a level folder. The headers modified will be:
- levels/my\_level/leveldata.c
- levels/my\_level/header.h
### Decomp And Extended RAM
By default decomp uses 4MB of RAM which means space runs out quickly when exporting custom assets. To handle this Fast64 will automatically add "#define USE\_EXT\_RAM" at the top of include/segments.h after the include guards.
### Exporting Geolayouts to C
Set the "Name" field to the actor folder name.
To replace an actor model, set the enum to "Actor" and set the correct group name. To find the group name, look at the group or common header files in actors/ to see where the actor is defined in.
To replace an actor model manually, replace its geo.inc.c and model.inc.c contents with the geolayout file and the dl file respectively. Use the contents of the header file to replace existing extern declarations in one of the group header files (ex. mario is in group0.h). Make sure that the name of your geolayout is the same the name of the geolayout you're replacing. Note that any function addresses in geolayout nodes will be converted to decomp function names if possible.
### Exporting Levels to C
Add an Empty and check its SM64 object type in the object properties sidebar. Change the type to "Level Root."
Add another Empty and change its type to "Area Root", and parent it to the level root object. You can now add any geometry/empties/splines as children of the area root and it will be exported with the area. They do not have to directly parent to the area root, just to something within the area root's descendants. Empties are also used for placing specials, macros, objects, water boxes, and camera volumes. You can search the available options using the relevant search operators, or set an option to "Custom" to write in your own values. NURBS curves are used to export spline data. Backgrounds are set in level root options, and warp nodes are set in area root options.
To replace a level manually, replace the contents of a level folder with your exported folder. Then,
- Add '#include "levels/mylevel/geo.inc.c"' to geo.c.
- Add '#include "levels/mylevel/leveldata.inc.c"' to leveldata.c.
- Add '#include "levels/mylevel/header.inc.h"' to header.h.
- Add '#include "levels/mylevel/texture\_include.inc.c"' to texture.inc.c. (If saving textures as PNGs.)
The level exporter will modify these files:
- src/game/camera.c (sZoomOutAreaMasks)
- levels/level\_defines.h
- levels/course\_defines.h
### Exporting HUD to C
The HUD exporter will export a texture and a function to draw it to the screen as a texture rectangle. The data will be written to segment2, and the headers modified will be:
- src/game/segment2.h (texture declaration)
- bin/segment2.c (texture definition)
- textures/segment2 (texture file, if saving pngs separately)
This will also write the drawing function to either src/game/hud.c or src/game/ingame_menu.c depending on the "Export Type". Note that this operator can be called multiple times and it will replace the old code from previous exports. It will not delete old textures in the textures/segment2 folder, although they won't be built in the project. It also won't delete any palette data.
The draw function will be in the format "void myfunc(x, y, width, height, s, t)". Width and height are used to take advantage of texture clamp/repeating, or to mask parts of the texture (ex. a health meter). s and t are the rectangles start UVs, which can be used to scroll the image. Negative positions are automatically handled. For basic texture drawing, set width/height to your texture dimensions and s/t to 0.
### Scrolling Textures in Decomp
Scrolling texture settings can be found in the material properties window before the "Geomtry Mode Settings" tab.
If you want to disable scrolling texture code generation, you can do so in the SM64 File Settings.
This is the process for how scrolling textures is implemented:
- Add a sSegmentROMTable to src/game/memory.c/h in order to keep track of which ROM locations are loaded into memory. ROM locations will be stored in this table during segment loading function calls.
- Add src/game/texscroll.c/h which will scroll any vertex data that is currently loaded.
- Add src/game/texscroll/ which contains segment specific texture scroll files (Ex. group0_texscroll.inc.c/h). These will be included in the texscroll.c/h file.
- The segment specific files will include texscroll.inc.c/h files from all the geometry within it that uses scrolling. These files will be generated in the same location as the vertex data being scrolled (Ex. where model.inc.c, leveldata.c is).
- Add a function call to update_level in src/game/level_update.c which calls the main scroll function.
### Large Texture Mode
In F3D material properties, you can enable "Large Texture Mode". This will let you use textures up to 1024x1024 as long as each triangle in the mesh has UVs that can fit within a single tile load. Fast64 will categorize triangles into shared tile loads and load the portion of the texture when necessary.
### Decomp vs Homebrew Compatibility
There may occur cases where code is formatted differently based on the code use case. In the tools panel under the SM64 File Settings subheader, you can toggle decomp compatibility.
There may occur cases where code is formatted differently based on the code use case. In the tools panel under the Fast64 File Settings subheader, you can toggle homebrew compatibility.
### Switch Statements
To create a switch node, and a bone to your armature and set its geolayout type to "Switch". Any bones that will be switched should be parented to this switch bone. The switch bone can do either material, draw layer, or mesh switching.
### Converting To F3D v4 Materials
A new optimized shader graph was introduced to decrease processing times for material creation and exporting. If you have a project that still uses old materials, you may want to convert them to v4. To convert an old project, click the "Recreate F3D Materials As V4" operator near the top of the Fast64 tab in 3D view. This may take a while depending on the number of materials in the project. Then go to the outliner, change the display mode to "Orphan Data" (broken heart icon), then click "Purge" in the top right corner. Purge multiple times until all of the old node groups are gone.
To add a mesh switch option node, duplicate and separate your switch bone into its own armature and move it off to the side. Set the bone geolayout command to "Switch Option". This bone must be the root bone of all other bones in the armature. Skin your switch option geometry to this armature, then add your switch option armature to the switch bone options in your original armature.
### Insertable Binary Exporting
Insertable Binary exporting will generate a binary file, with a header containing metadata about pointer locations. It is formatted as such:
0x00-0x04 : Data Type
0 = Display List
1 = Geolayout
2 = Animation
3 = Collision
0x04-0x08 : Data Size (size in bytes of Data Section)
0x08-0x0C : Start Address (start address of data, relative to start of Data Section)
0x0C-0x10 : Number of Pointer Addresses (number of pointer addresses to be resolved)
0x10-N : List of 4-byte pointer addresses. Each address relative to start of Data Section.
N-end : Data Section (actual binary data)
To resolve pointer addresses, for each pointer address,
# Get data section only
data = fileData[N:]
# Get current offset
current_offset = data[pointer_address]
# Convert offset to segmented address
data[pointer_address] = encode_segmented_address(export_address + current_offset)
### Common Issues
Game crashes: Invalid function address for switch/function/held object bones.
Animation root translation/rotation not exporting: Make sure you are animating the root bone, not the armature object.
+26 -12
View File
@@ -145,9 +145,19 @@ class CreateMetarig(bpy.types.Operator):
self.report({'INFO'}, 'Created metarig.')
return {'FINISHED'} # must return a set
class SM64_AddWaterBox(AddWaterBox):
bl_idname = 'object.sm64_add_water_box'
scale : bpy.props.FloatProperty(default = 10)
preset : bpy.props.StringProperty(default = "Shaded Solid")
matName : bpy.props.StringProperty(default = "sm64_water_mat")
def setEmptyType(self, emptyObj):
emptyObj.sm64_obj_type = "Water Box"
class SM64_ArmatureToolsPanel(bpy.types.Panel):
bl_idname = "SM64_PT_armature_tools"
bl_label = "SM64 Armature Tools"
bl_label = "SM64 Tools"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'SM64'
@@ -162,10 +172,7 @@ class SM64_ArmatureToolsPanel(bpy.types.Panel):
col.operator(ArmatureApplyWithMesh.bl_idname)
col.operator(AddBoneGroups.bl_idname)
col.operator(CreateMetarig.bl_idname)
#col.operator(N64_AddF3dMat.bl_idname)
for i in range(panelSeparatorSize):
col.separator()
col.operator(SM64_AddWaterBox.bl_idname)
class F3D_GlobalSettingsPanel(bpy.types.Panel):
bl_idname = "F3D_PT_global_settings"
@@ -185,6 +192,11 @@ class F3D_GlobalSettingsPanel(bpy.types.Panel):
col.prop(context.scene, 'isHWv1')
col.prop(context.scene, 'saveTextures')
col.prop(context.scene, 'f3d_simple', text = "Simple Material UI")
col.prop(context.scene, 'generateF3DNodeGraph', text = "Generate F3D Node Graph For Materials")
col.prop(context.scene, 'decomp_compatible', invert_checkbox = True, text = 'Homebrew Compatibility')
col.prop(context.scene, 'ignoreTextureRestrictions')
if context.scene.ignoreTextureRestrictions:
col.box().label(text = "Width/height must be < 1024. Must be RGBA32. Must be png format.")
class Fast64_GlobalObjectPanel(bpy.types.Panel):
bl_label = "Global Object Inspector"
@@ -216,9 +228,8 @@ class Fast64_GlobalSettingsPanel(bpy.types.Panel):
# called every frame
def draw(self, context):
col = self.layout.column()
#prop_split(col, context.scene, 'gameEditorMode', "Game")
prop_split(col, context.scene, 'gameEditorMode', "Game")
col.prop(context.scene, 'fullTraceback')
col.prop(context.scene, 'experimentalMats')
#def updateGameEditor(scene, context):
@@ -242,6 +253,7 @@ classes = (
ArmatureApplyWithMesh,
AddBoneGroups,
CreateMetarig,
SM64_AddWaterBox,
#Fast64_GlobalObjectPanel,
F3D_GlobalSettingsPanel,
@@ -257,12 +269,14 @@ def register():
render_engine_register()
bsdf_conv_register()
sm64_register(True)
#oot_register(True)
oot_register(True)
bsdf_conv_panel_regsiter()
for cls in classes:
register_class(cls)
f3d_writer_register()
# ROM
@@ -276,13 +290,13 @@ def register():
name = 'Game', default = 'SM64', items = gameEditorEnum)
bpy.types.Scene.saveTextures = bpy.props.BoolProperty(
name = 'Save Textures As PNGs (Breaks CI Textures)')
bpy.types.Scene.experimentalMats = bpy.props.BoolProperty(
name = "Experimental Materials")
bpy.types.Scene.generateF3DNodeGraph = bpy.props.BoolProperty(name = "Generate F3D Node Graph", default = True)
# called on add-on disabling
def unregister():
f3d_writer_unregister()
sm64_unregister(True)
#oot_unregister(True)
oot_unregister(True)
mat_unregister()
bsdf_conv_unregister()
bsdf_conv_panel_unregsiter()
@@ -293,7 +307,7 @@ def unregister():
del bpy.types.Scene.ignoreTextureRestrictions
del bpy.types.Scene.saveTextures
del bpy.types.Scene.gameEditorMode
del bpy.types.Scene.experimentalMats
del bpy.types.Scene.generateF3DNodeGraph
for cls in classes:
unregister_class(cls)
+62 -12
View File
@@ -1364,7 +1364,7 @@ class TextureExportSettings:
self.includeDir = includeDir
self.exportPath = exportPath
class GameGfxFormatter:
class GfxFormatter:
def __init__(self, scrollMethod, texArrayBitSize):
self.scrollMethod = scrollMethod
self.texArrayBitSize = texArrayBitSize
@@ -1428,7 +1428,7 @@ class GameGfxFormatter:
# Called for handling vertex texture scrolling.
def vertexScrollToC(self, fScrollData, name, vertexCount):
raise PluginError("Use of unimplemented GameGfxFormatter function vertexScrollToC.")
raise PluginError("Use of unimplemented GfxFormatter function vertexScrollToC.")
# Called for building the entry point DL for drawing a model.
def drawToC(self, f3d, gfxList):
@@ -1715,10 +1715,14 @@ class FModel:
self.global_data = FGlobalData()
self.texturesSavedLastExport = 0 # hacky
# Called before SPEndDisplayList
def onMaterialCommandsBuilt(self, gfxList, revertList, material, drawLayer):
return
def getTextureSuffixFromFormat(self, texFmt):
return texFmt.lower()
def getDrawLayer(self, obj):
def getDrawLayerV3(self, obj):
return None
def getRenderMode(self, drawLayer):
@@ -1740,25 +1744,66 @@ class FModel:
def addTexture(self, key, value, fMaterial):
fMaterial.usedImages.append(key)
self.textures[key] = value
def addLight(self, key, value, fMaterial):
fMaterial.usedLights.append(key)
self.lights[key] = value
def addMesh(self, name, namePrefix, drawLayer, isSkinned):
meshName = getFMeshName(name, namePrefix, drawLayer, isSkinned)
checkUniqueBoneNames(self, meshName, name)
self.meshes[meshName] = FMesh(meshName, self.DLFormat)
return self.meshes[meshName]
def getTextureAndHandleShared(self, imageKey):
# Check if texture is in self
if imageKey in self.textures:
return self.textures[imageKey]
fImage = self.textures[imageKey]
fPalette = self.textures[fImage.paletteKey] if fImage.paletteKey is not None else None
return fImage, fPalette
if self.parentModel is not None:
# Check if texture is in parent
if imageKey in self.parentModel.textures:
return self.parentModel.textures[imageKey]
fImage = self.parentModel.textures[imageKey]
fPalette = self.parentModel.textures[fImage.paletteKey] if fImage.paletteKey is not None else None
return fImage, fPalette
# Check if texture is in siblings
for subModel in self.parentModel.subModels:
if imageKey in subModel.textures:
imageItem = subModel.textures.pop(imageKey)
self.parentModel.textures[imageKey] = imageItem
return imageItem
fImage = subModel.textures.pop(imageKey)
self.parentModel.textures[imageKey] = fImage
paletteKey = fImage.paletteKey
fPalette = None
if paletteKey is not None:
fPalette = subModel.textures.pop(paletteKey)
self.parentModel.textures[paletteKey] = fPalette
return fImage, fPalette
return None, None
else:
return None, None
def getLightAndHandleShared(self, lightName):
# Check if light is in self
if lightName in self.lights:
return self.lights[lightName]
if self.parentModel is not None:
# Check if light is in parent
if lightName in self.parentModel.lights:
return self.parentModel.lights[lightName]
# Check if light is in siblings
for subModel in self.parentModel.subModels:
if lightName in subModel.lights:
light = subModel.lights.pop(lightName)
self.parentModel.lights[lightName] = light
return light
else:
return None
def getMaterialAndHandleShared(self, materialKey):
# Check if material is in self
@@ -1778,9 +1823,14 @@ class FModel:
# If material is in sibling, handle the material's textures as well.
for imageKey in materialItem[0].usedImages:
imageItem = self.getTextureAndHandleShared(imageKey)
if imageItem is None:
fImage, fPalette = self.getTextureAndHandleShared(imageKey)
if fImage is None:
raise PluginError("Error: If a material exists, its textures should exist too.")
for lightName in materialItem[0].usedLights:
light = self.getLightAndHandleShared(lightName)
if light is None:
raise PluginError("Error: If a material exists, its lights should exist too.")
return materialItem
else:
return None
@@ -2126,7 +2176,7 @@ class FMaterial:
# Used for keeping track of shared resources in FModel hierarchy
self.usedImages = [] # array of (image, texFormat, paletteType) = imageKey
self.usedLights = [] # array of light names
# Used for tile scrolling
self.tileSizeCommands = {} # dict of {texIndex : DPSetTileSize}
@@ -2404,7 +2454,7 @@ class FImage:
self.filename = filename
self.converted = converted
self.isLargeTexture = False
self.palette = None # another FImage reference
self.paletteKey = None # another FImage reference
def size(self):
return len(self.data)
@@ -0,0 +1,29 @@
import os
basePath = 'presets/f3d'
data = ''
presetList = "material_presets = {\n"
for subdir in os.listdir(basePath):
subPath = os.path.join(basePath, subdir)
if subdir != '__pycache__' and subdir != 'user' and os.path.isdir(subPath):
presetList += '\t"' + subdir + '" : {\n'
for filename in os.listdir(subPath):
presetPath = os.path.join(subPath, filename)
if os.path.isfile(presetPath):
print(presetPath)
presetFile = open(presetPath, 'r')
presetData = presetFile.read()
presetFile.close()
data += filename[:-3] + " = '''\n" + presetData + "'''\n\n"
presetList += '\t\t"' + filename[:-3] + '" : ' + filename[:-3] + ',\n'
presetList += '\t},\n'
presetList += '}\n'
data += presetList
outFile = open('f3d_material_presets.py', 'w')
outFile.write(data)
outFile.close()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+294 -133
View File
@@ -1,6 +1,7 @@
import bpy, bmesh, mathutils, os, re, copy, math
from math import pi, ceil
from io import BytesIO
from bpy.utils import register_class, unregister_class
from .f3d_constants import *
from .f3d_material import all_combiner_uses, getMaterialScrollDimensions, getTmemWordUsage, getTmemMax, bitSizeDict, texBitSizeOf, texFormatOf
@@ -126,6 +127,8 @@ def fixLargeUVs(obj):
raise PluginError("Object \'" + obj.name + "\' does not have a UV layer named \'UVMap.\'")
texSizeDict = {}
if len(obj.data.materials) == 0:
raise PluginError("This object needs an f3d material on it.")
for material in obj.data.materials:
if material is None:
raise PluginError("There are some faces on your mesh that are assigned to an empty material slot.")
@@ -374,14 +377,11 @@ def saveStaticModel(fModel, obj, transformMatrix, ownerName, DLFormat, convertTe
drawLayer = getattr(material.f3d_mat.draw_layer, drawLayerField)
drawLayerName = drawLayer
else:
drawLayer = fModel.getDrawLayer(obj)
drawLayer = fModel.getDrawLayerV3(obj)
drawLayerName = None
if drawLayer not in fMeshes:
meshName = getFMeshName(obj.original_name, ownerName, drawLayerName, False)
checkUniqueBoneNames(fModel, meshName, obj.original_name)
fMesh = FMesh(meshName, DLFormat)
fModel.meshes[meshName] = fMesh
fMesh = fModel.addMesh(obj.original_name, ownerName, drawLayerName, False)
fMeshes[drawLayer] = fMesh
if obj.use_f3d_culling and (fModel.f3d.F3DEX_GBI or fModel.f3d.F3DEX_GBI_2):
@@ -442,7 +442,7 @@ def addCullCommand(obj, fMesh, transformMatrix, matWriteMethod):
def exportF3DCommon(obj, fModel, transformMatrix, includeChildren, name, DLFormat, convertTextureData):
tempObj, meshList = combineObjects(obj, includeChildren, None, None)
try:
drawLayer = fModel.getDrawLayer(obj)
drawLayer = fModel.getDrawLayerV3(obj)
fMesh = saveStaticModel(fModel, tempObj, transformMatrix, name,
DLFormat, convertTextureData, True, None)[drawLayer]
cleanupCombineObj(tempObj, meshList)
@@ -1000,30 +1000,23 @@ def convertVertexData(mesh, loopPos, loopUV, loopColorOrNormal,
return Vtx(position, uv, colorOrNormal)
def getLoopColor(loop, mesh, mat_ver):
if mat_ver > 3:
color_layer = mesh.vertex_colors.active.data if mesh.vertex_colors.active is not None else None
if color_layer is not None:
normalizedRGB = color_layer[loop.index].color
else:
normalizedRGB = [1,1,1,1]
return (normalizedRGB[0], normalizedRGB[1], normalizedRGB[2], normalizedRGB[3])
else:
color_layer = mesh.vertex_colors['Col'].data if 'Col' in \
mesh.vertex_colors else None
alpha_layer = mesh.vertex_colors['Alpha'].data if 'Alpha' in \
mesh.vertex_colors else None
if color_layer is not None:
normalizedRGB = color_layer[loop.index].color
else:
normalizedRGB = [1,1,1]
if alpha_layer is not None:
normalizedAColor = alpha_layer[loop.index].color
normalizedA = mathutils.Color(normalizedAColor[0:3]).v
else:
normalizedA = 1
return (normalizedRGB[0], normalizedRGB[1], normalizedRGB[2], normalizedA)
color_layer = mesh.vertex_colors['Col'].data if 'Col' in \
mesh.vertex_colors else None
alpha_layer = mesh.vertex_colors['Alpha'].data if 'Alpha' in \
mesh.vertex_colors else None
if color_layer is not None:
normalizedRGB = color_layer[loop.index].color
else:
normalizedRGB = [1,1,1]
if alpha_layer is not None:
normalizedAColor = alpha_layer[loop.index].color
normalizedA = mathutils.Color(normalizedAColor[0:3]).v
else:
normalizedA = 1
return (normalizedRGB[0], normalizedRGB[1], normalizedRGB[2], normalizedA)
def getLoopColorOrNormal(loop, face, mesh, obj, exportVertexColors):
material = obj.data.materials[face.material_index]
@@ -1286,7 +1279,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData):
int(color[3] * 255)))
if useDict['Shade'] and f3dMat.set_lights:
fLights = saveLightsDefinition(fModel, f3dMat,
fLights = saveLightsDefinition(fModel, fMaterial, f3dMat,
materialName + '_lights')
fMaterial.material.commands.extend([
SPSetLights(fLights) # TODO: handle synching: NO NEED?
@@ -1324,6 +1317,8 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData):
int(f3dMat.k4 * 255),
int(f3dMat.k5 * 255))
])
fModel.onMaterialCommandsBuilt(fMaterial.material, fMaterial.revert, material, drawLayer)
# End Display List
# For dynamic calls, materials will be called as functions and should not end the DL.
@@ -1509,7 +1504,6 @@ def saveTextureLoading(fMaterial, fImage, loadTexGfx, clamp_S, mirror_S, clamp_T
DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl, tl, sh, th),]) # added in
tileSizeCommand = DPSetTileSize(f3d.G_TX_RENDERTILE + texIndex, sl, tl, sh, th)
#tileSizeCommand = SPDisplayList(GfxList("0x08000000", GfxListTag.Material, DLFormat.Static))
loadTexGfx.commands.extend([
DPPipeSync(),
DPSetTile(fmt, siz, line, tmem, \
@@ -1558,8 +1552,10 @@ def saveOrGetPaletteDefinition(fMaterial, fModelOrTexRect, image, imageName, tex
# If image already loaded, return that data.
paletteName = toAlnum(imageName) + '_pal_' + palFmt.lower()
imageKey = (image, (texFmt, palFmt))
if imageKey in fModelOrTexRect.textures:
return fModelOrTexRect.textures[imageKey], fModelOrTexRect.textures[imageKey].palette
paletteKey = (image, (palFmt, 'PAL'))
fImage, fPalette = fModelOrTexRect.getTextureAndHandleShared(imageKey)
if fImage is not None:
return fImage, fPalette
palette = []
texture = []
@@ -1604,7 +1600,7 @@ def saveOrGetPaletteDefinition(fMaterial, fModelOrTexRect, image, imageName, tex
if fMaterial.useLargeTextures:
fImage.isLargeTexture = True
fPalette.isLargeTexture = True
fImage.palette = fPalette
fImage.paletteKey = paletteKey
#paletteImage = bpy.data.images.new(paletteName, 1, len(palette))
#paletteImage.pixels = palette
@@ -1652,9 +1648,9 @@ def saveOrGetTextureDefinition(fMaterial, fModel, image, imageName, texFormat, c
# If image already loaded, return that data.
imageKey = (image, (texFormat, 'NONE'))
imageItem = fModel.getTextureAndHandleShared(imageKey)
if imageItem is not None:
return imageItem
fImage, fPalette = fModel.getTextureAndHandleShared(imageKey)
if fImage is not None:
return fImage
if image.filepath == "":
name = image.name
@@ -1681,17 +1677,17 @@ def saveOrGetTextureDefinition(fMaterial, fModel, image, imageName, texFormat, c
# for j in reversed(range(image.size[1])) for i in range(image.size[0])] for byteVal in doubleByte])
fImage.data = bytearray([byteVal for doubleByte in [
((((int(image.pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F) & 0x1F) << 3) |
((int(image.pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F) & 0x1F) >> 2)),
((((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(image.pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F) & 0x02) << 6) | \
((int(image.pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F) & 0x1F) << 1) | \
(((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 + 2] * 0x1F)) & 0x1F) << 1) | \
(1 if image.pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0)))
for j in reversed(range(image.size[1])) for i in range(image.size[0])] for byteVal in doubleByte])
elif bitSize == 'G_IM_SIZ_32b':
fImage.data = bytearray([
int(image.pixels[(j * image.size[0] + i) * image.channels + field] * 0xFF) & 0xFF
int(round(image.pixels[(j * image.size[0] + i) * image.channels + field] * 0xFF)) & 0xFF
for j in reversed(range(image.size[1])) for i in range(image.size[0]) for field in range(image.channels)])
else:
raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
@@ -1709,49 +1705,49 @@ def saveOrGetTextureDefinition(fMaterial, fModel, image, imageName, texFormat, c
elif fmt == 'G_IM_FMT_IA':
if bitSize == 'G_IM_SIZ_4b':
fImage.data = bytearray([
((int(mathutils.Color(
((int(round(mathutils.Color(
image.pixels[
(j * image.size[0] + i) * image.channels :
(j * image.size[0] + i) * image.channels + 3
]).v * 0x7) & 0x7) << 1) | \
]).v * 0x7)) & 0x7) << 1) | \
(1 if image.pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0)
for j in reversed(range(image.size[1])) for i in range(image.size[0])])
elif bitSize == 'G_IM_SIZ_8b':
fImage.data = bytearray([
((int(mathutils.Color(
((int(round(mathutils.Color(
image.pixels[
(j * image.size[0] + i) * image.channels :
(j * image.size[0] + i) * image.channels + 3
]).v * 0xF) & 0xF) << 4) | \
(int(image.pixels[(j * image.size[0] + i) * image.channels + 3] * 0xF) & 0xF)
]).v * 0xF)) & 0xF) << 4) | \
(int(round(image.pixels[(j * image.size[0] + i) * image.channels + 3] * 0xF)) & 0xF)
for j in reversed(range(image.size[1])) for i in range(image.size[0])])
elif bitSize == 'G_IM_SIZ_16b':
fImage.data = bytearray([byteVal for doubleByte in [
(int(mathutils.Color(
(int(round(mathutils.Color(
image.pixels[
(j * image.size[0] + i) * image.channels :
(j * image.size[0] + i) * image.channels + 3
]).v * 0xFF) & 0xFF,
int(image.pixels[(j * image.size[0] + i) * image.channels + 3] * 0xFF) & 0xFF)
]).v * 0xFF)) & 0xFF,
int(round(image.pixels[(j * image.size[0] + i) * image.channels + 3] * 0xFF)) & 0xFF)
for j in reversed(range(image.size[1])) for i in range(image.size[0])] for byteVal in doubleByte])
else:
raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
elif fmt == 'G_IM_FMT_I':
if bitSize == 'G_IM_SIZ_4b':
fImage.data = bytearray([
int(mathutils.Color(
int(round(mathutils.Color(
image.pixels[
(j * image.size[0] + i) * image.channels :
(j * image.size[0] + i) * image.channels + 3
]).v * 0xF) & 0xF
]).v * 0xF)) & 0xF
for j in reversed(range(image.size[1])) for i in range(image.size[0])])
elif bitSize == 'G_IM_SIZ_8b':
fImage.data = bytearray([
int(mathutils.Color(
int(round(mathutils.Color(
image.pixels[
(j * image.size[0] + i) * image.channels :
(j * image.size[0] + i) * image.channels + 3
]).v * 0xFF) & 0xFF
]).v * 0xFF)) & 0xFF
for j in reversed(range(image.size[1])) for i in range(image.size[0])])
else:
raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
@@ -1769,75 +1765,80 @@ def saveOrGetTextureDefinition(fMaterial, fModel, image, imageName, texFormat, c
return fImage
# Ignore, old version not using list comprehension
if convertTextureData:
# N64 is -Y, Blender is +Y
for j in reversed(range(image.size[1])):
for i in range(image.size[0]):
color = [1,1,1,1]
for field in range(image.channels):
color[field] = image.pixels[
(j * image.size[0] + i) * image.channels + field]
if fmt == 'G_IM_FMT_RGBA':
if bitSize == 'G_IM_SIZ_16b':
words = \
((int(color[0] * 0x1F) & 0x1F) << 11) | \
((int(color[1] * 0x1F) & 0x1F) << 6) | \
((int(color[2] * 0x1F) & 0x1F) << 1) | \
(1 if color[3] > 0.5 else 0)
fImage.data.extend(bytearray(words.to_bytes(2, 'big')))
elif bitSize == 'G_IM_SIZ_32b':
fImage.data.extend(bytearray([
int(value * 0xFF) & 0xFF for value in color]))
else:
raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
# Warning, ints not rounded
#if convertTextureData:
# # N64 is -Y, Blender is +Y
# for j in reversed(range(image.size[1])):
# for i in range(image.size[0]):
# color = [1,1,1,1]
# for field in range(image.channels):
# color[field] = image.pixels[
# (j * image.size[0] + i) * image.channels + field]
# if fmt == 'G_IM_FMT_RGBA':
# if bitSize == 'G_IM_SIZ_16b':
# words = \
# ((int(color[0] * 0x1F) & 0x1F) << 11) | \
# ((int(color[1] * 0x1F) & 0x1F) << 6) | \
# ((int(color[2] * 0x1F) & 0x1F) << 1) | \
# (1 if color[3] > 0.5 else 0)
# fImage.data.extend(bytearray(words.to_bytes(2, 'big')))
# elif bitSize == 'G_IM_SIZ_32b':
# fImage.data.extend(bytearray([
# int(value * 0xFF) & 0xFF for value in color]))
# else:
# raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
#
# elif fmt == 'G_IM_FMT_YUV':
# raise PluginError("YUV not yet implemented.")
# if bitSize == 'G_IM_SIZ_16b':
# pass
# else:
# raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
#
# elif fmt == 'G_IM_FMT_CI':
# raise PluginError("CI not yet implemented.")
#
# elif fmt == 'G_IM_FMT_IA':
# intensity = mathutils.Color(color[0:3]).v
# alpha = color[3]
# if bitSize == 'G_IM_SIZ_4b':
# fImage.data.append(
# ((int(intensity * 0x7) & 0x7) << 1) | \
# (1 if alpha > 0.5 else 0))
# elif bitSize == 'G_IM_SIZ_8b':
# fImage.data.append(
# ((int(intensity * 0xF) & 0xF) << 4) | \
# (int(alpha * 0xF) & 0xF))
# elif bitSize == 'G_IM_SIZ_16b':
# fImage.data.extend(bytearray(
# [int(intensity * 0xFF), int(alpha * 0xFF)]))
# else:
# raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
# elif fmt == 'G_IM_FMT_I':
# intensity = mathutils.Color(color[0:3]).v
# if bitSize == 'G_IM_SIZ_4b':
# fImage.data.append(int(intensity * 0xF))
# elif bitSize == 'G_IM_SIZ_8b':
# fImage.data.append(int(intensity * 0xFF))
# else:
# raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
# else:
# raise PluginError("Invalid image format " + fmt)
#
# # We stored 4bit values in byte arrays, now to convert
# if bitSize == 'G_IM_SIZ_4b':
# fImage.data = \
# compactNibbleArray(fImage.data, image.size[0], image.size[1])
#
#fModel.addTexture((image, (texFormat, 'NONE')), fImage, fMaterial)
#
#return fImage
elif fmt == 'G_IM_FMT_YUV':
raise PluginError("YUV not yet implemented.")
if bitSize == 'G_IM_SIZ_16b':
pass
else:
raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
elif fmt == 'G_IM_FMT_CI':
raise PluginError("CI not yet implemented.")
elif fmt == 'G_IM_FMT_IA':
intensity = mathutils.Color(color[0:3]).v
alpha = color[3]
if bitSize == 'G_IM_SIZ_4b':
fImage.data.append(
((int(intensity * 0x7) & 0x7) << 1) | \
(1 if alpha > 0.5 else 0))
elif bitSize == 'G_IM_SIZ_8b':
fImage.data.append(
((int(intensity * 0xF) & 0xF) << 4) | \
(int(alpha * 0xF) & 0xF))
elif bitSize == 'G_IM_SIZ_16b':
fImage.data.extend(bytearray(
[int(intensity * 0xFF), int(alpha * 0xFF)]))
else:
raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
elif fmt == 'G_IM_FMT_I':
intensity = mathutils.Color(color[0:3]).v
if bitSize == 'G_IM_SIZ_4b':
fImage.data.append(int(intensity * 0xF))
elif bitSize == 'G_IM_SIZ_8b':
fImage.data.append(int(intensity * 0xFF))
else:
raise PluginError("Invalid combo: " + fmt + ', ' + bitSize)
else:
raise PluginError("Invalid image format " + fmt)
# We stored 4bit values in byte arrays, now to convert
if bitSize == 'G_IM_SIZ_4b':
fImage.data = \
compactNibbleArray(fImage.data, image.size[0], image.size[1])
fModel.addTexture((image, (texFormat, 'NONE')), fImage, fMaterial)
return fImage
def saveLightsDefinition(fModel, material, lightsName):
def saveLightsDefinition(fModel, fMaterial, material, lightsName):
lights = fModel.getLightAndHandleShared(lightsName)
if lights is not None:
return lights
lights = Lights(toAlnum(lightsName))
if material.use_default_lighting:
@@ -1876,7 +1877,7 @@ def saveLightsDefinition(fModel, material, lightsName):
if lightsName in fModel.lights:
raise PluginError("Duplicate light name.")
fModel.lights[lightsName] = lights
fModel.addLight(lightsName, lights, fMaterial)
return lights
def addLightDefinition(mat, f3d_light, fLights):
@@ -2041,17 +2042,24 @@ def saveOtherModeLDefinition(fMaterial, settings, defaults, defaultRenderMode, m
raise PluginError("Unhandled material write method: " + str(matWriteMethod))
def saveOtherModeLDefinitionAll(fMaterial, settings, defaults, defaultRenderMode):
cmd = SPSetOtherMode("G_SETOTHERMODE_L", 0, 32, [])
cmd.flagList.append(settings.g_mdsft_alpha_compare)
cmd.flagList.append(settings.g_mdsft_zsrcsel)
if settings.set_rendermode:
flagList, blendList = getRenderModeFlagList(settings, fMaterial)
cmd.flagList.extend(flagList)
if blendList is not None:
cmd.flagList.extend(blendList)
if not settings.set_rendermode and defaultRenderMode is None:
cmd = SPSetOtherMode("G_SETOTHERMODE_L", 0, 3, [])
cmd.flagList.append(settings.g_mdsft_alpha_compare)
cmd.flagList.append(settings.g_mdsft_zsrcsel)
else:
cmd.flagList.extend(defaultRenderMode)
cmd = SPSetOtherMode("G_SETOTHERMODE_L", 0, 32, [])
cmd.flagList.append(settings.g_mdsft_alpha_compare)
cmd.flagList.append(settings.g_mdsft_zsrcsel)
if settings.set_rendermode:
flagList, blendList = getRenderModeFlagList(settings, fMaterial)
cmd.flagList.extend(flagList)
if blendList is not None:
cmd.flagList.extend(blendList)
else:
cmd.flagList.extend(defaultRenderMode)
fMaterial.material.commands.append(cmd)
def saveOtherModeLDefinitionIndividual(fMaterial, settings, defaults, defaultRenderMode):
@@ -2137,3 +2145,156 @@ def saveOtherDefinition(fMaterial, material, defaults):
int(material.blend_color[1] * 255),
int(material.blend_color[2] * 255),
int(material.blend_color[3] * 255)))
enumMatWriteMethod = [
("Differing", "Write Differing And Revert", "Write Differing And Revert"),
("All", "Write All", "Write All")
]
matWriteMethodEnumDict = {
"Differing" : GfxMatWriteMethod.WriteDifferingAndRevert,
"All" : GfxMatWriteMethod.WriteAll
}
def getWriteMethodFromEnum(enumVal):
if enumVal not in matWriteMethodEnumDict:
raise PluginError("Enum value " + str(enumVal) + " not found in material write method dict.")
else:
return matWriteMethodEnumDict[enumVal]
def exportF3DtoC(dirPath, obj, DLFormat, transformMatrix,
f3dType, isHWv1, texDir, savePNG, texSeparate, name, matWriteMethod):
fModel = FModel(f3dType, isHWv1, name, DLFormat, matWriteMethod)
fMesh = exportF3DCommon(obj, fModel, transformMatrix,
True, name, DLFormat, not savePNG)
modelDirPath = os.path.join(dirPath, toAlnum(name))
if not os.path.exists(modelDirPath):
os.makedirs(modelDirPath)
gfxFormatter = GfxFormatter(ScrollMethod.Vertex, 64)
exportData = fModel.to_c(TextureExportSettings(texSeparate, savePNG, texDir, modelDirPath), gfxFormatter)
staticData = exportData.staticData
dynamicData = exportData.dynamicData
texC = exportData.textureData
if DLFormat == DLFormat.Static:
staticData.append(dynamicData)
else:
geoString = writeMaterialFiles(dirPath, modelDirPath,
'#include "actors/' + toAlnum(name) + '/header.h"',
'#include "actors/' + toAlnum(name) + '/material.inc.h"',
dynamicData.header, dynamicData.source, '', True)
if texSeparate:
texCFile = open(os.path.join(modelDirPath, 'texture.inc.c'), 'w', newline='\n')
texCFile.write(texC.source)
texCFile.close()
writeCData(staticData, os.path.join(modelDirPath, 'header.h'),
os.path.join(modelDirPath, 'model.inc.c'))
class F3D_ExportDL(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.f3d_export_dl'
bl_label = "Export Display List"
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):
if context.mode != 'OBJECT':
bpy.ops.object.mode_set("OBJECT")
try:
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.")
scaleValue = bpy.context.scene.blenderToSM64Scale
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')
exportPath = bpy.path.abspath(context.scene.DLExportPath)
dlFormat = DLFormat.Static if context.scene.DLExportisStatic else DLFormat.Dynamic
f3dType = context.scene.f3d_type
isHWv1 = context.scene.isHWv1
texDir = bpy.context.scene.DLTexDir
savePNG = bpy.context.scene.DLSaveTextures or bpy.context.scene.ignoreTextureRestrictions
separateTexDef = bpy.context.scene.DLSeparateTextureDef
DLName = bpy.context.scene.DLName
matWriteMethod = getWriteMethodFromEnum(context.scene.matWriteMethod)
exportF3DtoC(exportPath, obj, dlFormat, finalTransform,
f3dType, isHWv1, texDir, savePNG, separateTexDef, DLName, matWriteMethod)
self.report({'INFO'}, 'Success!')
applyRotation([obj], math.radians(-90), 'X')
return {'FINISHED'} # must return a set
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
class F3D_ExportDLPanel(bpy.types.Panel):
bl_idname = "F3D_PT_export_dl"
bl_label = "F3D Exporter"
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(F3D_ExportDL.bl_idname)
col.prop(context.scene, 'DLExportPath')
prop_split(col, context.scene, 'DLName', 'Name')
prop_split(col, context.scene, 'matWriteMethod', "Material Write Method")
col.prop(context.scene, 'DLExportisStatic')
if not bpy.context.scene.ignoreTextureRestrictions:
col.prop(context.scene, 'DLSaveTextures')
if context.scene.DLSaveTextures:
prop_split(col, context.scene, 'DLTexDir',
'Texture Include Path')
col.prop(context.scene, 'DLSeparateTextureDef')
f3d_writer_classes = (
F3D_ExportDL,
F3D_ExportDLPanel,
)
def f3d_writer_register():
for cls in f3d_writer_classes:
register_class(cls)
bpy.types.Scene.matWriteMethod = bpy.props.EnumProperty(items = enumMatWriteMethod)
def f3d_writer_unregister():
for cls in reversed(f3d_writer_classes):
unregister_class(cls)
del bpy.types.Scene.matWriteMethod
+24 -18
View File
@@ -70,7 +70,7 @@ def getV4PresetName(name):
if name in V4PresetName:
newName = V4PresetName[name]
else:
newName = name
newName = "Custom"
return newName
def convertF3DtoNewVersion(obj, index, material, materialDict, version):
@@ -81,24 +81,27 @@ def convertF3DtoNewVersion(obj, index, material, materialDict, version):
oldPreset = material.f3d_preset
if version > 3:
f3dMat = createF3DMat(obj, preset = getV4PresetName(oldPreset), index = index)
newMat = createF3DMat(obj, preset = getV4PresetName(oldPreset), index = index)
if material.mat_ver > 3:
copyPropertyGroup(material.f3d_mat, f3dMat.f3d_mat)
copyPropertyGroup(material.f3d_mat, newMat.f3d_mat)
else:
convertToNewMat(f3dMat, material)
if f3dMat.f3d_mat.draw_layer.sm64 != obj.draw_layer_static:
f3dMat.f3d_mat.draw_layer.sm64 = obj.draw_layer_static
convertToNewMat(newMat, material)
if newMat.f3d_mat.draw_layer.sm64 != obj.draw_layer_static:
newMat.f3d_mat.draw_layer.sm64 = obj.draw_layer_static
else:
f3dMat = createF3DMat(obj, preset = oldPreset, index = index)
newMat = createF3DMat(obj, preset = oldPreset, index = index)
matSettings = F3DMaterialSettings()
matSettings.loadFromMaterial(material, True)
matSettings.applyToMaterial(f3dMat, True, update_node_values_of_material, bpy.context)
matSettings.applyToMaterial(newMat, True, update_node_values_of_material, bpy.context)
copyPropertyGroup(material.ootMaterial, newMat.ootMaterial)
copyPropertyGroup(material.ootCollisionProperty, newMat.ootCollisionProperty)
colSettings = CollisionSettings()
colSettings.load(material)
colSettings.apply(f3dMat)
colSettings.apply(newMat)
updateMatWithNewVersionName(f3dMat, material, materialDict, version)
updateMatWithNewVersionName(newMat, material, materialDict, version)
def convertAllBSDFtoF3D(objs, renameUV):
# Dict of non-f3d materials : converted f3d materials
@@ -118,17 +121,19 @@ def convertAllBSDFtoF3D(objs, renameUV):
def convertBSDFtoF3D(obj, index, material, materialDict):
if not material.use_nodes:
f3dMat = createF3DMat(obj, preset = 'Shaded Solid', index = index)
newMaterial = createF3DMat(obj, preset = 'Shaded Solid', index = index)
f3dMat = newMaterial.f3d_mat if newMaterial.mat_ver > 3 else newMaterial
f3dMat.default_light_color = material.diffuse_color
updateMatWithName(f3dMat, material, materialDict)
updateMatWithName(newMaterial, material, materialDict)
elif "Principled BSDF" in material.node_tree.nodes:
tex0Node = material.node_tree.nodes['Principled BSDF'].inputs['Base Color']
tex1Node = material.node_tree.nodes['Principled BSDF'].inputs['Subsurface Color']
if len(tex0Node.links) == 0:
f3dMat = createF3DMat(obj, preset = 'Shaded Solid', index = index)
newMaterial = createF3DMat(obj, preset = getDefaultMaterialPreset("Shaded Solid"), index = index)
f3dMat = newMaterial.f3d_mat if newMaterial.mat_ver > 3 else newMaterial
f3dMat.default_light_color = tex0Node.default_value
updateMatWithName(f3dMat, material, materialDict)
updateMatWithName(newMaterial, material, materialDict)
else:
if isinstance(tex0Node.links[0].from_node, bpy.types.ShaderNodeTexImage):
if 'convert_preset' in material:
@@ -137,13 +142,14 @@ def convertBSDFtoF3D(obj, index, material, materialDict):
raise PluginError('During BSDF to F3D conversion, for material \'' + material.name + '\',' + \
' enum \'' + presetName + '\' was not found in material preset enum list.')
else:
presetName = 'Shaded Texture'
f3dMat = createF3DMat(obj, preset = presetName, index = index)
presetName = getDefaultMaterialPreset('Shaded Texture')
newMaterial = createF3DMat(obj, preset = presetName, index = index)
f3dMat = newMaterial.f3d_mat if newMaterial.mat_ver > 3 else newMaterial
f3dMat.tex0.tex = tex0Node.links[0].from_node.image
if len(tex1Node.links) > 0 and \
isinstance(tex1Node.links[0].from_node, bpy.types.ShaderNodeTexImage):
f3dMat.tex1.tex = tex1Node.links[0].from_node.image
updateMatWithName(f3dMat, material, materialDict)
updateMatWithName(newMaterial, material, materialDict)
else:
print("Principled BSDF material does not have an Image Node attached to its Base Color.")
else:
@@ -197,7 +203,7 @@ class BSDFConvert(bpy.types.Operator):
class MatUpdateConvert(bpy.types.Operator):
# set bl_ properties
version = 3
version = 4
bl_idname = 'object.convert_f3d_update'
bl_label = "Recreate F3D Materials As v" + str(version)
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
+43
View File
@@ -0,0 +1,43 @@
# Ocarina Of Time
### Getting Started
1. In the 3D view properties sidebar, go to the Fast64 tab -> Fast64 Global Properties and set "Game" to OOT.
2. Switch to the OOT tab. Set your decomp path to the path of your repository on disk.
3. Click "Add Scene" to create a basic scene.
4. Choose the scene to replace. Some scenes have some hardcoded things that will cause them to break, so choose something like "spot01".
5. Click "Export Scene" to export it.
6. Compile and run the game.
### Scene Overview
In Blender, the "empty" object type is used to define different types of OOT data.
For scenes, there must be a specific parenting hierarchy:
![](https://bitbucket.org/kurethedead/fast64/raw/master/images/oot_scene_hierarchy.png)
This means that any geometry/actors/etc. not parented to the scene hierarchy will NOT be included in the export.
Note that geometry/actors/etc. do not have to be directly parented to the room empty, as long as its in a descendant's hierachy somewhere.
Properties for empties/meshes can be found in the Blender object properties window.
![](https://bitbucket.org/kurethedead/fast64/raw/master/images/oot_object_properties.png)
To export a scene, the "Scene Object" must be set in the "OOT Scene Exporter" section in the tool properties sidebar. When you click "Add Scene" this is set automatically.
### Actors Variables
Actor variables can be found at https://wiki.cloudmodding.com/oot/Actor_List_(Variables).
### Exits
The debug menu scene select can be found at sScenes in src/overlays/gamestates/ovl_select/z_select.c.
The last field of a SceneSelectEntry is the index into gEntranceTable (found in src/code/z_scene_table.c).
All exits are basically an index into this table. Due to the way it works it makes it difficult to add/remove entries without breaking everything. For now the user will have to manually manage this table. For more info check out https://wiki.cloudmodding.com/oot/Entrance_Table.
### Scene Draw Configuration And Dynamic Material Properties
The scene object has a property called "Draw Config." This is an index into sSceneDrawHandlers in src/code/z_scene_table.c.
Each function in this table will load certain Gfx commands such as scrolling textures or setting color registers into the beginning of a RAM segment using gsSPSegment(). In Blender, in the material properties window you can choose which commands are called at the beginning of the material drawing.
![](https://bitbucket.org/kurethedead/fast64/raw/master/images/oot_dynamic_material.png)
Note that there are different segments loaded between the OPA (opaque) and XLU (transparent) draw layers.
Additionally, for functions like Gfx_TexScroll(), the x,y inputs are pre-shifted by <<2. For example, a % 128 means a repeat of the texture every 32 pixels.
### Collision
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.
+6 -1
View File
@@ -11,6 +11,7 @@ from .c_writer import *
from .oot_spline import *
from .oot_anim import *
from .oot_skeleton import *
from .oot_operators import *
#from .oot_f3d_parser import *
import bpy
@@ -47,6 +48,8 @@ oot_classes = (
)
def oot_panel_register():
oot_operator_panel_register()
oot_dl_writer_panel_register()
oot_col_panel_register()
#oot_bone_panel_register()
oot_obj_panel_register()
@@ -54,12 +57,12 @@ def oot_panel_register():
#oot_geo_writer_panel_register()
oot_level_panel_register()
oot_spline_panel_register()
oot_dl_writer_panel_register()
#oot_dl_parser_panel_register()
oot_anim_panel_register()
oot_skeleton_panel_register()
def oot_panel_unregister():
oot_operator_panel_unregister()
oot_col_panel_unregister()
#oot_bone_panel_unregister()
oot_obj_panel_unregister()
@@ -76,6 +79,7 @@ def oot_register(registerPanels):
for cls in oot_classes:
register_class(cls)
oot_operator_register()
oot_utility_register()
oot_col_register() # register first, so panel goes above mat panel
#oot_bone_register()
@@ -104,6 +108,7 @@ def oot_unregister(unregisterPanels):
for cls in reversed(oot_classes):
unregister_class(cls)
oot_operator_unregister()
oot_utility_unregister()
oot_col_unregister() # register first, so panel goes above mat panel
#oot_bone_unregister()
+8 -1
View File
@@ -1,2 +1,9 @@
from .oot_collision_c import *
from .oot_level_c import *
from .oot_level_c import *
from .oot_dma_manager_c import *
from .oot_scene_id_c import *
from .oot_scene_table_c import *
from .oot_segment_symbols_c import *
from .oot_dmadata_s import *
from .oot_spec import *
from .oot_scene_folder import *
@@ -31,23 +31,25 @@ def ootCameraDataToC(camData):
posC = CData()
camC = CData()
if len(camData.camPosDict) > 0:
posDataName = "CamPosData " + camData.camPositionsName() + '[' + str(len(camData.camPosDict)) + ']'
camDataName = "CamData " + camData.camDataName() + "[" + str(len(camData.camPosDict)) + "]"
posC.source = posDataName + ' = {\n'
posC.header = "extern " + posDataName + ';\n'
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)):
posC.source += ootCameraPosToC(camData.camPosDict[i])
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
@@ -67,7 +69,7 @@ def ootCameraEntryToC(camPos, camData, camPosIndex):
return "{ " +\
str(camPos.camSType) + ', ' +\
('3' if camPos.hasPositionData else '0') + ', ' +\
"&" + ((camData.camPositionsName() + '[' + str(camPosIndex) + ']')
(("&" + camData.camPositionsName() + '[' + str(camPosIndex) + ']')
if camPos.hasPositionData else "0") + ' },\n'
def ootCollisionToC(collision):
@@ -0,0 +1,61 @@
import os, re
from ...utility import *
def getDmaMgrFileNameEntriesBySceneName(dmaMgrFileNames, sceneName):
return [entry for entry in dmaMgrFileNames if \
re.match(sceneName + "\_scene", entry) or re.match(sceneName + "\_room\_(\d)+", entry)]
def readDmaMgrFileNames(exportPath):
fileData = readFile(os.path.join(exportPath, 'src/boot/z_std_dma.c'))
matchResult = re.search('sDmaMgrFileNames\s*\[[^\]]*\]\s*=\s*{([^\}]*)\}', fileData, re.DOTALL)
if matchResult is None:
raise PluginError("z_std_dma.c does not have sDmaMgrFileNames in it.")
dmaMgrFileNames = parseDmaMgrFileNames(matchResult.group(1))
return dmaMgrFileNames, fileData, matchResult.start(0), matchResult.end(0)
def parseDmaMgrFileNames(data):
dmaMgrFileNames = []
for match in re.finditer('"([^"]*)",', data):
name = match.group(1).strip()
dmaMgrFileNames.append(name)
return dmaMgrFileNames
def dmaMgrFileNamesToString(dmaMgrFileNames):
data = ''
for entry in dmaMgrFileNames:
data += '\t"' + entry + '",\n'
return data
def writeDmaMgrFileNames(dmaMgrFileNames, fileData, start, end, exportPath):
dmaMgrFileNamesData = "sDmaMgrFileNames[" + format(len(dmaMgrFileNames), "#05x") + "] = {\n" +\
dmaMgrFileNamesToString(dmaMgrFileNames) + '}'
newFileData = fileData[:start] + dmaMgrFileNamesData + fileData[end:]
if newFileData != fileData:
writeFile(os.path.join(exportPath, 'src/boot/z_std_dma.c'), newFileData)
def modifyDmaMgrFileNames(scene, exportInfo):
exportPath = exportInfo.exportPath
dmaMgrFileNames, fileData, start, end = readDmaMgrFileNames(exportPath)
entries = getDmaMgrFileNameEntriesBySceneName(dmaMgrFileNames, scene.name if scene is not None else exportInfo.name)
if len(entries) > 0:
firstIndex = dmaMgrFileNames.index(entries[0])
for entry in entries:
dmaMgrFileNames.remove(entry)
else:
firstIndex = len(dmaMgrFileNames)
if scene is not None:
dmaMgrFileNames.insert(firstIndex, scene.name + "_scene")
firstIndex += 1
for i in range(len(scene.rooms)):
dmaMgrFileNames.insert(firstIndex, scene.name + "_room_" + str(i))
firstIndex += 1
writeDmaMgrFileNames(dmaMgrFileNames, fileData, start, end, exportPath)
@@ -0,0 +1,59 @@
import os, re
from ...utility import *
def getDmaTableEntryEntriesBySceneName(dmaTableEntries, sceneName):
return [entry for entry in dmaTableEntries if \
re.match(sceneName + "\_scene", entry) or re.match(sceneName + "\_room\_(\d)+", entry)]
def readDmaTableEntries(exportPath):
fileData = readFile(os.path.join(exportPath, 'asm/dmadata.s'))
matchResult = re.search('glabel\s*gDmaDataTable\s*(.*)\.space\s*0x100', fileData, re.DOTALL)
if matchResult is None:
raise PluginError("dmadata.s does not have gDmaDataTable in it.")
dmaTableEntries = parseDmaTableEntries(matchResult.group(1))
return dmaTableEntries, fileData, matchResult.start(1), matchResult.end(1)
def parseDmaTableEntries(data):
dmaTableEntries = []
for match in re.finditer('DMA_TABLE_ENTRY\s*([^\s]*)', data):
name = match.group(1).strip()
dmaTableEntries.append(name)
return dmaTableEntries
def dmaTableEntriesToString(dmaTableEntries):
data = ''
for entry in dmaTableEntries:
data += 'DMA_TABLE_ENTRY ' + entry + '\n'
return data
def writeDmaTableEntries(dmaTableEntries, fileData, start, end, exportPath):
dmaTableEntriesData = dmaTableEntriesToString(dmaTableEntries)
newFileData = fileData[:start] + dmaTableEntriesData + fileData[end:]
if newFileData != fileData:
writeFile(os.path.join(exportPath, 'asm/dmadata.s'), newFileData)
def modifyDmaTableEntries(scene, exportInfo):
exportPath = exportInfo.exportPath
dmaTableEntries, fileData, start, end = readDmaTableEntries(exportPath)
entries = getDmaTableEntryEntriesBySceneName(dmaTableEntries, scene.name if scene is not None else exportInfo.name)
if len(entries) > 0:
firstIndex = dmaTableEntries.index(entries[0])
for entry in entries:
dmaTableEntries.remove(entry)
else:
firstIndex = len(dmaTableEntries)
if scene is not None:
dmaTableEntries.insert(firstIndex, scene.name + "_scene")
firstIndex += 1
for i in range(len(scene.rooms)):
dmaTableEntries.insert(firstIndex, scene.name + "_room_" + str(i))
firstIndex += 1
writeDmaTableEntries(dmaTableEntries, fileData, start, end, exportPath)
+11 -19
View File
@@ -205,9 +205,9 @@ def ootActorToC(actor):
str(int(round(actor.position[0]))) + ', ' + \
str(int(round(actor.position[1]))) + ', ' + \
str(int(round(actor.position[2]))) + ', ' + \
str(int(round(math.degrees(actor.rotation[0])))) + ', ' + \
str(int(round(math.degrees(actor.rotation[1])))) + ', ' + \
str(int(round(math.degrees(actor.rotation[2])))) + ', ' + \
str(int(round(actor.rotation[0]))) + ', ' + \
str(int(round(actor.rotation[1]))) + ', ' + \
str(int(round(actor.rotation[2]))) + ', ' + \
str(actor.actorParam) + ' },\n'
def ootActorListToC(room, headerIndex):
@@ -258,7 +258,8 @@ def ootMeshEntryToC(meshEntry, meshType):
data = "{ "
if meshType == "1":
raise PluginError("MeshHeader1 not supported.")
if meshType == "2":
elif meshType == "2":
raise PluginError("MeshHeader2 not supported.") # TODO: Below is broken
if meshEntry.cullVolume is None:
data += "0x7FFF, 0x7FFF, 0x8000, 0x8000, "
else:
@@ -445,7 +446,7 @@ def ootTransitionActorToC(transActor):
str(int(round(transActor.position[0]))) + ', ' + \
str(int(round(transActor.position[1]))) + ', ' + \
str(int(round(transActor.position[2]))) + ', ' + \
str(int(round(math.degrees(transActor.rotationY)))) + ', ' + \
str(int(round(transActor.rotationY))) + ', ' + \
str(transActor.actorParam) + ' },\n'
def ootTransitionActorListToC(scene, headerIndex):
@@ -494,32 +495,23 @@ def ootVectorToC(vector):
return str(vector[0]) + ', ' + str(vector[1]) + ', ' + str(vector[2])
def ootLightToC(light):
return "{ " + \
return "\t{ " + \
ootVectorToC(light.ambient) + ', ' +\
ootVectorToC(light.diffuse0) + ', ' +\
ootVectorToC(light.diffuseDir0) + ', ' +\
ootVectorToC(light.diffuse1) + ', ' +\
ootVectorToC(light.diffuse0) + ', ' +\
ootVectorToC(light.diffuseDir1) + ', ' +\
ootVectorToC(light.diffuse1) + ', ' +\
ootVectorToC(light.fogColor) + ', ' +\
light.getBlendFogShort() + ', ' +\
str(light.drawDistance) + ' },\n'
def ootLightGroupToC(lightGroup):
data = ""
data += '\t' + ootLightToC(lightGroup.dawn)
if lightGroup.day is not None:
data += '\t' + ootLightToC(lightGroup.day)
data += '\t' + ootLightToC(lightGroup.dusk)
data += '\t' + ootLightToC(lightGroup.night)
return data
def ootLightSettingsToC(scene, useIndoorLighting, headerIndex):
data = CData()
lightArraySize = len(scene.lights) * (1 if useIndoorLighting else 4)
data.header = "extern LightSettings " + scene.lightListName(headerIndex) + "[" + str(lightArraySize) + "];\n"
data.source = "LightSettings " + scene.lightListName(headerIndex) + "[" + str(lightArraySize) + "] = {\n"
for lightGroup in scene.lights:
data.source += ootLightGroupToC(lightGroup)
for light in scene.lights:
data.source += ootLightToC(light)
data.source += '};\n\n'
return data
@@ -0,0 +1,27 @@
import os, re, shutil
from ...utility import *
from ..oot_utility import *
def modifySceneFiles(scene, exportInfo):
exportPath = exportInfo.exportPath
if exportInfo.customSubPath is not None:
sceneDir = exportInfo.customSubPath + exportInfo.name
else:
sceneDir = getSceneDirFromLevelName(scene.name)
scenePath = os.path.join(exportPath, sceneDir)
for filename in os.listdir(scenePath):
filepath = os.path.join(scenePath, filename)
if os.path.isfile(filepath):
match = re.match(scene.name + "\_room\_(\d+)\.[ch]", filename)
if match is not None and int(match.group(1)) >= len(scene.rooms):
os.remove(filepath)
def deleteSceneFiles(exportInfo):
exportPath = exportInfo.exportPath
if exportInfo.customSubPath is not None:
sceneDir = exportInfo.customSubPath + exportInfo.name
else:
sceneDir = getSceneDirFromLevelName(exportInfo.name)
scenePath = os.path.join(exportPath, sceneDir)
if os.path.exists(scenePath):
shutil.rmtree(scenePath)
@@ -0,0 +1,56 @@
import os, re
from ...utility import *
def getSceneIDEntriesBySceneName(sceneIDs, sceneName):
return [entry for entry in sceneIDs if \
entry.name[6:] == sceneName.upper()]
def readSceneIDs(exportPath):
fileData = readFile(os.path.join(exportPath, 'include/z64scene.h'))
matchResult = re.search('typedef\s*enum\s*\{([^\}]*)\}\s*SceneID\s*;', fileData, re.DOTALL)
if matchResult is None:
raise PluginError("z64scene.h does not have the SceneID enum in it.")
sceneIDs = parseSceneIDs(matchResult.group(1))
return sceneIDs, fileData, matchResult.start(1), matchResult.end(1)
def parseSceneIDs(data):
sceneIDs = []
for match in re.finditer('([^\s\/]*)\s*,', data):
name = match.group(1).strip()
if name != "SCENE_ID_MAX":
sceneIDs.append(name)
return sceneIDs
def sceneIDsToString(sceneIDs):
data = '\n'
index = 0
for entry in sceneIDs:
data += "\t/* " + format(index, '#04x') + " */ " + entry + ',\n'
index += 1
data += "\t/* " + format(index, '#04x') + " */ SCENE_ID_MAX\n"
return data
def writeSceneIDs(sceneIDs, fileData, start, end, exportPath):
sceneIDsData = sceneIDsToString(sceneIDs)
newFileData = fileData[:start] + sceneIDsData + fileData[end:]
if newFileData != fileData:
writeFile(os.path.join(exportPath, 'include/z64scene.h'), newFileData)
def modifySceneIDs(scene, exportInfo):
exportPath = exportInfo.exportPath
sceneID = 'SCENE_' + (scene.name.upper() if scene is not None else exportInfo.name.upper())
sceneIDs, fileData, start, end = readSceneIDs(exportPath)
if scene is not None:
if sceneID not in sceneIDs:
sceneIDs.append(sceneID)
else:
if sceneID in sceneIDs:
sceneIDs.remove(sceneID)
writeSceneIDs(sceneIDs, fileData, start, end, exportPath)
@@ -0,0 +1,75 @@
import os, re
from ...utility import *
class SceneTableEntry:
def __init__(self, sceneType, name, title, unk10, config, unk12):
self.sceneType = sceneType
self.name = name
self.title = title
self.unk10 = unk10
self.config = config
self.unk12 = unk12
def toC(self):
return "\t" + self.sceneType + "(" + self.name + ", " + \
((self.title + ", ") if self.title is not None else "") +\
self.unk10 + ", " + self.config + ", " + self.unk12 + "),\n"
def getSceneTableEntryBySceneName(sceneTable, sceneName):
for entry in sceneTable:
if entry.name[:-6] == sceneName:
return entry
return None
def readSceneTable(exportPath):
fileData = readFile(os.path.join(exportPath, 'src/code/z_scene_table.c'))
matchResult = re.search('Scene\s*gSceneTable\[\]\s*=\s*\{([^\}]*)\}', fileData, re.DOTALL)
if matchResult is None:
raise PluginError("z_scene_table.c does not have gSceneTable in it.")
sceneTable = parseSceneTableData(matchResult.group(1))
return sceneTable, fileData, matchResult.start(0), matchResult.end(0)
def parseSceneTableData(data):
table = []
for match in re.finditer('([^,]*)\s*\(([^,]*),\s*([^,]*),\s*([^,]*),\s*([^,]*)(,\s*([^,]*))?\)\s*,', data):
name = match.group(2)
sceneType = match.group(1).strip()
if sceneType == "UNTITLED_SCENE":
table.append(SceneTableEntry(sceneType, name, None, match.group(3), match.group(4), match.group(5)))
elif sceneType == "TITLED_SCENE":
table.append(SceneTableEntry(sceneType, name, match.group(3), match.group(4), match.group(5), match.group(7)))
else:
raise PluginError("Unhandled scene entry type: " + str(sceneType))
return table
def sceneTableToString(sceneTable):
data = 'Scene gSceneTable[] = {\n'
for entry in sceneTable:
data += entry.toC()
data += '}'
return data
def writeSceneTable(sceneTable, fileData, start, end, exportPath):
sceneTableData = sceneTableToString(sceneTable)
newFileData = fileData[:start] + sceneTableData + fileData[end:]
if newFileData != fileData:
writeFile(os.path.join(exportPath, 'src/code/z_scene_table.c'), newFileData)
def modifySceneTable(scene, exportInfo):
exportPath = exportInfo.exportPath
sceneTable, sceneFileData, start, end = readSceneTable(exportPath)
entry = getSceneTableEntryBySceneName(sceneTable, scene.name if scene is not None else exportInfo.name)
if scene is not None:
if entry is None:
sceneTable.append(SceneTableEntry("UNTITLED_SCENE", scene.name + "_scene", None, '0', str(scene.sceneTableEntry.drawConfig), '0'))
else:
entry.sceneType = entry.sceneType
entry.config = str(scene.sceneTableEntry.drawConfig)
else:
if entry is not None:
sceneTable.remove(entry)
writeSceneTable(sceneTable, sceneFileData, start, end, exportPath)
@@ -0,0 +1,69 @@
import os, re
from ...utility import *
class SegmentSymbolEntry():
def __init__(self, segmentType, name):
self.segmentType = segmentType
self.name = name
def toC(self):
return self.segmentType + "(" + self.name + ")\n"
def getSegmentSymbolEntriesBySceneName(segmentSymbols, sceneName):
return [entry for entry in segmentSymbols if \
re.match(sceneName + "\_scene", entry.name) or re.match(sceneName + "\_room\_(\d)+", entry.name)]
def readSegmentSymbols(exportPath):
fileData = readFile(os.path.join(exportPath, 'include/segment_symbols.h'))
matchResult = re.search('(DECLARE_SEGMENT\s*\(\s*boot\s*\).*)\#endif', fileData, re.DOTALL)
if matchResult is None:
raise PluginError("segment_symbols.h does not have DECLARE_SEGMENT(boot) in it.")
segmentSymbols = parseSegmentSymbols(matchResult.group(1))
return segmentSymbols, fileData, matchResult.start(1), matchResult.end(1)
def parseSegmentSymbols(data):
segmentSymbols = []
for match in re.finditer('([^\(]*)\(([^\s]*)\)', data):
segmentType = match.group(1).strip()
name = match.group(2).strip()
segmentSymbols.append(SegmentSymbolEntry(segmentType, name))
return segmentSymbols
def segmentSymbolsToString(segmentSymbols):
data = ''
for entry in segmentSymbols:
data += entry.toC()
return data
def writeSegmentSymbols(segmentSymbols, fileData, start, end, exportPath):
segmentSymbolsData = segmentSymbolsToString(segmentSymbols) + "\n"
newFileData = fileData[:start] + segmentSymbolsData + fileData[end:]
if newFileData != fileData:
writeFile(os.path.join(exportPath, 'include/segment_symbols.h'), newFileData)
def modifySegmentSymbols(scene, exportInfo):
exportPath = exportInfo.exportPath
segmentSymbols, fileData, start, end = readSegmentSymbols(exportPath)
entries = getSegmentSymbolEntriesBySceneName(segmentSymbols, scene.name if scene is not None else exportInfo.name)
if len(entries) > 0:
firstIndex = segmentSymbols.index(entries[0])
for entry in entries:
segmentSymbols.remove(entry)
else:
firstIndex = len(segmentSymbols)
if scene is not None:
segmentSymbols.insert(firstIndex, SegmentSymbolEntry("DECLARE_ROM_SEGMENT", scene.name + "_scene"))
firstIndex += 1
for i in range(len(scene.rooms)):
segmentSymbols.insert(firstIndex, SegmentSymbolEntry("DECLARE_ROM_SEGMENT", scene.name + "_room_" + str(i)))
firstIndex += 1
writeSegmentSymbols(segmentSymbols, fileData, start, end, exportPath)
+74
View File
@@ -0,0 +1,74 @@
import os, re
from ...utility import *
from ..oot_utility import *
def getSegmentDefinitionEntryBySceneName(segmentDefinition, sceneName):
entries = []
for entry in segmentDefinition:
matchText = "\s*name\s*\"" + sceneName + "\_"
if re.match(matchText + "scene\"", entry) or re.match(matchText + "room\_\d+\"", entry):
entries.append(entry)
return entries
def readSegmentDefinition(exportPath):
fileData = readFile(os.path.join(exportPath, 'spec'))
segmentDefinition = parseSegmentDefinitionData(fileData)
return segmentDefinition, fileData
def parseSegmentDefinitionData(data):
table = []
for match in re.finditer('beginseg(((?!endseg).)*)endseg', data, re.DOTALL):
segData = match.group(1)
table.append(segData)
return table
def segmentDefinitionToString(segmentDefinitions):
data = '/*\n * ROM spec file\n */\n\n'
for entry in segmentDefinitions:
data += "beginseg" + entry + "endseg\n\n"
return data
def writeSegmentDefinition(segmentDefinition, fileData, exportPath):
newFileData = segmentDefinitionToString(segmentDefinition)
if newFileData != fileData:
writeFile(os.path.join(exportPath, 'spec'), newFileData)
def modifySegmentDefinition(scene, exportInfo):
exportPath = exportInfo.exportPath
segmentDefinitions, fileData = readSegmentDefinition(exportPath)
sceneName = scene.name if scene is not None else exportInfo.name
entries = getSegmentDefinitionEntryBySceneName(segmentDefinitions, sceneName)
if exportInfo.customSubPath is not None:
includeDir = 'build/' + exportInfo.customSubPath + sceneName + '/' + sceneName
else:
includeDir = 'build/' + getSceneDirFromLevelName(sceneName) + '/' + sceneName
if len(entries) > 0:
firstIndex = segmentDefinitions.index(entries[0])
for entry in entries:
segmentDefinitions.remove(entry)
else:
firstIndex = len(segmentDefinitions)
if scene is not None:
segmentDefinitions.insert(firstIndex,
'\n\tname "' + scene.name + '_scene"\n' +\
"\tromalign 0x1000\n" +\
'\tinclude "' + includeDir + '_scene.o"\n' +\
"\tnumber 2\n")
firstIndex += 1
for i in range(len(scene.rooms)):
roomSuffix = "_room_" + str(i)
segmentDefinitions.insert(firstIndex,
'\n\tname "' + scene.name + roomSuffix + '"\n' +\
"\tromalign 0x1000\n" +\
'\tinclude "' + includeDir + roomSuffix + '.o"\n' +\
"\tnumber 3\n")
firstIndex += 1
writeSegmentDefinition(segmentDefinitions, fileData, exportPath)
+28 -18
View File
@@ -17,14 +17,16 @@ class OOT_SearchActorIDEnumOperator(bpy.types.Operator):
actorID : bpy.props.EnumProperty(items = ootEnumActorID, default = "ACTOR_PLAYER")
actorUser : bpy.props.StringProperty(default = "Actor")
objName : bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects[self.objName]
if self.actorUser == "Transition Actor":
context.object.ootTransitionActorProperty.actor.actorID = self.actorID
obj.ootTransitionActorProperty.actor.actorID = self.actorID
elif self.actorUser == "Actor":
context.object.ootActorProperty.actorID = self.actorID
obj.ootActorProperty.actorID = self.actorID
elif self.actorUser == "Entrance":
context.object.ootEntranceProperty.actor.actorID = self.actorID
obj.ootEntranceProperty.actor.actorID = self.actorID
else:
raise PluginError("Invalid actor user for search: " + str(self.actorUser))
@@ -48,7 +50,7 @@ class OOTActorHeaderProperty(bpy.types.PropertyGroup):
adultNightHeader : bpy.props.BoolProperty(name = "Adult Night Header", default = True)
cutsceneHeaders : bpy.props.CollectionProperty(type = OOTActorHeaderItemProperty)
def drawActorHeaderProperty(layout, headerProp, propUser, altProp):
def drawActorHeaderProperty(layout, headerProp, propUser, altProp, objName):
headerSetup = layout.column()
#headerSetup.box().label(text = "Alternate Headers")
prop_split(headerSetup, headerProp, "sceneSetupPreset", "Scene Setup Preset")
@@ -67,17 +69,17 @@ def drawActorHeaderProperty(layout, headerProp, propUser, altProp):
adultNightRow.enabled = not altProp.adultNightHeader.usePreviousHeader if altProp is not None else True
for i in range(len(headerProp.cutsceneHeaders)):
drawActorHeaderItemProperty(headerSetup, propUser, headerProp.cutsceneHeaders[i], i, altProp)
drawAddButton(headerSetup, len(headerProp.cutsceneHeaders), propUser, None)
drawActorHeaderItemProperty(headerSetup, propUser, headerProp.cutsceneHeaders[i], i, altProp, objName)
drawAddButton(headerSetup, len(headerProp.cutsceneHeaders), propUser, None, objName)
def drawActorHeaderItemProperty(layout, propUser, headerItemProp, index, altProp):
def drawActorHeaderItemProperty(layout, propUser, headerItemProp, index, altProp, objName):
box = layout.box()
box.prop(headerItemProp, 'expandTab', text = 'Header ' + \
str(headerItemProp.headerIndex), icon = 'TRIA_DOWN' if headerItemProp.expandTab else \
'TRIA_RIGHT')
if headerItemProp.expandTab:
drawCollectionOps(box, index, propUser, None)
drawCollectionOps(box, index, propUser, None, objName)
prop_split(box, headerItemProp, 'headerIndex', 'Header Index')
if altProp is not None and headerItemProp.headerIndex >= len(altProp.cutsceneHeaders) + 4:
box.label(text = "Header does not exist.", icon = "ERROR")
@@ -85,15 +87,16 @@ def drawActorHeaderItemProperty(layout, propUser, headerItemProp, index, altProp
class OOTActorProperty(bpy.types.PropertyGroup):
actorID : bpy.props.EnumProperty(name = 'Actor', items = ootEnumActorID, default = 'ACTOR_PLAYER')
actorIDCustom : bpy.props.StringProperty(name = 'Actor ID', default = 'ACTOR_PLAYER')
actorParam : bpy.props.StringProperty(name = 'Actor Parameter', default = '0x0FFF') # default for player param
actorParam : bpy.props.StringProperty(name = 'Actor Parameter', default = '0x0000')
headerSettings : bpy.props.PointerProperty(type = OOTActorHeaderProperty)
def drawActorProperty(layout, actorProp, altRoomProp):
def drawActorProperty(layout, actorProp, altRoomProp, objName):
#prop_split(layout, actorProp, 'actorID', 'Actor')
actorIDBox = layout.column()
#actorIDBox.box().label(text = "Settings")
searchOp = actorIDBox.operator(OOT_SearchActorIDEnumOperator.bl_idname, icon = 'VIEWZOOM')
searchOp.actorUser = "Actor"
searchOp.objName = objName
split = actorIDBox.split(factor = 0.5)
split.label(text = "Actor ID")
@@ -106,7 +109,7 @@ def drawActorProperty(layout, actorProp, altRoomProp):
#layout.box().label(text = 'Actor IDs defined in include/z64actors.h.')
prop_split(actorIDBox, actorProp, "actorParam", 'Actor Parameter')
drawActorHeaderProperty(actorIDBox, actorProp.headerSettings, "Actor", altRoomProp)
drawActorHeaderProperty(actorIDBox, actorProp.headerSettings, "Actor", altRoomProp, objName)
class OOTTransitionActorProperty(bpy.types.PropertyGroup):
roomIndex : bpy.props.IntProperty(min = 0)
@@ -117,13 +120,14 @@ class OOTTransitionActorProperty(bpy.types.PropertyGroup):
actor : bpy.props.PointerProperty(type = OOTActorProperty)
def drawTransitionActorProperty(layout, transActorProp, altSceneProp):
def drawTransitionActorProperty(layout, transActorProp, altSceneProp, roomObj, objName):
actorIDBox = layout.column()
#actorIDBox.box().label(text = "Properties")
#prop_split(actorIDBox, transActorProp, 'actorID', 'Actor')
#actorIDBox.box().label(text = "Actor ID: " + getEnumName(ootEnumActorID, transActorProp.actor.actorID))
searchOp = actorIDBox.operator(OOT_SearchActorIDEnumOperator.bl_idname, icon = 'VIEWZOOM')
searchOp.actorUser = "Transition Actor"
searchOp.objName = objName
split = actorIDBox.split(factor = 0.5)
split.label(text = "Actor ID")
@@ -135,11 +139,16 @@ def drawTransitionActorProperty(layout, transActorProp, altSceneProp):
#layout.box().label(text = 'Actor IDs defined in include/z64actors.h.')
prop_split(actorIDBox, transActorProp.actor, "actorParam", 'Actor Parameter')
if roomObj is None:
actorIDBox.label(text = "This must be part of a Room empty's hierarchy.", icon = "ERROR")
else:
label_split(actorIDBox, "Room To Transition From", str(roomObj.ootRoomHeader.roomIndex))
prop_split(actorIDBox, transActorProp, "roomIndex", "Room To Transition To")
actorIDBox.label(text = "Y+ side of door faces toward the \"from\" room.", icon = "ERROR")
drawEnumWithCustom(actorIDBox, transActorProp, "cameraTransitionFront", "Camera Transition Front", "")
drawEnumWithCustom(actorIDBox, transActorProp, "cameraTransitionBack", "Camera Transition Back", "")
drawActorHeaderProperty(actorIDBox, transActorProp.actor.headerSettings, "Transition Actor", altSceneProp)
drawActorHeaderProperty(actorIDBox, transActorProp.actor.headerSettings, "Transition Actor", altSceneProp, objName)
class OOTEntranceProperty(bpy.types.PropertyGroup):
# This is also used in entrance list, and roomIndex is obtained from the room this empty is parented to.
@@ -147,15 +156,16 @@ class OOTEntranceProperty(bpy.types.PropertyGroup):
customActor : bpy.props.BoolProperty(name = "Use Custom Actor")
actor : bpy.props.PointerProperty(type = OOTActorProperty)
def drawEntranceProperty(layout, obj, altSceneProp):
def drawEntranceProperty(layout, obj, altSceneProp, objName):
box = layout.column()
#box.box().label(text = "Properties")
if obj.parent is not None and obj.parent.data is None and obj.parent.ootEmptyType == "Room":
roomObj = getRoomObj(obj)
if roomObj is not None:
split = box.split(factor = 0.5)
split.label(text = "Room Index")
split.label(text = str(obj.parent.ootRoomHeader.roomIndex))
split.label(text = str(roomObj.ootRoomHeader.roomIndex))
else:
box.label(text = "Entrance must be parented to a Room.")
box.label(text = "This must be part of a Room empty's hierarchy.", icon = "ERROR")
entranceProp = obj.ootEntranceProperty
prop_split(box, entranceProp, "spawnIndex", "Spawn Index")
@@ -164,4 +174,4 @@ def drawEntranceProperty(layout, obj, altSceneProp):
if entranceProp.customActor:
prop_split(box, entranceProp.actor, "actorIDCustom", "Actor ID Custom")
drawActorHeaderProperty(box, entranceProp.actor.headerSettings, "Entrance", altSceneProp)
drawActorHeaderProperty(box, entranceProp.actor.headerSettings, "Entrance", altSceneProp, objName)
+3 -2
View File
@@ -23,6 +23,7 @@ class OOTAnimation:
def toC(self):
data = CData()
data.source += '#include "ultra64.h"\n#include "global.h"\n\n'
# values
data.source += "s16 " + self.valuesName() + "[" + str(len(self.values)) + "] = {\n"
@@ -168,7 +169,7 @@ def exportAnimationC(armatureObj, exportPath, isCustomExport, folderName):
ootAnim = ootExportAnimationCommon(armatureObj, convertTransformMatrix, folderName)
ootAnimC = ootAnim.toC()
path = ootGetPath(exportPath, isCustomExport, 'assets/objects/', folderName)
path = ootGetPath(exportPath, isCustomExport, 'assets/objects/', '')
writeCData(ootAnimC,
os.path.join(path, folderName + '.h'),
os.path.join(path, folderName + '.c'))
@@ -236,7 +237,7 @@ oot_anim_classes = (
)
oot_anim_panels = (
OOT_ExportAnimPanel,
#OOT_ExportAnimPanel,
)
def oot_anim_panel_register():
+6 -5
View File
@@ -6,6 +6,7 @@ from ..utility import *
from bpy.utils import register_class, unregister_class
from io import BytesIO
import bpy, bmesh, os, math, re, shutil, mathutils
from .oot_scene_room import *
ootEnumFloorSetting = [
("Custom", "Custom", "Custom"),
@@ -375,10 +376,10 @@ def drawWaterBoxProperty(layout, waterBoxProp):
box.label(text = "Defined by top face of box empty.")
box.label(text = "No rotation allowed.")
def drawCameraPosProperty(layout, cameraRefProp, index, headerIndex):
def drawCameraPosProperty(layout, cameraRefProp, index, headerIndex, objName):
camBox = layout.box()
prop_split(camBox, cameraRefProp, "camera", "Camera " + str(index))
drawCollectionOps(camBox, index, "Camera Position", headerIndex)
drawCollectionOps(camBox, index, "Camera Position", headerIndex, objName)
class OOT_CameraPosPanel(bpy.types.Panel):
bl_label = "Camera Position Inspector"
@@ -403,6 +404,8 @@ class OOT_CameraPosPanel(bpy.types.Panel):
prop_split(box, obj.data, "angle", "Field Of View")
prop_split(box, obj.ootCameraPositionProperty, "jfifID", "JFIF ID")
box.prop(obj.ootCameraPositionProperty, 'hasPositionData')
#drawParentSceneRoom(box, context.object)
class OOT_CollisionPanel(bpy.types.Panel):
@@ -686,9 +689,7 @@ class OOT_ExportCollisionPanel(bpy.types.Panel):
prop_split(col, context.scene, 'colExportLevel', 'Level')
if context.scene.colExportLevel == 'Custom':
prop_split(col, context.scene, 'colLevelName', 'Level Name')
for i in range(panelSeparatorSize):
col.separator()
oot_col_classes = (
OOT_ExportCollision,
+110 -110
View File
@@ -1155,116 +1155,116 @@ ootEnumTransitionAnims = [
ootEnumSceneID = [
("Custom", "Custom", "Custom"),
("SCENE_YDAN", "Inside the Deku Tree", "Ydan"),
("SCENE_DDAN", "Dodongo's Cavern", "Ddan"),
("SCENE_BDAN", "Inside Jabu Jabu's Belly", "Bdan"),
("SCENE_BMORI1", "Forest Temple", "Bmori1"),
("SCENE_HIDAN", "Fire Temple", "Hidan"),
("SCENE_MIZUSIN", "Water Temple", "Mizusin"),
("SCENE_JYASINZOU", "Spirit Temple", "Jyasinzou"),
("SCENE_HAKADAN", "Shadow Temple", "Hakadan"),
("SCENE_HAKADANCH", "Bottom of the Well", "Hakadanch"),
("SCENE_ICE_DOUKUTO", "Ice Cavern", "Ice Doukuto"),
("SCENE_GANON", "Ganon's Tower", "Ganon"),
("SCENE_MEN", "Gerudo Training Ground", "Men"),
("SCENE_GERUDOWAY", "Thieves' Hideout", "Gerudoway"),
("SCENE_GANONTIKA", "Inside Ganon's Castle", "Ganontika"),
("SCENE_GANON_SONOGO", "Ganon's Tower (Collapsing)", "Ganon Sonogo"),
("SCENE_GANONTIKA_SONOGO", "Inside Ganon's Castle (Collapsing)", "Ganontika Sonogo"),
("SCENE_TAKARAYA", "Treasure Chest Shop", "Takaraya"),
("SCENE_YDAN_BOSS", "Gohma's Lair", "Ydan Boss"),
("SCENE_DDAN_BOSS", "King Dodongo's Lair", "Ddan Boss"),
("SCENE_BDAN_BOSS", "Barinade's Lair", "Bdan Boss"),
("SCENE_MORIBOSSROOM", "Phantom Ganon's Lair", "Moribossroom"),
("SCENE_FIRE_BS", "Volvagia's Lair", "Fire Bs"),
("SCENE_MIZUSIN_BS", "Morpha's Lair", "Mizusin Bs"),
("SCENE_JYASINBOSS", "Twinrova's Lair & Iron Knuckle Mini-Boss Room", "Jyasinboss"),
("SCENE_HAKADAN_BS", "Bongo Bongo's Lair", "Hakadan Bs"),
("SCENE_GANON_BOSS", "Ganondorf's Lair", "Ganon Boss"),
("SCENE_GANON_FINAL", "Ganondorf's Death Scene (Tower Escape Exterior)", "Ganon Final"),
("SCENE_ENTRA", "Market Entrance (Child - Day)", "Entra"),
("SCENE_ENTRA_N", "Market Entrance (Child - Night)", "Entra N"),
("SCENE_ENRUI", "Market Entrance (Ruins)", "Enrui"),
("SCENE_MARKET_ALLEY", "Back Alley (Day)", "Market Alley"),
("SCENE_MARKET_ALLEY_N", "Back Alley (Night)", "Market Alley N"),
("SCENE_MARKET_DAY", "Market (Child - Day)", "Market Day"),
("SCENE_MARKET_NIGHT", "Market (Child - Night)", "Market Night"),
("SCENE_MARKET_RUINS", "Market (Ruins)", "Market Ruins"),
("SCENE_SHRINE", "Temple of Time Exterior (Day)", "Shrine"),
("SCENE_SHRINE_N", "Temple of Time Exterior (Night)", "Shrine N"),
("SCENE_SHRINE_R", "Temple of Time Exterior (Ruins)", "Shrine R"),
("SCENE_KOKIRI_HOME", "Know-It-All Brothers' House", "Kokiri Home"),
("SCENE_KOKIRI_HOME3", "Twins' House", "Kokiri Home3"),
("SCENE_KOKIRI_HOME4", "Mido's House", "Kokiri Home4"),
("SCENE_KOKIRI_HOME5", "Saria's House", "Kokiri Home5"),
("SCENE_KAKARIKO", "Carpenter Boss's House", "Kakariko"),
("SCENE_KAKARIKO3", "Back Alley House (Man in Green)", "Kakariko3"),
("SCENE_SHOP1", "Bazaar", "Shop1"),
("SCENE_KOKIRI_SHOP", "Kokiri Shop", "Kokiri Shop"),
("SCENE_GOLON", "Goron Shop", "Golon"),
("SCENE_ZOORA", "Zora Shop", "Zoora"),
("SCENE_DRAG", "Kakariko Potion Shop", "Drag"),
("SCENE_ALLEY_SHOP", "Market Potion Shop", "Alley Shop"),
("SCENE_NIGHT_SHOP", "Bombchu Shop", "Night Shop"),
("SCENE_FACE_SHOP", "Happy Mask Shop", "Face Shop"),
("SCENE_LINK_HOME", "Link's House", "Link Home"),
("SCENE_IMPA", "Back Alley House (Dog Lady)", "Impa"),
("SCENE_MALON_STABLE", "Stable", "Malon Stable"),
("SCENE_LABO", "Impa's House", "Labo"),
("SCENE_HYLIA_LABO", "Lakeside Laboratory", "Hylia Labo"),
("SCENE_TENT", "Carpenters' Tent", "Tent"),
("SCENE_HUT", "Gravekeeper's Hut", "Hut"),
("SCENE_DAIYOUSEI_IZUMI", "Great Fairy's Fountain (Upgrades)", "Daiyousei Izumi"),
("SCENE_YOUSEI_IZUMI_TATE", "Fairy's Fountain (Healing Fairies)", "Yousei Izumi Tate"),
("SCENE_YOUSEI_IZUMI_YOKO", "Great Fairy's Fountain (Spells)", "Yousei Izumi Yoko"),
("SCENE_KAKUSIANA", "Grottos", "Kakusiana"),
("SCENE_HAKAANA", "Grave (Redead)", "Hakaana"),
("SCENE_HAKAANA2", "Grave (Fairy's Fountain)", "Hakaana2"),
("SCENE_HAKAANA_OUKE", "Royal Family's Tomb", "Hakaana Ouke"),
("SCENE_SYATEKIJYOU", "Shooting Gallery", "Syatekijyou"),
("SCENE_TOKINOMA", "Temple of Time", "Tokinoma"),
("SCENE_KENJYANOMA", "Chamber of the Sages", "Kenjyanoma"),
("SCENE_HAIRAL_NIWA", "Castle Hedge Maze (Day)", "Hairal Niwa"),
("SCENE_HAIRAL_NIWA_N", "Castle Hedge Maze (Night)", "Hairal Niwa N"),
("SCENE_HIRAL_DEMO", "Cutscene Map", "Hiral Demo"),
("SCENE_HAKASITARELAY", "Dampé's Grave & Windmill", "Hakasitarelay"),
("SCENE_TURIBORI", "Fishing Pond", "Turibori"),
("SCENE_NAKANIWA", "Castle Courtyard", "Nakaniwa"),
("SCENE_BOWLING", "Bombchu Bowling Alley", "Bowling"),
("SCENE_SOUKO", "Lon Lon Ranch House & Tower", "Souko"),
("SCENE_MIHARIGOYA", "Guard House", "Miharigoya"),
("SCENE_MAHOUYA", "Granny's Potion Shop", "Mahouya"),
("SCENE_GANON_DEMO", "Ganon's Tower Collapse & Battle Arena", "Ganon Demo"),
("SCENE_KINSUTA", "House of Skulltula", "Kinsuta"),
("SCENE_SPOT00", "Hyrule Field", "Spot00"),
("SCENE_SPOT01", "Kakariko Village", "Spot01"),
("SCENE_SPOT02", "Graveyard", "Spot02"),
("SCENE_SPOT03", "Zora's River", "Spot03"),
("SCENE_SPOT04", "Kokiri Forest", "Spot04"),
("SCENE_SPOT05", "Sacred Forest Meadow", "Spot05"),
("SCENE_SPOT06", "Lake Hylia", "Spot06"),
("SCENE_SPOT07", "Zora's Domain", "Spot07"),
("SCENE_SPOT08", "Zora's Fountain", "Spot08"),
("SCENE_SPOT09", "Gerudo Valley", "Spot09"),
("SCENE_SPOT10", "Lost Woods", "Spot10"),
("SCENE_SPOT11", "Desert Colossus", "Spot11"),
("SCENE_SPOT12", "Gerudo's Fortress", "Spot12"),
("SCENE_SPOT13", "Haunted Wasteland", "Spot13"),
("SCENE_SPOT15", "Hyrule Castle", "Spot15"),
("SCENE_SPOT16", "Death Mountain Trail", "Spot16"),
("SCENE_SPOT17", "Death Mountain Crater", "Spot17"),
("SCENE_SPOT18", "Goron City", "Spot18"),
("SCENE_SPOT20", "Lon Lon Ranch", "Spot20"),
("SCENE_GANON_TOU", "Ganon's Castle Exterior", "Ganon Tou"),
("SCENE_TEST01", "Jungle Gym", "Test01"),
("SCENE_BESITU", "Ganandorf Test Room", "Besitu"),
("SCENE_DEPTH_TEST", "Depth Test", "Depth Test"),
("SCENE_SYOTES", "Stalfos Mini-Boss Room", "Syotes"),
("SCENE_SYOTES2", "Stalfos Boss ROom", "Syotes2"),
("SCENE_SUTARU", "Sutaru", "Sutaru"),
("SCENE_HAIRAL_NIWA2", "Castle Hedge Maze (Early)", "Hairal Niwa2"),
("SCENE_SASATEST", "Sasatest", "Sasatest"),
("SCENE_TESTROOM", "Treasure Chest Room", "Testroom"),
("SCENE_YDAN", "Inside the Deku Tree (Ydan)", "Ydan"),
("SCENE_DDAN", "Dodongo's Cavern (Ddan)", "Ddan"),
("SCENE_BDAN", "Inside Jabu Jabu's Belly (Bdan)", "Bdan"),
("SCENE_BMORI1", "Forest Temple (Bmori1)", "Bmori1"),
("SCENE_HIDAN", "Fire Temple (Hidan)", "Hidan"),
("SCENE_MIZUSIN", "Water Temple (Mizusin)", "Mizusin"),
("SCENE_JYASINZOU", "Spirit Temple (Jyasinzou)", "Jyasinzou"),
("SCENE_HAKADAN", "Shadow Temple (Hakadan)", "Hakadan"),
("SCENE_HAKADANCH", "Bottom of the Well (Hakadanch)", "Hakadanch"),
("SCENE_ICE_DOUKUTO", "Ice Cavern (Ice Doukuto)", "Ice Doukuto"),
("SCENE_GANON", "Ganon's Tower (Ganon)", "Ganon"),
("SCENE_MEN", "Gerudo Training Ground (Men)", "Men"),
("SCENE_GERUDOWAY", "Thieves' Hideout (Gerudoway)", "Gerudoway"),
("SCENE_GANONTIKA", "Inside Ganon's Castle (Ganontika)", "Ganontika"),
("SCENE_GANON_SONOGO", "Ganon's Tower (Collapsing) (Ganon Sonogo)", "Ganon Sonogo"),
("SCENE_GANONTIKA_SONOGO", "Inside Ganon's Castle (Collapsing) (Ganontika Sonogo)", "Ganontika Sonogo"),
("SCENE_TAKARAYA", "Treasure Chest Shop (Takaraya)", "Takaraya"),
("SCENE_YDAN_BOSS", "Gohma's Lair (Ydan Boss)", "Ydan Boss"),
("SCENE_DDAN_BOSS", "King Dodongo's Lair (Ddan Boss)", "Ddan Boss"),
("SCENE_BDAN_BOSS", "Barinade's Lair (Bdan Boss)", "Bdan Boss"),
("SCENE_MORIBOSSROOM", "Phantom Ganon's Lair (Moribossroom)", "Moribossroom"),
("SCENE_FIRE_BS", "Volvagia's Lair (Fire Bs)", "Fire Bs"),
("SCENE_MIZUSIN_BS", "Morpha's Lair (Mizusin Bs)", "Mizusin Bs"),
("SCENE_JYASINBOSS", "Twinrova's Lair & Iron Knuckle Mini-Boss Room (Jyasinboss)", "Jyasinboss"),
("SCENE_HAKADAN_BS", "Bongo Bongo's Lair (Hakadan Bs)", "Hakadan Bs"),
("SCENE_GANON_BOSS", "Ganondorf's Lair (Ganon Boss)", "Ganon Boss"),
("SCENE_GANON_FINAL", "Ganondorf's Death Scene (Tower Escape Exterior) (Ganon Final)", "Ganon Final"),
("SCENE_ENTRA", "Market Entrance (Child - Day) (Entra)", "Entra"),
("SCENE_ENTRA_N", "Market Entrance (Child - Night) (Entra N)", "Entra N"),
("SCENE_ENRUI", "Market Entrance (Ruins) (Enrui)", "Enrui"),
("SCENE_MARKET_ALLEY", "Back Alley (Day) (Market Alley)", "Market Alley"),
("SCENE_MARKET_ALLEY_N", "Back Alley (Night) (Market Alley N)", "Market Alley N"),
("SCENE_MARKET_DAY", "Market (Child - Day) (Market Day)", "Market Day"),
("SCENE_MARKET_NIGHT", "Market (Child - Night) (Market Night)", "Market Night"),
("SCENE_MARKET_RUINS", "Market (Ruins) (Market Ruins)", "Market Ruins"),
("SCENE_SHRINE", "Temple of Time Exterior (Day) (Shrine)", "Shrine"),
("SCENE_SHRINE_N", "Temple of Time Exterior (Night) (Shrine N)", "Shrine N"),
("SCENE_SHRINE_R", "Temple of Time Exterior (Ruins) (Shrine R)", "Shrine R"),
("SCENE_KOKIRI_HOME", "Know-It-All Brothers' House (Kokiri Home)", "Kokiri Home"),
("SCENE_KOKIRI_HOME3", "Twins' House (Kokiri Home3)", "Kokiri Home3"),
("SCENE_KOKIRI_HOME4", "Mido's House (Kokiri Home4)", "Kokiri Home4"),
("SCENE_KOKIRI_HOME5", "Saria's House (Kokiri Home5)", "Kokiri Home5"),
("SCENE_KAKARIKO", "Carpenter Boss's House (Kakariko)", "Kakariko"),
("SCENE_KAKARIKO3", "Back Alley House (Man in Green) (Kakariko3)", "Kakariko3"),
("SCENE_SHOP1", "Bazaar (Shop1)", "Shop1"),
("SCENE_KOKIRI_SHOP", "Kokiri Shop (Kokiri Shop)", "Kokiri Shop"),
("SCENE_GOLON", "Goron Shop (Golon)", "Golon"),
("SCENE_ZOORA", "Zora Shop (Zoora)", "Zoora"),
("SCENE_DRAG", "Kakariko Potion Shop (Drag)", "Drag"),
("SCENE_ALLEY_SHOP", "Market Potion Shop (Alley Shop)", "Alley Shop"),
("SCENE_NIGHT_SHOP", "Bombchu Shop (Night Shop)", "Night Shop"),
("SCENE_FACE_SHOP", "Happy Mask Shop (Face Shop)", "Face Shop"),
("SCENE_LINK_HOME", "Link's House (Link Home)", "Link Home"),
("SCENE_IMPA", "Back Alley House (Dog Lady) (Impa)", "Impa"),
("SCENE_MALON_STABLE", "Stable (Malon Stable)", "Malon Stable"),
("SCENE_LABO", "Impa's House (Labo)", "Labo"),
("SCENE_HYLIA_LABO", "Lakeside Laboratory (Hylia Labo)", "Hylia Labo"),
("SCENE_TENT", "Carpenters' Tent (Tent)", "Tent"),
("SCENE_HUT", "Gravekeeper's Hut (Hut)", "Hut"),
("SCENE_DAIYOUSEI_IZUMI", "Great Fairy's Fountain (Upgrades) (Daiyousei Izumi)", "Daiyousei Izumi"),
("SCENE_YOUSEI_IZUMI_TATE", "Fairy's Fountain (Healing Fairies) (Yousei Izumi Tate)", "Yousei Izumi Tate"),
("SCENE_YOUSEI_IZUMI_YOKO", "Great Fairy's Fountain (Spells) (Yousei Izumi Yoko)", "Yousei Izumi Yoko"),
("SCENE_KAKUSIANA", "Grottos (Kakusiana)", "Kakusiana"),
("SCENE_HAKAANA", "Grave (Redead) (Hakaana)", "Hakaana"),
("SCENE_HAKAANA2", "Grave (Fairy's Fountain) (Hakaana2)", "Hakaana2"),
("SCENE_HAKAANA_OUKE", "Royal Family's Tomb (Hakaana Ouke)", "Hakaana Ouke"),
("SCENE_SYATEKIJYOU", "Shooting Gallery (Syatekijyou)", "Syatekijyou"),
("SCENE_TOKINOMA", "Temple of Time (Tokinoma)", "Tokinoma"),
("SCENE_KENJYANOMA", "Chamber of the Sages (Kenjyanoma)", "Kenjyanoma"),
("SCENE_HAIRAL_NIWA", "Castle Hedge Maze (Day) (Hairal Niwa)", "Hairal Niwa"),
("SCENE_HAIRAL_NIWA_N", "Castle Hedge Maze (Night) (Hairal Niwa N)", "Hairal Niwa N"),
("SCENE_HIRAL_DEMO", "Cutscene Map (Hiral Demo)", "Hiral Demo"),
("SCENE_HAKASITARELAY", "Dampé's Grave & Windmill (Hakasitarelay)", "Hakasitarelay"),
("SCENE_TURIBORI", "Fishing Pond (Turibori)", "Turibori"),
("SCENE_NAKANIWA", "Castle Courtyard (Nakaniwa)", "Nakaniwa"),
("SCENE_BOWLING", "Bombchu Bowling Alley (Bowling)", "Bowling"),
("SCENE_SOUKO", "Lon Lon Ranch House & Tower (Souko)", "Souko"),
("SCENE_MIHARIGOYA", "Guard House (Miharigoya)", "Miharigoya"),
("SCENE_MAHOUYA", "Granny's Potion Shop (Mahouya)", "Mahouya"),
("SCENE_GANON_DEMO", "Ganon's Tower Collapse & Battle Arena (Ganon Demo)", "Ganon Demo"),
("SCENE_KINSUTA", "House of Skulltula (Kinsuta)", "Kinsuta"),
("SCENE_SPOT00", "Hyrule Field (Spot00)", "Spot00"),
("SCENE_SPOT01", "Kakariko Village (Spot01)", "Spot01"),
("SCENE_SPOT02", "Graveyard (Spot02)", "Spot02"),
("SCENE_SPOT03", "Zora's River (Spot03)", "Spot03"),
("SCENE_SPOT04", "Kokiri Forest (Spot04)", "Spot04"),
("SCENE_SPOT05", "Sacred Forest Meadow (Spot05)", "Spot05"),
("SCENE_SPOT06", "Lake Hylia (Spot06)", "Spot06"),
("SCENE_SPOT07", "Zora's Domain (Spot07)", "Spot07"),
("SCENE_SPOT08", "Zora's Fountain (Spot08)", "Spot08"),
("SCENE_SPOT09", "Gerudo Valley (Spot09)", "Spot09"),
("SCENE_SPOT10", "Lost Woods (Spot10)", "Spot10"),
("SCENE_SPOT11", "Desert Colossus (Spot11)", "Spot11"),
("SCENE_SPOT12", "Gerudo's Fortress (Spot12)", "Spot12"),
("SCENE_SPOT13", "Haunted Wasteland (Spot13)", "Spot13"),
("SCENE_SPOT15", "Hyrule Castle (Spot15)", "Spot15"),
("SCENE_SPOT16", "Death Mountain Trail (Spot16)", "Spot16"),
("SCENE_SPOT17", "Death Mountain Crater (Spot17)", "Spot17"),
("SCENE_SPOT18", "Goron City (Spot18)", "Spot18"),
("SCENE_SPOT20", "Lon Lon Ranch (Spot20)", "Spot20"),
("SCENE_GANON_TOU", "Ganon's Castle Exterior (Ganon Tou)", "Ganon Tou"),
("SCENE_TEST01", "Jungle Gym (Test01)", "Test01"),
("SCENE_BESITU", "Ganandorf Test Room (Besitu)", "Besitu"),
("SCENE_DEPTH_TEST", "Depth Test (Depth Test)", "Depth Test"),
("SCENE_SYOTES", "Stalfos Mini-Boss Room (Syotes)", "Syotes"),
("SCENE_SYOTES2", "Stalfos Boss ROom (Syotes2)", "Syotes2"),
("SCENE_SUTARU", "Sutaru (Sutaru)", "Sutaru"),
("SCENE_HAIRAL_NIWA2", "Castle Hedge Maze (Early) (Hairal Niwa2)", "Hairal Niwa2"),
("SCENE_SASATEST", "Sasatest (Sasatest)", "Sasatest"),
("SCENE_TESTROOM", "Treasure Chest Room (Testroom)", "Testroom"),
]
ootEnumCamTransition = [
+89 -15
View File
@@ -5,12 +5,13 @@ from ..f3d.f3d_material import *
from bpy.utils import register_class, unregister_class
from .oot_constants import *
from .oot_utility import *
from .oot_scene_room import *
class OOTModel(FModel):
def __init__(self, f3dType, isHWv1, name, DLFormat):
FModel.__init__(self, f3dType, isHWv1, name, DLFormat, GfxMatWriteMethod.WriteAll)
def getDrawLayer(self, obj):
def getDrawLayerV3(self, obj):
return obj.ootDrawLayer
def getRenderMode(self, drawLayer):
@@ -25,9 +26,22 @@ class OOTModel(FModel):
else:
return texFmt.lower()
class OOTGfxFormatter(GameGfxFormatter):
def onMaterialCommandsBuilt(self, gfxList, revertList, material, drawLayer):
for i in range(8, 14):
matDrawLayer = getattr(material.ootMaterial, drawLayer.lower())
if getattr(matDrawLayer, "segment" + format(i, 'X')):
gfxList.commands.append(SPDisplayList(
GfxList("0x" + format(i,'X') + '000000', GfxListTag.Material, DLFormat.Static)))
return
class OOTDynamicMaterialDrawLayer:
def __init__(self, opaque, transparent):
self.opaque = opaque
self.transparent = transparent
class OOTGfxFormatter(GfxFormatter):
def __init__(self, scrollMethod):
GameGfxFormatter.__init__(self, scrollMethod, 64)
GfxFormatter.__init__(self, scrollMethod, 64)
# This code is not functional, only used for an example
def drawToC(self, f3d, gfxList):
@@ -156,9 +170,7 @@ def ootProcessVertexGroup(fModel, meshObj, vertexGroup, convertTransformMatrix,
drawLayer = drawLayerOverride
if drawLayerKey not in fMeshes:
meshName = getFMeshName(vertexGroup, namePrefix, drawLayer, False)
checkUniqueBoneNames(fModel, meshName, vertexGroup)
fMesh = FMesh(meshName, fModel.DLFormat)
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayer, False)
fMeshes[drawLayerKey] = fMesh
checkForF3dMaterialInFaces(meshObj, material)
@@ -181,9 +193,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.
meshName = getFMeshName(vertexGroup, namePrefix, drawLayer, False)
checkUniqueBoneNames(fModel, meshName, vertexGroup)
fMesh = FMesh(meshName, fModel.DLFormat)
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayer, False)
fMeshes[drawLayerKey] = fMesh
checkForF3dMaterialInFaces(meshObj, material)
@@ -201,6 +211,11 @@ def ootProcessVertexGroup(fModel, meshObj, vertexGroup, convertTransformMatrix,
return fMeshes[drawLayerKey], len(skinnedFaces) > 0
ootEnumObjectMenu = [
("Scene", "Parent Scene Settings", "Scene"),
("Room", "Parent Room Settings", "Room"),
]
class OOT_DisplayListPanel(bpy.types.Panel):
bl_label = "Display List Inspector"
bl_idname = "OBJECT_PT_OOT_DL_Inspector"
@@ -219,17 +234,18 @@ class OOT_DisplayListPanel(bpy.types.Panel):
box.box().label(text = 'OOT DL Inspector')
obj = context.object
prop_split(box, obj, "ootDrawLayer", "Draw Layer")
#prop_split(box, obj, "ootDrawLayer", "Draw Layer")
box.prop(obj, "ignore_render")
box.prop(obj, "ignore_collision")
#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)
fModel = OOTModel(f3dType, isHWv1, name, DLFormat, 0)
fMesh = exportF3DCommon(obj, fModel, transformMatrix,
includeChildren, name, DLFormat, not savePNG)
@@ -433,9 +449,6 @@ class OOT_ExportDLPanel(bpy.types.Panel):
# context.scene.ootDLName, context.scene.ootDLLevelName, context.scene.ootDLLevelOption)
col.prop(context.scene, 'ootDLincludeChildren')
for i in range(panelSeparatorSize):
col.separator()
class OOTDefaultRenderModesProperty(bpy.types.PropertyGroup):
expandTab : bpy.props.BoolProperty()
@@ -474,8 +487,64 @@ class OOT_DrawLayersPanel(bpy.types.Panel):
prop_split(inputGroup, ootDefaultRenderModeProp, "overlayCycle1", "Overlay Cycle 1")
prop_split(inputGroup, ootDefaultRenderModeProp, "overlayCycle2", "Overlay Cycle 2")
class OOT_MaterialPanel(bpy.types.Panel):
bl_label = "OOT Material"
bl_idname = "MATERIAL_PT_OOT_Material_Inspector"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
return context.material is not None and context.scene.gameEditorMode == "OOT"
def draw(self, context):
layout = self.layout
mat = context.material
col = layout.column()
drawOOTMaterialProperty(col.box().column(), mat.ootMaterial, mat.f3d_mat.draw_layer.oot)
def drawOOTMaterialDrawLayerProperty(layout, matDrawLayerProp, suffix):
#layout.box().row().label(text = title)
row = layout.row()
for colIndex in range(2):
col = row.column()
for rowIndex in range(3):
i = 8 + colIndex * 3 + rowIndex
name = "Segment " + format(i, 'X') + " " + suffix
col.prop(matDrawLayerProp, "segment" + format(i, 'X'), text = name)
drawLayerSuffix = {
"Opaque" : "OPA",
"Transparent" : 'XLU',
"Overlay" : "OVL"
}
def drawOOTMaterialProperty(layout, matProp, drawLayer):
suffix = "(" + drawLayerSuffix[drawLayer] + ")"
layout.box().column().label(text = "OOT Dynamic Material Properties " + suffix)
layout.label(text = "See gSPSegment calls in z_scene_table.c.")
layout.label(text = "Based off draw config index in gSceneTable.")
drawOOTMaterialDrawLayerProperty(layout.column(), getattr(matProp, drawLayer.lower()), suffix)
class OOTDynamicMaterialDrawLayerProperty(bpy.types.PropertyGroup):
segment8 : bpy.props.BoolProperty()
segment9 : bpy.props.BoolProperty()
segmentA : bpy.props.BoolProperty()
segmentB : bpy.props.BoolProperty()
segmentC : bpy.props.BoolProperty()
segmentD : bpy.props.BoolProperty()
class OOTDynamicMaterialProperty(bpy.types.PropertyGroup):
opaque : bpy.props.PointerProperty(type = OOTDynamicMaterialDrawLayerProperty)
transparent : bpy.props.PointerProperty(type = OOTDynamicMaterialDrawLayerProperty)
oot_dl_writer_classes = (
OOTDefaultRenderModesProperty,
OOTDynamicMaterialDrawLayerProperty,
OOTDynamicMaterialProperty,
#OOT_ExportDL,
)
@@ -483,6 +552,7 @@ oot_dl_writer_panel_classes = (
#OOT_ExportDLPanel,
OOT_DisplayListPanel,
OOT_DrawLayersPanel,
OOT_MaterialPanel,
)
def oot_dl_writer_panel_register():
@@ -527,6 +597,9 @@ def oot_dl_writer_register():
bpy.types.Scene.ootDLLevelOption = bpy.props.EnumProperty(
items = ootEnumSceneID, name = 'Level', default = 'SCENE_YDAN')
bpy.types.Material.ootMaterial = bpy.props.PointerProperty(type = OOTDynamicMaterialProperty)
bpy.types.Object.ootObjectMenu = bpy.props.EnumProperty(items = ootEnumObjectMenu)
def oot_dl_writer_unregister():
for cls in reversed(oot_dl_writer_classes):
unregister_class(cls)
@@ -543,4 +616,5 @@ def oot_dl_writer_unregister():
del bpy.types.Scene.ootDLExportHeaderType
del bpy.types.Scene.ootDLGroupName
del bpy.types.Scene.ootDLLevelName
del bpy.types.Scene.ootDLLevelOption
del bpy.types.Scene.ootDLLevelOption
del bpy.types.Material.ootMaterial
+46 -30
View File
@@ -52,22 +52,6 @@ def handleRefreshDiffModelIDs(modelID):
return modelID
def getAltSceneObjProp(obj):
while not (obj is None or (obj is not None and obj.data is None and obj.ootEmptyType == "Scene")):
obj = obj.parent
if obj is None:
return None
else:
return obj.ootAlternateSceneHeaders
def getAltRoomObjProp(obj):
while not (obj is None or (obj is not None and obj.data is None and obj.ootEmptyType == "Room")):
obj = obj.parent
if obj is None:
return None
else:
return obj.ootAlternateRoomHeaders
class OOTObjectPanel(bpy.types.Panel):
bl_label = "Object Inspector"
bl_idname = "OBJECT_PT_OOT_Object_Inspector"
@@ -81,49 +65,80 @@ class OOTObjectPanel(bpy.types.Panel):
return context.scene.gameEditorMode == "OOT" and (context.object is not None and context.object.data is None)
def draw(self, context):
prop_split(self.layout, context.scene, "gameEditorMode", "Game")
box = self.layout.box()
box.box().label(text = 'OOT Object Inspector')
obj = context.object
objName = obj.name
prop_split(box, obj, 'ootEmptyType', 'Object Type')
altSceneProp = getAltSceneObjProp(obj)
altRoomProp = getAltRoomObjProp(obj)
sceneObj = getSceneObj(obj)
roomObj = getRoomObj(obj)
altSceneProp = sceneObj.ootAlternateSceneHeaders if sceneObj is not None else None
altRoomProp = roomObj.ootAlternateRoomHeaders if roomObj is not None else None
if obj.ootEmptyType == 'Actor':
drawActorProperty(box, obj.ootActorProperty, altRoomProp)
drawActorProperty(box, obj.ootActorProperty, altRoomProp, objName)
elif obj.ootEmptyType == 'Transition Actor':
drawTransitionActorProperty(box, obj.ootTransitionActorProperty, altSceneProp)
drawTransitionActorProperty(box, obj.ootTransitionActorProperty, altSceneProp, roomObj, objName)
elif obj.ootEmptyType == 'Water Box':
drawWaterBoxProperty(box, obj.ootWaterBoxProperty)
elif obj.ootEmptyType == 'Scene':
drawSceneHeaderProperty(box, obj.ootSceneHeader, None, None)
drawSceneHeaderProperty(box, obj.ootSceneHeader, None, None, objName)
if obj.ootSceneHeader.menuTab == 'Alternate':
drawAlternateSceneHeaderProperty(box, obj.ootAlternateSceneHeaders)
drawAlternateSceneHeaderProperty(box, obj.ootAlternateSceneHeaders, objName)
elif obj.ootEmptyType == 'Room':
drawRoomHeaderProperty(box, obj.ootRoomHeader, None, None)
drawRoomHeaderProperty(box, obj.ootRoomHeader, None, None, objName)
if obj.ootRoomHeader.menuTab == 'Alternate':
drawAlternateRoomHeaderProperty(box, obj.ootAlternateRoomHeaders)
drawAlternateRoomHeaderProperty(box, obj.ootAlternateRoomHeaders, objName)
elif obj.ootEmptyType == 'Entrance':
drawEntranceProperty(box, obj, altSceneProp)
drawEntranceProperty(box, obj, altSceneProp, objName)
elif obj.ootEmptyType == "Cull Volume":
drawCullVolumeProperty(box, obj)
drawCullVolumeProperty(box)
elif obj.ootEmptyType == 'None':
box.label(text = 'Geometry can be parented to this.')
def drawCullVolumeProperty(box, obj):
#if obj.ootEmptyType != "Scene" and obj.ootEmptyType != "Room":
# drawParentSceneRoom(box, context.object)
def drawCullVolumeProperty(box):
box.label(text = "No rotation allowed.")
def onUpdateObjectType(self, context):
if self.sm64_obj_type == 'Water Box':
def setLightPropertyValues(lightProp, ambient, diffuse0, diffuse1, fogColor, fogNear):
lightProp.ambient = gammaInverse([value / 255 for value in ambient]) + [1]
lightProp.diffuse0 = gammaInverse([value / 255 for value in diffuse0]) + [1]
lightProp.diffuse1 = gammaInverse([value / 255 for value in diffuse1]) + [1]
lightProp.fogColor = gammaInverse([value / 255 for value in fogColor]) + [1]
lightProp.fogNear = fogNear
def onUpdateOOTEmptyType(self, context):
isNoneEmpty = self.ootEmptyType == "None"
isBoxEmpty = self.ootEmptyType == 'Water Box' or self.ootEmptyType == 'Cull Volume'
self.show_name = not (isBoxEmpty or isNoneEmpty)
self.show_axis = not (isBoxEmpty or isNoneEmpty)
if isBoxEmpty:
self.empty_display_type = "CUBE"
if self.ootEmptyType == "Scene":
if len(self.ootSceneHeader.lightList) == 0:
light = self.ootSceneHeader.lightList.add()
if not self.ootSceneHeader.timeOfDayLights.defaultsSet:
self.ootSceneHeader.timeOfDayLights.defaultsSet = True
timeOfDayLights = self.ootSceneHeader.timeOfDayLights
setLightPropertyValues(timeOfDayLights.dawn, [70, 45, 57], [180, 154, 138], [20, 20, 60], [140, 120, 100], 0x3E1)
setLightPropertyValues(timeOfDayLights.day, [105, 90, 90], [255, 255, 240], [50, 50, 90], [100, 100, 120], 0x3E4)
setLightPropertyValues(timeOfDayLights.dusk, [120, 90, 0], [250, 135, 50], [30, 30, 60], [120, 70, 50], 0x3E3)
setLightPropertyValues(timeOfDayLights.night, [40, 70, 100], [20, 20, 35], [50, 50, 100], [0, 0, 30], 0x3E0)
oot_obj_classes = (
OOT_SearchActorIDEnumOperator,
OOT_SearchMusicSeqEnumOperator,
@@ -133,6 +148,7 @@ oot_obj_classes = (
OOTLightGroupProperty,
OOTObjectProperty,
OOTExitProperty,
OOTSceneTableEntryProperty,
OOTActorHeaderItemProperty,
OOTActorHeaderProperty,
@@ -164,7 +180,7 @@ def oot_obj_register():
register_class(cls)
bpy.types.Object.ootEmptyType = bpy.props.EnumProperty(
name = 'OOT Object Type', items = ootEnumEmptyType, default = 'None', update = onUpdateObjectType)
name = 'OOT Object Type', items = ootEnumEmptyType, default = 'None', update = onUpdateOOTEmptyType)
bpy.types.Object.ootActorProperty = bpy.props.PointerProperty(type = OOTActorProperty)
bpy.types.Object.ootTransitionActorProperty = bpy.props.PointerProperty(type = OOTTransitionActorProperty)
+5 -7
View File
@@ -39,13 +39,6 @@ class OOTEntrance:
self.roomIndex = roomIndex
self.startPositionIndex = startPositionIndex
class OOTLightGroup:
def __init__(self):
self.dawn = None
self.day = None
self.dusk = None
self.night = None
class OOTLight:
def __init__(self):
self.ambient = (0,0,0)
@@ -61,6 +54,10 @@ class OOTLight:
def getBlendFogShort(self):
return format((self.transitionSpeed << 10) | self.fogNear, "#06x")
class OOTSceneTableEntry:
def __init__(self):
self.drawConfig = 0
class OOTScene:
def __init__(self, name, model):
self.name = toAlnum(name)
@@ -97,6 +94,7 @@ class OOTScene:
self.cameraList = []
self.custcene = None
self.sceneTableEntry = OOTSceneTableEntry()
def getAlternateHeaderScene(self, name):
scene = OOTScene(name, self.model)
+118 -189
View File
@@ -8,7 +8,7 @@ from .oot_utility import *
from .oot_f3d_writer import *
from .oot_collision import *
from .oot_spline import *
from .c_writer.oot_level_c import *
from .c_writer import *
from ..utility import *
@@ -23,143 +23,6 @@ import bpy, bmesh, os, math, re, shutil, mathutils
# self.maxBounds = [2**8 - 1, 2**8 - 1]
ootSceneDungeons = [
"bdan",
"bdan_boss",
"bmori1",
"ddan",
"ddan_boss",
"fire_bs",
"ganon",
"ganontika",
"ganontikasonogo",
"ganon_boss",
"ganon_demo",
"ganon_final",
"ganon_sonogo",
"ganon_tou",
"gerudoway",
"hakadan",
"hakadanch",
"hakadan_bs",
"hidan",
"ice_doukutu",
"jyasinboss",
"jyasinzou",
"men",
"mizusin",
"mizusin_bs",
"moribossroom",
"ydan",
"ydan_boss",
]
ootSceneIndoors = [
"bowling",
"daiyousei_izumi",
"hairal_niwa",
"hairal_niwa2",
"hairal_niwa_n",
"hakasitarelay",
"hut",
"hylia_labo",
"impa",
"kakariko",
"kenjyanoma",
"kokiri_home",
"kokiri_home3",
"kokiri_home4",
"kokiri_home5",
"labo",
"link_home",
"mahouya",
"malon_stable",
"miharigoya",
"nakaniwa",
"syatekijyou",
"takaraya",
"tent",
"tokinoma",
"yousei_izumi_tate",
"yousei_izumi_yoko",
]
ootSceneMisc = [
"enrui",
"entra_n",
"hakaana",
"hakaana2",
"hakaana_ouke",
"hiral_demo",
"kakariko3",
"kakusiana",
"kinsuta",
"market_alley",
"market_alley_n",
"market_day",
"market_night",
"market_ruins",
"shrine",
"shrine_n",
"shrine_r",
"turibori",
]
ootSceneOverworld = [
"entra",
"souko",
"spot00",
"spot01",
"spot02",
"spot03",
"spot04",
"spot05",
"spot06",
"spot07",
"spot08",
"spot09",
"spot10",
"spot11",
"spot12",
"spot13",
"spot15",
"spot16",
"spot17",
"spot18",
"spot20",
]
ootSceneShops = [
"alley_shop",
"drag",
"face_shop",
"golon",
"kokiri_shop",
"night_shop",
"shop1",
"zoora",
]
ootSceneTest_levels = [
"besitu",
"depth_test",
"sasatest",
"sutaru",
"syotes",
"syotes2",
"test01",
"testroom",
]
ootSceneDirs = {
'assets/scenes/dungeons/' : ootSceneDungeons,
'assets/scenes/indoors/' : ootSceneIndoors,
'assets/scenes/misc/' : ootSceneMisc,
'assets/scenes/overworld/' : ootSceneOverworld,
'assets/scenes/shops/' : ootSceneShops,
'assets/scenes/test_levels/' : ootSceneTest_levels,
}
class OOTObjectCategorizer:
def __init__(self):
self.sceneObj = None
@@ -227,6 +90,7 @@ def ootDuplicateHierarchy(obj, ignoreAttr, includeEmpties, objectCategorizer):
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)
@@ -254,13 +118,18 @@ def sceneNameFromID(sceneID):
return sceneID[6:].lower()
def ootExportSceneToC(originalSceneObj, transformMatrix,
f3dType, isHWv1, sceneName, DLFormat, savePNG, exportPath, isCustomExport):
f3dType, isHWv1, sceneName, DLFormat, savePNG, exportInfo):
checkObjectReference(originalSceneObj, "Scene object")
isCustomExport = exportInfo.isCustomExportPath
exportPath = exportInfo.exportPath
scene = ootConvertScene(originalSceneObj, transformMatrix,
f3dType, isHWv1, sceneName, DLFormat, not savePNG)
exportSubdir = ""
if not isCustomExport:
if exportInfo.customSubPath is not None:
exportSubdir = exportInfo.customSubPath
if not isCustomExport and exportInfo.customSubPath is None:
for sceneSubdir, sceneNames in ootSceneDirs.items():
if sceneName in sceneNames:
exportSubdir = sceneSubdir
@@ -278,11 +147,21 @@ def ootExportSceneToC(originalSceneObj, transformMatrix,
writeCData(room,
os.path.join(levelPath, roomName + '.h'),
os.path.join(levelPath, roomName + '.c'))
if not isCustomExport:
pass # Modify other level files here
writeOtherSceneProperties(scene, exportInfo)
def writeOtherSceneProperties(scene, exportInfo):
modifySceneTable(scene, exportInfo)
modifySegmentSymbols(scene, exportInfo)
modifySceneIDs(scene, exportInfo)
modifyDmaMgrFileNames(scene, exportInfo)
modifyDmaTableEntries(scene, exportInfo)
modifySegmentDefinition(scene, exportInfo)
modifySceneFiles(scene, exportInfo)
def readSceneData(scene, sceneHeader, alternateSceneHeaders):
scene.sceneTableEntry.drawConfig = sceneHeader.sceneTableEntry.drawConfig
scene.globalObject = getCustomProperty(sceneHeader, "globalObject")
scene.naviCup = getCustomProperty(sceneHeader, "naviCup")
scene.skyboxID = getCustomProperty(sceneHeader, "skyboxID")
@@ -294,8 +173,14 @@ def readSceneData(scene, sceneHeader, alternateSceneHeaders):
scene.nightSeq = getCustomProperty(sceneHeader, "nightSeq")
scene.audioSessionPreset = getCustomProperty(sceneHeader, "audioSessionPreset")
for lightGroupProp in sceneHeader.lightList:
scene.lights.append(getLightGroupData(lightGroupProp, scene.skyboxLighting == '0x01'))
if sceneHeader.skyboxLighting == '0x00': # Time of Day
scene.lights.append(getLightData(sceneHeader.timeOfDayLights.dawn))
scene.lights.append(getLightData(sceneHeader.timeOfDayLights.day))
scene.lights.append(getLightData(sceneHeader.timeOfDayLights.dusk))
scene.lights.append(getLightData(sceneHeader.timeOfDayLights.night))
else:
for lightProp in sceneHeader.lightList:
scene.lights.append(getLightData(lightProp))
for exitProp in sceneHeader.exitList:
scene.exitList.append(getExitData(exitProp))
@@ -322,41 +207,35 @@ def readSceneData(scene, sceneHeader, alternateSceneHeaders):
scene.cutsceneHeaders.append(cutsceneHeader)
def getConvertedTransform(transformMatrix, sceneObj, obj, handleOrientation):
relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world
blenderTranslation, blenderRotation, scale = relativeTransform.decompose()
# Hacky solution to handle Z-up to Y-up conversion
# We cannot apply rotation to empty, as that modifies scale
if handleOrientation:
orientedRotation = blenderRotation @ mathutils.Quaternion((1, 0, 0), math.radians(90.0))
orientation = mathutils.Quaternion((1, 0, 0), math.radians(90.0))
else:
orientedRotation = blenderRotation
orientation = mathutils.Matrix.Identity(4)
return getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation)
translation = ootConvertTranslation(blenderTranslation)
rotation = ootConvertRotation(orientedRotation)
def getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation):
relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world
blenderTranslation, blenderRotation, scale = relativeTransform.decompose()
rotation = blenderRotation @ orientation
convertedTranslation = ootConvertTranslation(blenderTranslation)
convertedRotation = ootConvertRotation(rotation)
return translation, rotation, scale, orientedRotation
return convertedTranslation, convertedRotation, scale, rotation
def getExitData(exitProp):
if exitProp.exitIndex != "Custom":
raise PluginError("Exit index enums not implemented yet.")
return OOTExit(exitProp.exitIndexCustom)
def getLightGroupData(lightGroupProp, useIndoorLighting):
lightGroup = OOTLightGroup()
lightGroup.dawn = getLightData(lightGroupProp.dawn)
if not useIndoorLighting:
lightGroup.day = getLightData(lightGroupProp.day)
lightGroup.dusk = getLightData(lightGroupProp.dusk)
lightGroup.night = getLightData(lightGroupProp.night)
return lightGroup
def getLightData(lightProp):
light = OOTLight()
light.ambient = getLightColor(lightProp.ambient)
if lightProp.useCustomDiffuse0:
if lightProp.diffuse0Custom is None:
raise PluginError("Error: Fill light object not set in a scene lighting property.")
raise PluginError("Error: Diffuse 0 light object not set in a scene lighting property.")
light.diffuse0 = getLightColor(lightProp.diffuse0Custom.color)
light.diffuseDir0 = getLightRotation(lightProp.diffuse0Custom)
else:
@@ -365,7 +244,7 @@ def getLightData(lightProp):
if lightProp.useCustomDiffuse1:
if lightProp.diffuse1Custom is None:
raise PluginError("Error: Key light object not set in a scene lighting property.")
raise PluginError("Error: Diffuse 1 light object not set in a scene lighting property.")
light.diffuse1 = getLightColor(lightProp.diffuse1Custom.color)
light.diffuseDir1 = getLightRotation(lightProp.diffuse1Custom)
else:
@@ -422,8 +301,10 @@ def readRoomData(room, roomHeader, alternateRoomHeaders):
room.cutsceneHeaders.append(cutsceneHeader)
def readCamPos(camPosProp, obj, scene, sceneObj, transformMatrix):
# Camera faces opposite direction
orientation = mathutils.Quaternion((0, 1, 0), math.radians(180.0))
translation, rotation, scale, orientedRotation = \
getConvertedTransform(transformMatrix, sceneObj, obj, True)
getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation)
camPosProp = obj.ootCameraPositionProperty
index = camPosProp.index
# TODO: FOV conversion?
@@ -439,8 +320,9 @@ def ootConvertScene(originalSceneObj, transformMatrix,
if originalSceneObj.data is not None or originalSceneObj.ootEmptyType != "Scene":
raise PluginError(originalSceneObj.name + " is not an empty with the \"Scene\" empty type.")
# Don't remove ignore_render, as we want to resuse this for collision
sceneObj, allObjs = \
ootDuplicateHierarchy(originalSceneObj, 'ignore_render', True, OOTObjectCategorizer())
ootDuplicateHierarchy(originalSceneObj, None, True, OOTObjectCategorizer())
roomObjs = [child for child in sceneObj.children if child.data is None and child.ootEmptyType == 'Room']
if len(roomObjs) == 0:
raise PluginError("The scene has no child empties with the 'Room' empty type.")
@@ -498,7 +380,7 @@ def ootProcessMesh(roomMesh, roomMeshGroup, sceneObj, obj, transformMatrix, conv
roomMeshGroup = roomMesh.addMeshGroup(BoxEmpty(
ootConvertTranslation(translation), scale, obj.empty_display_size))
elif isinstance(obj.data, bpy.types.Mesh):
elif isinstance(obj.data, bpy.types.Mesh) and not obj.ignore_render:
fMeshes = saveStaticModel(roomMesh.model, obj, relativeTransform, roomMesh.model.name,
roomMesh.model.DLFormat, convertTextureData, False, 'oot')
if roomMeshGroup is None:
@@ -572,14 +454,17 @@ class OOT_ExportScene(bpy.types.Operator):
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
def execute(self, context):
activeObj = None
try:
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.selected_objects[0]
if obj.data is not None or obj.ootEmptyType != 'Scene':
raise PluginError("The selected object is not an empty with the Scene type.")
bpy.ops.object.mode_set(mode = "OBJECT")
activeObj = context.view_layer.objects.active
obj = context.scene.ootSceneExportObj
if obj is None:
raise PluginError("Scene object input not set.")
elif obj.data is not None or obj.ootEmptyType != 'Scene':
raise PluginError("The input object is not an empty with the Scene type.")
#obj = context.active_object
@@ -591,23 +476,22 @@ class OOT_ExportScene(bpy.types.Operator):
raisePluginError(self, e)
return {'CANCELLED'} # must return a set
try:
applyRotation([obj], math.radians(90), 'X')
levelName = context.scene.ootSceneName
if context.scene.ootSceneCustomExport:
exportPath = bpy.path.abspath(context.scene.ootSceneExportPath)
levelName = context.scene.ootSceneName
exportInfo = ExportInfo(True, bpy.path.abspath(context.scene.ootSceneExportPath), None, levelName)
else:
exportPath = bpy.path.abspath(context.scene.ootDecompPath)
if context.scene.ootSceneOption == 'Custom':
levelName = context.scene.ootSceneName
if context.scene.ootSceneOption == 'Custom':
subfolder = 'assets/scenes/' + context.scene.ootSceneSubFolder + '/'
else:
levelName = sceneNameFromID(context.scene.ootSceneOption)
subfolder = None
exportInfo = ExportInfo(False, bpy.path.abspath(context.scene.ootDecompPath), subfolder, levelName)
#if not context.scene.ootSceneCustomExport:
# applyBasicTweaks(exportPath)
ootExportSceneToC(obj, finalTransform,
context.scene.f3d_type, context.scene.isHWv1, levelName, DLFormat.Static,
context.scene.saveTextures or bpy.context.scene.ignoreTextureRestrictions,
exportPath, context.scene.ootSceneCustomExport)
context.scene.saveTextures or bpy.context.scene.ignoreTextureRestrictions, exportInfo)
#ootExportScene(obj, finalTransform,
# context.scene.f3d_type, context.scene.isHWv1, levelName, exportPath,
@@ -615,21 +499,55 @@ class OOT_ExportScene(bpy.types.Operator):
# context.scene.ootSceneCustomExport, DLFormat.Dynamic)
self.report({'INFO'}, 'Success!')
applyRotation([obj], math.radians(-90), 'X')
context.view_layer.objects.active = activeObj
if activeObj is not None:
activeObj.select_set(True)
#applyRotation(obj.children, math.radians(0), 'X')
return {'FINISHED'} # must return a set
except Exception as e:
if context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode = 'OBJECT')
applyRotation([obj], math.radians(-90), 'X')
obj.select_set(True)
context.view_layer.objects.active = obj
context.view_layer.objects.active = activeObj
if activeObj is not None:
activeObj.select_set(True)
raisePluginError(self, e)
return {'CANCELLED'} # must return a set
def ootRemoveSceneC(exportInfo):
modifySceneTable(None, exportInfo)
modifySegmentSymbols(None, exportInfo)
modifySceneIDs(None, exportInfo)
modifyDmaMgrFileNames(None, exportInfo)
modifyDmaTableEntries(None, exportInfo)
modifySegmentDefinition(None, exportInfo)
deleteSceneFiles(exportInfo)
class OOT_RemoveScene(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.oot_remove_level'
bl_label = "Remove Scene"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
def execute(self, context):
levelName = context.scene.ootSceneName
if context.scene.ootSceneCustomExport:
operator.report({'ERROR'}, "You can only remove scenes from your decomp path.")
return {"FINISHED"}
if context.scene.ootSceneOption == 'Custom':
subfolder = 'assets/scenes/' + context.scene.ootSceneSubFolder + '/'
else:
levelName = sceneNameFromID(context.scene.ootSceneOption)
subfolder = None
exportInfo = ExportInfo(False, bpy.path.abspath(context.scene.ootDecompPath), subfolder, levelName)
ootRemoveSceneC(exportInfo)
self.report({'INFO'}, 'Success!')
return {'FINISHED'} # must return a set
class OOT_ExportScenePanel(bpy.types.Panel):
bl_idname = "OOT_PT_export_level"
bl_label = "OOT Scene Exporter"
@@ -647,19 +565,27 @@ class OOT_ExportScenePanel(bpy.types.Panel):
col.operator(OOT_ExportScene.bl_idname)
#if not bpy.context.scene.ignoreTextureRestrictions:
# col.prop(context.scene, 'saveTextures')
prop_split(col, context.scene, 'ootSceneExportObj', "Scene Object")
col.prop(context.scene, 'ootSceneCustomExport')
if context.scene.ootSceneCustomExport:
prop_split(col, context.scene, 'ootSceneExportPath', 'Directory')
prop_split(col, context.scene, 'ootSceneName', 'Name')
customExportWarning(col)
else:
col.prop(context.scene, 'ootSceneOption')
col.operator(OOT_SearchSceneEnumOperator.bl_idname, icon = 'VIEWZOOM')
col.box().column().label(text = getEnumName(ootEnumSceneID, context.scene.ootSceneOption))
#col.prop(context.scene, 'ootSceneOption')
if context.scene.ootSceneOption == 'Custom':
prop_split(col, context.scene, 'ootSceneSubFolder', 'Subfolder')
prop_split(col, context.scene, 'ootSceneName', 'Name')
col.operator(OOT_RemoveScene.bl_idname)
def isSceneObj(self, obj):
return obj.data is None and obj.ootEmptyType == "Scene"
oot_level_classes = (
OOT_ExportScene,
OOT_RemoveScene,
)
oot_level_panel_classes = (
@@ -678,12 +604,14 @@ def oot_level_register():
for cls in oot_level_classes:
register_class(cls)
bpy.types.Scene.ootSceneName = bpy.props.StringProperty(name = 'Name', default = 'bob')
bpy.types.Scene.ootSceneName = bpy.props.StringProperty(name = 'Name', default = 'spot03')
bpy.types.Scene.ootSceneSubFolder = bpy.props.StringProperty(name = "Subfolder", default = 'overworld')
bpy.types.Scene.ootSceneOption = bpy.props.EnumProperty(name = "Scene", items = ootEnumSceneID, default = 'SCENE_YDAN')
bpy.types.Scene.ootSceneExportPath = bpy.props.StringProperty(
name = 'Directory', subtype = 'FILE_PATH')
bpy.types.Scene.ootSceneCustomExport = bpy.props.BoolProperty(
name = 'Custom Export Path')
bpy.types.Scene.ootSceneExportObj = bpy.props.PointerProperty(type = bpy.types.Object, poll = isSceneObj)
def oot_level_unregister():
for cls in reversed(oot_level_classes):
@@ -692,4 +620,5 @@ def oot_level_unregister():
del bpy.types.Scene.ootSceneName
del bpy.types.Scene.ootSceneExportPath
del bpy.types.Scene.ootSceneCustomExport
del bpy.types.Scene.ootSceneOption
del bpy.types.Scene.ootSceneOption
del bpy.types.Scene.ootSceneSubFolder
+151
View File
@@ -0,0 +1,151 @@
import bpy, mathutils, math
from bpy.utils import register_class, unregister_class
from ..utility import *
from ..f3d.f3d_material import *
from ..operators import *
class OOT_AddWaterBox(AddWaterBox):
bl_idname = 'object.oot_add_water_box'
scale : bpy.props.FloatProperty(default = 10)
preset : bpy.props.StringProperty(default = "oot_shaded_texture_transparent")
matName : bpy.props.StringProperty(default = "oot_water_mat")
def setEmptyType(self, emptyObj):
emptyObj.ootEmptyType = "Water Box"
class OOT_AddDoor(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.oot_add_door'
bl_label = "Add Door"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
scale : bpy.props.FloatProperty(default = 2)
preset : bpy.props.StringProperty(default = "oot_shaded_solid")
matName : bpy.props.StringProperty(default = "unused_mat")
def execute(self, context):
if context.mode != "OBJECT":
bpy.ops.object.mode_set(mode = "OBJECT")
bpy.ops.object.select_all(action = "DESELECT")
objScale = (3.6 * self.scale, 0.7 * self.scale, 5.5 * self.scale)
location = mathutils.Vector(bpy.context.scene.cursor.location) +\
mathutils.Vector([0,0, 0.5 * objScale[2]])
bpy.ops.mesh.primitive_cube_add(align='WORLD', location=location[:], scale = objScale)
cubeObj = context.view_layer.objects.active
cubeObj.ignore_render = True
cubeObj.show_axis = True
cubeObj.name = "Door Collision"
addMaterialByName(cubeObj, self.matName, self.preset)
location += mathutils.Vector([0,0, - 0.5 * objScale[2]])
bpy.ops.object.empty_add(type='CUBE', radius = 1, align='WORLD', location=location[:])
emptyObj = context.view_layer.objects.active
emptyObj.ootEmptyType = "Transition Actor"
emptyObj.name = "Door Actor"
emptyObj.ootTransitionActorProperty.actor.actorID = "ACTOR_DOOR_SHUTTER"
emptyObj.ootTransitionActorProperty.actor.actorParam = "0x0000"
parentObject(cubeObj, emptyObj)
setOrigin(emptyObj, cubeObj)
return {"FINISHED"}
class OOT_AddScene(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.oot_add_scene'
bl_label = "Add Scene"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
scale : bpy.props.FloatProperty(default = 30)
preset : bpy.props.StringProperty(default = "oot_shaded_solid")
matName : bpy.props.StringProperty(default = "floor_mat")
def execute(self, context):
if context.mode != "OBJECT":
bpy.ops.object.mode_set(mode = "OBJECT")
bpy.ops.object.select_all(action = "DESELECT")
location = mathutils.Vector(bpy.context.scene.cursor.location)
bpy.ops.mesh.primitive_plane_add(size=2 * self.scale, enter_editmode=False, align='WORLD', location=location[:])
planeObj = context.view_layer.objects.active
planeObj.name = "Floor"
addMaterialByName(planeObj, self.matName, self.preset)
bpy.ops.object.empty_add(type='CONE', radius = 1, align='WORLD', location=location[:])
entranceObj = context.view_layer.objects.active
entranceObj.ootEmptyType = "Entrance"
entranceObj.name = "Entrance"
entranceObj.ootEntranceProperty.actor.actorParam = "0x0FFF"
parentObject(planeObj, entranceObj)
location += mathutils.Vector([0,0, 10])
bpy.ops.object.empty_add(type='SPHERE', radius = 1, align='WORLD', location=location[:])
roomObj = context.view_layer.objects.active
roomObj.ootEmptyType = "Room"
roomObj.name = "Room"
parentObject(roomObj, planeObj)
location += mathutils.Vector([0,0, 2])
bpy.ops.object.empty_add(type='SPHERE', radius = 1, align='WORLD', location=location[:])
sceneObj = context.view_layer.objects.active
sceneObj.ootEmptyType = "Scene"
sceneObj.name = "Scene"
parentObject(sceneObj, roomObj)
bpy.context.scene.ootSceneExportObj = sceneObj
#setOrigin(emptyObj, cubeObj)
return {"FINISHED"}
class OOT_OperatorsPanel(bpy.types.Panel):
bl_idname = "OOT_PT_operators"
bl_label = "OOT Tools"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'OOT'
@classmethod
def poll(cls, context):
return True
# called every frame
def draw(self, context):
col = self.layout.column()
col.operator(OOT_AddScene.bl_idname)
col.operator(OOT_AddWaterBox.bl_idname)
col.operator(OOT_AddDoor.bl_idname)
oot_operator_classes = (
OOT_AddWaterBox,
OOT_AddDoor,
OOT_AddScene
)
oot_operator_panel_classes = (
OOT_OperatorsPanel,
)
def oot_operator_panel_register():
for cls in oot_operator_panel_classes:
register_class(cls)
def oot_operator_panel_unregister():
for cls in oot_operator_panel_classes:
unregister_class(cls)
def oot_operator_register():
for cls in oot_operator_classes:
register_class(cls)
def oot_operator_unregister():
for cls in reversed(oot_operator_classes):
unregister_class(cls)
+1 -11
View File
@@ -1,4 +1,5 @@
import re
from ..utility import *
class BlenderEnumItem:
def __init__(self, key, name, description):
@@ -9,17 +10,6 @@ class BlenderEnumItem:
def toC(self):
return '\t("' + self.key + '", "' + self.name + '", "' + self.description + '"),\n'
def readFile(filepath):
datafile = open(filepath, 'r')
data = datafile.read()
datafile.close()
return data
def writeFile(filepath, data):
datafile = open(filepath, 'w')
datafile.write(data)
datafile.close()
def createEnum(enumName, enumList):
enumData = enumName + ' = [\n'
for item in enumList:
+141 -87
View File
@@ -7,7 +7,7 @@ from ..f3d.f3d_gbi import *
from .oot_constants import *
from .oot_utility import *
from .oot_actor import *
from .oot_collision import *
#from .oot_collision import *
from ..utility import *
@@ -19,18 +19,20 @@ class OOT_SearchMusicSeqEnumOperator(bpy.types.Operator):
ootMusicSeq : bpy.props.EnumProperty(items = ootEnumMusicSeq, default = "0x02")
headerIndex : bpy.props.IntProperty(default = 0, min = 0)
objName : bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects[self.objName]
if self.headerIndex == 0:
sceneHeader = context.object.ootSceneHeader
sceneHeader = obj.ootSceneHeader
elif self.headerIndex == 1:
sceneHeader = context.object.ootAlternateSceneHeaders.childNightHeader
sceneHeader = obj.ootAlternateSceneHeaders.childNightHeader
elif self.headerIndex == 2:
sceneHeader = context.object.ootAlternateSceneHeaders.adultDayHeader
sceneHeader = obj.ootAlternateSceneHeaders.adultDayHeader
elif self.headerIndex == 3:
sceneHeader = context.object.ootAlternateSceneHeaders.adultNightHeader
sceneHeader = obj.ootAlternateSceneHeaders.adultNightHeader
else:
sceneHeader = context.object.ootAlternateSceneHeaders.cutsceneHeaders[self.headerIndex - 4]
sceneHeader = obj.ootAlternateSceneHeaders.cutsceneHeaders[self.headerIndex - 4]
sceneHeader.musicSeq = self.ootMusicSeq
bpy.context.region.tag_redraw()
@@ -50,18 +52,20 @@ class OOT_SearchObjectEnumOperator(bpy.types.Operator):
ootObjectID : bpy.props.EnumProperty(items = ootEnumObjectID, default = "OBJECT_HUMAN")
headerIndex : bpy.props.IntProperty(default = 0, min = 0)
index : bpy.props.IntProperty(default = 0, min = 0)
objName : bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects[self.objName]
if self.headerIndex == 0:
roomHeader = context.object.ootRoomHeader
roomHeader = obj.ootRoomHeader
elif self.headerIndex == 1:
roomHeader = context.object.ootAlternateRoomHeaders.childNightHeader
roomHeader = obj.ootAlternateRoomHeaders.childNightHeader
elif self.headerIndex == 2:
roomHeader = context.object.ootAlternateRoomHeaders.adultDayHeader
roomHeader = obj.ootAlternateRoomHeaders.adultDayHeader
elif self.headerIndex == 3:
roomHeader = context.object.ootAlternateRoomHeaders.adultNightHeader
roomHeader = obj.ootAlternateRoomHeaders.adultNightHeader
else:
roomHeader = context.object.ootAlternateRoomHeaders.cutsceneHeaders[self.headerIndex - 4]
roomHeader = obj.ootAlternateRoomHeaders.cutsceneHeaders[self.headerIndex - 4]
roomHeader.objectList[self.index].objectID = self.ootObjectID
bpy.context.region.tag_redraw()
@@ -74,27 +78,33 @@ class OOT_SearchObjectEnumOperator(bpy.types.Operator):
class OOT_SearchSceneEnumOperator(bpy.types.Operator):
bl_idname = "object.oot_search_scene_enum_operator"
bl_label = "Search Scene ID"
bl_label = "Choose Scene"
bl_property = "ootSceneID"
bl_options = {'REGISTER', 'UNDO'}
ootSceneID : bpy.props.EnumProperty(items = ootEnumSceneID, default = "SCENE_YDAN")
headerIndex : bpy.props.IntProperty(default = -1, min = -1)
index : bpy.props.IntProperty(default = 0, min = 0)
objName : bpy.props.StringProperty()
def execute(self, context):
if self.objName != "":
obj = bpy.data.objects[self.objName]
else:
obj = None
if self.headerIndex == -1:
pass
elif self.headerIndex == 0:
sceneHeader = context.object.ootSceneHeader
sceneHeader = obj.ootSceneHeader
elif self.headerIndex == 1:
sceneHeader = context.object.ootAlternateSceneHeaders.childNightHeader
sceneHeader = obj.ootAlternateSceneHeaders.childNightHeader
elif self.headerIndex == 2:
sceneHeader = context.object.ootAlternateSceneHeaders.adultDayHeader
sceneHeader = obj.ootAlternateSceneHeaders.adultDayHeader
elif self.headerIndex == 3:
sceneHeader = context.object.ootAlternateSceneHeaders.adultNightHeader
sceneHeader = obj.ootAlternateSceneHeaders.adultNightHeader
else:
sceneHeader = context.object.ootAlternateSceneHeaders.cutsceneHeaders[self.headerIndex - 4]
sceneHeader = obj.ootAlternateSceneHeaders.cutsceneHeaders[self.headerIndex - 4]
if self.headerIndex == -1:
context.scene.ootSceneOption = self.ootSceneID
@@ -108,25 +118,26 @@ class OOT_SearchSceneEnumOperator(bpy.types.Operator):
context.window_manager.invoke_search_popup(self)
return {'RUNNING_MODAL'}
def drawAlternateRoomHeaderProperty(layout, headerProp):
def drawAlternateRoomHeaderProperty(layout, headerProp, objName):
headerSetup = layout.column()
#headerSetup.box().label(text = "Alternate Headers")
headerSetupBox = headerSetup.column()
headerSetupBox.row().prop(headerProp, "headerMenuTab", expand = True)
if headerProp.headerMenuTab == "Child Night":
drawRoomHeaderProperty(headerSetupBox, headerProp.childNightHeader, None, 1)
drawRoomHeaderProperty(headerSetupBox, headerProp.childNightHeader, None, 1, objName)
elif headerProp.headerMenuTab == "Adult Day":
drawRoomHeaderProperty(headerSetupBox, headerProp.adultDayHeader, None, 2)
drawRoomHeaderProperty(headerSetupBox, headerProp.adultDayHeader, None, 2, objName)
elif headerProp.headerMenuTab == "Adult Night":
drawRoomHeaderProperty(headerSetupBox, headerProp.adultNightHeader, None, 3)
drawRoomHeaderProperty(headerSetupBox, headerProp.adultNightHeader, None, 3, objName)
elif headerProp.headerMenuTab == "Cutscene":
#headerSetup.box().label(text = "Cutscene Headers")
for i in range(len(headerProp.cutsceneHeaders)):
box = headerSetup.box()
drawRoomHeaderProperty(box, headerProp.cutsceneHeaders[i], "Header " + str(i + 4), i + 4)
drawAddButton(headerSetup, len(headerProp.cutsceneHeaders), "Room", None)
prop_split(headerSetup, headerProp, 'currentCutsceneIndex', "Cutscene Index")
drawAddButton(headerSetup, len(headerProp.cutsceneHeaders), "Room", None, objName)
index = headerProp.currentCutsceneIndex
if index - 4 < len(headerProp.cutsceneHeaders):
drawRoomHeaderProperty(headerSetup, headerProp.cutsceneHeaders[index - 4], None, index, objName)
else:
headerSetup.label(text = "No cutscene header for this index.", icon = "ERROR")
class OOTExitProperty(bpy.types.PropertyGroup):
expandTab : bpy.props.BoolProperty(name = "Expand Tab")
@@ -146,13 +157,13 @@ class OOTExitProperty(bpy.types.PropertyGroup):
fadeOutAnim : bpy.props.EnumProperty(items = ootEnumTransitionAnims, default = '0x02')
fadeOutAnimCustom : bpy.props.StringProperty(default = '0x02')
def drawExitProperty(layout, exitProp, index, headerIndex):
def drawExitProperty(layout, exitProp, index, headerIndex, objName):
box = layout.box()
box.prop(exitProp, 'expandTab', text = 'Exit ' + \
str(index), icon = 'TRIA_DOWN' if exitProp.expandTab else \
str(index + 1), icon = 'TRIA_DOWN' if exitProp.expandTab else \
'TRIA_RIGHT')
if exitProp.expandTab:
drawCollectionOps(box, index, "Exit", headerIndex)
drawCollectionOps(box, index, "Exit", headerIndex, objName)
drawEnumWithCustom(box, exitProp, "exitIndex", "Exit Index", "")
if exitProp.exitIndex != "Custom":
box.label(text = "This is unfinished, use \"Custom\".")
@@ -170,16 +181,17 @@ class OOTObjectProperty(bpy.types.PropertyGroup):
objectID : bpy.props.EnumProperty(items = ootEnumObjectID, default = 'OBJECT_HUMAN')
objectIDCustom : bpy.props.StringProperty(default = 'OBJECT_HUMAN')
def drawObjectProperty(layout, objectProp, headerIndex, index):
def drawObjectProperty(layout, objectProp, headerIndex, index, objName):
objItemBox = layout.box()
objectName = getEnumName(ootEnumObjectID, objectProp.objectID)
objItemBox.prop(objectProp, 'expandTab', text = objectName,
icon = 'TRIA_DOWN' if objectProp.expandTab else \
'TRIA_RIGHT')
if objectProp.expandTab:
drawCollectionOps(objItemBox, index, "Object", headerIndex)
drawCollectionOps(objItemBox, index, "Object", headerIndex, objName)
objSearch = objItemBox.operator(OOT_SearchObjectEnumOperator.bl_idname, icon = 'VIEWZOOM')
objSearch.objName = objName
objItemBox.column().label(text = "ID: " + objectName)
#prop_split(objItemBox, objectProp, "objectID", name = "ID")
if objectProp.objectID == "Custom":
@@ -189,12 +201,12 @@ def drawObjectProperty(layout, objectProp, headerIndex, index):
class OOTLightProperty(bpy.types.PropertyGroup):
ambient : bpy.props.FloatVectorProperty(name = "Ambient Color", size = 4, min = 0, max = 1, default = (70/255, 40/255, 57/255 ,1), subtype = 'COLOR')
useCustomDiffuse0 : bpy.props.BoolProperty(name = 'Use Custom Light Object')
useCustomDiffuse1 : bpy.props.BoolProperty(name = 'Use Custom Light Object')
useCustomDiffuse0 : bpy.props.BoolProperty(name = 'Use Custom Diffuse 0 Light Object')
useCustomDiffuse1 : bpy.props.BoolProperty(name = 'Use Custom Diffuse 1 Light Object')
diffuse0 : bpy.props.FloatVectorProperty(name = "", size = 4, min = 0, max = 1, default = (180/255, 154/255, 138/255 ,1), subtype = 'COLOR')
diffuse1 : bpy.props.FloatVectorProperty(name = "", size = 4, min = 0, max = 1, default = (20/255, 20/255, 60/255 ,1), subtype = 'COLOR')
diffuse0Custom : bpy.props.PointerProperty(name = "Fill Light", type = bpy.types.Light)
diffuse1Custom : bpy.props.PointerProperty(name = "Key Light", type = bpy.types.Light)
diffuse0Custom : bpy.props.PointerProperty(name = "Diffuse 0", type = bpy.types.Light)
diffuse1Custom : bpy.props.PointerProperty(name = "Diffuse 1", type = bpy.types.Light)
fogColor : bpy.props.FloatVectorProperty(name = "", size = 4, min = 0, max = 1, default = (140/255, 120/255, 110/255 ,1), subtype = 'COLOR')
fogNear : bpy.props.IntProperty(name = "", default = 993, min = 0, max = 2**10 - 1)
transitionSpeed : bpy.props.IntProperty(name = "", default = 1, min = 0, max = 63)
@@ -208,31 +220,25 @@ class OOTLightGroupProperty(bpy.types.PropertyGroup):
day : bpy.props.PointerProperty(type = OOTLightProperty)
dusk : bpy.props.PointerProperty(type = OOTLightProperty)
night : bpy.props.PointerProperty(type = OOTLightProperty)
defaultsSet : bpy.props.BoolProperty()
def drawLightGroupProperty(layout, timeOfDayLighting, lightGroupProp, index, sceneHeaderIndex):
box = layout.box()
box.prop(lightGroupProp, 'expandTab', text = 'Lighting ' + \
str(index), icon = 'TRIA_DOWN' if lightGroupProp.expandTab else \
'TRIA_RIGHT')
if lightGroupProp.expandTab:
drawCollectionOps(box, index, "Light", sceneHeaderIndex)
if timeOfDayLighting:
box.row().prop(lightGroupProp, 'menuTab', expand = True)
if lightGroupProp.menuTab == "Dawn":
drawLightProperty(box, lightGroupProp.dawn, "Dawn", False)
if lightGroupProp.menuTab == "Day":
drawLightProperty(box, lightGroupProp.day, "Day", False)
if lightGroupProp.menuTab == "Dusk":
drawLightProperty(box, lightGroupProp.dusk, "Dusk", False)
if lightGroupProp.menuTab == "Night":
drawLightProperty(box, lightGroupProp.night, "Night", False)
else:
drawLightProperty(box, lightGroupProp.dawn, "Light", False)
def drawLightGroupProperty(layout, lightGroupProp):
def drawLightProperty(layout, lightProp, name, showExpandTab):
box = layout.column()
box.row().prop(lightGroupProp, 'menuTab', expand = True)
if lightGroupProp.menuTab == "Dawn":
drawLightProperty(box, lightGroupProp.dawn, "Dawn", False, None, None, None)
if lightGroupProp.menuTab == "Day":
drawLightProperty(box, lightGroupProp.day, "Day", False, None, None, None)
if lightGroupProp.menuTab == "Dusk":
drawLightProperty(box, lightGroupProp.dusk, "Dusk", False, None, None, None)
if lightGroupProp.menuTab == "Night":
drawLightProperty(box, lightGroupProp.night, "Night", False, None, None, None)
def drawLightProperty(layout, lightProp, name, showExpandTab, index, sceneHeaderIndex, objName):
if showExpandTab:
box = layout.box()
box = layout.box().column()
box.prop(lightProp, 'expandTab', text = name,
icon = 'TRIA_DOWN' if lightProp.expandTab else \
'TRIA_RIGHT')
@@ -242,18 +248,22 @@ def drawLightProperty(layout, lightProp, name, showExpandTab):
expandTab = True
if expandTab:
if index is not None:
drawCollectionOps(box, index, "Light", sceneHeaderIndex, objName)
prop_split(box, lightProp, 'ambient', 'Ambient Color')
if lightProp.useCustomDiffuse0:
prop_split(box, lightProp, 'diffuse0Custom', 'Fill Light')
prop_split(box, lightProp, 'diffuse0Custom', 'Diffuse 0')
box.label(text = "Make sure light is not part of scene hierarchy.", icon = "ERROR")
else:
prop_split(box, lightProp, 'diffuse0', 'Fill Light')
prop_split(box, lightProp, 'diffuse0', 'Diffuse 0')
box.prop(lightProp, "useCustomDiffuse0")
if lightProp.useCustomDiffuse1:
prop_split(box, lightProp, 'diffuse1Custom', 'Key Light')
prop_split(box, lightProp, 'diffuse1Custom', 'Diffuse 1')
box.label(text = "Make sure light is not part of scene hierarchy.", icon = "ERROR")
else:
prop_split(box, lightProp, 'diffuse1', 'Key Light')
prop_split(box, lightProp, 'diffuse1', 'Diffuse 1')
box.prop(lightProp, "useCustomDiffuse1")
prop_split(box, lightProp, 'fogColor', 'Fog Color')
@@ -261,6 +271,10 @@ def drawLightProperty(layout, lightProp, name, showExpandTab):
prop_split(box, lightProp, 'drawDistance', 'Draw Distance')
prop_split(box, lightProp, 'transitionSpeed', 'Transition Speed')
class OOTSceneTableEntryProperty(bpy.types.PropertyGroup):
drawConfig : bpy.props.IntProperty(name = "Scene Draw Config", min = 0)
hasTitle : bpy.props.BoolProperty(default = True)
class OOTSceneHeaderProperty(bpy.types.PropertyGroup):
expandTab : bpy.props.BoolProperty(name = "Expand Tab")
usePreviousHeader : bpy.props.BoolProperty(name = "Use Previous Header", default = True)
@@ -270,7 +284,7 @@ class OOTSceneHeaderProperty(bpy.types.PropertyGroup):
naviCup : bpy.props.EnumProperty(name = "Navi Hints", default = '0x00', items = ootEnumNaviHints)
naviCupCustom : bpy.props.StringProperty(name = "Navi Hints Custom", default = '0x00')
skyboxID : bpy.props.EnumProperty(name = "Skybox", items = ootEnumSkybox, default = "0x00")
skyboxID : bpy.props.EnumProperty(name = "Skybox", items = ootEnumSkybox, default = "0x01")
skyboxIDCustom : bpy.props.StringProperty(name = "Skybox ID", default = '0')
skyboxCloudiness : bpy.props.EnumProperty(name = "Cloudiness", items = ootEnumCloudiness, default = "0x00")
skyboxCloudinessCustom : bpy.props.StringProperty(name = "Cloudiness ID", default = '0x00')
@@ -289,20 +303,26 @@ class OOTSceneHeaderProperty(bpy.types.PropertyGroup):
audioSessionPreset : bpy.props.EnumProperty(name = "Audio Session Preset", items = ootEnumAudioSessionPreset, default = "0x00")
audioSessionPresetCustom : bpy.props.StringProperty(name = "Audio Session Preset", default = "0x00")
lightList : bpy.props.CollectionProperty(type = OOTLightGroupProperty, name = 'Lighting List')
timeOfDayLights : bpy.props.PointerProperty(type = OOTLightGroupProperty, name = "Time Of Day Lighting")
lightList : bpy.props.CollectionProperty(type = OOTLightProperty, name = 'Lighting List')
exitList : bpy.props.CollectionProperty(type = OOTExitProperty, name = "Exit List")
sceneTableEntry : bpy.props.PointerProperty(type = OOTSceneTableEntryProperty)
menuTab : bpy.props.EnumProperty(name = "Menu", items = ootEnumSceneMenu)
altMenuTab : bpy.props.EnumProperty(name = "Menu", items = ootEnumSceneMenuAlternate)
def drawSceneHeaderProperty(layout, sceneProp, dropdownLabel, headerIndex):
def drawSceneTableEntryProperty(layout, sceneTableEntryProp):
prop_split(layout, sceneTableEntryProp, "drawConfig", "Draw Config")
def drawSceneHeaderProperty(layout, sceneProp, dropdownLabel, headerIndex, objName):
if dropdownLabel is not None:
layout.prop(sceneProp, 'expandTab', text = dropdownLabel,
icon = 'TRIA_DOWN' if sceneProp.expandTab else 'TRIA_RIGHT')
if not sceneProp.expandTab:
return
if headerIndex is not None and headerIndex > 3:
drawCollectionOps(layout, headerIndex - 4, "Scene", None)
drawCollectionOps(layout, headerIndex - 4, "Scene", None, objName)
if headerIndex is not None and headerIndex > 0 and headerIndex < 4:
layout.prop(sceneProp, "usePreviousHeader", text = "Use Previous Header")
@@ -319,6 +339,8 @@ def drawSceneHeaderProperty(layout, sceneProp, dropdownLabel, headerIndex):
if menuTab == 'General':
general = layout.column()
general.box().label(text = "General")
if headerIndex is None or headerIndex == 0:
drawSceneTableEntryProperty(layout, sceneProp.sceneTableEntry)
drawEnumWithCustom(general, sceneProp, 'globalObject', "Global Object", "")
drawEnumWithCustom(general, sceneProp, 'naviCup', "Navi Hints", "")
@@ -328,6 +350,7 @@ def drawSceneHeaderProperty(layout, sceneProp, dropdownLabel, headerIndex):
drawEnumWithCustom(skyboxAndSound, sceneProp, 'skyboxCloudiness', "Cloudiness", "")
drawEnumWithCustom(skyboxAndSound, sceneProp, 'musicSeq', "Music Sequence", "")
musicSearch = skyboxAndSound.operator(OOT_SearchMusicSeqEnumOperator.bl_idname, icon = 'VIEWZOOM')
musicSearch.objName = objName
musicSearch.headerIndex = headerIndex if headerIndex is not None else 0
drawEnumWithCustom(skyboxAndSound, sceneProp, 'nightSeq', "Nighttime SFX", "")
drawEnumWithCustom(skyboxAndSound, sceneProp, 'audioSessionPreset', "Audio Session Preset", "")
@@ -341,19 +364,21 @@ def drawSceneHeaderProperty(layout, sceneProp, dropdownLabel, headerIndex):
lighting = layout.column()
lighting.box().label(text = "Lighting List")
drawEnumWithCustom(lighting, sceneProp, 'skyboxLighting', "Lighting Mode", "")
for i in range(len(sceneProp.lightList)):
drawLightGroupProperty(lighting, sceneProp.skyboxLighting == "0x00", sceneProp.lightList[i], i, headerIndex)
drawAddButton(lighting, len(sceneProp.lightList), "Light", headerIndex)
if sceneProp.skyboxLighting == '0x00': # Time of Day
drawLightGroupProperty(lighting, sceneProp.timeOfDayLights)
else:
for i in range(len(sceneProp.lightList)):
drawLightProperty(lighting, sceneProp.lightList[i], "Lighting " + str(i), True, i, headerIndex, objName)
drawAddButton(lighting, len(sceneProp.lightList), "Light", headerIndex, objName)
if menuTab == 'Exits':
if headerIndex is None or headerIndex == 0:
exitBox = layout.column()
exitBox.box().label(text = "Exit List")
for i in range(len(sceneProp.exitList)):
drawExitProperty(exitBox, sceneProp.exitList[i], i, headerIndex)
drawExitProperty(exitBox, sceneProp.exitList[i], i, headerIndex, objName)
drawAddButton(exitBox, len(sceneProp.exitList), "Exit", headerIndex)
drawAddButton(exitBox, len(sceneProp.exitList), "Exit", headerIndex, objName)
else:
layout.label(text = "Exits are edited in main header.")
@@ -395,7 +420,7 @@ class OOTRoomHeaderProperty(bpy.types.PropertyGroup):
meshType : bpy.props.EnumProperty(items = ootEnumMeshType, default = '0')
def drawRoomHeaderProperty(layout, roomProp, dropdownLabel, headerIndex):
def drawRoomHeaderProperty(layout, roomProp, dropdownLabel, headerIndex, objName):
if dropdownLabel is not None:
layout.prop(roomProp, 'expandTab', text = dropdownLabel,
@@ -403,7 +428,7 @@ def drawRoomHeaderProperty(layout, roomProp, dropdownLabel, headerIndex):
if not roomProp.expandTab:
return
if headerIndex is not None and headerIndex > 3:
drawCollectionOps(layout, headerIndex - 4, "Room", None)
drawCollectionOps(layout, headerIndex - 4, "Room", None, objName)
if headerIndex is not None and headerIndex > 0 and headerIndex < 4:
layout.prop(roomProp, "usePreviousHeader", text = "Use Previous Header")
@@ -425,6 +450,8 @@ def drawRoomHeaderProperty(layout, roomProp, dropdownLabel, headerIndex):
prop_split(general, roomProp, 'meshType', "Mesh Type")
if roomProp.meshType == '1':
general.box().label(text = "Mesh Type 1 not supported at this time.")
elif roomProp.meshType == '2':
general.box().label(text = "Mesh Type 2 not supported at this time.")
# Behaviour
behaviourBox = layout.column()
@@ -465,8 +492,8 @@ def drawRoomHeaderProperty(layout, roomProp, dropdownLabel, headerIndex):
objBox = layout.column()
objBox.box().label(text = "Objects")
for i in range(len(roomProp.objectList)):
drawObjectProperty(objBox, roomProp.objectList[i], headerIndex, i)
drawAddButton(objBox, len(roomProp.objectList), "Object", headerIndex)
drawObjectProperty(objBox, roomProp.objectList[i], headerIndex, i, objName)
drawAddButton(objBox, len(roomProp.objectList), "Object", headerIndex, objName)
class OOTAlternateSceneHeaderProperty(bpy.types.PropertyGroup):
childNightHeader : bpy.props.PointerProperty(name = "Child Night Header", type = OOTSceneHeaderProperty)
@@ -475,26 +502,28 @@ class OOTAlternateSceneHeaderProperty(bpy.types.PropertyGroup):
cutsceneHeaders : bpy.props.CollectionProperty(type = OOTSceneHeaderProperty)
headerMenuTab : bpy.props.EnumProperty(name = "Header Menu", items = ootEnumHeaderMenu)
currentCutsceneIndex : bpy.props.IntProperty(min = 4, default = 4)
def drawAlternateSceneHeaderProperty(layout, headerProp):
def drawAlternateSceneHeaderProperty(layout, headerProp, objName):
headerSetup = layout.column()
#headerSetup.box().label(text = "Alternate Headers")
headerSetupBox = headerSetup.column()
headerSetupBox.row().prop(headerProp, "headerMenuTab", expand = True)
if headerProp.headerMenuTab == "Child Night":
drawSceneHeaderProperty(headerSetupBox, headerProp.childNightHeader, None, 1)
drawSceneHeaderProperty(headerSetupBox, headerProp.childNightHeader, None, 1, objName)
elif headerProp.headerMenuTab == "Adult Day":
drawSceneHeaderProperty(headerSetupBox, headerProp.adultDayHeader, None, 2)
drawSceneHeaderProperty(headerSetupBox, headerProp.adultDayHeader, None, 2, objName)
elif headerProp.headerMenuTab == "Adult Night":
drawSceneHeaderProperty(headerSetupBox, headerProp.adultNightHeader, None, 3)
drawSceneHeaderProperty(headerSetupBox, headerProp.adultNightHeader, None, 3, objName)
elif headerProp.headerMenuTab == "Cutscene":
#headerSetup.box().label(text = "Cutscene Headers")
for i in range(len(headerProp.cutsceneHeaders)):
box = headerSetup.box()
drawSceneHeaderProperty(box, headerProp.cutsceneHeaders[i], "Header " + str(i + 4), i + 4)
drawAddButton(headerSetup, len(headerProp.cutsceneHeaders), "Scene", None)
prop_split(headerSetup, headerProp, 'currentCutsceneIndex', "Cutscene Index")
drawAddButton(headerSetup, len(headerProp.cutsceneHeaders), "Scene", None, objName)
index = headerProp.currentCutsceneIndex
if index - 4 < len(headerProp.cutsceneHeaders):
drawSceneHeaderProperty(headerSetup, headerProp.cutsceneHeaders[index - 4], None, index, objName)
else:
headerSetup.label(text = "No cutscene header for this index.", icon = "ERROR")
class OOTAlternateRoomHeaderProperty(bpy.types.PropertyGroup):
childNightHeader : bpy.props.PointerProperty(name = "Child Night Header", type = OOTRoomHeaderProperty)
@@ -502,4 +531,29 @@ class OOTAlternateRoomHeaderProperty(bpy.types.PropertyGroup):
adultNightHeader : bpy.props.PointerProperty(name = "Adult Night Header", type = OOTRoomHeaderProperty)
cutsceneHeaders : bpy.props.CollectionProperty(type = OOTRoomHeaderProperty)
headerMenuTab : bpy.props.EnumProperty(name = "Header Menu", items = ootEnumHeaderMenu)
headerMenuTab : bpy.props.EnumProperty(name = "Header Menu", items = ootEnumHeaderMenu)
currentCutsceneIndex : bpy.props.IntProperty(min = 4, default = 4)
def drawParentSceneRoom(box, obj):
sceneObj = getSceneObj(obj)
roomObj = getRoomObj(obj)
#box = layout.box().column()
box.box().column().label(text = "Parent Scene/Room Settings")
box.row().prop(obj, 'ootObjectMenu', expand = True)
if obj.ootObjectMenu == "Scene":
if sceneObj is not None:
drawSceneHeaderProperty(box, sceneObj.ootSceneHeader, None, None, sceneObj.name)
if sceneObj.ootSceneHeader.menuTab == 'Alternate':
drawAlternateSceneHeaderProperty(box, sceneObj.ootAlternateSceneHeaders, sceneObj.name)
else:
box.label(text = "This object is not part of any Scene hierarchy.", icon = "ERROR")
elif obj.ootObjectMenu == "Room":
if roomObj is not None:
drawRoomHeaderProperty(box, roomObj.ootRoomHeader, None, None, roomObj.name)
if roomObj.ootRoomHeader.menuTab == 'Alternate':
drawAlternateRoomHeaderProperty(box, roomObj.ootAlternateRoomHeaders, roomObj.name)
else:
box.label(text = "This object is not part of any Room hierarchy.", icon = "ERROR")
+8 -7
View File
@@ -341,20 +341,21 @@ def ootProcessBone(fModel, boneName, parentLimb, nextIndex, meshObj, armatureObj
return nextIndex
def ootConvertArmatureToC(originalArmatureObj, convertTransformMatrix,
f3dType, isHWv1, skeletonName, folderName, DLFormat, convertTextureData, exportPath, isCustomExport):
f3dType, isHWv1, skeletonName, folderName, DLFormat, savePNG, exportPath, isCustomExport):
skeleton, fModel = ootConvertArmatureToSkeletonWithMesh(originalArmatureObj, convertTransformMatrix,
f3dType, isHWv1, skeletonName, DLFormat, convertTextureData)
f3dType, isHWv1, skeletonName, DLFormat, not savePNG)
data = CData()
data.source += '#include "ultra64.h"\n#include "global.h"\n\n'
exportData = fModel.to_c(
TextureExportSettings(False, not convertTextureData, "test"), OOTGfxFormatter(ScrollMethod.Vertex))
TextureExportSettings(False, savePNG, "test"), OOTGfxFormatter(ScrollMethod.Vertex))
skeletonC = skeleton.toC()
data.append(exportData.all())
data.append(skeletonC)
path = ootGetPath(exportPath, isCustomExport, 'assets/objects/', folderName)
path = ootGetPath(exportPath, isCustomExport, 'assets/objects/', '')
writeCData(data,
os.path.join(path, folderName + '.h'),
os.path.join(path, folderName + '.c'))
@@ -373,7 +374,7 @@ class OOT_ExportSkeleton(bpy.types.Operator):
def execute(self, context):
armatureObj = None
if context.mode != 'OBJECT':
raise PluginError("Operator can only be used in object mode.")
bpy.ops.object.mode_set(mode = "OBJECT")
if len(context.selected_objects) == 0:
raise PluginError("Armature not selected.")
armatureObj = context.active_object
@@ -393,7 +394,7 @@ class OOT_ExportSkeleton(bpy.types.Operator):
# context.scene.geoExportPath, context.scene.geoLevelName,
# context.scene.geoLevelOption)
saveTextures = bpy.context.scene.ootSaveTextures or bpy.context.scene.ignoreTextureRestrictions
saveTextures = bpy.context.scene.saveTextures or bpy.context.scene.ignoreTextureRestrictions
isHWv1 = context.scene.isHWv1
f3dType = context.scene.f3d_type
skeletonName = context.scene.ootSkeletonName
@@ -440,7 +441,7 @@ oot_skeleton_classes = (
)
oot_skeleton_panels = (
OOT_ExportSkeletonPanel,
#OOT_ExportSkeletonPanel,
)
def oot_skeleton_panel_register():
+3
View File
@@ -3,6 +3,7 @@ from bpy.utils import register_class, unregister_class
from .oot_constants import *
from .oot_utility import *
from .oot_scene_room import *
class OOTPath:
def __init__(self, ownerName, splineIndex):
@@ -46,6 +47,8 @@ class OOTSplinePanel(bpy.types.Panel):
curve = context.object.data
if curve.splines[0].type != 'NURBS':
box.label(text = 'Only NURBS curves are compatible.')
#drawParentSceneRoom(box, context.object)
def isCurveValid(obj):
curve = obj.data
+180 -8
View File
@@ -2,6 +2,172 @@ from ..utility import *
import bpy, math, mathutils, os
from bpy.utils import register_class, unregister_class
ootSceneDungeons = [
"bdan",
"bdan_boss",
"bmori1",
"ddan",
"ddan_boss",
"fire_bs",
"ganon",
"ganontika",
"ganontikasonogo",
"ganon_boss",
"ganon_demo",
"ganon_final",
"ganon_sonogo",
"ganon_tou",
"gerudoway",
"hakadan",
"hakadanch",
"hakadan_bs",
"hidan",
"ice_doukutu",
"jyasinboss",
"jyasinzou",
"men",
"mizusin",
"mizusin_bs",
"moribossroom",
"ydan",
"ydan_boss",
]
ootSceneIndoors = [
"bowling",
"daiyousei_izumi",
"hairal_niwa",
"hairal_niwa2",
"hairal_niwa_n",
"hakasitarelay",
"hut",
"hylia_labo",
"impa",
"kakariko",
"kenjyanoma",
"kokiri_home",
"kokiri_home3",
"kokiri_home4",
"kokiri_home5",
"labo",
"link_home",
"mahouya",
"malon_stable",
"miharigoya",
"nakaniwa",
"syatekijyou",
"takaraya",
"tent",
"tokinoma",
"yousei_izumi_tate",
"yousei_izumi_yoko",
]
ootSceneMisc = [
"enrui",
"entra_n",
"hakaana",
"hakaana2",
"hakaana_ouke",
"hiral_demo",
"kakariko3",
"kakusiana",
"kinsuta",
"market_alley",
"market_alley_n",
"market_day",
"market_night",
"market_ruins",
"shrine",
"shrine_n",
"shrine_r",
"turibori",
]
ootSceneOverworld = [
"entra",
"souko",
"spot00",
"spot01",
"spot02",
"spot03",
"spot04",
"spot05",
"spot06",
"spot07",
"spot08",
"spot09",
"spot10",
"spot11",
"spot12",
"spot13",
"spot15",
"spot16",
"spot17",
"spot18",
"spot20",
]
ootSceneShops = [
"alley_shop",
"drag",
"face_shop",
"golon",
"kokiri_shop",
"night_shop",
"shop1",
"zoora",
]
ootSceneTest_levels = [
"besitu",
"depth_test",
"sasatest",
"sutaru",
"syotes",
"syotes2",
"test01",
"testroom",
]
ootSceneDirs = {
'assets/scenes/dungeons/' : ootSceneDungeons,
'assets/scenes/indoors/' : ootSceneIndoors,
'assets/scenes/misc/' : ootSceneMisc,
'assets/scenes/overworld/' : ootSceneOverworld,
'assets/scenes/shops/' : ootSceneShops,
'assets/scenes/test_levels/' : ootSceneTest_levels,
}
def getSceneDirFromLevelName(name):
for sceneDir, dirLevels in ootSceneDirs.items():
if name in dirLevels:
return sceneDir + name
return None
class ExportInfo:
def __init__(self, isCustomExport, exportPath, customSubPath, name):
self.isCustomExportPath = isCustomExport
self.exportPath = exportPath
self.customSubPath = customSubPath
self.name = name
def getSceneObj(obj):
while not (obj is None or (obj is not None and obj.data is None and obj.ootEmptyType == "Scene")):
obj = obj.parent
if obj is None:
return None
else:
return obj
def getRoomObj(obj):
while not (obj is None or (obj is not None and obj.data is None and obj.ootEmptyType == "Room")):
obj = obj.parent
if obj is None:
return None
else:
return obj
def checkEmptyName(name):
if name == "":
raise PluginError("No name entered for the exporter.")
@@ -79,7 +245,7 @@ def ootConvertTranslation(translation):
def ootConvertRotation(rotation):
# see BINANG_TO_DEGF
return [int(round(math.degrees(value) * 65535 / 360)) for value in rotation.to_euler()]
return [int(round((math.degrees(value) % 360) / 360 * (2**16))) % (2**16) for value in rotation.to_euler()]
def getCollectionFromIndex(obj, prop, subIndex, isRoom):
if not isRoom:
@@ -135,15 +301,16 @@ def getCollection(obj, collectionType, subIndex):
return collection
def drawAddButton(layout, index, collectionType, subIndex):
def drawAddButton(layout, index, collectionType, subIndex, objName):
if subIndex is None:
subIndex = 0
addOp = layout.operator(OOTCollectionAdd.bl_idname)
addOp.option = index
addOp.collectionType = collectionType
addOp.subIndex = subIndex
addOp.objName = objName
def drawCollectionOps(layout, index, collectionType, subIndex):
def drawCollectionOps(layout, index, collectionType, subIndex, objName):
if subIndex is None:
subIndex = 0
@@ -153,11 +320,13 @@ def drawCollectionOps(layout, index, collectionType, subIndex):
addOp.option = index + 1
addOp.collectionType = collectionType
addOp.subIndex = subIndex
addOp.objName = objName
removeOp = buttons.operator(OOTCollectionRemove.bl_idname, text = 'Delete', icon = "REMOVE")
removeOp.option = index
removeOp.collectionType = collectionType
removeOp.subIndex = subIndex
removeOp.objName = objName
#moveButtons = layout.row(align = True)
moveButtons = buttons
@@ -167,12 +336,14 @@ def drawCollectionOps(layout, index, collectionType, subIndex):
moveUp.offset = -1
moveUp.collectionType = collectionType
moveUp.subIndex = subIndex
moveUp.objName = objName
moveDown = moveButtons.operator(OOTCollectionMove.bl_idname, text = 'Down', icon = "TRIA_DOWN")
moveDown.option = index
moveDown.offset = 1
moveDown.collectionType = collectionType
moveDown.subIndex = subIndex
moveDown.objName = objName
class OOTCollectionAdd(bpy.types.Operator):
bl_idname = 'object.oot_collection_add'
@@ -182,10 +353,10 @@ class OOTCollectionAdd(bpy.types.Operator):
option : bpy.props.IntProperty()
collectionType : bpy.props.StringProperty(default = "Actor")
subIndex : bpy.props.IntProperty(default = 0)
objName : bpy.props.StringProperty()
def execute(self, context):
obj = context.object
collection = getCollection(obj, self.collectionType, self.subIndex)
collection = getCollection(bpy.data.objects[self.objName], self.collectionType, self.subIndex)
collection.add()
collection.move(len(collection)-1, self.option)
@@ -200,9 +371,10 @@ class OOTCollectionRemove(bpy.types.Operator):
option : bpy.props.IntProperty()
collectionType : bpy.props.StringProperty(default = "Actor")
subIndex : bpy.props.IntProperty(default = 0)
objName : bpy.props.StringProperty()
def execute(self, context):
collection = getCollection(context.object, self.collectionType, self.subIndex)
collection = getCollection(bpy.data.objects[self.objName], self.collectionType, self.subIndex)
collection.remove(self.option)
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
@@ -215,11 +387,11 @@ class OOTCollectionMove(bpy.types.Operator):
option : bpy.props.IntProperty()
offset : bpy.props.IntProperty()
subIndex : bpy.props.IntProperty(default = 0)
objName : bpy.props.StringProperty()
collectionType : bpy.props.StringProperty(default = "Actor")
def execute(self, context):
obj = context.object
collection = getCollection(obj, self.collectionType, self.subIndex)
collection = getCollection(bpy.data.objects[self.objName], self.collectionType, self.subIndex)
collection.move(self.option, self.option + self.offset)
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
+49
View File
@@ -0,0 +1,49 @@
import bpy, mathutils, math
from bpy.utils import register_class, unregister_class
from .utility import *
from .f3d.f3d_material import *
def addMaterialByName(obj, matName, preset):
if matName in bpy.data.materials:
bpy.ops.object.material_slot_add()
obj.material_slots[0].material = bpy.data.materials[matName]
else:
material = createF3DMat(obj, preset = preset)
material.name = matName
class AddWaterBox(bpy.types.Operator):
# set bl_ properties
bl_idname = 'object.add_water_box'
bl_label = "Add Water Box"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
scale : bpy.props.FloatProperty(default = 10)
preset : bpy.props.StringProperty(default = "Shaded Solid")
matName : bpy.props.StringProperty(default = "water_mat")
def setEmptyType(self, emptyObj):
return None
def execute(self, context):
if context.mode != "OBJECT":
bpy.ops.object.mode_set(mode = "OBJECT")
bpy.ops.object.select_all(action = "DESELECT")
location = mathutils.Vector(bpy.context.scene.cursor.location)
bpy.ops.mesh.primitive_plane_add(size=2 * self.scale, enter_editmode=False, align='WORLD', location=location[:])
planeObj = context.view_layer.objects.active
planeObj.ignore_collision = True
planeObj.name = "Water Box Mesh"
addMaterialByName(planeObj, self.matName, self.preset)
location += mathutils.Vector([0,0,-self.scale])
bpy.ops.object.empty_add(type='CUBE', radius = self.scale, align='WORLD', location=location[:])
emptyObj = context.view_layer.objects.active
emptyObj.name = "Water Box"
self.setEmptyType(emptyObj)
parentObject(planeObj, emptyObj)
return {"FINISHED"}
+185
View File
@@ -0,0 +1,185 @@
# Super Mario 64
### Tool Locations (SM64)
The armature geolayout inspector can be found in the properties editor under the bone tab in Pose Mode.
The object geolayout inspector can be found in the properties editor under the object tab.
The level camera settings can be found in the properties editor under the camera tab.
The RDP default settings can be found in the properties editor under the world tab.
### ROM File Settings
When importing from a ROM, the plugin will import from the ROM at filepath 'Import ROM', When exporting to a ROM, the plugin will make a copy of the file at 'Export ROM', modify it, and save the file to 'Output ROM'. The ROM must be expanded.
### Where Can I Export To (Binary)?
By default, when exporting to ROM, the plugin will copy the contents of bank 4 to the range (0x11A35B8 - 0x11FFF00). This will leave you with free space to export assets to range (0x11D8930 - 0x11FFF00).
### RDP State Optimization
A material will not set every single geometry mode / other mode option. Instead, each property will be checked against the default settings located in the world tab in the properties sidebar. If the values are the same, then the code to change it is not added.
### Geolayouts (Armature)
There are many different geolayout node types. In Blender, each node corresponds to a bone, whose type is defined in the armature geolayout inspector (properties editor under the bone tab in Pose Mode). Any imported bone is also added to a bone group, for the sake of color coding different types. These groups are for visuals only, and don't determine the node type. Imported geolayouts will already have these bone groups, but if you want to add them to your own armature use the 'Add Bone Groups' operator in the SM64 Armature Tools header. All regular bones (aka 0x13 commands) and 0x15 command bones are placed on armature layer 0. All other geolayout commands are placed on layer 1. Metarig generated bones are placed on layers 3 and 4 (see 'Animating Existing Geolayouts'). If you change a bone's geolayout command type and it dissapears, it might have been moved to a different layer. Make sure to go to the armature properties editor and set both layers 0 and 1 to be visible. Geolayout command types can be edited in the properties editor under the bone tab in pose mode.
### Geolayouts (Object)
Alternatively, for simple static geolayouts you can export object hierarchies. Geolayout properties such as draw layer can be located in the object properties tab.
### Exporting Geolayouts and Skinned Meshes
The N64 supports binary skinning, meaning each vertex will be influenced by one bone only. When skinning an exported geolayout, do NOT use automatic skinning, as this results in a smooth weight falloff. Instead, when weight painting set the weight to either 1 or 0, and set the brush Falloff to square. Skinning can only occur between an immediate parent and a child deform bone, not between siblings / across ancestors.
### Importing/Exporting SM64 Geolayouts
Download these documents by VL Tone:
[SM64MainLevelScripts.txt](http://qubedstudios.rustedlogic.net/SM64MainLevelScripts.txt)
[SM64GeoLayoutPtrsByLevels.txt](http://qubedstudios.rustedlogic.net/SM64GeoLayoutPtrsByLevels.txt)
For importing/exporting geolayouts:
- In SM64GeoLayoutPtrsByLevels.txt, search the name of the model.
- There you can get the modelID (Obj) and geolayout start (ROM Address start + offset)
- Note that these are decimal values, and must be converted to hex when used as inputs for fast64.
- In SM64MainLevelScripts, search '22 08 00 MM' where MM = modelID.
- There may be multiple instances, in which case you must use the offset field from before and check if it matches the last 3 bytes of the line.
- There you can get the level command, which is the first number before the slash on that line.
Plug these values into the SM64 Geolayout Exporter/Importer panels.
### Replacing Existing SM64 Geolayout Geometry
SM64 geolayouts are often in strange rest poses, which makes it hard to modify their geometry. It often helps to import an animation belonging to that geolayout to see what the idle pose of a geolayout should be. Once you know, you can rotate the bones of the armature in pose mode to a usable position and then use the 'Apply as Rest Pose' operator under the SM64 Armature Tools header. Skin your new mesh to that armature, then rotate the bones back to the original position and use 'Apply as Rest Pose' again. You can now export the geolayout to SM64 and it will be able to use existing animations.
For example, for Mario you would rotate the four limb joints around the Y-axis 180 degrees, then just the arms 90/-90 degrees as such:
![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/mario_t_pose.gif)
Then after applying the rest pose and skinning, you would apply those operations in reverse order then apply rest pose again.
### Importing/Exporting Binary SM64 Animations (Not Mario)
- Note: SM64 animations only allow for rotations, and translation only on the root bone.
- Download Quad64, open the desired level, and go to Misc -> Script Dumps.
- Go to the objects header, find the object you want, and view the Behaviour Script tab.
- For most models with animation, you can will see a 27 command, and optionally a 28 command.
For importing:
- The last 4 bytes of the 27 command will be the animation list pointer.
- Make sure 'Is DMA Animation' is unchecked, 'Is Anim List' is checked, and 'Is Segmented Pointer' is checked.
- Set the animation importer start address as those 4 bytes.
- If a 28 command exists, then the second byte will be the anim list index.
- Otherwise, the anim list index is usually 0.
For exporting:
- Make sure 'Set Anim List Entry' is checked.
- Copy the addresses of the 27 command, which is the first number before the slash on that line.
- Optionally do the same for the 28 command, which may not exist.
- If a 28 command exists, then the second byte will be the anim list index.
- Otherwise, the anim list index is usually 0.
Select an armature for the animation, and press 'Import/Export animation'.
### Importing/Exporting Binary Mario Animations
Mario animations use a DMA table, which contains 8 byte entries of (offset from table start, animation size). Documentation about this table is here:
https://dudaw.webs.com/sm64docs/sm64_marios_animation_table.txt
Basically, Mario's DMA table starts at 0x4EC000. There is an 8 byte header, and then the animation entries afterward. Thus the 'climb up ledge' DMA entry is at 0x4EC008. The first 4 bytes at that address indicate the offset from 0x4EC000 at which the actual animation exists. Thus the 'climb up ledge' animation entry address is at 0x4EC690. Using this table you can find animations you want to overwrite. Make sure the 'Is DMA Animation' option is checked and 'Is Segmented Pointer' is unchecked when importing/exporting. Check "Overwrite DMA Entry", set the start address to 4EC000 (for Mario), and set the entry address to the DMA entry obtained previously.
### Animating Existing Geolayouts
Often times it is hard to rig an existing SM64 geolayout, as there are many intermediate non-deform bones and bones don't point to their children. To make this easier you can use the 'Create Animatable Metarig' operator in the SM64 Armature Tools header. This will generate a metarig which can be used with IK. The metarig bones will be placed on armature layers 3 and 4.
## Decomp
To start, set your base decomp folder in SM64 File Settings. This allows the plugin to automatically add headers/includes to the correct locations. You can always choose to export to a custom location, although headers/includes won't be written.
### Decomp Export Types
Most exports will let you choose an export type.
"Actor" will export data to an actor folder. The headers modified will be:
- actors/my\_group.h
- actors/my\_group.c
- actors/my\_group\_geo.c (for geolayouts)
"Level" will export data to a level folder. The headers modified will be:
- levels/my\_level/leveldata.c
- levels/my\_level/header.h
### Decomp And Extended RAM
By default decomp uses 4MB of RAM which means space runs out quickly when exporting custom assets. To handle this Fast64 will automatically add "#define USE\_EXT\_RAM" at the top of include/segments.h after the include guards.
### Exporting Geolayouts to C
Set the "Name" field to the actor folder name.
To replace an actor model, set the enum to "Actor" and set the correct group name. To find the group name, look at the group or common header files in actors/ to see where the actor is defined in.
To replace an actor model manually, replace its geo.inc.c and model.inc.c contents with the geolayout file and the dl file respectively. Use the contents of the header file to replace existing extern declarations in one of the group header files (ex. mario is in group0.h). Make sure that the name of your geolayout is the same the name of the geolayout you're replacing. Note that any function addresses in geolayout nodes will be converted to decomp function names if possible.
### Exporting Levels to C
Add an Empty and check its SM64 object type in the object properties sidebar. Change the type to "Level Root."
Add another Empty and change its type to "Area Root", and parent it to the level root object. You can now add any geometry/empties/splines as children of the area root and it will be exported with the area. They do not have to directly parent to the area root, just to something within the area root's descendants. Empties are also used for placing specials, macros, objects, water boxes, and camera volumes. You can search the available options using the relevant search operators, or set an option to "Custom" to write in your own values. NURBS curves are used to export spline data. Backgrounds are set in level root options, and warp nodes are set in area root options.
To replace a level manually, replace the contents of a level folder with your exported folder. Then,
- Add '#include "levels/mylevel/geo.inc.c"' to geo.c.
- Add '#include "levels/mylevel/leveldata.inc.c"' to leveldata.c.
- Add '#include "levels/mylevel/header.inc.h"' to header.h.
- Add '#include "levels/mylevel/texture\_include.inc.c"' to texture.inc.c. (If saving textures as PNGs.)
The level exporter will modify these files:
- src/game/camera.c (sZoomOutAreaMasks)
- levels/level\_defines.h
- levels/course\_defines.h
### Exporting HUD to C
The HUD exporter will export a texture and a function to draw it to the screen as a texture rectangle. The data will be written to segment2, and the headers modified will be:
- src/game/segment2.h (texture declaration)
- bin/segment2.c (texture definition)
- textures/segment2 (texture file, if saving pngs separately)
This will also write the drawing function to either src/game/hud.c or src/game/ingame_menu.c depending on the "Export Type". Note that this operator can be called multiple times and it will replace the old code from previous exports. It will not delete old textures in the textures/segment2 folder, although they won't be built in the project. It also won't delete any palette data.
The draw function will be in the format "void myfunc(x, y, width, height, s, t)". Width and height are used to take advantage of texture clamp/repeating, or to mask parts of the texture (ex. a health meter). s and t are the rectangles start UVs, which can be used to scroll the image. Negative positions are automatically handled. For basic texture drawing, set width/height to your texture dimensions and s/t to 0.
### Scrolling Textures in Decomp
Scrolling texture settings can be found in the material properties window before the "Geomtry Mode Settings" tab.
If you want to disable scrolling texture code generation, you can do so in the SM64 File Settings.
This is the process for how scrolling textures is implemented:
- Add a sSegmentROMTable to src/game/memory.c/h in order to keep track of which ROM locations are loaded into memory. ROM locations will be stored in this table during segment loading function calls.
- Add src/game/texscroll.c/h which will scroll any vertex data that is currently loaded.
- Add src/game/texscroll/ which contains segment specific texture scroll files (Ex. group0_texscroll.inc.c/h). These will be included in the texscroll.c/h file.
- The segment specific files will include texscroll.inc.c/h files from all the geometry within it that uses scrolling. These files will be generated in the same location as the vertex data being scrolled (Ex. where model.inc.c, leveldata.c is).
- Add a function call to update_level in src/game/level_update.c which calls the main scroll function.
### Switch Statements
To create a switch node, and a bone to your armature and set its geolayout type to "Switch". Any bones that will be switched should be parented to this switch bone. The switch bone can do either material, draw layer, or mesh switching.
To add a mesh switch option node, duplicate and separate your switch bone into its own armature and move it off to the side. Set the bone geolayout command to "Switch Option". This bone must be the root bone of all other bones in the armature. Skin your switch option geometry to this armature, then add your switch option armature to the switch bone options in your original armature.
### Insertable Binary Exporting
Insertable Binary exporting will generate a binary file, with a header containing metadata about pointer locations. It is formatted as such:
0x00-0x04 : Data Type
0 = Display List
1 = Geolayout
2 = Animation
3 = Collision
0x04-0x08 : Data Size (size in bytes of Data Section)
0x08-0x0C : Start Address (start address of data, relative to start of Data Section)
0x0C-0x10 : Number of Pointer Addresses (number of pointer addresses to be resolved)
0x10-N : List of 4-byte pointer addresses. Each address relative to start of Data Section.
N-end : Data Section (actual binary data)
To resolve pointer addresses, for each pointer address,
# Get data section only
data = fileData[N:]
# Get current offset
current_offset = data[pointer_address]
# Convert offset to segmented address
data[pointer_address] = encode_segmented_address(export_address + current_offset)
### Common Issues
Game crashes: Invalid function address for switch/function/held object bones.
Animation root translation/rotation not exporting: Make sure you are animating the root bone, not the armature object.
+2 -5
View File
@@ -86,11 +86,8 @@ class SM64_FileSettingsPanel(bpy.types.Panel):
col.prop(context.scene, 'outputRom')
col.prop(context.scene, 'extendBank4')
col.prop(context.scene, 'decomp_compatible')
col.prop(context.scene, 'disableScroll')
col.prop(context.scene, 'ignoreTextureRestrictions')
if context.scene.ignoreTextureRestrictions:
col.box().label(text = "Width/height must be < 1024. Must be RGBA32. Must be png format.")
col.prop(context.scene, 'decompPath')
prop_split(col, context.scene, 'refreshVer', 'Decomp Func Map')
@@ -200,7 +197,7 @@ def sm64_register(registerPanels):
name = 'Disable Scrolling Textures')
bpy.types.Scene.blenderToSM64Scale = bpy.props.FloatProperty(
name = 'Blender To SM64 Scale', default = 212.766)
name = 'Blender To SM64 Scale', default = 100) # 212.766
bpy.types.Scene.decompPath = bpy.props.StringProperty(
name ='Decomp Folder', subtype = 'FILE_PATH')
+1 -8
View File
@@ -766,10 +766,6 @@ class SM64_ExportAnimPanel(bpy.types.Panel):
prop_split(col, context.scene, 'animExportStart', 'Start Address')
prop_split(col, context.scene, 'animExportEnd', 'End Address')
for i in range(panelSeparatorSize):
col.separator()
class SM64_ImportAnimMario(bpy.types.Operator):
bl_idname = 'object.sm64_import_anim'
bl_label = "Import Animation"
@@ -844,9 +840,6 @@ class SM64_ImportAnimPanel(bpy.types.Panel):
prop_split(col, context.scene, 'animStartImport', 'Start Address')
col.prop(context.scene, 'animIsSegPtr')
col.prop(context.scene, 'levelAnimImport')
for i in range(panelSeparatorSize):
col.separator()
sm64_anim_classes = (
SM64_ExportAnimMario,
@@ -890,7 +883,7 @@ def sm64_anim_register():
bpy.types.Scene.addr_0x28 = bpy.props.StringProperty(
name = '0x28 Command Address', default = '21CD08')
bpy.types.Scene.animExportType = bpy.props.EnumProperty(
items = enumExportType, name = 'Export', default = 'Binary')
items = enumExportType, name = 'Export', default = 'C')
bpy.types.Scene.animExportPath = bpy.props.StringProperty(
name = 'Directory', subtype = 'FILE_PATH')
bpy.types.Scene.animOverwriteDMAEntry = bpy.props.BoolProperty(
+1 -3
View File
@@ -607,8 +607,6 @@ class SM64_ExportCollisionPanel(bpy.types.Panel):
if context.scene.set_addr_0x2A:
prop_split(col, context.scene, 'addr_0x2A',
'0x2A Behaviour Command Address')
for i in range(panelSeparatorSize):
col.separator()
sm64_col_classes = (
@@ -636,7 +634,7 @@ def sm64_col_register():
bpy.types.Scene.colExportPath = bpy.props.StringProperty(
name = 'Directory', subtype = 'FILE_PATH')
bpy.types.Scene.colExportType = bpy.props.EnumProperty(
items = enumExportType, name = 'Export', default = 'Binary')
items = enumExportType, name = 'Export', default = 'C')
bpy.types.Scene.colExportLevel = bpy.props.EnumProperty(items = level_enums,
name = 'Level Used By Collision', default = 'WF')
bpy.types.Scene.addr_0x2A = bpy.props.StringProperty(
-3
View File
@@ -71,9 +71,6 @@ class SM64_ImportDLPanel(bpy.types.Panel):
col.prop(context.scene, 'isSegmentedAddrDLImport')
col.box().label(text = "Only Fast3D mesh importing allowed.")
for i in range(panelSeparatorSize):
col.separator()
sm64_dl_parser_classes = (
SM64_ImportDL,
)
+38 -17
View File
@@ -1,7 +1,7 @@
import shutil, copy, bpy, cProfile, pstats
from ..f3d.f3d_writer import *
from ..f3d.f3d_material import TextureProperty, tmemUsageUI
from ..f3d.f3d_material import *
from .sm64_texscroll import *
from bpy.utils import register_class, unregister_class
from .sm64_constants import level_enums, enumLevelNames
@@ -24,7 +24,7 @@ class SM64Model(FModel):
def __init__(self, f3dType, isHWv1, name, DLFormat):
FModel.__init__(self, f3dType, isHWv1, name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert)
def getDrawLayer(self, obj):
def getDrawLayerV3(self, obj):
return int(obj.draw_layer_static)
def getRenderMode(self, drawLayer):
@@ -32,10 +32,10 @@ class SM64Model(FModel):
cycle2 = getattr(bpy.context.scene.world, 'draw_layer_' + str(drawLayer) + '_cycle_2')
return [cycle1, cycle2]
class SM64GfxFormatter(GameGfxFormatter):
class SM64GfxFormatter(GfxFormatter):
def __init__(self, scrollMethod):
self.functionNodeDraw = False
GameGfxFormatter.__init__(self, scrollMethod, 8)
GfxFormatter.__init__(self, scrollMethod, 8)
def vertexScrollToC(self, fScrollData, name, count):
data = CData()
@@ -267,7 +267,7 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData):
return fTexRect
def exportF3DtoC(basePath, obj, DLFormat, transformMatrix,
def sm64ExportF3DtoC(basePath, obj, DLFormat, transformMatrix,
f3dType, isHWv1, texDir, savePNG, texSeparate, includeChildren, name, levelName, groupName, customExport, headerType):
dirPath, texDir = getExportDir(customExport, basePath, headerType,
levelName, texDir, name)
@@ -488,7 +488,7 @@ class SM64_ExportDL(bpy.types.Operator):
context.scene.DLLevelOption)
if not context.scene.DLCustomExport:
applyBasicTweaks(exportPath)
exportF3DtoC(exportPath, obj,
sm64ExportF3DtoC(exportPath, obj,
DLFormat.Static if context.scene.DLExportisStatic else DLFormat.Dynamic, finalTransform,
context.scene.f3d_type, context.scene.isHWv1,
bpy.context.scene.DLTexDir,
@@ -497,7 +497,7 @@ class SM64_ExportDL(bpy.types.Operator):
bpy.context.scene.DLincludeChildren, bpy.context.scene.DLName, levelName, context.scene.DLGroupName,
context.scene.DLCustomExport,
context.scene.DLExportHeaderType)
#cProfile.runctx('exportF3DtoC(exportPath, obj,' +\
#cProfile.runctx('sm64ExportF3DtoC(exportPath, obj,' +\
# 'DLFormat.Static if context.scene.DLExportisStatic else DLFormat.Dynamic, finalTransform,' +\
# 'context.scene.f3d_type, context.scene.isHWv1,' +\
# 'bpy.context.scene.DLTexDir,' +\
@@ -656,9 +656,6 @@ class SM64_ExportDLPanel(bpy.types.Panel):
prop_split(col, context.scene, 'DLExportGeoPtr',
'Geolayout Pointer')
col.prop(context.scene, 'DLincludeChildren')
for i in range(panelSeparatorSize):
col.separator()
class ExportTexRectDraw(bpy.types.Operator):
# set bl_ properties
@@ -673,12 +670,11 @@ class ExportTexRectDraw(bpy.types.Operator):
if context.scene.texrect.tex is None:
raise PluginError("No texture selected.")
else:
if context.scene.decompPath == "":
raise PluginError("Decomp path has not been set in File Settings.")
if context.scene.TexRectCustomExport:
exportPath = context.scene.TexRectExportPath
else:
if context.scene.decompPath == "":
raise PluginError("Decomp path has not been set in File Settings.")
exportPath = context.scene.decompPath
if not context.scene.TexRectCustomExport:
applyBasicTweaks(exportPath)
@@ -760,9 +756,6 @@ class ExportTexRectDrawPanel(bpy.types.Panel):
infoBox.label(text = enumHUDPaths[context.scene.TexRectExportType][0] + ': ')
infoBox.label(text = enumHUDPaths[context.scene.TexRectExportType][1] + '.')
for i in range(panelSeparatorSize):
col.separator()
class SM64_DrawLayersPanel(bpy.types.Panel):
bl_label = "SM64 Draw Layers"
bl_idname = "WORLD_PT_SM64_Draw_Layers_Panel"
@@ -794,6 +787,33 @@ def drawLayerUI(layout, drawLayer, world):
row.prop(world, 'draw_layer_' + str(drawLayer) + '_cycle_1', text = '')
row.prop(world, 'draw_layer_' + str(drawLayer) + '_cycle_2', text = '')
class SM64_MaterialPanel(bpy.types.Panel):
bl_label = "SM64 Material"
bl_idname = "MATERIAL_PT_SM64_Material_Inspector"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
return context.material is not None and context.scene.gameEditorMode == "SM64"
def draw(self, context):
layout = self.layout
material = context.material
col = layout.column()
if material.mat_ver > 3:
f3dMat = material.f3d_mat
else:
f3dMat = material
useDict = all_combiner_uses(f3dMat)
if useDict['Texture']:
ui_procAnim(material, col, useDict['Texture 0'], useDict['Texture 1'],
"SM64 UV Texture Scroll", False)
sm64_dl_writer_classes = (
SM64_ExportDL,
@@ -802,6 +822,7 @@ sm64_dl_writer_classes = (
)
sm64_dl_writer_panel_classes = (
SM64_MaterialPanel,
SM64_DrawLayersPanel,
SM64_ExportDLPanel,
ExportTexRectDrawPanel,
@@ -847,7 +868,7 @@ def sm64_dl_writer_register():
bpy.types.Scene.overwriteGeoPtr = bpy.props.BoolProperty(
name = "Overwrite geolayout pointer", default = False)
bpy.types.Scene.DLExportType = bpy.props.EnumProperty(
items = enumExportType, name = 'Export', default = 'Binary')
items = enumExportType, name = 'Export', default = 'C')
bpy.types.Scene.DLExportPath = bpy.props.StringProperty(
name = 'Directory', subtype = 'FILE_PATH')
bpy.types.Scene.DLExportisStatic = bpy.props.BoolProperty(
+8 -2
View File
@@ -73,7 +73,7 @@ def drawGeoInfo(panel, bone):
if bone.geo_cmd in ['TranslateRotate', 'Translate', 'Rotate',
'Billboard', 'DisplayList', 'Scale', 'DisplayListWithOffset']:
prop_split(col, bone, 'draw_layer', 'Draw layer')
drawLayerWarningBox(col, bone, "draw_layer")
if bone.geo_cmd == 'Scale':
prop_split(col, bone, 'geo_scale', 'Scale')
@@ -168,6 +168,12 @@ class GeolayoutArmaturePanel(bpy.types.Panel):
col.box().label(text = 'This is in blender units.')
prop_split(col, obj, 'culling_radius', 'Culling Radius')
def drawLayerWarningBox(layout, prop, data):
warningBox = layout.box().column()
prop_split(warningBox, prop, data, 'Draw Layer (v3)')
warningBox.label(text = "This applies to v3 materials and down only.", icon = "ERROR")
warningBox.label(text = "This is moved to material settings in v4+.")
class GeolayoutObjectPanel(bpy.types.Panel):
bl_label = "Object Geolayout Inspector"
bl_idname = "OBJECT_PT_SM64_Object_Geolayout_Inspector"
@@ -187,7 +193,7 @@ class GeolayoutObjectPanel(bpy.types.Panel):
col.box().label(text = 'Object Geolayout Inspector')
prop_split(col, obj, 'geo_cmd_static', 'Geolayout Command')
prop_split(col, obj, 'draw_layer_static', 'Draw layer')
drawLayerWarningBox(col, obj, "draw_layer_static")
col.prop(obj, 'use_render_area')
if obj.use_render_area:
renderAreaBox = col.box()
@@ -1225,8 +1225,6 @@ class SM64_ImportGeolayoutPanel(bpy.types.Panel):
boxLayout.label(text = "WARNING: May take a long time.")
boxLayout.label(text = "Switch nodes won't be setup.")
col.box().label(text = "Only Fast3D mesh importing allowed.")
for i in range(panelSeparatorSize):
col.separator()
sm64_geo_parser_classes = (
SM64_ImportGeolayout,
+23 -27
View File
@@ -367,9 +367,9 @@ def saveGeolayoutC(geoName, dirName, geolayoutGraph, fModel, exportDir, texDir,
staticData.source += '\n' + dynamicData.source
staticData.header = geoData.header + staticData.header + dynamicData.header
else:
geoData = writeMaterialFiles(exportDir, geoDirPath,
geoData.source = writeMaterialFiles(exportDir, geoDirPath,
headerInclude, matHInclude,
dynamicData.header, dynamicData.source, geoData, customExport)
dynamicData.header, dynamicData.source, geoData.source, customExport)
modelPath = os.path.join(geoDirPath, 'model.inc.c')
modelFile = open(modelPath, 'w', newline='\n')
@@ -797,7 +797,7 @@ def setRooms(obj, roomIndex = None):
else:
alphabeticalChildren = getSwitchChildren(obj)
for i in range(len(alphabeticalChildren)):
setRooms(alphabeticalChildren[i], i + 1) # index starts at 1
setRooms(alphabeticalChildren[i], i) # index starts at 1, but 0 is reserved for no room.
def isZeroRotation(rotate):
eulerRot = rotate.to_euler(geoNodeRotateOrder)
@@ -873,22 +873,27 @@ def processMesh(fModel, obj, transformMatrix, parentTransformNode,
# Rooms are not set here (since this is just a copy of the original hierarchy)
# They should be set previously, using setRooms()
preRoomSwitchParentNode = parentTransformNode
parentTransformNode = addParentNode(parentTransformNode, SwitchNode(switchFunc, switchParam, obj.original_name))
alphabeticalChildren = getSwitchChildren(obj)
for i in range(len(alphabeticalChildren)):
childObj = alphabeticalChildren[i]
optionGeolayout = geolayoutGraph.addGeolayout(
childObj, fModel.name + '_' + childObj.original_name + '_geo')
geolayoutGraph.addJumpNode(parentTransformNode, geolayout,
optionGeolayout)
if not zeroRotation or not zeroTranslation:
startNode = TransformNode(getOptimalNode(translate, rotate, 1, False,
zeroTranslation, zeroRotation))
if i == 0: # Outside room system
processMesh(fModel, childObj, transformMatrix, preRoomSwitchParentNode,
geolayout, geolayoutGraph, False, convertTextureData)
else:
startNode = TransformNode(StartNode())
optionGeolayout.nodes.append(startNode)
processMesh(fModel, childObj, transformMatrix, startNode,
optionGeolayout, geolayoutGraph, False, convertTextureData)
optionGeolayout = geolayoutGraph.addGeolayout(
childObj, fModel.name + '_' + childObj.original_name + '_geo')
geolayoutGraph.addJumpNode(parentTransformNode, geolayout,
optionGeolayout)
if not zeroRotation or not zeroTranslation:
startNode = TransformNode(getOptimalNode(translate, rotate, 1, False,
zeroTranslation, zeroRotation))
else:
startNode = TransformNode(StartNode())
optionGeolayout.nodes.append(startNode)
processMesh(fModel, childObj, transformMatrix, startNode,
optionGeolayout, geolayoutGraph, False, convertTextureData)
else:
if obj.geo_cmd_static == 'Optimal' or useGeoEmpty:
@@ -1660,11 +1665,8 @@ def saveModelGivenVertexGroup(fModel, obj, vertexGroup,
# Save unskinned mesh
for drawLayer, materialFaces in groupFaces.items():
if drawLayer not in fMeshes:
meshName = getFMeshName(vertexGroup, namePrefix, drawLayer, False)
checkUniqueBoneNames(fModel, meshName, vertexGroup)
fMeshes[drawLayer] = FMesh(meshName, fModel.DLFormat)
fModel.meshes[meshName] = fMeshes[drawLayer]
fMesh = fModel.addMesh(vertexGroup, namePrefix, drawLayer, False)
fMeshes[drawLayer] = fMesh
for material_index, bFaces in materialFaces.items():
material = obj.data.materials[material_index]
@@ -2393,13 +2395,7 @@ class SM64_ExportGeolayoutPanel(bpy.types.Panel):
col.prop(context.scene, 'textDumpGeo')
if context.scene.textDumpGeo:
col.prop(context.scene, 'textDumpGeoPath')
#col.prop(context.scene, 'saveCameraSettings')
#if context.scene.saveCameraSettings:
# prop_split(col, context.scene, 'levelCamera', 'Level Camera')
for i in range(panelSeparatorSize):
col.separator()
sm64_geo_writer_classes = (
SM64_ExportGeolayoutObject,
@@ -2441,7 +2437,7 @@ def sm64_geo_writer_register():
bpy.types.Scene.textDumpGeoPath = bpy.props.StringProperty(
name ='Text Dump Path', subtype = 'FILE_PATH')
bpy.types.Scene.geoExportType = bpy.props.EnumProperty(
items = enumExportType, name = 'Export', default = 'Binary')
items = enumExportType, name = 'Export', default = 'C')
bpy.types.Scene.geoExportPath = bpy.props.StringProperty(
name = 'Directory', subtype = 'FILE_PATH')
bpy.types.Scene.geoUseBank0 = bpy.props.BoolProperty(name = 'Use Bank 0')
@@ -899,11 +899,6 @@ class SM64_ExportLevelPanel(bpy.types.Panel):
writeBox.label(text = 'levels/' + toAlnum(levelName) + ' (data).')
writeBox.label(text = 'src/game/camera.c (camera volume).')
writeBox.label(text = 'levels/level_defines.h (camera volume).')
#extendedRAMLabel(col)
#prop_split(col, context.scene, 'levelCamera', 'Camera')
for i in range(panelSeparatorSize):
col.separator()
sm64_level_classes = (
SM64_ExportLevel,
+11 -5
View File
@@ -640,7 +640,8 @@ class SM64ObjectPanel(bpy.types.Panel):
return context.scene.gameEditorMode == "SM64" and (context.object is not None and context.object.data is None)
def draw(self, context):
box = self.layout.box()
prop_split(self.layout, context.scene, "gameEditorMode", "Game")
box = self.layout.box().column()
box.box().label(text = 'SM64 Object Inspector')
obj = context.object
prop_split(box, obj, 'sm64_obj_type', 'Object Type')
@@ -762,9 +763,9 @@ class SM64ObjectPanel(bpy.types.Panel):
box.prop(obj, 'enableRoomSwitch')
if obj.enableRoomSwitch:
infoBox = box.box()
infoBox.label(text = 'Every child hierarchy of the area root will be treated as its own room.')
infoBox.label(text = 'Every child hierarchy of the area root will be treated as its own room (except for the first one.)')
infoBox.label(text = 'You can use empties with the "None" type as empty geolayout nodes to group related geometry under.')
infoBox.label(text = 'Children will ordered alphabetically.')
infoBox.label(text = 'Children will ordered alphabetically, with the first child being always visible.')
box.prop(obj, 'useDefaultScreenRect')
if not obj.useDefaultScreenRect:
prop_split(box, obj, 'screenPos', 'Screen Position')
@@ -874,7 +875,7 @@ class RemoveWarpNode(bpy.types.Operator):
return {'FINISHED'}
def drawWarpNodeProperty(layout, warpNode, index):
box = layout.box()
box = layout.box().column()
#box.box().label(text = 'Switch Option ' + str(index + 1))
box.prop(warpNode, 'expand', text = 'Warp Node ' + \
str(warpNode.warpID), icon = 'TRIA_DOWN' if warpNode.expand else \
@@ -957,7 +958,12 @@ class StarGetCutscenesProperty(bpy.types.PropertyGroup):
prop_split(layout, self, 'star7_value', '')
def onUpdateObjectType(self, context):
if self.sm64_obj_type == 'Water Box':
isNoneEmpty = self.sm64_obj_type == "None"
isBoxEmpty = self.sm64_obj_type == 'Water Box' or self.sm64_obj_type == 'Camera Volume'
self.show_name = not (isBoxEmpty or isNoneEmpty)
self.show_axis = not (isBoxEmpty or isNoneEmpty)
if isBoxEmpty:
self.empty_display_type = "CUBE"
sm64_obj_classes = (
+40 -9
View File
@@ -11,7 +11,6 @@ class VertexWeightError(PluginError):
geoNodeRotateOrder = 'ZXY'
sm64BoneUp = Vector([1,0,0])
panelSeparatorSize = 5
axis_enums = [
('X', 'X', 'X'),
@@ -37,6 +36,29 @@ enumCompressionFormat = [
('yay0', 'YAY0', 'YAY0'),
]
def readFile(filepath):
datafile = open(filepath, 'r', newline = '\n', encoding = 'utf-8')
data = datafile.read()
datafile.close()
return data
def writeFile(filepath, data):
datafile = open(filepath, 'w', newline = '\n', encoding = 'utf-8')
datafile.write(data)
datafile.close()
def checkObjectReference(obj, title):
if obj.name not in bpy.context.view_layer.objects:
raise PluginError(title + " not in current view layer.\n The object is either in a different view layer or is deleted.")
def parentObject(parent, child):
bpy.ops.object.select_all(action = "DESELECT")
child.select_set(True)
parent.select_set(True)
bpy.context.view_layer.objects.active = parent
bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
def attemptModifierApply(modifier):
try:
bpy.ops.object.modifier_apply(modifier=modifier.name)
@@ -96,11 +118,11 @@ def copyPropertyGroup(oldProp, newProp):
setattr(newProp, sub_value_attr, sub_value)
def writeCData(data, headerPath, sourcePath):
sourceFile = open(sourcePath, 'w', newline = '\n')
sourceFile = open(sourcePath, 'w', newline = '\n', encoding = 'utf-8')
sourceFile.write(data.source)
sourceFile.close()
headerFile = open(headerPath, 'w', newline = '\n')
headerFile = open(headerPath, 'w', newline = '\n', encoding = 'utf-8')
headerFile.write(data.header)
headerFile.close()
@@ -212,8 +234,7 @@ def writeMaterialHeaders(exportDir, matCInclude, matHInclude):
'\n' + matHInclude, '#endif')
def writeMaterialFiles(exportDir, assetDir, headerInclude, matHInclude,
headerDynamic, dynamic_data, geoData, customExport):
geoString = geoData.source
headerDynamic, dynamic_data, geoString, customExport):
if not customExport:
writeMaterialBase(exportDir)
levelMatCPath = os.path.join(assetDir, 'material.inc.c')
@@ -259,15 +280,15 @@ def writeMaterialBase(baseDir):
matCFile.close()
def getRGBA16Tuple(color):
return ((int(color[0] * 0x1F) & 0x1F) << 11) | \
((int(color[1] * 0x1F) & 0x1F) << 6) | \
((int(color[2] * 0x1F) & 0x1F) << 1) | \
return ((int(round(color[0] * 0x1F)) & 0x1F) << 11) | \
((int(round(color[1] * 0x1F)) & 0x1F) << 6) | \
((int(round(color[2] * 0x1F)) & 0x1F) << 1) | \
(1 if color[3] > 0.5 else 0)
def getIA16Tuple(color):
intensity = mathutils.Color(color[0:3]).v
alpha = color[3]
return (int(intensity * 0xFF) << 8) | int(alpha * 0xFF)
return (int(round(intensity * 0xFF)) << 8) | int(alpha * 0xFF)
def convertRadiansToS16(value):
value = math.degrees(value)
@@ -628,6 +649,16 @@ def tempName(name):
letters = string.digits
return name + '_temp' + "".join(random.choice(letters) for i in range(10))
def label_split(layout, name, text):
split = layout.split(factor = 0.5)
split.label(text = name)
split.label(text = text)
def enum_label_split(layout, name, data, prop, enumItems):
split = layout.split(factor = 0.5)
split.label(text = name)
split.enum_item_name(data, prop, enumItems)
def prop_split(layout, data, field, name):
split = layout.split(factor = 0.5)
split.label(text = name)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
View File
Binary file not shown.