Skip to content

Commit

Permalink
Asset Explorer Game View, Model Replacements Fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Kizari committed Feb 3, 2022
1 parent dbbaab2 commit 4ec1dc3
Show file tree
Hide file tree
Showing 84 changed files with 4,468 additions and 1,051 deletions.
16 changes: 11 additions & 5 deletions Flagrum.Blender/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
from bpy.utils import register_class, unregister_class

from . import addon_updater_ops
from .globals import FlagrumGlobals
from .import_export.menu import ImportOperator, ExportOperator
from .panel.cleanup_panel import CleanupPanel, DeleteUnusedBonesOperator, DeleteUnusedVGroupsOperator, \
NormaliseWeightsOperator
from .panel.material_data import MaterialSettings, FlagrumMaterialProperty, FlagrumMaterialPropertyCollection
from .panel.material_panel import MaterialEditorPanel, MaterialResetOperator, TextureSlotOperator, \
ClearTextureOperator, MaterialImportOperator, MaterialCopyOperator, MaterialPasteOperator
from .panel.normals_panel import UseCustomNormalsOperator, NormalsPanel
from .panel.normals_panel import UseCustomNormalsOperator, NormalsPanel, SplitEdgesOperator, MergeNormalsOperator

bl_info = {
"name": "Flagrum",
"version": (1, 0, 4),
"version": (1, 0, 5),
"blender": (2, 93, 0),
"location": "File > Import-Export",
"description": "Blender add-on for Flagrum",
Expand Down Expand Up @@ -81,11 +82,14 @@ def draw(self, context):
MaterialEditorPanel,
MaterialSettings,
UseCustomNormalsOperator,
SplitEdgesOperator,
MergeNormalsOperator,
DeleteUnusedBonesOperator,
DeleteUnusedVGroupsOperator,
NormaliseWeightsOperator,
CleanupPanel,
NormalsPanel
NormalsPanel,
FlagrumGlobals
)


Expand All @@ -106,11 +110,13 @@ def register():
bpy.types.TOPBAR_MT_file_import.append(import_menu_item)
bpy.types.TOPBAR_MT_file_export.append(export_menu_item)
bpy.types.Object.flagrum_material = PointerProperty(type=MaterialSettings)
bpy.types.Scene.flagrum_material_clipboard = PointerProperty(type=FlagrumMaterialPropertyCollection)
bpy.types.WindowManager.flagrum_material_clipboard = PointerProperty(type=FlagrumMaterialPropertyCollection)
bpy.types.WindowManager.flagrum_globals = PointerProperty(type=FlagrumGlobals)


def unregister():
del bpy.types.Scene.flagrum_material_clipboard
del bpy.types.WindowManager.flagrum_globals
del bpy.types.WindowManager.flagrum_material_clipboard
del bpy.types.Object.flagrum_material
bpy.types.TOPBAR_MT_file_import.remove(export_menu_item)
bpy.types.TOPBAR_MT_file_import.remove(import_menu_item)
Expand Down
11 changes: 11 additions & 0 deletions Flagrum.Blender/globals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from bpy.props import BoolProperty
from bpy.types import PropertyGroup


class FlagrumGlobals(PropertyGroup):
retain_base_armature: BoolProperty(
name="Retain base armature",
description="Prevents removal of unused bones from removing base bones even if no vertices are weighted "
"to them",
default=False
)
7 changes: 7 additions & 0 deletions Flagrum.Blender/import_export/export_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from dataclasses import dataclass


@dataclass
class ExportContext:
smooth_normals: bool
distance: float
28 changes: 25 additions & 3 deletions Flagrum.Blender/import_export/menu.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import bpy
from bpy.props import StringProperty
from bpy.props import StringProperty, FloatProperty, BoolProperty
from bpy.types import Operator
from bpy_extras.io_utils import ImportHelper, ExportHelper

from .export_context import ExportContext
from .generate_armature import generate_armature
from .generate_mesh import generate_mesh
from .import_context import ImportContext
Expand Down Expand Up @@ -47,8 +47,30 @@ class ExportOperator(Operator, ExportHelper):
options={'HIDDEN'}
)

smooth_normals: BoolProperty(
name="Smooth Normals on Doubles",
description="When the exporter splits edges for compatibility with FFXV, this functionality will smooth "
"the seams caused by the edge-splitting",
default=False
)

distance: FloatProperty(
name="Distance",
description="The maximum distance between doubles for which to merge normals",
default=0.0001,
min=0.0001,
precision=4
)

def draw(self, context):
layout = self.layout
layout.label(text="FMD Export Options")
layout.prop(self, property="smooth_normals")
layout.prop(self, property="distance")

def execute(self, context):
data = pack_mesh()
export_context = ExportContext(self.smooth_normals, self.distance)
data = pack_mesh(export_context)
Interop.export_mesh(self.filepath, data)

return {'FINISHED'}
9 changes: 8 additions & 1 deletion Flagrum.Blender/import_export/pack_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from bpy.types import Object, Mesh
from mathutils import Matrix, Vector

from .export_context import ExportContext
from ..entities import Gpubin, UV, Vector3, MeshData, UVMap, ColorMap, Color4, Normal, MaterialData
from ..operations.merge_normals import merge_normals
from ..panel.material_data import material_weight_limit

# Matrix that converts the axes back to FBX coordinate system
Expand All @@ -14,7 +16,7 @@
])


def pack_mesh():
def pack_mesh(export_context: ExportContext):
mesh_data = Gpubin()
mesh_data.Meshes = []

Expand Down Expand Up @@ -70,6 +72,11 @@ def pack_mesh():
bmesh.update_edit_mesh(mesh_copy.data)
bpy.ops.object.mode_set(mode='OBJECT')

# Merge normals on doubles if set in export settings
if export_context.smooth_normals:
merge_normals(mesh_copy.data, export_context.distance)

# Pack the mesh data
mesh.VertexPositions = _pack_vertex_positions(mesh_copy)
mesh.FaceIndices = _pack_faces(mesh_copy)
mesh.UVMaps = _pack_uv_maps(mesh_copy)
Expand Down
67 changes: 67 additions & 0 deletions Flagrum.Blender/operations/merge_normals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from bpy.types import Mesh
from mathutils import Vector


def merge_normals(mesh: Mesh, distance: float):
new_normals = {}

# Split vertices up into a grid by the set merge distance
grid = {}
for vertex in mesh.vertices:
x = vertex.co[0] // distance
y = vertex.co[1] // distance
z = vertex.co[2] // distance

if x not in grid:
grid[x] = {}
if y not in grid[x]:
grid[x][y] = {}
if z not in grid[x][y]:
grid[x][y][z] = []

grid[x][y][z].append(vertex)

# Merge custom normals for vertices in range
vertices = set(vertex for vertex in mesh.vertices)
while vertices:
vertex = vertices.pop()
vertex_normal = Vector()
x = vertex.co[0] // distance
y = vertex.co[1] // distance
z = vertex.co[2] // distance

# Find list of all vertices in adjacent parts of the grid
adjacent = []
for i in [x - 1, x, x + 1]:
for j in [y - 1, y, y + 1]:
for k in [z - 1, z, z + 1]:
if i in grid and j in grid[i] and k in grid[i][j]:
adjacent.extend(grid[i][j][k])

# Remove vertices that are out of the merge distance
in_range = [v for v in adjacent if (v.co - vertex.co).length_squared <= distance ** 2]

# Average all the normals of the vertices in range
for other in in_range:
vertex_normal += other.normal

vertex_normal.normalize()

# Add new normals for merged vertices
for other in in_range:
new_normals[other.index] = vertex_normal

# Remove merged vertices from the set
processed = [v for v in in_range]
vertices.difference_update(processed)

# Apply new normals to the mesh
normals = []
for i in range(len(mesh.vertices)):
if i in new_normals:
normals.append([new_normals[i].x, new_normals[i].y, new_normals[i].z])
else:
normals.append(mesh.vertices[i].normal)

mesh.normals_split_custom_set_from_vertices(normals)
mesh.use_auto_smooth = True
30 changes: 28 additions & 2 deletions Flagrum.Blender/panel/cleanup_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class NormaliseWeightsOperator(Operator):
bl_idname = "flagrum.cleanup_normalise_weights"
bl_label = "Normalise Weights"
bl_description = "Normalises vertex weights to the limits defined by the selected Flagrum materials to " \
"ensure a consistent result with the FMD exporter"
"ensure a consistent result with the FMD exporter"

@classmethod
def poll(cls, context):
Expand Down Expand Up @@ -93,6 +93,29 @@ def execute(self, context):
meshes.append(obj)

bones_to_keep = ["C_Hip"]
if context.window_manager.flagrum_globals.retain_base_armature:
bones_to_keep = ["C_Hip", "C_Spine1", "C_Spine1Sub", "C_Spine2", "C_Spine2W", "C_Spine3", "C_Spine3W",
"C_Neck1", "C_Head", "Facial_A", "C_HairRoot", "C_HeadEnd", "Facial_B", "C_Throat_B",
"C_NeckSub", "C_NeckSubEnd", "C_Neck1W", "C_Neck1WEnd", "L_Shoulder", "L_Forearm",
"L_Hand", "L_Socket", "L_Thumb1", "L_Thumb2", "L_Thumb3", "L_ThumbEnd", "L_Index1",
"L_Index2", "L_Index3", "L_IndexBulge", "L_Middle1", "L_Middle2", "L_Middle3",
"L_MiddleBulge", "L_RingMeta", "L_Ring1", "L_Ring2", "L_Ring3", "L_RingBulge", "L_RingSub",
"L_PinkyMeta", "L_Pinky1", "L_Pinky2", "L_Pinky3", "L_PinkyBulge", "L_PinkySub",
"L_IndexSub", "L_MiddleSub", "L_ForearmrollA", "L_ForearmrollB", "L_ForearmrollC",
"L_Wrist", "L_sleeveSub", "L_DeltoidA", "L_DeltoidB", "L_DeltoidC", "L_Elbow", "L_Bust",
"L_armpit", "L_ShoulderSub", "R_Shoulder", "R_UpperArm", "R_Forearm", "R_Hand", "R_Socket",
"R_Thumb1", "R_Thumb2", "R_Thumb3", "R_ThumbEnd", "R_Index1", "R_Index2", "R_Index3",
"R_IndexBulge", "R_Middle1", "R_Middle2", "R_Middle3", "R_MiddleBulge", "R_RingMeta",
"R_Ring1", "R_Ring2", "R_Ring3", "R_RingSub", "R_PinkyMeta", "R_Pinky1", "R_Pinky2",
"R_Pinky3", "R_PinkyBulge", "R_PinkySub", "R_IndexSub", "R_MiddleSub", "R_ForearmrollA",
"R_ForearmrollB", "R_ForearmrollC", "R_Wrist", "R_sleeveSub", "R_DeltoidA", "R_DeltoidB",
"R_DeltoidC", "R_Elbow", "R_Bust", "R_ShoulderSub", "R_armpit", "L_UpperLeg", "L_Foreleg",
"L_Foot", "L_Toe", "L_ToeEnd", "L_CalfB", "L_CalfF", "L_ankle", "L_ankleB", "L_FemorisA",
"L_FemorisAsub", "L_FemorisB", "L_FemorisC", "L_Knee", "L_UpperLegSub", "L_UpperLegSubEnd",
"R_UpperLeg", "R_Foreleg", "R_Foot", "R_Toe", "R_ToeEnd", "R_CalfB", "R_CalfF", "R_ankle",
"R_FemorisA", "R_FemorisAsub", "R_FemorisB", "R_FemorisC", "R_Knee", "C_HipW", "C_Spine1W",
"C_Spine1WEnd", "L_Hip", "L_Hipback", "L_HipSub", "R_Hip", "R_Hipback", "R_HipSub",
"R_HipSubEnd", "c_BeltKdi"]

for mesh in meshes:
groups = {i: False for i, k in enumerate(mesh.vertex_groups)}
Expand Down Expand Up @@ -127,6 +150,9 @@ class CleanupPanel(Panel):

def draw(self, context):
layout = self.layout
layout.operator(DeleteUnusedBonesOperator.bl_idname)
row = layout.row(align=True)
row.operator(DeleteUnusedBonesOperator.bl_idname)
row.prop(context.window_manager.flagrum_globals, property="retain_base_armature", icon='OUTLINER_OB_ARMATURE',
icon_only=True)
layout.operator(DeleteUnusedVGroupsOperator.bl_idname)
layout.operator(NormaliseWeightsOperator.bl_idname)
6 changes: 3 additions & 3 deletions Flagrum.Blender/panel/material_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class MaterialCopyOperator(Operator):
bl_label = "Copy"

def execute(self, context):
clipboard = context.scene.flagrum_material_clipboard
clipboard = context.window_manager.flagrum_material_clipboard
material: MaterialSettings = context.view_layer.objects.active.flagrum_material
active_material_data = None
for property_definition in material.property_collection:
Expand All @@ -132,7 +132,7 @@ class MaterialPasteOperator(Operator):

@classmethod
def poll(cls, context):
material_id = context.scene.flagrum_material_clipboard.material_id
material_id = context.window_manager.flagrum_material_clipboard.material_id
material: MaterialSettings = context.view_layer.objects.active.flagrum_material
active_material_data = None
for property_definition in material.property_collection:
Expand All @@ -141,7 +141,7 @@ def poll(cls, context):
return material_id is not None and material_id != '' and active_material_data.material_id == material_id

def execute(self, context):
clipboard = context.scene.flagrum_material_clipboard
clipboard = context.window_manager.flagrum_material_clipboard
material: MaterialSettings = context.view_layer.objects.active.flagrum_material
active_material_data = None
for property_definition in material.property_collection:
Expand Down
74 changes: 74 additions & 0 deletions Flagrum.Blender/panel/normals_panel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from array import array

import bmesh
import bpy
from bpy.types import Panel, Operator, Mesh

from ..operations.merge_normals import merge_normals


class UseCustomNormalsOperator(Operator):
bl_idname = "flagrum.use_custom_normals"
Expand Down Expand Up @@ -51,6 +55,74 @@ def execute(self, context):
return {'FINISHED'}


class SplitEdgesOperator(Operator):
bl_idname = "flagrum.split_edges"
bl_label = "Split Edges"
bl_description = "Creates doubles along UV boundaries for compatibility with Luminous"

@classmethod
def poll(cls, context):
selected_meshes = []
for obj in context.view_layer.objects.selected:
if obj.type == 'MESH':
selected_meshes.append(obj)
return len(selected_meshes) > 0

def execute(self, context):
for obj in context.view_layer.objects.selected:
if obj.type == 'MESH':
# Make sure all verts are selected otherwise some of the bmesh operations shit themselves
for vertex in obj.data.vertices:
vertex.select = True

bpy.ops.object.mode_set(mode='EDIT')
bmesh_copy = bmesh.from_edit_mesh(obj.data)

# Clear seams as we need to use them for splitting
for edge in bmesh_copy.edges:
if edge.seam:
edge.seam = False

# Select all UV verts as seams_from_islands relies on this to function
uv_layer = bmesh_copy.loops.layers.uv.verify()
for face in bmesh_copy.faces:
for loop in face.loops:
loop_uv = loop[uv_layer]
loop_uv.select = True

# Split edges
bpy.ops.uv.seams_from_islands()
island_boundaries = [edge for edge in bmesh_copy.edges if edge.seam and len(edge.link_faces) == 2]
bmesh.ops.split_edges(bmesh_copy, edges=island_boundaries)

# Apply the changes to the mesh
bmesh.update_edit_mesh(obj.data)
bpy.ops.object.mode_set(mode='OBJECT')

return {'FINISHED'}


class MergeNormalsOperator(Operator):
bl_idname = "flagrum.merge_normals"
bl_label = "Merge Normals"
bl_description = "Averages normals across doubles on selected meshes to smooth out the shading along seams"

@classmethod
def poll(cls, context):
selected_meshes = []
for obj in context.view_layer.objects.selected:
if obj.type == 'MESH':
selected_meshes.append(obj)
return len(selected_meshes) > 0

def execute(self, context):
for obj in context.view_layer.objects.selected:
if obj.type == 'MESH':
merge_normals(obj.data, 0.0001)

return {'FINISHED'}


class NormalsPanel(Panel):
bl_idname = "VIEW_3D_PT_flagrum_normals"
bl_label = "Custom Normals"
Expand All @@ -61,3 +133,5 @@ class NormalsPanel(Panel):
def draw(self, context):
layout = self.layout
layout.operator(UseCustomNormalsOperator.bl_idname)
layout.operator(SplitEdgesOperator.bl_idname)
layout.operator(MergeNormalsOperator.bl_idname)
Loading

0 comments on commit 4ec1dc3

Please sign in to comment.