refactor project. preparing v3.0 development. no debug current
This commit is contained in:
parent
9c24569a06
commit
e264c85a04
@ -50,7 +50,7 @@ In the dialog, you can select the material to be used. You can also choose the u
|
|||||||
|
|
||||||
You can also select the projection axis for better UV distribution.
|
You can also select the projection axis for better UV distribution.
|
||||||
|
|
||||||
### Flatten UV
|
#### Flatten UV
|
||||||
|
|
||||||
In the object editing mode, it is a operator which is used to attach the currently selected surface to the UV. And you can specific the edge which will be attached into the V axis. Note that only convex faces are supported.
|
In the object editing mode, it is a operator which is used to attach the currently selected surface to the UV. And you can specific the edge which will be attached into the V axis. Note that only convex faces are supported.
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ Ballance 3D是一套简单的用于制图3D相关的轻型工具集合,可以
|
|||||||
|
|
||||||
还可以选择投影轴以获取更好的UV分布。
|
还可以选择投影轴以获取更好的UV分布。
|
||||||
|
|
||||||
### Flatten UV
|
#### Flatten UV
|
||||||
|
|
||||||
在物体编辑模式下,用于将当前选中面按某一边贴附到V轴上的模式,展开到UV上。注意,只支持凸边面。
|
在物体编辑模式下,用于将当前选中面按某一边贴附到V轴上的模式,展开到UV上。注意,只支持凸边面。
|
||||||
|
|
||||||
|
363
ballance_blender_plugin/BMFILE_export.py
Normal file
363
ballance_blender_plugin/BMFILE_export.py
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
import bpy,bmesh,bpy_extras,mathutils
|
||||||
|
import pathlib,zipfile,time,os,tempfile,math
|
||||||
|
import struct, shutil
|
||||||
|
from bpy_extras import io_utils, node_shader_utils
|
||||||
|
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper
|
||||||
|
|
||||||
|
class BALLANCE_OT_export_bm(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
|
||||||
|
"""Save a Ballance Map File (BM file spec 1.4)"""
|
||||||
|
bl_idname = "ballance.export_bm"
|
||||||
|
bl_label = 'Export BM'
|
||||||
|
bl_options = {'PRESET'}
|
||||||
|
filename_ext = ".bmx"
|
||||||
|
|
||||||
|
export_mode: bpy.props.EnumProperty(
|
||||||
|
name="Export mode",
|
||||||
|
items=(('COLLECTION', "Collection", "Export a collection"),
|
||||||
|
('OBJECT', "Objects", "Export an objects"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
if (self.export_mode == 'COLLECTION' and context.scene.BallanceBlenderPluginProperty.collection_picker is None) or
|
||||||
|
(self.export_mode == 'OBJECT' and context.scene.BallanceBlenderPluginProperty.object_picker is None):
|
||||||
|
UTILS_functions.show_message_box(("No specific target", ), "Lost parameter", 'ERROR')
|
||||||
|
else:
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
|
||||||
|
if self.export_mode == 'COLLECTION':
|
||||||
|
export_bm(context, self.filepath,
|
||||||
|
prefs.no_component_collection,
|
||||||
|
self.export_mode, context.scene.BallanceBlenderPluginProperty.collection_picker)
|
||||||
|
elif self.export_mode == 'OBJECT':
|
||||||
|
export_bm(context, self.filepath,
|
||||||
|
prefs.no_component_collection,
|
||||||
|
self.export_mode, context.scene.BallanceBlenderPluginProperty.object_picker)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "export_mode")
|
||||||
|
if self.export_mode == 'COLLECTION':
|
||||||
|
layout.prop(context.scene.BallanceBlenderPluginProperty, "collection_picker")
|
||||||
|
elif self.export_mode == 'OBJECT':
|
||||||
|
layout.prop(context.scene.BallanceBlenderPluginProperty, "object_picker")
|
||||||
|
|
||||||
|
|
||||||
|
def export_bm(context, bmx_filepath, prefs_fncg, opts_exportMode, opts_exportTarget):
|
||||||
|
# ============================================ alloc a temp folder
|
||||||
|
utils_tempFolderObj = tempfile.TemporaryDirectory()
|
||||||
|
utils_tempFolder = tempFolderObj.name
|
||||||
|
utils_tempTextureFolder = os.path.join(utils_tempFolder, "Texture")
|
||||||
|
os.makedirs(utils_tempTextureFolder)
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# find export target.
|
||||||
|
# do not need check them validation in there.
|
||||||
|
# just collect them.
|
||||||
|
if opts_exportMode== "COLLECTION":
|
||||||
|
objectList = opts_exportTarget.objects
|
||||||
|
else:
|
||||||
|
objectList = [opts_exportTarget, ]
|
||||||
|
|
||||||
|
# try get fncg collection
|
||||||
|
# fncg stands with forced non-component group
|
||||||
|
try:
|
||||||
|
object_fncgCollection = bpy.data.collections[prefs_fncg]
|
||||||
|
except:
|
||||||
|
object_fncgCollection = None
|
||||||
|
|
||||||
|
# ============================================ export
|
||||||
|
with open(os.path.join(utils_tempFolder, "index.bm"), "wb") as finfo:
|
||||||
|
UTILS_file_io.write_uint32(finfo, bm_current_version)
|
||||||
|
|
||||||
|
# ====================== export object
|
||||||
|
meshSet = set()
|
||||||
|
meshList = []
|
||||||
|
meshCount = 0
|
||||||
|
with open(os.path.join(utils_tempFolder, "object.bm"), "wb") as fobject:
|
||||||
|
for obj in objectList:
|
||||||
|
# only export mesh object
|
||||||
|
if obj.type != 'MESH':
|
||||||
|
continue
|
||||||
|
|
||||||
|
# clean no mesh object
|
||||||
|
object_blenderMesh = obj.data
|
||||||
|
if object_blenderMesh is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# check component
|
||||||
|
if (object_fncgCollection is not None) and (obj.name in object_fncgCollection.objects):
|
||||||
|
# it should be set as normal object forcely
|
||||||
|
object_isComponent = False
|
||||||
|
else:
|
||||||
|
# check isComponent normally
|
||||||
|
object_isComponent = is_component(obj.name)
|
||||||
|
|
||||||
|
# triangle first and then group
|
||||||
|
if not object_isComponent:
|
||||||
|
if object_blenderMesh not in meshSet:
|
||||||
|
_mesh_triangulate(object_blenderMesh)
|
||||||
|
meshSet.add(object_blenderMesh)
|
||||||
|
meshList.append(object_blenderMesh)
|
||||||
|
object_meshIndex = meshCount
|
||||||
|
meshCount += 1
|
||||||
|
else:
|
||||||
|
object_meshIndex = meshList.index(object_blenderMesh)
|
||||||
|
else:
|
||||||
|
object_meshIndex = get_component_id(obj.name)
|
||||||
|
|
||||||
|
# get visibility
|
||||||
|
object_isHidden = not obj.visible_get()
|
||||||
|
|
||||||
|
# try get grouping data
|
||||||
|
object_groupList = _try_get_custom_property(obj, 'virtools-group')
|
||||||
|
object_groupList = _set_value_when_none(object_groupList, [])
|
||||||
|
|
||||||
|
# =======================
|
||||||
|
# write to files
|
||||||
|
# write finfo first
|
||||||
|
UTILS_file_io.write_string(finfo, obj.name)
|
||||||
|
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.OBJECT)
|
||||||
|
UTILS_file_io.write_uint64(finfo, fobject.tell())
|
||||||
|
|
||||||
|
# write fobject
|
||||||
|
UTILS_file_io.write_bool(fobject, object_isComponent)
|
||||||
|
UTILS_file_io.write_bool(fobject, object_isHidden)
|
||||||
|
UTILS_file_io.write_worldMatrix(fobject, obj.matrix_world)
|
||||||
|
UTILS_file_io.write_uint32(fobject, len(object_groupList))
|
||||||
|
for item in object_groupList:
|
||||||
|
UTILS_file_io.write_string(fobject, item)
|
||||||
|
UTILS_file_io.write_uint32(fobject, object_meshIndex)
|
||||||
|
|
||||||
|
# ====================== export mesh
|
||||||
|
materialSet = set()
|
||||||
|
materialList = []
|
||||||
|
with open(os.path.join(utils_tempFolder, "mesh.bm"), "wb") as fmesh:
|
||||||
|
for mesh in meshList:
|
||||||
|
# split normals
|
||||||
|
mesh.calc_normals_split()
|
||||||
|
|
||||||
|
# write finfo first
|
||||||
|
UTILS_file_io.write_string(finfo, mesh.name)
|
||||||
|
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.MESH)
|
||||||
|
UTILS_file_io.write_uint64(finfo, fmesh.tell())
|
||||||
|
|
||||||
|
# write fmesh
|
||||||
|
# vertices
|
||||||
|
mesh_vecList = mesh.vertices[:]
|
||||||
|
UTILS_file_io.write_uint32(fmesh, len(mesh_vecList))
|
||||||
|
for vec in mesh_vecList:
|
||||||
|
#swap yz
|
||||||
|
UTILS_file_io.write_3vector(fmesh,vec.co[0],vec.co[2],vec.co[1])
|
||||||
|
|
||||||
|
# uv
|
||||||
|
mesh_faceIndexPairs = [(face, index) for index, face in enumerate(mesh.polygons)]
|
||||||
|
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs) * 3)
|
||||||
|
if mesh.uv_layers.active is not None:
|
||||||
|
uv_layer = mesh.uv_layers.active.data[:]
|
||||||
|
for f, f_index in mesh_faceIndexPairs:
|
||||||
|
# it should be triangle face, otherwise throw a error
|
||||||
|
if (f.loop_total != 3):
|
||||||
|
raise Exception("Not a triangle", f.poly.loop_total)
|
||||||
|
|
||||||
|
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
|
||||||
|
uv = uv_layer[loop_index].uv
|
||||||
|
# reverse v
|
||||||
|
UTILS_file_io.write_2vector(fmesh, uv[0], -uv[1])
|
||||||
|
else:
|
||||||
|
# no uv data. write garbage
|
||||||
|
for i in range(len(mesh_faceIndexPairs) * 3):
|
||||||
|
UTILS_file_io.write_2vector(fmesh, 0.0, 0.0)
|
||||||
|
|
||||||
|
# normals
|
||||||
|
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs) * 3)
|
||||||
|
for f, f_index in mesh_faceIndexPairs:
|
||||||
|
# no need to check triangle again
|
||||||
|
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
|
||||||
|
nml = mesh.loops[loop_index].normal
|
||||||
|
# swap yz
|
||||||
|
UTILS_file_io.write_3vector(fmesh, nml[0], nml[2], nml[1])
|
||||||
|
|
||||||
|
# face
|
||||||
|
# get material first
|
||||||
|
mesh_usedBlenderMtl = mesh.materials[:]
|
||||||
|
mesh_noMaterial = len(mesh_usedBlenderMtl) == 0
|
||||||
|
for mat in mesh_usedBlenderMtl:
|
||||||
|
if mat not in materialSet:
|
||||||
|
materialSet.add(mat)
|
||||||
|
materialList.append(mat)
|
||||||
|
|
||||||
|
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs))
|
||||||
|
mesh_vtIndex = []
|
||||||
|
mesh_vnIndex = []
|
||||||
|
mesh_vIndex = []
|
||||||
|
for f, f_index in mesh_faceIndexPairs:
|
||||||
|
# confirm material use
|
||||||
|
if mesh_noMaterial:
|
||||||
|
mesh_materialIndex = 0
|
||||||
|
else:
|
||||||
|
mesh_materialIndex = materialList.index(mesh_usedBlenderMtl[f.material_index])
|
||||||
|
|
||||||
|
# export face
|
||||||
|
mesh_vtIndex.clear()
|
||||||
|
mesh_vnIndex.clear()
|
||||||
|
mesh_vIndex.clear()
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
|
||||||
|
mesh_vIndex.append(mesh.loops[loop_index].vertex_index)
|
||||||
|
mesh_vnIndex.append(f_index * 3 + counter)
|
||||||
|
mesh_vtIndex.append(f_index * 3 + counter)
|
||||||
|
counter += 1
|
||||||
|
# reverse vertices sort
|
||||||
|
UTILS_file_io.write_face(fmesh,
|
||||||
|
mesh_vIndex[2], mesh_vtIndex[2], mesh_vnIndex[2],
|
||||||
|
mesh_vIndex[1], mesh_vtIndex[1], mesh_vnIndex[1],
|
||||||
|
mesh_vIndex[0], mesh_vtIndex[0], mesh_vnIndex[0])
|
||||||
|
|
||||||
|
# set used material
|
||||||
|
UTILS_file_io.write_bool(fmesh, not mesh_noMaterial)
|
||||||
|
UTILS_file_io.write_uint32(fmesh, mesh_materialIndex)
|
||||||
|
|
||||||
|
# free splited normals
|
||||||
|
mesh.free_normals_split()
|
||||||
|
|
||||||
|
# ====================== export material
|
||||||
|
textureSet = set()
|
||||||
|
textureList = []
|
||||||
|
textureCount = 0
|
||||||
|
with open(os.path.join(utils_tempFolder, "material.bm"), "wb") as fmaterial:
|
||||||
|
for material in materialList:
|
||||||
|
# write finfo first
|
||||||
|
UTILS_file_io.write_string(finfo, material.name)
|
||||||
|
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.MATERIAL)
|
||||||
|
UTILS_file_io.write_uint64(finfo, fmaterial.tell())
|
||||||
|
|
||||||
|
# try get original written data
|
||||||
|
material_colAmbient = _try_get_custom_property(material, 'virtools-ambient')
|
||||||
|
material_colDiffuse = _try_get_custom_property(material, 'virtools-diffuse')
|
||||||
|
material_colSpecular = _try_get_custom_property(material, 'virtools-specular')
|
||||||
|
material_colEmissive = _try_get_custom_property(material, 'virtools-emissive')
|
||||||
|
material_specularPower = _try_get_custom_property(material, 'virtools-power')
|
||||||
|
|
||||||
|
# get basic color
|
||||||
|
mat_wrap = node_shader_utils.PrincipledBSDFWrapper(material)
|
||||||
|
if mat_wrap:
|
||||||
|
use_mirror = mat_wrap.metallic != 0.0
|
||||||
|
if use_mirror:
|
||||||
|
material_colAmbient = _set_value_when_none(material_colAmbient, (mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic))
|
||||||
|
else:
|
||||||
|
material_colAmbient = _set_value_when_none(material_colAmbient, (1.0, 1.0, 1.0))
|
||||||
|
material_colDiffuse = _set_value_when_none(material_colDiffuse, (mat_wrap.base_color[0], mat_wrap.base_color[1], mat_wrap.base_color[2]))
|
||||||
|
material_colSpecular = _set_value_when_none(material_colSpecular, (mat_wrap.specular, mat_wrap.specular, mat_wrap.specular))
|
||||||
|
material_colEmissive = _set_value_when_none(material_colEmissive, mat_wrap.emission_color[:3])
|
||||||
|
material_specularPower = _set_value_when_none(material_specularPower, 0.0)
|
||||||
|
|
||||||
|
# confirm texture
|
||||||
|
tex_wrap = getattr(mat_wrap, "base_color_texture", None)
|
||||||
|
if tex_wrap:
|
||||||
|
image = tex_wrap.image
|
||||||
|
if image:
|
||||||
|
# add into texture list
|
||||||
|
if image not in textureSet:
|
||||||
|
textureSet.add(image)
|
||||||
|
textureList.append(image)
|
||||||
|
textureIndex = textureCount
|
||||||
|
textureCount += 1
|
||||||
|
else:
|
||||||
|
textureIndex = textureList.index(image)
|
||||||
|
|
||||||
|
material_useTexture = True
|
||||||
|
material_textureIndex = textureIndex
|
||||||
|
else:
|
||||||
|
# no texture
|
||||||
|
material_useTexture = False
|
||||||
|
material_textureIndex = 0
|
||||||
|
else:
|
||||||
|
# no texture
|
||||||
|
material_useTexture = False
|
||||||
|
material_textureIndex = 0
|
||||||
|
|
||||||
|
else:
|
||||||
|
# no Principled BSDF. write garbage
|
||||||
|
material_colAmbient = _set_value_when_none(material_colAmbient, (0.8, 0.8, 0.8))
|
||||||
|
material_colDiffuse = _set_value_when_none(material_colDiffuse, (0.8, 0.8, 0.8))
|
||||||
|
material_colSpecular = _set_value_when_none(material_colSpecular, (0.8, 0.8, 0.8))
|
||||||
|
material_colEmissive = _set_value_when_none(material_colEmissive, (0.8, 0.8, 0.8))
|
||||||
|
material_specularPower = _set_value_when_none(material_specularPower, 0.0)
|
||||||
|
|
||||||
|
material_useTexture = False
|
||||||
|
material_textureIndex = 0
|
||||||
|
|
||||||
|
UTILS_file_io.write_color(fmaterial, material_colAmbient)
|
||||||
|
UTILS_file_io.write_color(fmaterial, material_colDiffuse)
|
||||||
|
UTILS_file_io.write_color(fmaterial, material_colSpecular)
|
||||||
|
UTILS_file_io.write_color(fmaterial, material_colEmissive)
|
||||||
|
UTILS_file_io.write_float(fmaterial, material_specularPower)
|
||||||
|
UTILS_file_io.write_bool(fmaterial, material_useTexture)
|
||||||
|
UTILS_file_io.write_uint32(fmaterial, material_textureIndex)
|
||||||
|
|
||||||
|
|
||||||
|
# ====================== export texture
|
||||||
|
texture_blenderFilePath = os.path.dirname(bpy.data.filepath)
|
||||||
|
texture_existedTextureFilepath = set()
|
||||||
|
with open(os.path.join(utils_tempFolder, "texture.bm"), "wb") as ftexture:
|
||||||
|
for texture in textureList:
|
||||||
|
# write finfo first
|
||||||
|
UTILS_file_io.write_string(finfo, texture.name)
|
||||||
|
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.TEXTURE)
|
||||||
|
UTILS_file_io.write_uint64(finfo, ftexture.tell())
|
||||||
|
|
||||||
|
# confirm whether it is internal texture
|
||||||
|
# get absolute texture path
|
||||||
|
texture_filepath = io_utils.path_reference(texture.filepath, texture_blenderFilePath, utils_tempTextureFolder,
|
||||||
|
'ABSOLUTE', "", None, texture.library)
|
||||||
|
# get file name and write it
|
||||||
|
texture_filename = os.path.basename(texture_filepath)
|
||||||
|
UTILS_file_io.write_string(ftexture, texture_filename)
|
||||||
|
|
||||||
|
if (_is_external_texture(texture_filename)):
|
||||||
|
# write directly, use Ballance texture
|
||||||
|
UTILS_file_io.write_bool(ftexture, True)
|
||||||
|
else:
|
||||||
|
# copy internal texture, if this file is copied, do not copy it again
|
||||||
|
UTILS_file_io.write_bool(ftexture, False)
|
||||||
|
if texture_filename not in texture_existedTextureFilepath:
|
||||||
|
shutil.copy(texture_filepath, os.path.join(utils_tempTextureFolder, texture_filename))
|
||||||
|
texture_existedTextureFilepath.add(texture_filename)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# save zip and clean up folder
|
||||||
|
UTILS_zip_helper.compress(utils_tempFolder, bmx_filepath)
|
||||||
|
utils_tempFolderObj.cleanup()
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# blender related functions
|
||||||
|
|
||||||
|
def _is_external_texture(name):
|
||||||
|
if name in config.external_texture_list:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _mesh_triangulate(me):
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(me)
|
||||||
|
bmesh.ops.triangulate(bm, faces=bm.faces)
|
||||||
|
bm.to_mesh(me)
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
def _try_get_custom_property(obj, field):
|
||||||
|
try:
|
||||||
|
return obj[field]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _set_value_when_none(obj, newValue):
|
||||||
|
if obj is None:
|
||||||
|
return newValue
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
357
ballance_blender_plugin/BMFILE_import.py
Normal file
357
ballance_blender_plugin/BMFILE_import.py
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
import bpy,bmesh,bpy_extras,mathutils
|
||||||
|
import pathlib,zipfile,time,os,tempfile,math
|
||||||
|
import struct,shutil
|
||||||
|
from bpy_extras import io_utils,node_shader_utils
|
||||||
|
from bpy_extras.io_utils import unpack_list
|
||||||
|
from bpy_extras.image_utils import load_image
|
||||||
|
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper
|
||||||
|
|
||||||
|
class BALLANCE_OT_import_bm(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
|
||||||
|
"""Load a Ballance Map File (BM file spec 1.4)"""
|
||||||
|
bl_idname = "ballance.import_bm"
|
||||||
|
bl_label = "Import BM "
|
||||||
|
bl_options = {'PRESET', 'UNDO'}
|
||||||
|
filename_ext = ".bmx"
|
||||||
|
|
||||||
|
texture_conflict_strategy: bpy.props.EnumProperty(
|
||||||
|
name="Texture name conflict",
|
||||||
|
items=(('NEW', "New instance", "Create a new instance"),
|
||||||
|
('CURRENT', "Use current", "Use current"),),
|
||||||
|
description="Define how to process texture name conflict",
|
||||||
|
default='CURRENT',
|
||||||
|
)
|
||||||
|
|
||||||
|
material_conflict_strategy: bpy.props.EnumProperty(
|
||||||
|
name="Material name conflict",
|
||||||
|
items=(('RENAME', "Rename", "Rename the new one"),
|
||||||
|
('CURRENT', "Use current", "Use current"),),
|
||||||
|
description="Define how to process material name conflict",
|
||||||
|
default='RENAME',
|
||||||
|
)
|
||||||
|
|
||||||
|
mesh_conflict_strategy: bpy.props.EnumProperty(
|
||||||
|
name="Mesh name conflict",
|
||||||
|
items=(('RENAME', "Rename", "Rename the new one"),
|
||||||
|
('CURRENT', "Use current", "Use current"),),
|
||||||
|
description="Define how to process mesh name conflict",
|
||||||
|
default='RENAME',
|
||||||
|
)
|
||||||
|
|
||||||
|
object_conflict_strategy: bpy.props.EnumProperty(
|
||||||
|
name="Object name conflict",
|
||||||
|
items=(('RENAME', "Rename", "Rename the new one"),
|
||||||
|
('CURRENT', "Use current", "Use current"),),
|
||||||
|
description="Define how to process object name conflict",
|
||||||
|
default='RENAME',
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(self, context):
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
return (os.path.isdir(prefs.temp_texture_folder) and os.path.isdir(prefs.external_folder))
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
import_bm(context, self.filepath,
|
||||||
|
prefs.no_component_collection, prefs.external_folder, prefs.temp_texture_folder,
|
||||||
|
self.texture_conflict_strategy, self.material_conflict_strategy,
|
||||||
|
self.mesh_conflict_strategy, self.object_conflict_strategy)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
def import_bm(context, bmx_filepath, prefs_fncg, prefs_externalTexture, prefs_tempTextureFolder, opts_texture, opts_material, opts_mesh, opts_object):
|
||||||
|
# ============================================
|
||||||
|
# alloc a temp folder for decompress
|
||||||
|
utils_tempFolderObj = tempfile.TemporaryDirectory()
|
||||||
|
utils_tempFolder = utils_tempFolderObj.name
|
||||||
|
utils_tempTextureFolder = os.path.join(utils_tempFolder, "Texture")
|
||||||
|
# decompress
|
||||||
|
UTILS_zip_helper.decompress(utils_tempFolder, bmx_filepath)
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# read bmx file officially
|
||||||
|
# index.bm
|
||||||
|
objectList = []
|
||||||
|
meshList = []
|
||||||
|
materialList = []
|
||||||
|
textureList = []
|
||||||
|
with open(os.path.join(utils_tempFolder, "index.bm"), "rb") as findex:
|
||||||
|
# check version first
|
||||||
|
index_gottenVersion = UTILS_file_io.read_uint32(findex)
|
||||||
|
if (index_gottenVersion != UTILS_constants.bmfile_currentVersion):
|
||||||
|
# clean temp folder, output error
|
||||||
|
UTILS_functions.show_message_box(
|
||||||
|
("Unsupported BM spec. Expect: {} Gotten: {}".format(UTILS_constants.bmfile_currentVersion, index_gottenVersion), ),
|
||||||
|
"Unsupported BM spec", 'ERROR')
|
||||||
|
findex.close()
|
||||||
|
utils_tempFolderObj.cleanup()
|
||||||
|
return
|
||||||
|
|
||||||
|
# collect block header data
|
||||||
|
while len(peek_stream(findex)) != 0:
|
||||||
|
# read
|
||||||
|
index_name = UTILS_file_io.read_string(findex)
|
||||||
|
index_type = UTILS_file_io.read_uint8(findex)
|
||||||
|
index_offset = UTILS_file_io.read_uint64(findex)
|
||||||
|
index_blockCache = _InfoBlockHelper(index_name, index_offset)
|
||||||
|
|
||||||
|
# grouping into list
|
||||||
|
if index_type == UTILS_constants.BmfileInfoType.OBJECT:
|
||||||
|
objectList.append(index_blockCache)
|
||||||
|
elif index_type == UTILS_constants.BmfileInfoType.MESH:
|
||||||
|
meshList.append(index_blockCache)
|
||||||
|
elif index_type == UTILS_constants.BmfileInfoType.MATERIAL:
|
||||||
|
materialList.append(index_blockCache)
|
||||||
|
elif index_type == UTILS_constants.BmfileInfoType.TEXTURE:
|
||||||
|
textureList.append(index_blockCache)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# texture.bm
|
||||||
|
with open(os.path.join(utils_tempFolder, "texture.bm"), "rb") as ftexture:
|
||||||
|
for item in textureList:
|
||||||
|
# seek to block
|
||||||
|
ftexture.seek(item.offset, os.SEEK_SET)
|
||||||
|
|
||||||
|
# read data
|
||||||
|
texture_filename = UTILS_file_io.read_string(ftexture)
|
||||||
|
texture_isExternal = UTILS_file_io.read_bool(ftexture)
|
||||||
|
if texture_isExternal:
|
||||||
|
(texture_target, skip_init) = UTILS_functions.create_instance_with_option(
|
||||||
|
UTILS_constants.BmfileInfoType.TEXTURE, item.name, opts_texture,
|
||||||
|
extra_texture_path= texture_filename, extra_texture_path= prefs_externalTexture)
|
||||||
|
else:
|
||||||
|
# not external. copy temp file into blender temp. then use it.
|
||||||
|
# try copy. if fail, don't need to do more
|
||||||
|
try:
|
||||||
|
shutil.copy(os.path.join(utils_tempTextureFolder, texture_filename),
|
||||||
|
os.path.join(prefs_tempTextureFolder, texture_filename))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
(texture_target, skip_init) = UTILS_functions.create_instance_with_option(
|
||||||
|
UTILS_constants.BmfileInfoType.TEXTURE, item.name, opts_texture,
|
||||||
|
extra_texture_path= texture_filename, extra_texture_path= prefs_tempTextureFolder)
|
||||||
|
|
||||||
|
# setup name and blender data for header
|
||||||
|
item.blender_data = texture_target
|
||||||
|
|
||||||
|
# material.bm
|
||||||
|
# WARNING: this code is shared with add_floor - create_or_get_material()
|
||||||
|
with open(os.path.join(utils_tempFolder, "material.bm"), "rb") as fmaterial:
|
||||||
|
for item in materialList:
|
||||||
|
# seek to block
|
||||||
|
fmaterial.seek(item.offset, os.SEEK_SET)
|
||||||
|
|
||||||
|
# read data
|
||||||
|
material_colAmbient = UTILS_file_io.read_3vector(fmaterial)
|
||||||
|
material_colDiffuse = UTILS_file_io.read_3vector(fmaterial)
|
||||||
|
material_colSpecular = UTILS_file_io.read_3vector(fmaterial)
|
||||||
|
material_colEmissive = UTILS_file_io.read_3vector(fmaterial)
|
||||||
|
material_specularPower = UTILS_file_io.read_float(fmaterial)
|
||||||
|
material_useTexture = UTILS_file_io.read_bool(fmaterial)
|
||||||
|
material_texture = UTILS_file_io.read_uint32(fmaterial)
|
||||||
|
|
||||||
|
# alloc basic material
|
||||||
|
(material_target, skip_init) = create_instance_with_option(
|
||||||
|
UTILS_constants.BmfileInfoType.MATERIAL, item.name, opts_material)
|
||||||
|
item.blender_data = material_target
|
||||||
|
if skip_init:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# try create material nodes
|
||||||
|
UTILS_functions.create_material_nodes(material_target,
|
||||||
|
material_colAmbient, material_colDiffuse, material_colSpecular, material_colEmissive,
|
||||||
|
material_specularPower,
|
||||||
|
textureList[material_texture].blender_data if material_useTexture else None)
|
||||||
|
|
||||||
|
# mesh.bm
|
||||||
|
# WARNING: this code is shared with add_floor
|
||||||
|
with open(os.path.join(utils_tempFolder, "mesh.bm"), "rb") as fmesh:
|
||||||
|
mesh_vList=[]
|
||||||
|
mesh_vtList=[]
|
||||||
|
mesh_vnList=[]
|
||||||
|
mesh_faceList=[]
|
||||||
|
mesh_materialSolt = []
|
||||||
|
for item in meshList:
|
||||||
|
fmesh.seek(item.offset, os.SEEK_SET)
|
||||||
|
|
||||||
|
# create real mesh
|
||||||
|
(mesh_target, skip_init) = create_instance_with_option(
|
||||||
|
UTILS_constants.BmfileInfoType.MESH, item.name, opts_mesh)
|
||||||
|
item.blender_data = mesh_target
|
||||||
|
if skip_init:
|
||||||
|
continue
|
||||||
|
|
||||||
|
mesh_vList.clear()
|
||||||
|
mesh_vtList.clear()
|
||||||
|
mesh_vnList.clear()
|
||||||
|
mesh_faceList.clear()
|
||||||
|
mesh_materialSolt.clear()
|
||||||
|
# in first read, store all data into list
|
||||||
|
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
|
||||||
|
for i in range(mesh_listCount):
|
||||||
|
cache = UTILS_file_io.read_3vector(fmesh)
|
||||||
|
# switch yz
|
||||||
|
mesh_vList.append((cache[0], cache[2], cache[1]))
|
||||||
|
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
|
||||||
|
for i in range(mesh_listCount):
|
||||||
|
cache = UTILS_file_io.read_2vector(fmesh)
|
||||||
|
# reverse v
|
||||||
|
mesh_vtList.append((cache[0], -cache[1]))
|
||||||
|
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
|
||||||
|
for i in range(mesh_listCount):
|
||||||
|
cache = UTILS_file_io.read_3vector(fmesh)
|
||||||
|
# switch yz
|
||||||
|
mesh_vnList.append((cache[0], cache[2], cache[1]))
|
||||||
|
|
||||||
|
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
|
||||||
|
for i in range(mesh_listCount):
|
||||||
|
mesh_faceData = UTILS_file_io.read_face(fmesh)
|
||||||
|
mesh_useMaterial = UTILS_file_io.read_bool(fmesh)
|
||||||
|
mesh_materialIndex = UTILS_file_io.read_uint32(fmesh)
|
||||||
|
|
||||||
|
if mesh_useMaterial:
|
||||||
|
mesh_neededMaterial = materialList[mesh_materialIndex].blender_data
|
||||||
|
if mesh_neededMaterial in mesh_materialSolt:
|
||||||
|
mesh_blenderMtlIndex = materialSolt.index(mesh_neededMaterial)
|
||||||
|
else:
|
||||||
|
mesh_blenderMtlIndex = len(mesh_materialSolt)
|
||||||
|
mesh_materialSolt.append(mesh_neededMaterial)
|
||||||
|
else:
|
||||||
|
mesh_blenderMtlIndex = -1
|
||||||
|
|
||||||
|
# we need invert triangle sort
|
||||||
|
mesh_faceList.append((
|
||||||
|
mesh_faceData[6], mesh_faceData[7], mesh_faceData[8],
|
||||||
|
mesh_faceData[3], mesh_faceData[4], mesh_faceData[5],
|
||||||
|
mesh_faceData[0], mesh_faceData[1], mesh_faceData[2],
|
||||||
|
mesh_blenderMtlIndex
|
||||||
|
))
|
||||||
|
|
||||||
|
# and then we need add material solt for this mesh
|
||||||
|
for mat in mesh_materialSolt:
|
||||||
|
mesh_target.materials.append(mat)
|
||||||
|
|
||||||
|
# then, we need add correspond count for vertices
|
||||||
|
mesh_target.vertices.add(len(vList))
|
||||||
|
mesh_target.loops.add(len(faceList)*3) # triangle face confirm
|
||||||
|
mesh_target.polygons.add(len(faceList))
|
||||||
|
mesh_target.uv_layers.new(do_init=False)
|
||||||
|
mesh_target.create_normals_split()
|
||||||
|
|
||||||
|
# add vertices data
|
||||||
|
mesh_target.vertices.foreach_set("co", unpack_list(vList))
|
||||||
|
mesh_target.loops.foreach_set("vertex_index", unpack_list(_flat_vertices_index(mesh_faceList)))
|
||||||
|
mesh_target.loops.foreach_set("normal", unpack_list(_flat_vertices_normal(mesh_faceList, mesh_vnList)))
|
||||||
|
mesh_target.uv_layers[0].data.foreach_set("uv", unpack_list(_flat_vertices_uv(mesh_faceList, mesh_vtList)))
|
||||||
|
for i in range(len(faceList)):
|
||||||
|
mesh_target.polygons[i].loop_start = i * 3
|
||||||
|
mesh_target.polygons[i].loop_total = 3
|
||||||
|
if faceList[i][9] != -1:
|
||||||
|
mesh_target.polygons[i].material_index = faceList[i][9]
|
||||||
|
|
||||||
|
mesh_target.polygons[i].use_smooth = True
|
||||||
|
|
||||||
|
mesh_target.validate(clean_customdata=False)
|
||||||
|
mesh_target.update(calc_edges=False, calc_edges_loose=False)
|
||||||
|
|
||||||
|
|
||||||
|
# object
|
||||||
|
with open(os.path.join(utils_tempFolder, "object.bm"), "rb") as fobject:
|
||||||
|
|
||||||
|
# we need get needed collection first
|
||||||
|
blender_viewLayer = context.view_layer
|
||||||
|
blender_collection = blender_viewLayer.active_layer_collection.collection
|
||||||
|
if prefs_fncg == "":
|
||||||
|
# fncg stands with Forced Non-Component Group
|
||||||
|
object_fncgCollection = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
# try get collection
|
||||||
|
object_fncgCollection = bpy.data.collections[prefs_fncg]
|
||||||
|
except:
|
||||||
|
# fail to get, create new one under active collection instead
|
||||||
|
object_fncgCollection = bpy.data.collections.new(prefs_fncg)
|
||||||
|
blender_collection.children.link(object_fncgCollection)
|
||||||
|
|
||||||
|
# start process it
|
||||||
|
object_groupList = []
|
||||||
|
for item in objectList:
|
||||||
|
fobject.seek(item.offset, os.SEEK_SET)
|
||||||
|
|
||||||
|
# read data
|
||||||
|
object_isComponent = UTILS_file_io.read_bool(fobject)
|
||||||
|
#object_isForcedNoComponent = UTILS_file_io.read_bool(fobject)
|
||||||
|
object_isHidden = UTILS_file_io.read_bool(fobject)
|
||||||
|
object_worldMatrix = UTILS_file_io.read_worldMaterix(fobject)
|
||||||
|
object_groupListCount = UTILS_file_io.read_uint32(fobject)
|
||||||
|
object_groupList.clear()
|
||||||
|
for i in range(object_groupListCount):
|
||||||
|
object_groupList.append(UTILS_file_io.read_string(fobject))
|
||||||
|
object_meshIndex = UTILS_file_io.read_uint32(fobject)
|
||||||
|
|
||||||
|
# got mesh first
|
||||||
|
if object_isComponent:
|
||||||
|
object_neededMesh = UTILS_functions.load_component(object_meshIndex)
|
||||||
|
else:
|
||||||
|
object_neededMesh = meshList[object_meshIndex].blender_data
|
||||||
|
|
||||||
|
# create real object
|
||||||
|
(object_target, skip_init) = create_instance_with_option(
|
||||||
|
UTILS_constants.BmfileInfoType.OBJECT, item.name, opts_object,
|
||||||
|
extraMesh=object_neededMesh)
|
||||||
|
if skip_init:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# link to correct collection
|
||||||
|
if (object_fncgCollection is not None) and (not object_isComponent) and UTILS_functions.is_component(item.name):
|
||||||
|
# a object should be grouped into fncg should fufill following requirements
|
||||||
|
# fncg is not null
|
||||||
|
# this object is a normal object
|
||||||
|
# but its name match component format
|
||||||
|
object_fncgCollection.objects.link(object_target)
|
||||||
|
else:
|
||||||
|
# otherwise, group it into normal collection
|
||||||
|
blender_collection.objects.link(object_target)
|
||||||
|
object_target.matrix_world = object_worldMatrix
|
||||||
|
object_target.hide_set(object_isHidden)
|
||||||
|
|
||||||
|
# write custom property
|
||||||
|
if len(object_groupList) != 0:
|
||||||
|
object_target['virtools-group'] = tuple(object_groupList)
|
||||||
|
|
||||||
|
# update view layer after all objects has been imported
|
||||||
|
blender_viewLayer.update()
|
||||||
|
|
||||||
|
# release temp folder
|
||||||
|
utils_tempFolderObj.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# blender related functions
|
||||||
|
|
||||||
|
class _InfoBlockHelper():
|
||||||
|
def __init__(self, name, offset):
|
||||||
|
self.name = name
|
||||||
|
self.offset = offset
|
||||||
|
self.blender_data = None
|
||||||
|
|
||||||
|
def _flat_vertices_index(faceList):
|
||||||
|
for item in faceList:
|
||||||
|
yield (item[0], )
|
||||||
|
yield (item[3], )
|
||||||
|
yield (item[6], )
|
||||||
|
|
||||||
|
def _flat_vertices_normal(faceList, vnList):
|
||||||
|
for item in faceList:
|
||||||
|
yield vnList[item[2]]
|
||||||
|
yield vnList[item[5]]
|
||||||
|
yield vnList[item[8]]
|
||||||
|
|
||||||
|
def _flat_vertices_uv(faceList, vtList):
|
||||||
|
for item in faceList:
|
||||||
|
yield vtList[item[1]]
|
||||||
|
yield vtList[item[4]]
|
||||||
|
yield vtList[item[7]]
|
@ -1,5 +1,5 @@
|
|||||||
import bpy,mathutils
|
import bpy, mathutils
|
||||||
from . import utils
|
from . import UTILS_functions
|
||||||
|
|
||||||
class BALLANCE_OT_super_align(bpy.types.Operator):
|
class BALLANCE_OT_super_align(bpy.types.Operator):
|
||||||
"""Align object with 3ds Max way"""
|
"""Align object with 3ds Max way"""
|
||||||
@ -31,10 +31,10 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(self, context):
|
||||||
return check_align_target()
|
return _check_align_target()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
align_object(self.align_x, self.align_y, self.align_z, self.current_references, self.target_references)
|
_align_object(self.align_x, self.align_y, self.align_z, self.current_references, self.target_references)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@ -56,7 +56,7 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
|
|||||||
|
|
||||||
# ============================== method
|
# ============================== method
|
||||||
|
|
||||||
def check_align_target():
|
def _check_align_target():
|
||||||
if bpy.context.active_object is None:
|
if bpy.context.active_object is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -69,14 +69,14 @@ def check_align_target():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def align_object(use_x, use_y, use_z, currentMode, targetMode):
|
def _align_object(use_x, use_y, use_z, currentMode, targetMode):
|
||||||
if not (use_x or use_y or use_z):
|
if not (use_x or use_y or use_z):
|
||||||
return
|
return
|
||||||
|
|
||||||
# calc active object data
|
# calc active object data
|
||||||
currentObj = bpy.context.active_object
|
currentObj = bpy.context.active_object
|
||||||
currentObjBbox = [currentObj.matrix_world @ mathutils.Vector(corner) for corner in currentObj.bound_box]
|
currentObjBbox = [currentObj.matrix_world @ mathutils.Vector(corner) for corner in currentObj.bound_box]
|
||||||
currentObjRef = provideObjRefPoint(currentObj, currentObjBbox, currentMode)
|
currentObjRef = _provide_obj_reference_point(currentObj, currentObjBbox, currentMode)
|
||||||
|
|
||||||
# calc target
|
# calc target
|
||||||
targetObjList = bpy.context.selected_objects[:]
|
targetObjList = bpy.context.selected_objects[:]
|
||||||
@ -86,7 +86,7 @@ def align_object(use_x, use_y, use_z, currentMode, targetMode):
|
|||||||
# process each obj
|
# process each obj
|
||||||
for targetObj in targetObjList:
|
for targetObj in targetObjList:
|
||||||
targetObjBbox = [targetObj.matrix_world @ mathutils.Vector(corner) for corner in targetObj.bound_box]
|
targetObjBbox = [targetObj.matrix_world @ mathutils.Vector(corner) for corner in targetObj.bound_box]
|
||||||
targetObjRef = provideObjRefPoint(targetObj, targetObjBbox, targetMode)
|
targetObjRef = _provide_obj_reference_point(targetObj, targetObjBbox, targetMode)
|
||||||
|
|
||||||
if use_x:
|
if use_x:
|
||||||
targetObj.location.x += currentObjRef.x - targetObjRef.x
|
targetObj.location.x += currentObjRef.x - targetObjRef.x
|
||||||
@ -95,7 +95,7 @@ def align_object(use_x, use_y, use_z, currentMode, targetMode):
|
|||||||
if use_z:
|
if use_z:
|
||||||
targetObj.location.z += currentObjRef.z - targetObjRef.z
|
targetObj.location.z += currentObjRef.z - targetObjRef.z
|
||||||
|
|
||||||
def provideObjRefPoint(obj, vecList, mode):
|
def _provide_obj_reference_point(obj, vecList, mode):
|
||||||
refPoint = mathutils.Vector((0, 0, 0))
|
refPoint = mathutils.Vector((0, 0, 0))
|
||||||
|
|
||||||
if (mode == 'MIN'):
|
if (mode == 'MIN'):
|
@ -1,6 +1,6 @@
|
|||||||
import bpy,mathutils
|
import bpy,mathutils
|
||||||
import bmesh
|
import bmesh
|
||||||
from . import utils
|
from . import UTILS_functions
|
||||||
|
|
||||||
class BALLANCE_OT_flatten_uv(bpy.types.Operator):
|
class BALLANCE_OT_flatten_uv(bpy.types.Operator):
|
||||||
"""Flatten selected face UV. Only works for convex face"""
|
"""Flatten selected face UV. Only works for convex face"""
|
||||||
@ -9,7 +9,7 @@ class BALLANCE_OT_flatten_uv(bpy.types.Operator):
|
|||||||
bl_options = {'UNDO'}
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
reference_edge : bpy.props.IntProperty(
|
reference_edge : bpy.props.IntProperty(
|
||||||
name="Reference_edge",
|
name="Reference edge",
|
||||||
description="The references edge of UV. It will be placed in V axis.",
|
description="The references edge of UV. It will be placed in V axis.",
|
||||||
min=0,
|
min=0,
|
||||||
soft_min=0,
|
soft_min=0,
|
||||||
@ -33,16 +33,19 @@ class BALLANCE_OT_flatten_uv(bpy.types.Operator):
|
|||||||
return wm.invoke_props_dialog(self)
|
return wm.invoke_props_dialog(self)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
no_processed_count = real_flatten_uv(bpy.context.active_object.data, self.reference_edge)
|
no_processed_count = _real_flatten_uv(bpy.context.active_object.data, self.reference_edge)
|
||||||
if no_processed_count != 0:
|
if no_processed_count != 0:
|
||||||
utils.ShowMessageBox(("{} faces may not be processed correctly because they have problem.".format(no_processed_count), ), "Warning", 'ERROR')
|
UTILS_functions.show_message_box(
|
||||||
|
("{} faces may not be processed correctly because they have problem.".format(no_processed_count), ),
|
||||||
|
"Warning", 'ERROR'
|
||||||
|
)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.prop(self, "reference_edge")
|
layout.prop(self, "reference_edge")
|
||||||
|
|
||||||
def real_flatten_uv(mesh, reference_edge):
|
def _real_flatten_uv(mesh, reference_edge):
|
||||||
no_processed_count = 0
|
no_processed_count = 0
|
||||||
|
|
||||||
if mesh.uv_layers.active is None:
|
if mesh.uv_layers.active is None:
|
@ -1,7 +1,7 @@
|
|||||||
import bpy,bmesh
|
import bpy,bmesh
|
||||||
import mathutils
|
import mathutils
|
||||||
import bpy.types
|
import bpy.types
|
||||||
from . import utils, preferences
|
from . import UTILS_functions
|
||||||
|
|
||||||
class BALLANCE_OT_rail_uv(bpy.types.Operator):
|
class BALLANCE_OT_rail_uv(bpy.types.Operator):
|
||||||
"""Create a UV for rail"""
|
"""Create a UV for rail"""
|
||||||
@ -38,7 +38,7 @@ class BALLANCE_OT_rail_uv(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(self, context):
|
||||||
return check_rail_target()
|
return _check_rail_target()
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
wm = context.window_manager
|
wm = context.window_manager
|
||||||
@ -46,9 +46,9 @@ class BALLANCE_OT_rail_uv(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
if context.scene.BallanceBlenderPluginProperty.material_picker == None:
|
if context.scene.BallanceBlenderPluginProperty.material_picker == None:
|
||||||
utils.ShowMessageBox(("No specific material", ), "Lost parameter", 'ERROR')
|
UTILS_functions.show_message_box(("No specific material", ), "Lost parameter", 'ERROR')
|
||||||
else:
|
else:
|
||||||
create_rail_uv(self.uv_type, context.scene.BallanceBlenderPluginProperty.material_picker, self.uv_scale, self.projection_axis)
|
_create_rail_uv(self.uv_type, context.scene.BallanceBlenderPluginProperty.material_picker, self.uv_scale, self.projection_axis)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
@ -62,7 +62,7 @@ class BALLANCE_OT_rail_uv(bpy.types.Operator):
|
|||||||
|
|
||||||
# ====================== method
|
# ====================== method
|
||||||
|
|
||||||
def check_rail_target():
|
def _check_rail_target():
|
||||||
for obj in bpy.context.selected_objects:
|
for obj in bpy.context.selected_objects:
|
||||||
if obj.type != 'MESH':
|
if obj.type != 'MESH':
|
||||||
continue
|
continue
|
||||||
@ -71,7 +71,7 @@ def check_rail_target():
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_distance(iterator):
|
def _get_distance(iterator):
|
||||||
is_first_min = True
|
is_first_min = True
|
||||||
is_first_max = True
|
is_first_max = True
|
||||||
max_value = 0.0
|
max_value = 0.0
|
||||||
@ -93,7 +93,7 @@ def get_distance(iterator):
|
|||||||
|
|
||||||
return max_value - min_value
|
return max_value - min_value
|
||||||
|
|
||||||
def create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
|
def _create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
|
||||||
objList = []
|
objList = []
|
||||||
ignoredObj = []
|
ignoredObj = []
|
||||||
for obj in bpy.context.selected_objects:
|
for obj in bpy.context.selected_objects:
|
||||||
@ -125,18 +125,18 @@ def create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
|
|||||||
# calc proper scale
|
# calc proper scale
|
||||||
if projection_axis == 'X':
|
if projection_axis == 'X':
|
||||||
maxLength = max(
|
maxLength = max(
|
||||||
get_distance(vec.co[1] for vec in vecList),
|
_get_distance(vec.co[1] for vec in vecList),
|
||||||
get_distance(vec.co[2] for vec in vecList)
|
_get_distance(vec.co[2] for vec in vecList)
|
||||||
)
|
)
|
||||||
elif projection_axis == 'Y':
|
elif projection_axis == 'Y':
|
||||||
maxLength = max(
|
maxLength = max(
|
||||||
get_distance(vec.co[0] for vec in vecList),
|
_get_distance(vec.co[0] for vec in vecList),
|
||||||
get_distance(vec.co[2] for vec in vecList)
|
_get_distance(vec.co[2] for vec in vecList)
|
||||||
)
|
)
|
||||||
elif projection_axis == 'Z':
|
elif projection_axis == 'Z':
|
||||||
maxLength = max(
|
maxLength = max(
|
||||||
get_distance(vec.co[0] for vec in vecList),
|
_get_distance(vec.co[0] for vec in vecList),
|
||||||
get_distance(vec.co[1] for vec in vecList)
|
_get_distance(vec.co[1] for vec in vecList)
|
||||||
)
|
)
|
||||||
real_scale = 1.0 / maxLength
|
real_scale = 1.0 / maxLength
|
||||||
|
|
||||||
@ -166,4 +166,7 @@ def create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
|
|||||||
uv_layer[loop_index].uv[1] = vecList[index].co[1] * real_scale
|
uv_layer[loop_index].uv[1] = vecList[index].co[1] * real_scale
|
||||||
|
|
||||||
if len(ignoredObj) != 0:
|
if len(ignoredObj) != 0:
|
||||||
utils.ShowMessageBox(("Following objects are not processed due to they are not suit for this function now: ", ) + tuple(ignoredObj), "Execution result", 'INFO')
|
UTILS_functions.show_message_box(
|
||||||
|
("Following objects are not processed due to they are not suit for this function now: ", ) + tuple(ignoredObj),
|
||||||
|
"Execution result", 'INFO'
|
||||||
|
)
|
35
ballance_blender_plugin/NAMES_rename_via_group.py
Normal file
35
ballance_blender_plugin/NAMES_rename_via_group.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import bpy,bmesh
|
||||||
|
import mathutils
|
||||||
|
import bpy.types
|
||||||
|
from . import UTILS_functions
|
||||||
|
|
||||||
|
class BALLANCE_OT_rename_via_group(bpy.types.Operator):
|
||||||
|
"""Rename object via Virtools groups"""
|
||||||
|
bl_idname = "ballance.rename_via_group"
|
||||||
|
bl_label = "Rename via Group"
|
||||||
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
|
name_standard: bpy.props.EnumProperty(
|
||||||
|
name="Name Standard",
|
||||||
|
description="Choose your prefered name standard",
|
||||||
|
items=(
|
||||||
|
("YYC", "YYC Tools Chains", "YYC Tools Chains name standard."),
|
||||||
|
("IMENGYU", "Imengyu Ballance", "Auto grouping name standard for Imengyu/Ballance")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(self, context):
|
||||||
|
return True
|
||||||
|
#return _check_rail_target()
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
wm = context.window_manager
|
||||||
|
return wm.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "name_standard")
|
56
ballance_blender_plugin/OBJS_add_components.py
Normal file
56
ballance_blender_plugin/OBJS_add_components.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import bpy, mathutils
|
||||||
|
from . import UTILS_constants, UTILS_functions
|
||||||
|
|
||||||
|
# ================================================= actual add
|
||||||
|
|
||||||
|
class BALLANCE_OT_add_components(bpy.types.Operator):
|
||||||
|
"""Add sector related elements"""
|
||||||
|
bl_idname = "ballance.add_components"
|
||||||
|
bl_label = "Add elements"
|
||||||
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
|
elements_type: bpy.props.EnumProperty(
|
||||||
|
name="Type",
|
||||||
|
description="This element type",
|
||||||
|
items=tuple(map(lambda x: (x, x, ""), UTILS_constants.componentList)),
|
||||||
|
)
|
||||||
|
|
||||||
|
attentionElements = ["PC_TwoFlames", "PR_Resetpoint"]
|
||||||
|
uniqueElements = ["PS_FourFlames", "PE_Balloon"]
|
||||||
|
|
||||||
|
elements_sector: bpy.props.IntProperty(
|
||||||
|
name="Sector",
|
||||||
|
description="Define which sector the object will be grouped in",
|
||||||
|
min=1,
|
||||||
|
max=8,
|
||||||
|
default=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
# get name
|
||||||
|
if self.elements_type in self.uniqueElements:
|
||||||
|
finalObjectName = self.elements_type + "_01"
|
||||||
|
elif self.elements_type in self.attentionElements:
|
||||||
|
finalObjectName = self.elements_type + "_0" + str(self.elements_sector)
|
||||||
|
else:
|
||||||
|
finalObjectName = self.elements_type + "_0" + str(self.elements_sector) + "_"
|
||||||
|
|
||||||
|
# create object
|
||||||
|
loadedMesh = UTILS_functions.load_component(
|
||||||
|
UTILS_constants.componentList.index(self.elements_type))
|
||||||
|
obj = bpy.data.objects.new(finalObjectName, loadedMesh)
|
||||||
|
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
wm = context.window_manager
|
||||||
|
return wm.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "elements_type")
|
||||||
|
if self.elements_type not in self.uniqueElements:
|
||||||
|
layout.prop(self, "elements_sector")
|
||||||
|
if self.elements_type in self.attentionElements:
|
||||||
|
layout.label(text="Please note that sector is suffix.")
|
@ -3,18 +3,18 @@ import os, math
|
|||||||
from bpy_extras import io_utils,node_shader_utils
|
from bpy_extras import io_utils,node_shader_utils
|
||||||
# from bpy_extras.io_utils import unpack_list
|
# from bpy_extras.io_utils import unpack_list
|
||||||
from bpy_extras.image_utils import load_image
|
from bpy_extras.image_utils import load_image
|
||||||
from . import utils, config
|
from . import UTILS_constants, UTILS_functions
|
||||||
|
|
||||||
class BALLANCE_OT_add_floor(bpy.types.Operator):
|
class BALLANCE_OT_add_floors(bpy.types.Operator):
|
||||||
"""Add Ballance floor"""
|
"""Add Ballance floor"""
|
||||||
bl_idname = "ballance.add_floor"
|
bl_idname = "ballance.add_floors"
|
||||||
bl_label = "Add floor"
|
bl_label = "Add floor"
|
||||||
bl_options = {'UNDO'}
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
floor_type: bpy.props.EnumProperty(
|
floor_type: bpy.props.EnumProperty(
|
||||||
name="Type",
|
name="Type",
|
||||||
description="Floor type",
|
description="Floor type",
|
||||||
items=tuple((x, x, "") for x in config.floor_block_dict.keys()),
|
items=tuple((x, x, "") for x in UTILS_constants.floor_blockDict.keys()),
|
||||||
)
|
)
|
||||||
|
|
||||||
expand_length_1 : bpy.props.IntProperty(
|
expand_length_1 : bpy.props.IntProperty(
|
||||||
@ -39,16 +39,16 @@ class BALLANCE_OT_add_floor(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
use_2d_top : bpy.props.BoolProperty(
|
use_2d_top : bpy.props.BoolProperty(
|
||||||
name="Top side"
|
name="Top edge"
|
||||||
)
|
)
|
||||||
use_2d_right : bpy.props.BoolProperty(
|
use_2d_right : bpy.props.BoolProperty(
|
||||||
name="Right side"
|
name="Right edge"
|
||||||
)
|
)
|
||||||
use_2d_bottom : bpy.props.BoolProperty(
|
use_2d_bottom : bpy.props.BoolProperty(
|
||||||
name="Bottom side"
|
name="Bottom edge"
|
||||||
)
|
)
|
||||||
use_2d_left : bpy.props.BoolProperty(
|
use_2d_left : bpy.props.BoolProperty(
|
||||||
name="Left side"
|
name="Left edge"
|
||||||
)
|
)
|
||||||
use_3d_top : bpy.props.BoolProperty(
|
use_3d_top : bpy.props.BoolProperty(
|
||||||
name="Top face"
|
name="Top face"
|
||||||
@ -65,10 +65,14 @@ class BALLANCE_OT_add_floor(bpy.types.Operator):
|
|||||||
return os.path.isdir(prefs.external_folder)
|
return os.path.isdir(prefs.external_folder)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
# get prefs
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
prefs_externalTexture = prefs.external_folder
|
||||||
|
|
||||||
# load mesh
|
# load mesh
|
||||||
objmesh = bpy.data.meshes.new('done_')
|
objmesh = bpy.data.meshes.new('done_')
|
||||||
if self.floor_type in config.floor_basic_block_list:
|
if self.floor_type in UTILS_constants.floor_basicBlockList:
|
||||||
load_basic_floor(
|
_load_basic_floor(
|
||||||
objmesh,
|
objmesh,
|
||||||
self.floor_type,
|
self.floor_type,
|
||||||
'R0',
|
'R0',
|
||||||
@ -81,9 +85,10 @@ class BALLANCE_OT_add_floor(bpy.types.Operator):
|
|||||||
self.use_2d_left,
|
self.use_2d_left,
|
||||||
self.use_3d_top,
|
self.use_3d_top,
|
||||||
self.use_3d_bottom),
|
self.use_3d_bottom),
|
||||||
(0.0, 0.0))
|
(0.0, 0.0),
|
||||||
elif self.floor_type in config.floor_derived_block_list:
|
prefs_externalTexture)
|
||||||
load_derived_floor(
|
elif self.floor_type in UTILS_constants.floor_derivedBlockList:
|
||||||
|
_load_derived_floor(
|
||||||
objmesh,
|
objmesh,
|
||||||
self.floor_type,
|
self.floor_type,
|
||||||
self.height_multiplier,
|
self.height_multiplier,
|
||||||
@ -94,7 +99,10 @@ class BALLANCE_OT_add_floor(bpy.types.Operator):
|
|||||||
self.use_2d_bottom,
|
self.use_2d_bottom,
|
||||||
self.use_2d_left,
|
self.use_2d_left,
|
||||||
self.use_3d_top,
|
self.use_3d_top,
|
||||||
self.use_3d_bottom))
|
self.use_3d_bottom),
|
||||||
|
prefs_externalTexture)
|
||||||
|
else:
|
||||||
|
raise Exception("Fatal error: unknow floor type.")
|
||||||
|
|
||||||
# normalization mesh
|
# normalization mesh
|
||||||
objmesh.validate(clean_customdata=False)
|
objmesh.validate(clean_customdata=False)
|
||||||
@ -102,7 +110,7 @@ class BALLANCE_OT_add_floor(bpy.types.Operator):
|
|||||||
|
|
||||||
# create object and link it
|
# create object and link it
|
||||||
obj=bpy.data.objects.new('A_Floor_BMERevenge_', objmesh)
|
obj=bpy.data.objects.new('A_Floor_BMERevenge_', objmesh)
|
||||||
utils.AddSceneAndMove2Cursor(obj)
|
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@ -111,7 +119,7 @@ class BALLANCE_OT_add_floor(bpy.types.Operator):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
# get floor prototype
|
# get floor prototype
|
||||||
floor_prototype = config.floor_block_dict[self.floor_type]
|
floor_prototype = UTILS_constants.floor_blockDict[self.floor_type]
|
||||||
|
|
||||||
# try sync default value
|
# try sync default value
|
||||||
if self.previous_floor_type != self.floor_type:
|
if self.previous_floor_type != self.floor_type:
|
||||||
@ -142,13 +150,13 @@ class BALLANCE_OT_add_floor(bpy.types.Operator):
|
|||||||
col.label(text="Expand mode: " + floor_prototype['ExpandType'])
|
col.label(text="Expand mode: " + floor_prototype['ExpandType'])
|
||||||
grids = col.grid_flow(row_major=True, columns=3)
|
grids = col.grid_flow(row_major=True, columns=3)
|
||||||
grids.separator()
|
grids.separator()
|
||||||
grids.label(text=config.floor_expand_direction_map[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][0])
|
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][0])
|
||||||
grids.separator()
|
grids.separator()
|
||||||
grids.label(text=config.floor_expand_direction_map[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][3])
|
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][3])
|
||||||
grids.template_icon(icon_value = config.blenderIcon_floor_dict[self.floor_type])
|
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type])
|
||||||
grids.label(text=config.floor_expand_direction_map[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][1])
|
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][1])
|
||||||
grids.separator()
|
grids.separator()
|
||||||
grids.label(text=config.floor_expand_direction_map[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][2])
|
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][2])
|
||||||
grids.separator()
|
grids.separator()
|
||||||
|
|
||||||
col.separator()
|
col.separator()
|
||||||
@ -164,13 +172,13 @@ class BALLANCE_OT_add_floor(bpy.types.Operator):
|
|||||||
grids.prop(self, "use_2d_top")
|
grids.prop(self, "use_2d_top")
|
||||||
grids.separator()
|
grids.separator()
|
||||||
grids.prop(self, "use_2d_left")
|
grids.prop(self, "use_2d_left")
|
||||||
grids.template_icon(icon_value = config.blenderIcon_floor_dict[self.floor_type])
|
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type])
|
||||||
grids.prop(self, "use_2d_right")
|
grids.prop(self, "use_2d_right")
|
||||||
grids.separator()
|
grids.separator()
|
||||||
grids.prop(self, "use_2d_bottom")
|
grids.prop(self, "use_2d_bottom")
|
||||||
grids.separator()
|
grids.separator()
|
||||||
|
|
||||||
def face_fallback(normal_face, expand_face, height):
|
def _face_fallback(normal_face, expand_face, height):
|
||||||
if expand_face == None:
|
if expand_face == None:
|
||||||
return normal_face
|
return normal_face
|
||||||
|
|
||||||
@ -179,43 +187,44 @@ def face_fallback(normal_face, expand_face, height):
|
|||||||
else:
|
else:
|
||||||
return expand_face
|
return expand_face
|
||||||
|
|
||||||
def create_or_get_material(material_name):
|
def _create_or_get_material(material_name, prefs_externalTexture):
|
||||||
# WARNING: this code is shared with bm_import_export
|
# WARNING: this code is shared with bm_import_export
|
||||||
deconflict_name = "BMERevenge_" + material_name
|
deconflict_mtl_name = "BMERevenge_" + material_name
|
||||||
try:
|
|
||||||
m = bpy.data.materials[deconflict_name]
|
|
||||||
except:
|
|
||||||
# it is not existed, we need create a new one
|
|
||||||
m = bpy.data.materials.new(deconflict_name)
|
|
||||||
# we need init it.
|
|
||||||
# load texture first
|
|
||||||
externalTextureFolder = bpy.context.preferences.addons[__package__].preferences.external_folder
|
|
||||||
txur = load_image(config.floor_texture_corresponding_map[material_name], externalTextureFolder, check_existing=False) # force reload, we don't want it is shared with normal material
|
|
||||||
# create material and link texture
|
|
||||||
m.use_nodes=True
|
|
||||||
for node in m.node_tree.nodes:
|
|
||||||
m.node_tree.nodes.remove(node)
|
|
||||||
bnode=m.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled")
|
|
||||||
mnode=m.node_tree.nodes.new(type="ShaderNodeOutputMaterial")
|
|
||||||
m.node_tree.links.new(bnode.outputs[0],mnode.inputs[0])
|
|
||||||
|
|
||||||
inode=m.node_tree.nodes.new(type="ShaderNodeTexImage")
|
# create or get material
|
||||||
inode.image=txur
|
(mtl, skip_init) = UTILS_functions.create_instance_with_option(
|
||||||
m.node_tree.links.new(inode.outputs[0],bnode.inputs[0])
|
UTILS_constants.BmfileInfoType.MATERIAL,
|
||||||
|
deconflict_mtl_name, 'CURRENT'
|
||||||
|
)
|
||||||
|
if skip_init:
|
||||||
|
return mtl
|
||||||
|
|
||||||
# write custom property
|
# initialize material parameter
|
||||||
for try_item in config.floor_material_statistic:
|
# load texture first
|
||||||
if material_name in try_item['member']:
|
texture_filename = UTILS_constants.floor_textureReflactMap[material_name]
|
||||||
m['virtools-ambient'] = try_item['data']['ambient']
|
deconflict_texture_name = "BMERevenge_" + texture_filename
|
||||||
m['virtools-diffuse'] = try_item['data']['diffuse']
|
(texture, skip_init) = UTILS_functions.create_instance_with_option(
|
||||||
m['virtools-specular'] = try_item['data']['specular']
|
UTILS_constants.BmfileInfoType.TEXTURE,
|
||||||
m['virtools-emissive'] = try_item['data']['emissive']
|
deconflict_texture_name, 'CURRENT',
|
||||||
m['virtools-power'] = try_item['data']['power']
|
extra_texture_path = prefs_externalTexture, extra_texture_filename = texture_filename
|
||||||
break
|
)
|
||||||
|
|
||||||
|
# iterate material statistic to get corresponding mtl data
|
||||||
|
for try_item in UTILS_constants.floor_materialStatistic:
|
||||||
|
if material_name in try_item['member']:
|
||||||
|
# got it
|
||||||
|
# set material data
|
||||||
|
UTILS_functions.create_material_nodes(mtl,
|
||||||
|
try_item['data']['ambient'], try_item['data']['diffuse'],
|
||||||
|
try_item['data']['specular'], try_item['data']['emissive'],
|
||||||
|
try_item['data']['power'],
|
||||||
|
texture)
|
||||||
|
break
|
||||||
|
|
||||||
|
# return mtl
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def solve_vec_data(str_data, d1, d2, d3, unit, unit_height):
|
def _solve_vec_data(str_data, d1, d2, d3, unit, unit_height):
|
||||||
sp = str_data.split(';')
|
sp = str_data.split(';')
|
||||||
sp_point = sp[0].split(',')
|
sp_point = sp[0].split(',')
|
||||||
vec = [float(sp_point[0]), float(sp_point[1]), float(sp_point[2])]
|
vec = [float(sp_point[0]), float(sp_point[1]), float(sp_point[2])]
|
||||||
@ -236,7 +245,7 @@ def solve_vec_data(str_data, d1, d2, d3, unit, unit_height):
|
|||||||
|
|
||||||
return vec
|
return vec
|
||||||
|
|
||||||
def rotate_translate_vec(vec, rotation, unit, extra_translate):
|
def _rotate_translate_vec(vec, rotation, unit, extra_translate):
|
||||||
vec[0] -= unit / 2
|
vec[0] -= unit / 2
|
||||||
vec[1] -= unit / 2
|
vec[1] -= unit / 2
|
||||||
|
|
||||||
@ -260,7 +269,7 @@ def rotate_translate_vec(vec, rotation, unit, extra_translate):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def solve_uv_data(str_data, d1, d2, d3, unit):
|
def _solve_uv_data(str_data, d1, d2, d3, unit):
|
||||||
sp = str_data.split(';')
|
sp = str_data.split(';')
|
||||||
sp_point = sp[0].split(',')
|
sp_point = sp[0].split(',')
|
||||||
vec = [float(sp_point[0]), float(sp_point[1])]
|
vec = [float(sp_point[0]), float(sp_point[1])]
|
||||||
@ -281,7 +290,7 @@ def solve_uv_data(str_data, d1, d2, d3, unit):
|
|||||||
|
|
||||||
return tuple(vec)
|
return tuple(vec)
|
||||||
|
|
||||||
def solve_normal_data(point1, point2, point3):
|
def _solve_normal_data(point1, point2, point3):
|
||||||
vector1 = (
|
vector1 = (
|
||||||
point2[0] - point1[0],
|
point2[0] - point1[0],
|
||||||
point2[1] - point1[1],
|
point2[1] - point1[1],
|
||||||
@ -309,7 +318,7 @@ def solve_normal_data(point1, point2, point3):
|
|||||||
|
|
||||||
return tuple(nor)
|
return tuple(nor)
|
||||||
|
|
||||||
def solve_smashed_position(str_data, d1, d2):
|
def _solve_smashed_position(str_data, d1, d2):
|
||||||
sp=str_data.split(';')
|
sp=str_data.split(';')
|
||||||
sp_pos = sp[0].split(',')
|
sp_pos = sp[0].split(',')
|
||||||
sp_sync = sp[1].split(',')
|
sp_sync = sp[1].split(',')
|
||||||
@ -325,7 +334,7 @@ def solve_smashed_position(str_data, d1, d2):
|
|||||||
|
|
||||||
return tuple(vec)
|
return tuple(vec)
|
||||||
|
|
||||||
def virtual_foreach_set(collection, field, base_num, data):
|
def _virtual_foreach_set(collection, field, base_num, data):
|
||||||
counter = 0
|
counter = 0
|
||||||
for i in data:
|
for i in data:
|
||||||
exec("a[j]." + field + "=q", {}, {
|
exec("a[j]." + field + "=q", {}, {
|
||||||
@ -345,8 +354,8 @@ sides_struct should be a tuple and it always have 6 bool items
|
|||||||
WARNING: this code is shared with bm import export
|
WARNING: this code is shared with bm import export
|
||||||
|
|
||||||
'''
|
'''
|
||||||
def load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, sides_struct, extra_translate):
|
def _load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, sides_struct, extra_translate, prefs_externalTexture):
|
||||||
floor_prototype = config.floor_block_dict[floor_type]
|
floor_prototype = UTILS_constants.floor_blockDict[floor_type]
|
||||||
|
|
||||||
# set some unit
|
# set some unit
|
||||||
height_unit = 5.0
|
height_unit = 5.0
|
||||||
@ -360,13 +369,13 @@ def load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, side
|
|||||||
# got all needed faces
|
# got all needed faces
|
||||||
needCreatedFaces = []
|
needCreatedFaces = []
|
||||||
if sides_struct[0]:
|
if sides_struct[0]:
|
||||||
needCreatedFaces.append(face_fallback(floor_prototype['TwoDTopSide'], floor_prototype['TwoDTopSideExpand'], height_multiplier))
|
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDTopSide'], floor_prototype['TwoDTopSideExpand'], height_multiplier))
|
||||||
if sides_struct[1]:
|
if sides_struct[1]:
|
||||||
needCreatedFaces.append(face_fallback(floor_prototype['TwoDRightSide'], floor_prototype['TwoDRightSideExpand'], height_multiplier))
|
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDRightSide'], floor_prototype['TwoDRightSideExpand'], height_multiplier))
|
||||||
if sides_struct[2]:
|
if sides_struct[2]:
|
||||||
needCreatedFaces.append(face_fallback(floor_prototype['TwoDBottomSide'], floor_prototype['TwoDBottomSideExpand'], height_multiplier))
|
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDBottomSide'], floor_prototype['TwoDBottomSideExpand'], height_multiplier))
|
||||||
if sides_struct[3]:
|
if sides_struct[3]:
|
||||||
needCreatedFaces.append(face_fallback(floor_prototype['TwoDLeftSide'], floor_prototype['TwoDLeftSideExpand'], height_multiplier))
|
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDLeftSide'], floor_prototype['TwoDLeftSideExpand'], height_multiplier))
|
||||||
if sides_struct[4]:
|
if sides_struct[4]:
|
||||||
needCreatedFaces.append(floor_prototype['ThreeDTopFace'])
|
needCreatedFaces.append(floor_prototype['ThreeDTopFace'])
|
||||||
if sides_struct[5]:
|
if sides_struct[5]:
|
||||||
@ -382,7 +391,7 @@ def load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, side
|
|||||||
new_texture = face['Textures']
|
new_texture = face['Textures']
|
||||||
if new_texture not in materialDict.keys():
|
if new_texture not in materialDict.keys():
|
||||||
# try get from existed solt
|
# try get from existed solt
|
||||||
pending_material = create_or_get_material(new_texture)
|
pending_material = _create_or_get_material(new_texture, prefs_externalTexture)
|
||||||
if pending_material not in allmat:
|
if pending_material not in allmat:
|
||||||
# no matched. add it
|
# no matched. add it
|
||||||
mesh.materials.append(pending_material)
|
mesh.materials.append(pending_material)
|
||||||
@ -406,12 +415,12 @@ def load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, side
|
|||||||
for face_define in needCreatedFaces:
|
for face_define in needCreatedFaces:
|
||||||
base_indices = len(vecList)
|
base_indices = len(vecList)
|
||||||
for vec in face_define['Vertices']:
|
for vec in face_define['Vertices']:
|
||||||
vecList.append(rotate_translate_vec(
|
vecList.append(_rotate_translate_vec(
|
||||||
solve_vec_data(vec, d1, d2, height_multiplier, block_3dworld_unit, height_unit),
|
_solve_vec_data(vec, d1, d2, height_multiplier, block_3dworld_unit, height_unit),
|
||||||
rotation, block_3dworld_unit, extra_translate))
|
rotation, block_3dworld_unit, extra_translate))
|
||||||
|
|
||||||
for uv in face_define['UVs']:
|
for uv in face_define['UVs']:
|
||||||
uvList.append(solve_uv_data(uv, d1, d2, height_multiplier, block_uvworld_unit))
|
uvList.append(_solve_uv_data(uv, d1, d2, height_multiplier, block_uvworld_unit))
|
||||||
|
|
||||||
for face in face_define['Faces']:
|
for face in face_define['Faces']:
|
||||||
if face['Type'] == 'RECTANGLE':
|
if face['Type'] == 'RECTANGLE':
|
||||||
@ -431,7 +440,7 @@ def load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, side
|
|||||||
indCount = 3
|
indCount = 3
|
||||||
|
|
||||||
# we need calc normal and push it into list
|
# we need calc normal and push it into list
|
||||||
point_normal = solve_normal_data(vecList[vec_indices[0]], vecList[vec_indices[1]], vecList[vec_indices[2]])
|
point_normal = _solve_normal_data(vecList[vec_indices[0]], vecList[vec_indices[1]], vecList[vec_indices[2]])
|
||||||
for i in range(indCount):
|
for i in range(indCount):
|
||||||
normalList.append(point_normal)
|
normalList.append(point_normal)
|
||||||
|
|
||||||
@ -454,10 +463,10 @@ def load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, side
|
|||||||
# if no uv, create it
|
# if no uv, create it
|
||||||
mesh.uv_layers.new(do_init=False)
|
mesh.uv_layers.new(do_init=False)
|
||||||
|
|
||||||
virtual_foreach_set(mesh.vertices, "co", global_offset_vec, vecList)
|
_virtual_foreach_set(mesh.vertices, "co", global_offset_vec, vecList)
|
||||||
virtual_foreach_set(mesh.loops, "vertex_index", global_offset_loops, faceList)
|
_virtual_foreach_set(mesh.loops, "vertex_index", global_offset_loops, faceList)
|
||||||
virtual_foreach_set(mesh.loops, "normal", global_offset_loops, normalList)
|
_virtual_foreach_set(mesh.loops, "normal", global_offset_loops, normalList)
|
||||||
virtual_foreach_set(mesh.uv_layers[0].data, "uv", global_offset_loops, uvList)
|
_virtual_foreach_set(mesh.uv_layers[0].data, "uv", global_offset_loops, uvList)
|
||||||
|
|
||||||
cache_counter = 0
|
cache_counter = 0
|
||||||
for i in range(len(faceMatList)):
|
for i in range(len(faceMatList)):
|
||||||
@ -469,8 +478,8 @@ def load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, side
|
|||||||
cache_counter += indCount
|
cache_counter += indCount
|
||||||
|
|
||||||
|
|
||||||
def load_derived_floor(mesh, floor_type, height_multiplier, d1, d2, sides_struct):
|
def _load_derived_floor(mesh, floor_type, height_multiplier, d1, d2, sides_struct, prefs_externalTexture):
|
||||||
floor_prototype = config.floor_block_dict[floor_type]
|
floor_prototype = UTILS_constants.floor_blockDict[floor_type]
|
||||||
|
|
||||||
# set some unit
|
# set some unit
|
||||||
if floor_prototype['UnitSize'] == 'Small':
|
if floor_prototype['UnitSize'] == 'Small':
|
||||||
@ -492,13 +501,13 @@ def load_derived_floor(mesh, floor_type, height_multiplier, d1, d2, sides_struct
|
|||||||
|
|
||||||
# iterate smahsed blocks
|
# iterate smahsed blocks
|
||||||
for blk in floor_prototype['SmashedBlocks']:
|
for blk in floor_prototype['SmashedBlocks']:
|
||||||
start_pos = solve_smashed_position(blk['StartPosition'], d1, d2)
|
start_pos = _solve_smashed_position(blk['StartPosition'], d1, d2)
|
||||||
expand_pos = solve_smashed_position(blk['ExpandPosition'], d1, d2)
|
expand_pos = _solve_smashed_position(blk['ExpandPosition'], d1, d2)
|
||||||
|
|
||||||
sides_data = tuple(sides_dict[x] for x in blk['SideSync'].split(';'))
|
sides_data = tuple(sides_dict[x] for x in blk['SideSync'].split(';'))
|
||||||
|
|
||||||
# call basic floor creator
|
# call basic floor creator
|
||||||
load_basic_floor(
|
_load_basic_floor(
|
||||||
mesh,
|
mesh,
|
||||||
blk['Type'],
|
blk['Type'],
|
||||||
blk['Rotation'],
|
blk['Rotation'],
|
||||||
@ -506,7 +515,8 @@ def load_derived_floor(mesh, floor_type, height_multiplier, d1, d2, sides_struct
|
|||||||
expand_pos[0],
|
expand_pos[0],
|
||||||
expand_pos[1],
|
expand_pos[1],
|
||||||
sides_data,
|
sides_data,
|
||||||
(start_pos[0] * block_3dworld_unit, start_pos[1] * block_3dworld_unit)
|
(start_pos[0] * block_3dworld_unit, start_pos[1] * block_3dworld_unit),
|
||||||
|
prefs_externalTexture
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1,62 +1,9 @@
|
|||||||
import bpy,mathutils
|
import bpy, mathutils
|
||||||
from . import utils, config, bm_import_export
|
from . import UTILS_functions
|
||||||
|
|
||||||
# ================================================= actual add
|
class BALLANCE_OT_add_rails(bpy.types.Operator):
|
||||||
|
|
||||||
class BALLANCE_OT_add_elements(bpy.types.Operator):
|
|
||||||
"""Add sector related elements"""
|
|
||||||
bl_idname = "ballance.add_elements"
|
|
||||||
bl_label = "Add elements"
|
|
||||||
bl_options = {'UNDO'}
|
|
||||||
|
|
||||||
elements_type: bpy.props.EnumProperty(
|
|
||||||
name="Type",
|
|
||||||
description="This element type",
|
|
||||||
items=tuple(map(lambda x: (x, x, ""), config.component_list)),
|
|
||||||
)
|
|
||||||
|
|
||||||
attentionElements = ["PC_TwoFlames", "PR_Resetpoint"]
|
|
||||||
uniqueElements = ["PS_FourFlames", "PE_Balloon"]
|
|
||||||
|
|
||||||
elements_sector: bpy.props.IntProperty(
|
|
||||||
name="Sector",
|
|
||||||
description="Define which sector the object will be grouped in",
|
|
||||||
min=1,
|
|
||||||
max=8,
|
|
||||||
default=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
# get name
|
|
||||||
if self.elements_type in self.uniqueElements:
|
|
||||||
finalObjectName = self.elements_type + "_01"
|
|
||||||
elif self.elements_type in self.attentionElements:
|
|
||||||
finalObjectName = self.elements_type + "_0" + str(self.elements_sector)
|
|
||||||
else:
|
|
||||||
finalObjectName = self.elements_type + "_0" + str(self.elements_sector) + "_"
|
|
||||||
|
|
||||||
# create object
|
|
||||||
loadedMesh = bm_import_export.load_component(config.component_list.index(self.elements_type))
|
|
||||||
obj = bpy.data.objects.new(finalObjectName, loadedMesh)
|
|
||||||
utils.AddSceneAndMove2Cursor(obj)
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
|
||||||
wm = context.window_manager
|
|
||||||
return wm.invoke_props_dialog(self)
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.prop(self, "elements_type")
|
|
||||||
if self.elements_type not in self.uniqueElements:
|
|
||||||
layout.prop(self, "elements_sector")
|
|
||||||
if self.elements_type in self.attentionElements:
|
|
||||||
layout.label(text="Please note that sector is suffix.")
|
|
||||||
|
|
||||||
class BALLANCE_OT_add_rail(bpy.types.Operator):
|
|
||||||
"""Add rail"""
|
"""Add rail"""
|
||||||
bl_idname = "ballance.add_rail"
|
bl_idname = "ballance.add_rails"
|
||||||
bl_label = "Add rail section"
|
bl_label = "Add rail section"
|
||||||
bl_options = {'UNDO'}
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
@ -113,7 +60,7 @@ class BALLANCE_OT_add_rail(bpy.types.Operator):
|
|||||||
bpy.ops.object.join()
|
bpy.ops.object.join()
|
||||||
|
|
||||||
# apply 3d cursor
|
# apply 3d cursor
|
||||||
utils.Move2Cursor(firstObj)
|
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -127,3 +74,4 @@ class BALLANCE_OT_add_rail(bpy.types.Operator):
|
|||||||
layout.prop(self, "rail_radius")
|
layout.prop(self, "rail_radius")
|
||||||
if self.rail_type == 'DOUBLE':
|
if self.rail_type == 'DOUBLE':
|
||||||
layout.prop(self, "rail_span")
|
layout.prop(self, "rail_span")
|
||||||
|
|
@ -1,7 +1,18 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
external_texture_list = set([
|
bmfile_currentVersion = 14
|
||||||
|
bmfile_flagUnicode = 0x800
|
||||||
|
bmfile_flagDeflatedMaximum = 0x2
|
||||||
|
bmfile_globalComment = 'Use BM Spec 1.4'.encode('utf-8')
|
||||||
|
|
||||||
|
class BmfileInfoType():
|
||||||
|
OBJECT = 0
|
||||||
|
MESH = 1
|
||||||
|
MATERIAL = 2
|
||||||
|
TEXTURE = 3
|
||||||
|
|
||||||
|
bmfile_externalTextureSet = set([
|
||||||
"atari.avi",
|
"atari.avi",
|
||||||
"atari.bmp",
|
"atari.bmp",
|
||||||
"Ball_LightningSphere1.bmp",
|
"Ball_LightningSphere1.bmp",
|
||||||
@ -85,7 +96,7 @@ external_texture_list = set([
|
|||||||
"Wood_Raft.bmp"
|
"Wood_Raft.bmp"
|
||||||
])
|
])
|
||||||
|
|
||||||
component_list = [
|
bmfile_componentList = [
|
||||||
"P_Extra_Life",
|
"P_Extra_Life",
|
||||||
"P_Extra_Point",
|
"P_Extra_Point",
|
||||||
"P_Trafo_Paper",
|
"P_Trafo_Paper",
|
||||||
@ -120,7 +131,7 @@ format: key is diection, value is a dict
|
|||||||
dict's key is expand mode, value is a tuple
|
dict's key is expand mode, value is a tuple
|
||||||
tuple always have 4 items, it means (TOP_STR, RIGHT_STR, BOTTOM_STR, LEFT_STR)
|
tuple always have 4 items, it means (TOP_STR, RIGHT_STR, BOTTOM_STR, LEFT_STR)
|
||||||
'''
|
'''
|
||||||
floor_expand_direction_map = {
|
floor_expandDirectionMap = {
|
||||||
"PositiveX": {
|
"PositiveX": {
|
||||||
"Static": ("X", "X", "X", "X"),
|
"Static": ("X", "X", "X", "X"),
|
||||||
"Column": ("X", "X", "D1", "X"),
|
"Column": ("X", "X", "D1", "X"),
|
||||||
@ -143,7 +154,7 @@ floor_expand_direction_map = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
floor_texture_corresponding_map = {
|
floor_textureReflactionMap = {
|
||||||
"FloorSide": "Floor_Side.bmp",
|
"FloorSide": "Floor_Side.bmp",
|
||||||
"FloorTopBorder": "Floor_Top_Border.bmp",
|
"FloorTopBorder": "Floor_Top_Border.bmp",
|
||||||
"FloorTopBorder_ForSide": "Floor_Top_Border.bmp",
|
"FloorTopBorder_ForSide": "Floor_Top_Border.bmp",
|
||||||
@ -157,8 +168,8 @@ floor_texture_corresponding_map = {
|
|||||||
"BallStone": "Ball_Stone.bmp"
|
"BallStone": "Ball_Stone.bmp"
|
||||||
}
|
}
|
||||||
|
|
||||||
# WARNING: this data is shared with BallanceVirtoolsPlugin - mapping_BM.cpp - fix_blender_texture
|
# WARNING: this data is shared with `BallanceVirtoolsPlugin/bvh/features/mapping/fix_texture.cpp`
|
||||||
floor_material_statistic = [
|
floor_materialStatistic = [
|
||||||
{
|
{
|
||||||
"member": [
|
"member": [
|
||||||
"FloorSide",
|
"FloorSide",
|
||||||
@ -215,19 +226,19 @@ floor_material_statistic = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
floor_block_dict = {}
|
floor_blockDict = {}
|
||||||
floor_basic_block_list = []
|
floor_basicBlockList = []
|
||||||
floor_derived_block_list = []
|
floor_derivedBlockList = []
|
||||||
with open(os.path.join(os.path.dirname(__file__), "json", "BasicBlock.json")) as fp:
|
with open(os.path.join(os.path.dirname(__file__), "json", "BasicBlock.json")) as fp:
|
||||||
for item in json.load(fp):
|
for item in json.load(fp):
|
||||||
floor_basic_block_list.append(item["Type"])
|
floor_basicBlockList.append(item["Type"])
|
||||||
floor_block_dict[item["Type"]] = item
|
floor_blockDict[item["Type"]] = item
|
||||||
with open(os.path.join(os.path.dirname(__file__), "json", "DerivedBlock.json")) as fp:
|
with open(os.path.join(os.path.dirname(__file__), "json", "DerivedBlock.json")) as fp:
|
||||||
for item in json.load(fp):
|
for item in json.load(fp):
|
||||||
floor_derived_block_list.append(item["Type"])
|
floor_derivedBlockList.append(item["Type"])
|
||||||
floor_block_dict[item["Type"]] = item
|
floor_blockDict[item["Type"]] = item
|
||||||
|
|
||||||
blenderIcon_floor = None
|
icons_floor = None
|
||||||
blenderIcon_floor_dict = {}
|
icons_floorDict = {}
|
||||||
# blenderIcon_elements = None
|
# blenderIcon_elements = None
|
||||||
# blenderIcon_elements_dict = {}
|
# blenderIcon_elements_dict = {}
|
96
ballance_blender_plugin/UTILS_file_io.py
Normal file
96
ballance_blender_plugin/UTILS_file_io.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import bpy,bmesh,bpy_extras,mathutils
|
||||||
|
import struct,shutil
|
||||||
|
|
||||||
|
# writer
|
||||||
|
|
||||||
|
def write_string(fs,str):
|
||||||
|
count=len(str)
|
||||||
|
write_uint32(fs,count)
|
||||||
|
fs.write(str.encode("utf_32_le"))
|
||||||
|
|
||||||
|
def write_uint8(fs,num):
|
||||||
|
fs.write(struct.pack("B", num))
|
||||||
|
|
||||||
|
def write_uint32(fs,num):
|
||||||
|
fs.write(struct.pack("I", num))
|
||||||
|
|
||||||
|
def write_uint64(fs,num):
|
||||||
|
fs.write(struct.pack("Q", num))
|
||||||
|
|
||||||
|
def write_bool(fs,boolean):
|
||||||
|
if boolean:
|
||||||
|
write_uint8(fs, 1)
|
||||||
|
else:
|
||||||
|
write_uint8(fs, 0)
|
||||||
|
|
||||||
|
def write_float(fs,fl):
|
||||||
|
fs.write(struct.pack("f", fl))
|
||||||
|
|
||||||
|
def write_worldMatrix(fs, matt):
|
||||||
|
mat = matt.transposed()
|
||||||
|
fs.write(struct.pack("ffffffffffffffff",
|
||||||
|
mat[0][0],mat[0][2], mat[0][1], mat[0][3],
|
||||||
|
mat[2][0],mat[2][2], mat[2][1], mat[2][3],
|
||||||
|
mat[1][0],mat[1][2], mat[1][1], mat[1][3],
|
||||||
|
mat[3][0],mat[3][2], mat[3][1], mat[3][3]))
|
||||||
|
|
||||||
|
def write_3vector(fs, x, y ,z):
|
||||||
|
fs.write(struct.pack("fff", x, y ,z))
|
||||||
|
|
||||||
|
def write_color(fs, colors):
|
||||||
|
write_3vector(fs, colors[0], colors[1], colors[2])
|
||||||
|
|
||||||
|
def write_2vector(fs, u, v):
|
||||||
|
fs.write(struct.pack("ff", u, v))
|
||||||
|
|
||||||
|
def write_face(fs, v1, vt1, vn1, v2, vt2, vn2, v3, vt3, vn3):
|
||||||
|
fs.write(struct.pack("IIIIIIIII", v1, vt1, vn1, v2, vt2, vn2, v3, vt3, vn3))
|
||||||
|
|
||||||
|
# reader
|
||||||
|
|
||||||
|
def peek_stream(fs):
|
||||||
|
res = fs.read(1)
|
||||||
|
fs.seek(-1, os.SEEK_CUR)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def read_float(fs):
|
||||||
|
return struct.unpack("f", fs.read(4))[0]
|
||||||
|
|
||||||
|
def read_uint8(fs):
|
||||||
|
return struct.unpack("B", fs.read(1))[0]
|
||||||
|
|
||||||
|
def read_uint32(fs):
|
||||||
|
return struct.unpack("I", fs.read(4))[0]
|
||||||
|
|
||||||
|
def read_uint64(fs):
|
||||||
|
return struct.unpack("Q", fs.read(8))[0]
|
||||||
|
|
||||||
|
def read_string(fs):
|
||||||
|
count = read_uint32(fs)
|
||||||
|
return fs.read(count*4).decode("utf_32_le")
|
||||||
|
|
||||||
|
def read_bool(fs):
|
||||||
|
return read_uint8(fs) != 0
|
||||||
|
|
||||||
|
def read_world_materix(fs):
|
||||||
|
p = struct.unpack("ffffffffffffffff", fs.read(4*4*4))
|
||||||
|
res = mathutils.Matrix((
|
||||||
|
(p[0], p[2], p[1], p[3]),
|
||||||
|
(p[8], p[10], p[9], p[11]),
|
||||||
|
(p[4], p[6], p[5], p[7]),
|
||||||
|
(p[12], p[14], p[13], p[15])))
|
||||||
|
return res.transposed()
|
||||||
|
|
||||||
|
def read_3vector(fs):
|
||||||
|
return struct.unpack("fff", fs.read(3*4))
|
||||||
|
|
||||||
|
def read_2vector(fs):
|
||||||
|
return struct.unpack("ff", fs.read(2*4))
|
||||||
|
|
||||||
|
def read_face(fs):
|
||||||
|
return struct.unpack("IIIIIIIII", fs.read(4*9))
|
||||||
|
|
||||||
|
def read_component_face(fs):
|
||||||
|
return struct.unpack("IIIIII", fs.read(4*6))
|
||||||
|
|
||||||
|
|
222
ballance_blender_plugin/UTILS_functions.py
Normal file
222
ballance_blender_plugin/UTILS_functions.py
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import bpy, bmesh, bpy_extras, mathutils
|
||||||
|
import struct, shutil
|
||||||
|
from bpy_extras.io_utils import unpack_list
|
||||||
|
from bpy_extras import io_utils, node_shader_utils
|
||||||
|
from . import UTILS_file_io, UTILS_constants
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# scene operation
|
||||||
|
|
||||||
|
def show_message_box(message, title, icon):
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
for item in message:
|
||||||
|
layout.label(text=item, translate=False)
|
||||||
|
|
||||||
|
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
|
||||||
|
|
||||||
|
def add_into_scene_and_move_to_cursor(obj):
|
||||||
|
Move2Cursor(obj)
|
||||||
|
|
||||||
|
view_layer = bpy.context.view_layer
|
||||||
|
collection = view_layer.active_layer_collection.collection
|
||||||
|
collection.objects.link(obj)
|
||||||
|
|
||||||
|
def move_to_cursor(obj):
|
||||||
|
obj.location = bpy.context.scene.cursor.location
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# is compoent
|
||||||
|
|
||||||
|
def is_component(name):
|
||||||
|
return get_component_id(name) != -1
|
||||||
|
|
||||||
|
def get_component_id(name):
|
||||||
|
for ind, comp in enumerate(UTILS_constants.bmfile_componentList):
|
||||||
|
if name.startswith(comp):
|
||||||
|
return ind
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# create material
|
||||||
|
|
||||||
|
def create_material_nodes(input_mtl, ambient, diffuse, specular, emissive,
|
||||||
|
specular_power, texture):
|
||||||
|
|
||||||
|
# adding material nodes
|
||||||
|
input_mtl.use_nodes=True
|
||||||
|
for node in input_mtl.node_tree.nodes:
|
||||||
|
input_mtl.node_tree.nodes.remove(node)
|
||||||
|
bnode = input_mtl.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled")
|
||||||
|
mnode = input_mtl.node_tree.nodes.new(type="ShaderNodeOutputMaterial")
|
||||||
|
input_mtl.node_tree.links.new(bnode.outputs[0],mnode.inputs[0])
|
||||||
|
|
||||||
|
input_mtl.metallic = sum(ambient) / 3
|
||||||
|
input_mtl.diffuse_color = [i for i in diffuse] + [1]
|
||||||
|
input_mtl.specular_color = specular
|
||||||
|
input_mtl.specular_intensity = specular_power
|
||||||
|
|
||||||
|
# adding a texture
|
||||||
|
if texture is not None:
|
||||||
|
inode = input_mtl.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||||
|
inode.image = texture
|
||||||
|
input_mtl.node_tree.links.new(inode.outputs[0], bnode.inputs[0])
|
||||||
|
|
||||||
|
# write custom property
|
||||||
|
input_mtl['virtools-ambient'] = ambient
|
||||||
|
input_mtl['virtools-diffuse'] = diffuse
|
||||||
|
input_mtl['virtools-specular'] = specular
|
||||||
|
input_mtl['virtools-emissive'] = emissive
|
||||||
|
input_mtl['virtools-power'] = specular_power
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# load component
|
||||||
|
|
||||||
|
def load_component(component_id):
|
||||||
|
# get file first
|
||||||
|
component_name = UTILS_constants.bmfile_componentList[component_id]
|
||||||
|
selected_file = os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'meshes',
|
||||||
|
component_name + '.bin'
|
||||||
|
)
|
||||||
|
|
||||||
|
# read file. please note this sector is sync with import_bm's mesh's code. when something change, please change each other.
|
||||||
|
fmesh = open(selected_file, 'rb')
|
||||||
|
|
||||||
|
# create real mesh, we don't need to consider name. blender will solve duplicated name
|
||||||
|
mesh = bpy.data.meshes.new('mesh_' + component_name)
|
||||||
|
|
||||||
|
vList = []
|
||||||
|
vnList = []
|
||||||
|
faceList = []
|
||||||
|
# in first read, store all data into list
|
||||||
|
listCount = UTILS_file_io.read_uint32(fmesh)
|
||||||
|
for i in range(listCount):
|
||||||
|
cache = UTILS_file_io.read_3vector(fmesh)
|
||||||
|
# switch yz
|
||||||
|
vList.append((cache[0], cache[2], cache[1]))
|
||||||
|
listCount = UTILS_file_io.read_uint32(fmesh)
|
||||||
|
for i in range(listCount):
|
||||||
|
cache = UTILS_file_io.read_3vector(fmesh)
|
||||||
|
# switch yz
|
||||||
|
vnList.append((cache[0], cache[2], cache[1]))
|
||||||
|
|
||||||
|
listCount = UTILS_file_io.read_uint32(fmesh)
|
||||||
|
for i in range(listCount):
|
||||||
|
faceData = UTILS_file_io.read_component_face(fmesh)
|
||||||
|
|
||||||
|
# we need invert triangle sort
|
||||||
|
faceList.append((
|
||||||
|
faceData[4], faceData[5],
|
||||||
|
faceData[2], faceData[3],
|
||||||
|
faceData[0], faceData[1]
|
||||||
|
))
|
||||||
|
|
||||||
|
# then, we need add correspond count for vertices
|
||||||
|
mesh.vertices.add(len(vList))
|
||||||
|
mesh.loops.add(len(faceList)*3) # triangle face confirmed
|
||||||
|
mesh.polygons.add(len(faceList))
|
||||||
|
mesh.create_normals_split()
|
||||||
|
|
||||||
|
# add vertices data
|
||||||
|
mesh.vertices.foreach_set("co", unpack_list(vList))
|
||||||
|
mesh.loops.foreach_set("vertex_index", unpack_list(_flat_component_vertices_index(faceList)))
|
||||||
|
mesh.loops.foreach_set("normal", unpack_list(_flat_component_vertices_normal(faceList, vnList)))
|
||||||
|
for i in range(len(faceList)):
|
||||||
|
mesh.polygons[i].loop_start = i * 3
|
||||||
|
mesh.polygons[i].loop_total = 3
|
||||||
|
|
||||||
|
mesh.polygons[i].use_smooth = True
|
||||||
|
|
||||||
|
mesh.validate(clean_customdata=False)
|
||||||
|
mesh.update(calc_edges=False, calc_edges_loose=False)
|
||||||
|
|
||||||
|
fmesh.close()
|
||||||
|
|
||||||
|
return mesh
|
||||||
|
|
||||||
|
|
||||||
|
def _flat_component_vertices_index(faceList):
|
||||||
|
for item in faceList:
|
||||||
|
yield (item[0], )
|
||||||
|
yield (item[2], )
|
||||||
|
yield (item[4], )
|
||||||
|
|
||||||
|
def _flat_component_vertices_normal(faceList, vnList):
|
||||||
|
for item in faceList:
|
||||||
|
yield vnList[item[1]]
|
||||||
|
yield vnList[item[3]]
|
||||||
|
yield vnList[item[5]]
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# create instance with option
|
||||||
|
|
||||||
|
def create_instance_with_option(instance_type, instance_name, instance_opt,
|
||||||
|
extra_mesh = None, extra_texture_path = None, extra_texture_filename = None):
|
||||||
|
"""
|
||||||
|
Create instance with opetions
|
||||||
|
|
||||||
|
`instance_type`, `instance_name`, `instance_opt` are essential for each type instances.
|
||||||
|
For object, you should provide `extra_mesh`.
|
||||||
|
For texture, you should provide `extra_texture_path` and `extra_texture_filename`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_instance():
|
||||||
|
try:
|
||||||
|
if instance_type == UTILS_constants.BmfileInfoType.OBJECT:
|
||||||
|
temp_instance = bpy.data.objects[instance_name]
|
||||||
|
elif instance_type == UTILS_constants.BmfileInfoType.MESH:
|
||||||
|
temp_instance = bpy.data.meshes[instance_name]
|
||||||
|
elif instance_type == UTILS_constants.BmfileInfoType.MATERIAL:
|
||||||
|
temp_instance = bpy.data.materials[instance_name]
|
||||||
|
elif instance_type == UTILS_constants.BmfileInfoType.TEXTURE:
|
||||||
|
temp_instance = bpy.data.textures[instance_name]
|
||||||
|
|
||||||
|
temp_is_existed = True
|
||||||
|
except:
|
||||||
|
temp_is_existed = False
|
||||||
|
|
||||||
|
return (temp_instance, temp_is_existed)
|
||||||
|
|
||||||
|
def create_instance():
|
||||||
|
if instType == UTILS_constants.BmfileInfoType.OBJECT:
|
||||||
|
instance_obj = bpy.data.objects.new(instance_name, extra_mesh)
|
||||||
|
instance_obj.name = instance_name
|
||||||
|
elif instType == UTILS_constants.BmfileInfoType.MESH:
|
||||||
|
instance_obj = bpy.data.meshes.new(instance_name)
|
||||||
|
instance_obj.name = instance_name
|
||||||
|
elif instType == UTILS_constants.BmfileInfoType.MATERIAL:
|
||||||
|
instance_obj = bpy.data.materials.new(instance_name)
|
||||||
|
instance_obj.name = instance_name
|
||||||
|
elif instance_type == UTILS_constants.BmfileInfoType.TEXTURE:
|
||||||
|
# this command will also check current available texture
|
||||||
|
# because `get_instance()` only check texture name
|
||||||
|
# but this strategy is not based on texture filepath, so this create method will
|
||||||
|
# correct this problem
|
||||||
|
instance_obj = load_image(extra_texture_filename, extra_texture_path, check_existing=(instance_opt == 'CURRENT'))
|
||||||
|
instance_obj.name = instance_name
|
||||||
|
|
||||||
|
return instance_obj
|
||||||
|
|
||||||
|
# analyze options
|
||||||
|
if (not isinstance(instance_opt, str)) or instance_opt == 'RENAME':
|
||||||
|
# create new instance
|
||||||
|
# or always create new instance if opts is not string
|
||||||
|
temp_instance = create_instance()
|
||||||
|
temp_skip_init = True
|
||||||
|
elif instance_opt == 'CURRENT':
|
||||||
|
# try get instance
|
||||||
|
(temp_instance, temp_is_existed) = get_instance()
|
||||||
|
# if got instance successfully, we do not create new one, otherwise, we should
|
||||||
|
# create new instance
|
||||||
|
if not temp_is_existed:
|
||||||
|
temp_instance = create_instance()
|
||||||
|
temp_skip_init = False
|
||||||
|
else:
|
||||||
|
temp_skip_init = True
|
||||||
|
|
||||||
|
return (temp_instance, temp_skip_init)
|
||||||
|
|
48
ballance_blender_plugin/UTILS_zip_helper.py
Normal file
48
ballance_blender_plugin/UTILS_zip_helper.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import pathlib, zipfile, os, shutil
|
||||||
|
from . import UTILS_constants
|
||||||
|
|
||||||
|
def compress(folder, zip_file):
|
||||||
|
# remove target file first
|
||||||
|
if os.path.isfile(zip_file):
|
||||||
|
os.remove(zip_file)
|
||||||
|
|
||||||
|
# compress data
|
||||||
|
with zipfile.ZipFile(zip_file, mode= 'w', compression= zipfile.ZIP_DEFLATED, compresslevel= 9, allowZip64= False) as zip_obj:
|
||||||
|
# set global comment
|
||||||
|
zip_obj.comment = UTILS_constants.bmfile_globalComment
|
||||||
|
|
||||||
|
# iterate folder and add files
|
||||||
|
for folder_name, subfolders, filenames in os.walk(folder):
|
||||||
|
for filename in filenames:
|
||||||
|
# construct zip_entry
|
||||||
|
abstract_filepath = os.path.join(folder_name, filename)
|
||||||
|
relative_filepath = os.path.relpath(abstract_filepath, folder)
|
||||||
|
zip_entry = zipfile.ZipInfo.from_file(abstract_filepath, arcname= relative_filepath)
|
||||||
|
zip_entry.compress_type = zipfile.ZIP_DEFLATED
|
||||||
|
|
||||||
|
# compress file
|
||||||
|
with open(abstract_filepath, 'rb') as fs_in:
|
||||||
|
with zip_obj.open(zip_entry, mode= 'w') as zip_in:
|
||||||
|
# References
|
||||||
|
# https://stackoverflow.com/questions/53254622/zipfile-header-language-encoding-bit-set-differently-between-python2-and-python3
|
||||||
|
# set unicode flag after opening internal file.
|
||||||
|
# for the shit implement of python module zipfile, we need set Deflated:Maximum manually
|
||||||
|
zip_entry.flag_bits |= UTILS_constants.bmfile_flagUnicode
|
||||||
|
zip_entry.flag_bits |= UTILS_constants.bmfile_flagDeflatedMaximum
|
||||||
|
# copy file
|
||||||
|
shutil.copyfileobj(fs_in, zip_in)
|
||||||
|
|
||||||
|
|
||||||
|
def decompress(folder, zip_file):
|
||||||
|
with zipfile.ZipFile(zip_file, mode= 'r', compression= zipfile.ZIP_DEFLATED, compresslevel= 9, allowZip64= False) as zip_obj:
|
||||||
|
for zip_entry in zip_obj.infolist():
|
||||||
|
if (zip_entry.flag_bits & UTILS_constants.bmfile_flagUnicode) == 0:
|
||||||
|
# lost unicode flag, throw error
|
||||||
|
raise Exception("Zip Entry lost UNICODE flag.")
|
||||||
|
|
||||||
|
# decompress file
|
||||||
|
zip_obj.extract(zip_entry, path= folder)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@ bl_info={
|
|||||||
"name":"Ballance Blender Plugin",
|
"name":"Ballance Blender Plugin",
|
||||||
"description":"Ballance mapping tools for Blender",
|
"description":"Ballance mapping tools for Blender",
|
||||||
"author":"yyc12345",
|
"author":"yyc12345",
|
||||||
"version":(2,0),
|
"version":(3,0),
|
||||||
"blender":(2,83,0),
|
"blender":(2,83,0),
|
||||||
"category":"Object",
|
"category":"Object",
|
||||||
"support":"TESTING",
|
"support":"TESTING",
|
||||||
@ -11,36 +11,55 @@ bl_info={
|
|||||||
"tracker_url": "https://github.com/yyc12345/BallanceBlenderHelper/issues"
|
"tracker_url": "https://github.com/yyc12345/BallanceBlenderHelper/issues"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================= import system
|
# =============================================
|
||||||
|
# import system
|
||||||
import bpy,bpy_extras
|
import bpy,bpy_extras
|
||||||
import bpy.utils.previews
|
import bpy.utils.previews
|
||||||
import os
|
import os
|
||||||
# import my code (with reload)
|
# import my code (with reload)
|
||||||
if "bpy" in locals():
|
if "bpy" in locals():
|
||||||
import importlib
|
import importlib
|
||||||
if "bm_import_export" in locals():
|
if "UTILS_constants" in locals():
|
||||||
importlib.reload(bm_import_export)
|
importlib.reload(UTILS_constants)
|
||||||
if "rail_uv" in locals():
|
if "UTILS_functions" in locals():
|
||||||
importlib.reload(rail_uv)
|
importlib.reload(UTILS_functions)
|
||||||
if "utils" in locals():
|
if "UTILS_preferences" in locals():
|
||||||
importlib.reload(utils)
|
importlib.reload(UTILS_preferences)
|
||||||
if "config" in locals():
|
if "UTILS_file_io" in locals():
|
||||||
importlib.reload(config)
|
importlib.reload(UTILS_file_io)
|
||||||
if "preferences" in locals():
|
if "UTILS_zip_helper" in locals():
|
||||||
importlib.reload(preferences)
|
importlib.reload(UTILS_zip_helper)
|
||||||
if "threedsmax_align" in locals():
|
|
||||||
importlib.reload(threedsmax_align)
|
|
||||||
if "no_uv_checker" in locals():
|
|
||||||
importlib.reload(no_uv_checker)
|
|
||||||
if "add_elements" in locals():
|
|
||||||
importlib.reload(add_elements)
|
|
||||||
if "add_floor" in locals():
|
|
||||||
importlib.reload(add_floor)
|
|
||||||
if "flatten_uv" in locals():
|
|
||||||
importlib.reload(flatten_uv)
|
|
||||||
from . import config, utils, bm_import_export, rail_uv, preferences, threedsmax_align, no_uv_checker, add_elements, add_floor, flatten_uv
|
|
||||||
|
|
||||||
# ============================================= menu system
|
if "BMFILE_export" in locals():
|
||||||
|
importlib.reload(BMFILE_export)
|
||||||
|
if "BMFILE_import" in locals():
|
||||||
|
importlib.reload(BMFILE_import)
|
||||||
|
|
||||||
|
if "MODS_rail_uv" in locals():
|
||||||
|
importlib.reload(MODS_rail_uv)
|
||||||
|
if "MODS_3dsmax_align" in locals():
|
||||||
|
importlib.reload(MODS_3dsmax_align)
|
||||||
|
if "MODS_flatten_uv" in locals():
|
||||||
|
importlib.reload(MODS_flatten_uv)
|
||||||
|
|
||||||
|
if "OBJS_add_components" in locals():
|
||||||
|
importlib.reload(OBJS_add_components)
|
||||||
|
if "OBJS_add_floors" in locals():
|
||||||
|
importlib.reload(OBJS_add_floors)
|
||||||
|
if "OBJS_add_rails" in locals():
|
||||||
|
importlib.reload(OBJS_add_rails)
|
||||||
|
|
||||||
|
if "NAMES_rename_via_group" in locals():
|
||||||
|
importlib.reload(NAMES_rename_via_group)
|
||||||
|
|
||||||
|
from . import UTILS_constants, UTILS_functions, UTILS_preferences
|
||||||
|
from . import BMFILE_export, BMFILE_import
|
||||||
|
from . import MODS_3dsmax_align, MODS_flatten_uv, MODS_rail_uv
|
||||||
|
from . import OBJS_add_components, OBJS_add_floors, OBJS_add_rails
|
||||||
|
from . import NAMES_rename_via_group
|
||||||
|
|
||||||
|
# =============================================
|
||||||
|
# menu system
|
||||||
|
|
||||||
class BALLANCE_MT_ThreeDViewerMenu(bpy.types.Menu):
|
class BALLANCE_MT_ThreeDViewerMenu(bpy.types.Menu):
|
||||||
"""Ballance related 3D operator"""
|
"""Ballance related 3D operator"""
|
||||||
@ -50,10 +69,9 @@ class BALLANCE_MT_ThreeDViewerMenu(bpy.types.Menu):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
layout.operator("ballance.super_align")
|
layout.operator(MODS_3dsmax_align.BALLANCE_OT_super_align.bl_idname)
|
||||||
layout.operator("ballance.rail_uv")
|
layout.operator(MODS_rail_uv.BALLANCE_OT_rail_uv.bl_idname)
|
||||||
layout.operator("ballance.no_uv_checker")
|
layout.operator(MODS_flatten_uv.BALLANCE_OT_flatten_uv.bl_idname)
|
||||||
layout.operator("ballance.flatten_uv")
|
|
||||||
|
|
||||||
class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
|
class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
|
||||||
"""Add Ballance floor"""
|
"""Add Ballance floor"""
|
||||||
@ -64,41 +82,46 @@ class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
|
|||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
layout.label(text="Basic floor")
|
layout.label(text="Basic floor")
|
||||||
for item in config.floor_basic_block_list:
|
for item in UTILS_constants.floor_basicBlock_list:
|
||||||
cop = layout.operator("ballance.add_floor", text=item, icon_value = config.blenderIcon_floor_dict[item])
|
cop = layout.operator(
|
||||||
|
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
|
||||||
|
text=item, icon_value = UTILS_constants.icons_floorDict[item])
|
||||||
cop.floor_type = item
|
cop.floor_type = item
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text="Derived floor")
|
layout.label(text="Derived floor")
|
||||||
for item in config.floor_derived_block_list:
|
for item in UTILS_constants.floor_derivedBlockList:
|
||||||
cop = layout.operator("ballance.add_floor", text=item, icon_value = config.blenderIcon_floor_dict[item])
|
cop = layout.operator(
|
||||||
|
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
|
||||||
|
text=item, icon_value = UTILS_constants.icons_floorDict[item])
|
||||||
cop.floor_type = item
|
cop.floor_type = item
|
||||||
|
|
||||||
|
|
||||||
# ============================================= blender call system
|
# =============================================
|
||||||
|
# blender call system
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
preferences.BallanceBlenderPluginPreferences,
|
UTILS_preferences.BallanceBlenderPluginPreferences,
|
||||||
preferences.MyPropertyGroup,
|
UTILS_preferences.MyPropertyGroup,
|
||||||
|
|
||||||
bm_import_export.BALLANCE_OT_import_bm,
|
BMFILE_import.BALLANCE_OT_import_bm,
|
||||||
bm_import_export.BALLANCE_OT_export_bm,
|
BMFILE_export.BALLANCE_OT_export_bm,
|
||||||
rail_uv.BALLANCE_OT_rail_uv,
|
|
||||||
threedsmax_align.BALLANCE_OT_super_align,
|
MODS_rail_uv.BALLANCE_OT_rail_uv,
|
||||||
no_uv_checker.BALLANCE_OT_no_uv_checker,
|
MODS_3dsmax_align.BALLANCE_OT_super_align,
|
||||||
flatten_uv.BALLANCE_OT_flatten_uv,
|
MODS_flatten_uv.BALLANCE_OT_flatten_uv,
|
||||||
BALLANCE_MT_ThreeDViewerMenu,
|
BALLANCE_MT_ThreeDViewerMenu,
|
||||||
|
|
||||||
add_elements.BALLANCE_OT_add_elements,
|
OBJS_add_components.BALLANCE_OT_add_components,
|
||||||
add_elements.BALLANCE_OT_add_rail,
|
OBJS_add_rails.BALLANCE_OT_add_rails,
|
||||||
add_floor.BALLANCE_OT_add_floor,
|
OBJS_add_floors.BALLANCE_OT_add_floors,
|
||||||
BALLANCE_MT_AddFloorMenu
|
BALLANCE_MT_AddFloorMenu
|
||||||
)
|
)
|
||||||
|
|
||||||
def menu_func_bm_import(self, context):
|
def menu_func_bm_import(self, context):
|
||||||
self.layout.operator(bm_import_export.BALLANCE_OT_import_bm.bl_idname, text="Ballance Map (.bmx)")
|
self.layout.operator(BMFILE_import.BALLANCE_OT_import_bm.bl_idname, text="Ballance Map (.bmx)")
|
||||||
def menu_func_bm_export(self, context):
|
def menu_func_bm_export(self, context):
|
||||||
self.layout.operator(bm_import_export.BALLANCE_OT_export_bm.bl_idname, text="Ballance Map (.bmx)")
|
self.layout.operator(BMFILE_export.BALLANCE_OT_export_bm.bl_idname, text="Ballance Map (.bmx)")
|
||||||
def menu_func_ballance_3d(self, context):
|
def menu_func_ballance_3d(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.menu(BALLANCE_MT_ThreeDViewerMenu.bl_idname)
|
layout.menu(BALLANCE_MT_ThreeDViewerMenu.bl_idname)
|
||||||
@ -106,29 +129,37 @@ def menu_func_ballance_add(self, context):
|
|||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text="Ballance")
|
layout.label(text="Ballance")
|
||||||
layout.operator_menu_enum("ballance.add_elements", "elements_type", icon='MESH_ICOSPHERE', text="Elements")
|
layout.operator_menu_enum(
|
||||||
layout.operator("ballance.add_rail", icon='MESH_CIRCLE', text="Rail section")
|
OBJS_add_components.BALLANCE_OT_add_components.bl_idname,
|
||||||
|
"elements_type", icon='MESH_ICOSPHERE', text="Elements")
|
||||||
|
layout.operator(OBJS_add_rails.BALLANCE_OT_add_rails.bl_idname, icon='MESH_CIRCLE', text="Rail section")
|
||||||
layout.menu(BALLANCE_MT_AddFloorMenu.bl_idname, icon='MESH_CUBE')
|
layout.menu(BALLANCE_MT_AddFloorMenu.bl_idname, icon='MESH_CUBE')
|
||||||
|
def menu_func_ballance_rename(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.separator()
|
||||||
|
layout.label(text="Ballance")
|
||||||
|
layout.operator(NAMES_rename_via_group.BALLANCE_OT_rename_via_group.bl_idname, text="Rename via Group")
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
# we need init all icon first
|
# we need init all icon first
|
||||||
icon_path = os.path.join(os.path.dirname(__file__), "icons")
|
icon_path = os.path.join(os.path.dirname(__file__), "icons")
|
||||||
config.blenderIcon_floor = bpy.utils.previews.new()
|
UTILS_constants.icons_floor = bpy.utils.previews.new()
|
||||||
for key, value in config.floor_block_dict.items():
|
for key, value in UTILS_constants.floor_blockDict.items():
|
||||||
blockIconName = "Ballance_FloorIcon_" + key
|
blockIconName = "Ballance_FloorIcon_" + key
|
||||||
config.blenderIcon_floor.load(blockIconName, os.path.join(icon_path, "floor", value["BindingDisplayTexture"]), 'IMAGE')
|
UTILS_constants.icons_floor.load(blockIconName, os.path.join(icon_path, "floor", value["BindingDisplayTexture"]), 'IMAGE')
|
||||||
config.blenderIcon_floor_dict[key] = config.blenderIcon_floor[blockIconName].icon_id
|
UTILS_constants.icons_floorDict[key] = UTILS_constants.icons_floor[blockIconName].icon_id
|
||||||
|
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
bpy.types.Scene.BallanceBlenderPluginProperty = bpy.props.PointerProperty(type=preferences.MyPropertyGroup)
|
bpy.types.Scene.BallanceBlenderPluginProperty = bpy.props.PointerProperty(type=UTILS_preferences.MyPropertyGroup)
|
||||||
|
|
||||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_bm_import)
|
bpy.types.TOPBAR_MT_file_import.append(menu_func_bm_import)
|
||||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_bm_export)
|
bpy.types.TOPBAR_MT_file_export.append(menu_func_bm_export)
|
||||||
|
|
||||||
bpy.types.VIEW3D_HT_header.append(menu_func_ballance_3d)
|
bpy.types.VIEW3D_HT_header.append(menu_func_ballance_3d)
|
||||||
bpy.types.VIEW3D_MT_add.append(menu_func_ballance_add)
|
bpy.types.VIEW3D_MT_add.append(menu_func_ballance_add)
|
||||||
|
bpy.types.COLLECTION_MT_context_menu.append(menu_func_ballance_rename)
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_bm_import)
|
bpy.types.TOPBAR_MT_file_import.remove(menu_func_bm_import)
|
||||||
@ -136,12 +167,13 @@ def unregister():
|
|||||||
|
|
||||||
bpy.types.VIEW3D_HT_header.remove(menu_func_ballance_3d)
|
bpy.types.VIEW3D_HT_header.remove(menu_func_ballance_3d)
|
||||||
bpy.types.VIEW3D_MT_add.remove(menu_func_ballance_add)
|
bpy.types.VIEW3D_MT_add.remove(menu_func_ballance_add)
|
||||||
|
bpy.types.COLLECTION_MT_context_menu.remove(menu_func_ballance_rename)
|
||||||
|
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
# we need uninstall all icon after all classes unregister
|
# we need uninstall all icon after all classes unregister
|
||||||
bpy.utils.previews.remove(config.blenderIcon_floor)
|
bpy.utils.previews.remove(UTILS_constants.icons_floor)
|
||||||
|
|
||||||
if __name__=="__main__":
|
if __name__=="__main__":
|
||||||
register()
|
register()
|
@ -1,907 +0,0 @@
|
|||||||
import bpy,bmesh,bpy_extras,mathutils
|
|
||||||
import pathlib,zipfile,time,os,tempfile,math
|
|
||||||
import struct,shutil
|
|
||||||
from bpy_extras import io_utils,node_shader_utils
|
|
||||||
from bpy_extras.io_utils import unpack_list
|
|
||||||
from bpy_extras.image_utils import load_image
|
|
||||||
from . import utils, config
|
|
||||||
|
|
||||||
class BALLANCE_OT_import_bm(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
|
|
||||||
"""Load a Ballance Map File (BM file spec 1.3)"""
|
|
||||||
bl_idname = "ballance.import_bm"
|
|
||||||
bl_label = "Import BM "
|
|
||||||
bl_options = {'PRESET', 'UNDO'}
|
|
||||||
filename_ext = ".bmx"
|
|
||||||
|
|
||||||
texture_conflict_strategy: bpy.props.EnumProperty(
|
|
||||||
name="Texture name conflict",
|
|
||||||
items=(('NEW', "New instance", "Create a new instance"),
|
|
||||||
('CURRENT', "Use current", "Use current"),),
|
|
||||||
description="Define how to process texture name conflict",
|
|
||||||
default='CURRENT',
|
|
||||||
)
|
|
||||||
|
|
||||||
material_conflict_strategy: bpy.props.EnumProperty(
|
|
||||||
name="Material name conflict",
|
|
||||||
items=(('RENAME', "Rename", "Rename the new one"),
|
|
||||||
('CURRENT', "Use current", "Use current"),),
|
|
||||||
description="Define how to process material name conflict",
|
|
||||||
default='RENAME',
|
|
||||||
)
|
|
||||||
|
|
||||||
mesh_conflict_strategy: bpy.props.EnumProperty(
|
|
||||||
name="Mesh name conflict",
|
|
||||||
items=(('RENAME', "Rename", "Rename the new one"),
|
|
||||||
('CURRENT', "Use current", "Use current"),),
|
|
||||||
description="Define how to process mesh name conflict",
|
|
||||||
default='RENAME',
|
|
||||||
)
|
|
||||||
|
|
||||||
object_conflict_strategy: bpy.props.EnumProperty(
|
|
||||||
name="Object name conflict",
|
|
||||||
items=(('RENAME', "Rename", "Rename the new one"),
|
|
||||||
('CURRENT', "Use current", "Use current"),),
|
|
||||||
description="Define how to process object name conflict",
|
|
||||||
default='RENAME',
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(self, context):
|
|
||||||
prefs = bpy.context.preferences.addons[__package__].preferences
|
|
||||||
return (os.path.isdir(prefs.temp_texture_folder) and os.path.isdir(prefs.external_folder))
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
prefs = bpy.context.preferences.addons[__package__].preferences
|
|
||||||
import_bm(context, self.filepath, prefs.external_folder, prefs.temp_texture_folder,
|
|
||||||
self.texture_conflict_strategy, self.material_conflict_strategy, self.mesh_conflict_strategy, self.object_conflict_strategy)
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
class BALLANCE_OT_export_bm(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
|
|
||||||
"""Save a Ballance Map File (BM file spec 1.3)"""
|
|
||||||
bl_idname = "ballance.export_bm"
|
|
||||||
bl_label = 'Export BM'
|
|
||||||
bl_options = {'PRESET'}
|
|
||||||
filename_ext = ".bmx"
|
|
||||||
|
|
||||||
export_mode: bpy.props.EnumProperty(
|
|
||||||
name="Export mode",
|
|
||||||
items=(('COLLECTION', "Collection", "Export a collection"),
|
|
||||||
('OBJECT', "Objects", "Export an objects"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
if (self.export_mode == 'COLLECTION' and context.scene.BallanceBlenderPluginProperty.collection_picker is None) or (self.export_mode == 'OBJECT' and context.scene.BallanceBlenderPluginProperty.object_picker is None):
|
|
||||||
utils.ShowMessageBox(("No specific target", ), "Lost parameter", 'ERROR')
|
|
||||||
else:
|
|
||||||
if self.export_mode == 'COLLECTION':
|
|
||||||
export_bm(context, self.filepath, self.export_mode, context.scene.BallanceBlenderPluginProperty.collection_picker)
|
|
||||||
elif self.export_mode == 'OBJECT':
|
|
||||||
export_bm(context, self.filepath, self.export_mode, context.scene.BallanceBlenderPluginProperty.object_picker)
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.prop(self, "export_mode")
|
|
||||||
if self.export_mode == 'COLLECTION':
|
|
||||||
layout.prop(context.scene.BallanceBlenderPluginProperty, "collection_picker")
|
|
||||||
elif self.export_mode == 'OBJECT':
|
|
||||||
layout.prop(context.scene.BallanceBlenderPluginProperty, "object_picker")
|
|
||||||
|
|
||||||
# ========================================== method
|
|
||||||
|
|
||||||
bm_current_version = 13
|
|
||||||
|
|
||||||
def import_bm(context,filepath,externalTexture,blenderTempFolder, textureOpt, materialOpt, meshOpt, objectOpt):
|
|
||||||
# ============================================ alloc a temp folder
|
|
||||||
tempFolderObj = tempfile.TemporaryDirectory()
|
|
||||||
tempFolder = tempFolderObj.name
|
|
||||||
# debug
|
|
||||||
# print(tempFolder)
|
|
||||||
tempTextureFolder = os.path.join(tempFolder, "Texture")
|
|
||||||
prefs = bpy.context.preferences.addons[__package__].preferences
|
|
||||||
blenderTempTextureFolder = prefs.temp_texture_folder
|
|
||||||
externalTextureFolder = prefs.external_folder
|
|
||||||
|
|
||||||
with zipfile.ZipFile(filepath, 'r', zipfile.ZIP_DEFLATED, 9) as zipObj:
|
|
||||||
zipObj.extractall(tempFolder)
|
|
||||||
|
|
||||||
# index.bm
|
|
||||||
objectList = []
|
|
||||||
meshList = []
|
|
||||||
materialList = []
|
|
||||||
textureList = []
|
|
||||||
with open(os.path.join(tempFolder, "index.bm"), "rb") as findex:
|
|
||||||
# judge version first
|
|
||||||
gotten_version = read_uint32(findex)
|
|
||||||
if (gotten_version != bm_current_version):
|
|
||||||
utils.ShowMessageBox(("Unsupported BM spec. Expect: {} Gotten: {}".format(bm_current_version, gotten_version), ), "Unsupported BM spec", 'ERROR')
|
|
||||||
findex.close()
|
|
||||||
tempFolderObj.cleanup()
|
|
||||||
return
|
|
||||||
|
|
||||||
while len(peek_stream(findex)) != 0:
|
|
||||||
index_name = read_string(findex)
|
|
||||||
index_type = read_uint8(findex)
|
|
||||||
index_offset = read_uint64(findex)
|
|
||||||
blockCache = info_block_helper(index_name, index_offset)
|
|
||||||
if index_type == info_bm_type.OBJECT:
|
|
||||||
objectList.append(blockCache)
|
|
||||||
elif index_type == info_bm_type.MESH:
|
|
||||||
meshList.append(blockCache)
|
|
||||||
elif index_type == info_bm_type.MATERIAL:
|
|
||||||
materialList.append(blockCache)
|
|
||||||
elif index_type == info_bm_type.TEXTURE:
|
|
||||||
textureList.append(blockCache)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# texture.bm
|
|
||||||
with open(os.path.join(tempFolder, "texture.bm"), "rb") as ftexture:
|
|
||||||
for item in textureList:
|
|
||||||
ftexture.seek(item.offset, os.SEEK_SET)
|
|
||||||
texture_filename = read_string(ftexture)
|
|
||||||
texture_isExternal = read_bool(ftexture)
|
|
||||||
if texture_isExternal:
|
|
||||||
txur = load_image(texture_filename, externalTextureFolder, check_existing=(textureOpt == 'CURRENT'))
|
|
||||||
item.blenderData = txur
|
|
||||||
else:
|
|
||||||
# not external. copy temp file into blender temp. then use it.
|
|
||||||
# try copy. if fail, don't need to do more
|
|
||||||
try:
|
|
||||||
shutil.copy(os.path.join(tempTextureFolder, texture_filename), os.path.join(blenderTempTextureFolder, texture_filename))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
txur = load_image(texture_filename, blenderTempTextureFolder, check_existing=(textureOpt == 'CURRENT'))
|
|
||||||
item.blenderData = txur
|
|
||||||
txur.name = item.name
|
|
||||||
|
|
||||||
# material.bm
|
|
||||||
# WARNING: this code is shared with add_floor - create_or_get_material()
|
|
||||||
with open(os.path.join(tempFolder, "material.bm"), "rb") as fmaterial:
|
|
||||||
for item in materialList:
|
|
||||||
fmaterial.seek(item.offset, os.SEEK_SET)
|
|
||||||
|
|
||||||
# read data
|
|
||||||
material_colAmbient = read_3vector(fmaterial)
|
|
||||||
material_colDiffuse = read_3vector(fmaterial)
|
|
||||||
material_colSpecular = read_3vector(fmaterial)
|
|
||||||
material_colEmissive = read_3vector(fmaterial)
|
|
||||||
material_specularPower = read_float(fmaterial)
|
|
||||||
material_useTexture = read_bool(fmaterial)
|
|
||||||
material_texture = read_uint32(fmaterial)
|
|
||||||
|
|
||||||
# create basic material
|
|
||||||
(m, needSkip) = createInstanceWithOption(info_bm_type.MATERIAL, item.name, materialOpt)
|
|
||||||
item.blenderData = m
|
|
||||||
if needSkip:
|
|
||||||
continue
|
|
||||||
|
|
||||||
m.use_nodes=True
|
|
||||||
for node in m.node_tree.nodes:
|
|
||||||
m.node_tree.nodes.remove(node)
|
|
||||||
bnode=m.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled")
|
|
||||||
mnode=m.node_tree.nodes.new(type="ShaderNodeOutputMaterial")
|
|
||||||
m.node_tree.links.new(bnode.outputs[0],mnode.inputs[0])
|
|
||||||
|
|
||||||
m.metallic = sum(material_colAmbient) / 3
|
|
||||||
m.diffuse_color = [i for i in material_colDiffuse] + [1]
|
|
||||||
m.specular_color = material_colSpecular
|
|
||||||
m.specular_intensity = material_specularPower
|
|
||||||
|
|
||||||
# create a texture
|
|
||||||
if material_useTexture:
|
|
||||||
inode=m.node_tree.nodes.new(type="ShaderNodeTexImage")
|
|
||||||
inode.image=textureList[material_texture].blenderData
|
|
||||||
m.node_tree.links.new(inode.outputs[0],bnode.inputs[0])
|
|
||||||
|
|
||||||
# write custom property
|
|
||||||
m['virtools-ambient'] = material_colAmbient
|
|
||||||
m['virtools-diffuse'] = material_colDiffuse
|
|
||||||
m['virtools-specular'] = material_colSpecular
|
|
||||||
m['virtools-emissive'] = material_colEmissive
|
|
||||||
m['virtools-power'] = material_specularPower
|
|
||||||
|
|
||||||
|
|
||||||
# mesh.bm
|
|
||||||
# WARNING: this code is shared with add_floor
|
|
||||||
with open(os.path.join(tempFolder, "mesh.bm"), "rb") as fmesh:
|
|
||||||
vList=[]
|
|
||||||
vtList=[]
|
|
||||||
vnList=[]
|
|
||||||
faceList=[]
|
|
||||||
materialSolt = []
|
|
||||||
for item in meshList:
|
|
||||||
fmesh.seek(item.offset, os.SEEK_SET)
|
|
||||||
|
|
||||||
# create real mesh
|
|
||||||
(mesh, needSkip) = createInstanceWithOption(info_bm_type.MESH, item.name, meshOpt)
|
|
||||||
item.blenderData = mesh
|
|
||||||
if needSkip:
|
|
||||||
continue
|
|
||||||
|
|
||||||
vList.clear()
|
|
||||||
vtList.clear()
|
|
||||||
vnList.clear()
|
|
||||||
faceList.clear()
|
|
||||||
materialSolt.clear()
|
|
||||||
# in first read, store all data into list
|
|
||||||
listCount = read_uint32(fmesh)
|
|
||||||
for i in range(listCount):
|
|
||||||
cache = read_3vector(fmesh)
|
|
||||||
# switch yz
|
|
||||||
vList.append((cache[0], cache[2], cache[1]))
|
|
||||||
listCount = read_uint32(fmesh)
|
|
||||||
for i in range(listCount):
|
|
||||||
cache = read_2vector(fmesh)
|
|
||||||
# reverse v
|
|
||||||
vtList.append((cache[0], -cache[1]))
|
|
||||||
listCount = read_uint32(fmesh)
|
|
||||||
for i in range(listCount):
|
|
||||||
cache = read_3vector(fmesh)
|
|
||||||
# switch yz
|
|
||||||
vnList.append((cache[0], cache[2], cache[1]))
|
|
||||||
|
|
||||||
listCount = read_uint32(fmesh)
|
|
||||||
for i in range(listCount):
|
|
||||||
faceData = read_face(fmesh)
|
|
||||||
mesh_useMaterial = read_bool(fmesh)
|
|
||||||
mesh_materialIndex = read_uint32(fmesh)
|
|
||||||
|
|
||||||
if mesh_useMaterial:
|
|
||||||
neededMaterial = materialList[mesh_materialIndex].blenderData
|
|
||||||
if neededMaterial in materialSolt:
|
|
||||||
neededIndex = materialSolt.index(neededMaterial)
|
|
||||||
else:
|
|
||||||
neededIndex = len(materialSolt)
|
|
||||||
materialSolt.append(neededMaterial)
|
|
||||||
else:
|
|
||||||
neededIndex = -1
|
|
||||||
|
|
||||||
# we need invert triangle sort
|
|
||||||
faceList.append((
|
|
||||||
faceData[6], faceData[7], faceData[8],
|
|
||||||
faceData[3], faceData[4], faceData[5],
|
|
||||||
faceData[0], faceData[1], faceData[2],
|
|
||||||
neededIndex
|
|
||||||
))
|
|
||||||
|
|
||||||
# and then we need add material solt for this mesh
|
|
||||||
for mat in materialSolt:
|
|
||||||
mesh.materials.append(mat)
|
|
||||||
|
|
||||||
# then, we need add correspond count for vertices
|
|
||||||
mesh.vertices.add(len(vList))
|
|
||||||
mesh.loops.add(len(faceList)*3) # triangle face confirm
|
|
||||||
mesh.polygons.add(len(faceList))
|
|
||||||
mesh.uv_layers.new(do_init=False)
|
|
||||||
mesh.create_normals_split()
|
|
||||||
|
|
||||||
# add vertices data
|
|
||||||
mesh.vertices.foreach_set("co", unpack_list(vList))
|
|
||||||
mesh.loops.foreach_set("vertex_index", unpack_list(flat_vertices_index(faceList)))
|
|
||||||
mesh.loops.foreach_set("normal", unpack_list(flat_vertices_normal(faceList, vnList)))
|
|
||||||
mesh.uv_layers[0].data.foreach_set("uv", unpack_list(flat_vertices_uv(faceList, vtList)))
|
|
||||||
for i in range(len(faceList)):
|
|
||||||
mesh.polygons[i].loop_start = i * 3
|
|
||||||
mesh.polygons[i].loop_total = 3
|
|
||||||
if faceList[i][9] != -1:
|
|
||||||
mesh.polygons[i].material_index = faceList[i][9]
|
|
||||||
|
|
||||||
mesh.polygons[i].use_smooth = True
|
|
||||||
|
|
||||||
mesh.validate(clean_customdata=False)
|
|
||||||
mesh.update(calc_edges=False, calc_edges_loose=False)
|
|
||||||
|
|
||||||
|
|
||||||
# object
|
|
||||||
with open(os.path.join(tempFolder, "object.bm"), "rb") as fobject:
|
|
||||||
|
|
||||||
# we need get needed collection first
|
|
||||||
view_layer = context.view_layer
|
|
||||||
collection = view_layer.active_layer_collection.collection
|
|
||||||
if prefs.no_component_collection == "":
|
|
||||||
forcedCollection = None
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
forcedCollection = bpy.data.collections[prefs.no_component_collection]
|
|
||||||
except:
|
|
||||||
forcedCollection = bpy.data.collections.new(prefs.no_component_collection)
|
|
||||||
view_layer.active_layer_collection.collection.children.link(forcedCollection)
|
|
||||||
|
|
||||||
# start process it
|
|
||||||
object_groupList = []
|
|
||||||
for item in objectList:
|
|
||||||
fobject.seek(item.offset, os.SEEK_SET)
|
|
||||||
|
|
||||||
# read data
|
|
||||||
object_isComponent = read_bool(fobject)
|
|
||||||
object_isForcedNoComponent = read_bool(fobject)
|
|
||||||
object_isHidden = read_bool(fobject)
|
|
||||||
object_worldMatrix = read_worldMaterix(fobject)
|
|
||||||
object_groupListCount = read_uint32(fobject)
|
|
||||||
object_groupList.clear()
|
|
||||||
for i in range(object_groupListCount):
|
|
||||||
object_groupList.append(read_string(fobject))
|
|
||||||
object_meshIndex = read_uint32(fobject)
|
|
||||||
|
|
||||||
# got mesh first
|
|
||||||
if object_isComponent:
|
|
||||||
neededMesh = load_component(object_meshIndex)
|
|
||||||
else:
|
|
||||||
neededMesh = meshList[object_meshIndex].blenderData
|
|
||||||
|
|
||||||
# create real object
|
|
||||||
(obj, needSkip) = createInstanceWithOption(info_bm_type.OBJECT, item.name, objectOpt, extraMesh=neededMesh)
|
|
||||||
if needSkip:
|
|
||||||
continue
|
|
||||||
if (not object_isComponent) and object_isForcedNoComponent and (forcedCollection is not None):
|
|
||||||
forcedCollection.objects.link(obj)
|
|
||||||
else:
|
|
||||||
collection.objects.link(obj)
|
|
||||||
obj.matrix_world = object_worldMatrix
|
|
||||||
obj.hide_set(object_isHidden)
|
|
||||||
|
|
||||||
# write custom property
|
|
||||||
if len(object_groupList) != 0:
|
|
||||||
obj['virtools-group'] = tuple(object_groupList)
|
|
||||||
|
|
||||||
view_layer.update()
|
|
||||||
|
|
||||||
tempFolderObj.cleanup()
|
|
||||||
|
|
||||||
def export_bm(context, filepath, export_mode, export_target):
|
|
||||||
# ============================================ alloc a temp folder
|
|
||||||
tempFolderObj = tempfile.TemporaryDirectory()
|
|
||||||
tempFolder = tempFolderObj.name
|
|
||||||
# debug
|
|
||||||
# tempFolder = "G:\\ziptest"
|
|
||||||
tempTextureFolder = os.path.join(tempFolder, "Texture")
|
|
||||||
os.makedirs(tempTextureFolder)
|
|
||||||
prefs = bpy.context.preferences.addons[__package__].preferences
|
|
||||||
|
|
||||||
# ============================================ find export target. don't need judge them in there. just collect them
|
|
||||||
if export_mode== "COLLECTION":
|
|
||||||
objectList = export_target.objects
|
|
||||||
else:
|
|
||||||
objectList = [export_target]
|
|
||||||
|
|
||||||
# try get forcedCollection
|
|
||||||
try:
|
|
||||||
forcedCollection = bpy.data.collections[prefs.no_component_collection]
|
|
||||||
except:
|
|
||||||
forcedCollection = None
|
|
||||||
|
|
||||||
# ============================================ export
|
|
||||||
with open(os.path.join(tempFolder, "index.bm"), "wb") as finfo:
|
|
||||||
write_uint32(finfo, bm_current_version)
|
|
||||||
|
|
||||||
# ====================== export object
|
|
||||||
meshSet = set()
|
|
||||||
meshList = []
|
|
||||||
meshCount = 0
|
|
||||||
with open(os.path.join(tempFolder, "object.bm"), "wb") as fobject:
|
|
||||||
for obj in objectList:
|
|
||||||
# only export mesh object
|
|
||||||
if obj.type != 'MESH':
|
|
||||||
continue
|
|
||||||
|
|
||||||
# clean no mesh object
|
|
||||||
currentMesh = obj.data
|
|
||||||
if currentMesh is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# judge component
|
|
||||||
object_isComponent = is_component(obj.name)
|
|
||||||
object_isForcedNoComponent = False
|
|
||||||
if (forcedCollection is not None) and (obj.name in forcedCollection.objects):
|
|
||||||
# change it to forced no component
|
|
||||||
object_isComponent = False
|
|
||||||
object_isForcedNoComponent = True
|
|
||||||
|
|
||||||
# triangle first and then group
|
|
||||||
if not object_isComponent:
|
|
||||||
if currentMesh not in meshSet:
|
|
||||||
mesh_triangulate(currentMesh)
|
|
||||||
meshSet.add(currentMesh)
|
|
||||||
meshList.append(currentMesh)
|
|
||||||
meshId = meshCount
|
|
||||||
meshCount += 1
|
|
||||||
else:
|
|
||||||
meshId = meshList.index(currentMesh)
|
|
||||||
else:
|
|
||||||
meshId = get_component_id(obj.name)
|
|
||||||
|
|
||||||
# get visibility
|
|
||||||
object_isHidden = not obj.visible_get()
|
|
||||||
|
|
||||||
# try get grouping data
|
|
||||||
object_groupList = try_get_custom_property(obj, 'virtools-group')
|
|
||||||
object_groupList = set_value_when_none(object_groupList, [])
|
|
||||||
|
|
||||||
# write finfo first
|
|
||||||
write_string(finfo, obj.name)
|
|
||||||
write_uint8(finfo, info_bm_type.OBJECT)
|
|
||||||
write_uint64(finfo, fobject.tell())
|
|
||||||
|
|
||||||
# write fobject
|
|
||||||
write_bool(fobject, object_isComponent)
|
|
||||||
write_bool(fobject, object_isForcedNoComponent)
|
|
||||||
write_bool(fobject, object_isHidden)
|
|
||||||
write_worldMatrix(fobject, obj.matrix_world)
|
|
||||||
write_uint32(fobject, len(object_groupList))
|
|
||||||
for item in object_groupList:
|
|
||||||
write_string(fobject, item)
|
|
||||||
write_uint32(fobject, meshId)
|
|
||||||
|
|
||||||
# ====================== export mesh
|
|
||||||
materialSet = set()
|
|
||||||
materialList = []
|
|
||||||
with open(os.path.join(tempFolder, "mesh.bm"), "wb") as fmesh:
|
|
||||||
for mesh in meshList:
|
|
||||||
mesh.calc_normals_split()
|
|
||||||
|
|
||||||
# write finfo first
|
|
||||||
write_string(finfo, mesh.name)
|
|
||||||
write_uint8(finfo, info_bm_type.MESH)
|
|
||||||
write_uint64(finfo, fmesh.tell())
|
|
||||||
|
|
||||||
# write fmesh
|
|
||||||
# vertices
|
|
||||||
vecList = mesh.vertices[:]
|
|
||||||
write_uint32(fmesh, len(vecList))
|
|
||||||
for vec in vecList:
|
|
||||||
#swap yz
|
|
||||||
write_3vector(fmesh,vec.co[0],vec.co[2],vec.co[1])
|
|
||||||
|
|
||||||
# uv
|
|
||||||
face_index_pairs = [(face, index) for index, face in enumerate(mesh.polygons)]
|
|
||||||
write_uint32(fmesh, len(face_index_pairs) * 3)
|
|
||||||
if mesh.uv_layers.active is not None:
|
|
||||||
uv_layer = mesh.uv_layers.active.data[:]
|
|
||||||
for f, f_index in face_index_pairs:
|
|
||||||
# it should be triangle face, otherwise throw a error
|
|
||||||
if (f.loop_total != 3):
|
|
||||||
raise Exception("Not a triangle", f.poly.loop_total)
|
|
||||||
|
|
||||||
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
|
|
||||||
uv = uv_layer[loop_index].uv
|
|
||||||
# reverse v
|
|
||||||
write_2vector(fmesh, uv[0], -uv[1])
|
|
||||||
else:
|
|
||||||
# no uv data. write garbage
|
|
||||||
for i in range(len(face_index_pairs) * 3):
|
|
||||||
write_2vector(fmesh, 0.0, 0.0)
|
|
||||||
|
|
||||||
# normals
|
|
||||||
write_uint32(fmesh, len(face_index_pairs) * 3)
|
|
||||||
for f, f_index in face_index_pairs:
|
|
||||||
# no need to check triangle again
|
|
||||||
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
|
|
||||||
nml = mesh.loops[loop_index].normal
|
|
||||||
# swap yz
|
|
||||||
write_3vector(fmesh, nml[0], nml[2], nml[1])
|
|
||||||
|
|
||||||
# face
|
|
||||||
# get material first
|
|
||||||
currentMat = mesh.materials[:]
|
|
||||||
noMaterial = len(currentMat) == 0
|
|
||||||
for mat in currentMat:
|
|
||||||
if mat not in materialSet:
|
|
||||||
materialSet.add(mat)
|
|
||||||
materialList.append(mat)
|
|
||||||
|
|
||||||
write_uint32(fmesh, len(face_index_pairs))
|
|
||||||
vtIndex = []
|
|
||||||
vnIndex = []
|
|
||||||
vIndex = []
|
|
||||||
for f, f_index in face_index_pairs:
|
|
||||||
# confirm material use
|
|
||||||
if noMaterial:
|
|
||||||
usedMat = 0
|
|
||||||
else:
|
|
||||||
usedMat = materialList.index(currentMat[f.material_index])
|
|
||||||
|
|
||||||
# export face
|
|
||||||
vtIndex.clear()
|
|
||||||
vnIndex.clear()
|
|
||||||
vIndex.clear()
|
|
||||||
|
|
||||||
counter = 0
|
|
||||||
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
|
|
||||||
vIndex.append(mesh.loops[loop_index].vertex_index)
|
|
||||||
vnIndex.append(f_index * 3 + counter)
|
|
||||||
vtIndex.append(f_index * 3 + counter)
|
|
||||||
counter += 1
|
|
||||||
# reverse vertices sort
|
|
||||||
write_face(fmesh,
|
|
||||||
vIndex[2], vtIndex[2], vnIndex[2],
|
|
||||||
vIndex[1], vtIndex[1], vnIndex[1],
|
|
||||||
vIndex[0], vtIndex[0], vnIndex[0])
|
|
||||||
|
|
||||||
# set used material
|
|
||||||
write_bool(fmesh, not noMaterial)
|
|
||||||
write_uint32(fmesh, usedMat)
|
|
||||||
|
|
||||||
mesh.free_normals_split()
|
|
||||||
|
|
||||||
# ====================== export material
|
|
||||||
textureSet = set()
|
|
||||||
textureList = []
|
|
||||||
textureCount = 0
|
|
||||||
with open(os.path.join(tempFolder, "material.bm"), "wb") as fmaterial:
|
|
||||||
for material in materialList:
|
|
||||||
# write finfo first
|
|
||||||
write_string(finfo, material.name)
|
|
||||||
write_uint8(finfo, info_bm_type.MATERIAL)
|
|
||||||
write_uint64(finfo, fmaterial.tell())
|
|
||||||
|
|
||||||
# try get original written data
|
|
||||||
material_colAmbient = try_get_custom_property(material, 'virtools-ambient')
|
|
||||||
material_colDiffuse = try_get_custom_property(material, 'virtools-diffuse')
|
|
||||||
material_colSpecular = try_get_custom_property(material, 'virtools-specular')
|
|
||||||
material_colEmissive = try_get_custom_property(material, 'virtools-emissive')
|
|
||||||
material_specularPower = try_get_custom_property(material, 'virtools-power')
|
|
||||||
|
|
||||||
# get basic color
|
|
||||||
mat_wrap = node_shader_utils.PrincipledBSDFWrapper(material)
|
|
||||||
if mat_wrap:
|
|
||||||
use_mirror = mat_wrap.metallic != 0.0
|
|
||||||
if use_mirror:
|
|
||||||
material_colAmbient = set_value_when_none(material_colAmbient, (mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic))
|
|
||||||
else:
|
|
||||||
material_colAmbient = set_value_when_none(material_colAmbient, (1.0, 1.0, 1.0))
|
|
||||||
material_colDiffuse = set_value_when_none(material_colDiffuse, (mat_wrap.base_color[0], mat_wrap.base_color[1], mat_wrap.base_color[2]))
|
|
||||||
material_colSpecular = set_value_when_none(material_colSpecular, (mat_wrap.specular, mat_wrap.specular, mat_wrap.specular))
|
|
||||||
material_colEmissive = set_value_when_none(material_colEmissive, mat_wrap.emission_color[:3])
|
|
||||||
material_specularPower = set_value_when_none(material_specularPower, 0.0)
|
|
||||||
|
|
||||||
# confirm texture
|
|
||||||
tex_wrap = getattr(mat_wrap, "base_color_texture", None)
|
|
||||||
if tex_wrap:
|
|
||||||
image = tex_wrap.image
|
|
||||||
if image:
|
|
||||||
# add into texture list
|
|
||||||
if image not in textureSet:
|
|
||||||
textureSet.add(image)
|
|
||||||
textureList.append(image)
|
|
||||||
currentTexture = textureCount
|
|
||||||
textureCount += 1
|
|
||||||
else:
|
|
||||||
currentTexture = textureList.index(image)
|
|
||||||
|
|
||||||
material_useTexture = True
|
|
||||||
material_texture = currentTexture
|
|
||||||
else:
|
|
||||||
# no texture
|
|
||||||
material_useTexture = False
|
|
||||||
material_texture = 0
|
|
||||||
else:
|
|
||||||
# no texture
|
|
||||||
material_useTexture = False
|
|
||||||
material_texture = 0
|
|
||||||
|
|
||||||
else:
|
|
||||||
# no Principled BSDF. write garbage
|
|
||||||
material_colAmbient = set_value_when_none(material_colAmbient, (0.8, 0.8, 0.8))
|
|
||||||
material_colDiffuse = set_value_when_none(material_colDiffuse, (0.8, 0.8, 0.8))
|
|
||||||
material_colSpecular = set_value_when_none(material_colSpecular, (0.8, 0.8, 0.8))
|
|
||||||
material_colEmissive = set_value_when_none(material_colEmissive, (0.8, 0.8, 0.8))
|
|
||||||
material_specularPower = set_value_when_none(material_specularPower, 0.0)
|
|
||||||
|
|
||||||
material_useTexture = False
|
|
||||||
material_texture = 0
|
|
||||||
|
|
||||||
write_color(fmaterial, material_colAmbient)
|
|
||||||
write_color(fmaterial, material_colDiffuse)
|
|
||||||
write_color(fmaterial, material_colSpecular)
|
|
||||||
write_color(fmaterial, material_colEmissive)
|
|
||||||
write_float(fmaterial, material_specularPower)
|
|
||||||
write_bool(fmaterial, material_useTexture)
|
|
||||||
write_uint32(fmaterial, material_texture)
|
|
||||||
|
|
||||||
|
|
||||||
# ====================== export texture
|
|
||||||
source_dir = os.path.dirname(bpy.data.filepath)
|
|
||||||
existed_texture = set()
|
|
||||||
with open(os.path.join(tempFolder, "texture.bm"), "wb") as ftexture:
|
|
||||||
for texture in textureList:
|
|
||||||
# write finfo first
|
|
||||||
write_string(finfo, texture.name)
|
|
||||||
write_uint8(finfo, info_bm_type.TEXTURE)
|
|
||||||
write_uint64(finfo, ftexture.tell())
|
|
||||||
|
|
||||||
# confirm internal
|
|
||||||
texture_filepath = io_utils.path_reference(texture.filepath, source_dir, tempTextureFolder,
|
|
||||||
'ABSOLUTE', "", None, texture.library)
|
|
||||||
filename = os.path.basename(texture_filepath)
|
|
||||||
write_string(ftexture, filename)
|
|
||||||
if (is_external_texture(filename)):
|
|
||||||
write_bool(ftexture, True)
|
|
||||||
else:
|
|
||||||
# copy internal texture, if this file is copied, do not copy it again
|
|
||||||
write_bool(ftexture, False)
|
|
||||||
if filename not in existed_texture:
|
|
||||||
shutil.copy(texture_filepath, os.path.join(tempTextureFolder, filename))
|
|
||||||
existed_texture.add(filename)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================ save zip and clean up folder
|
|
||||||
if os.path.isfile(filepath):
|
|
||||||
os.remove(filepath)
|
|
||||||
with zipfile.ZipFile(filepath, 'w', zipfile.ZIP_DEFLATED, 9) as zipObj:
|
|
||||||
for folderName, subfolders, filenames in os.walk(tempFolder):
|
|
||||||
for filename in filenames:
|
|
||||||
filePath = os.path.join(folderName, filename)
|
|
||||||
arcname=os.path.relpath(filePath, tempFolder)
|
|
||||||
zipObj.write(filePath, arcname)
|
|
||||||
tempFolderObj.cleanup()
|
|
||||||
|
|
||||||
# ======================================================================================= export / import assistant
|
|
||||||
|
|
||||||
# shared
|
|
||||||
|
|
||||||
class info_bm_type():
|
|
||||||
OBJECT = 0
|
|
||||||
MESH = 1
|
|
||||||
MATERIAL = 2
|
|
||||||
TEXTURE = 3
|
|
||||||
|
|
||||||
# import
|
|
||||||
|
|
||||||
class info_block_helper():
|
|
||||||
def __init__(self, name, offset):
|
|
||||||
self.name = name
|
|
||||||
self.offset = offset
|
|
||||||
self.blenderData = None
|
|
||||||
|
|
||||||
def createInstanceWithOption(instType, instName, instOpt, extraMesh = None):
|
|
||||||
if instType == info_bm_type.OBJECT:
|
|
||||||
target = bpy.data.objects
|
|
||||||
args = (instName, extraMesh)
|
|
||||||
elif instType == info_bm_type.MESH:
|
|
||||||
target = bpy.data.meshes
|
|
||||||
args = (instName, )
|
|
||||||
elif instType == info_bm_type.MATERIAL:
|
|
||||||
target = bpy.data.materials
|
|
||||||
args = (instName, )
|
|
||||||
|
|
||||||
if instOpt == 'RENAME':
|
|
||||||
tempInst = target.new(*args)
|
|
||||||
tempSkip = False
|
|
||||||
elif instOpt == 'CURRENT':
|
|
||||||
try:
|
|
||||||
tempInst = target[instName]
|
|
||||||
tempSkip = True
|
|
||||||
except:
|
|
||||||
tempInst = target.new(*args)
|
|
||||||
tempSkip = False
|
|
||||||
|
|
||||||
return (tempInst, tempSkip)
|
|
||||||
|
|
||||||
# NOTE: this function also used by add_elements.py
|
|
||||||
def load_component(component_id):
|
|
||||||
# get file first
|
|
||||||
compName = config.component_list[component_id]
|
|
||||||
selectedFile = os.path.join(
|
|
||||||
os.path.dirname(__file__),
|
|
||||||
'meshes',
|
|
||||||
compName + '.bin'
|
|
||||||
)
|
|
||||||
|
|
||||||
# read file. please note this sector is sync with import_bm's mesh's code. when something change, please change each other.
|
|
||||||
fmesh = open(selectedFile, 'rb')
|
|
||||||
|
|
||||||
# create real mesh, we don't need to consider name. blender will solve duplicated name
|
|
||||||
mesh = bpy.data.meshes.new('mesh_' + compName)
|
|
||||||
|
|
||||||
vList = []
|
|
||||||
vnList = []
|
|
||||||
faceList = []
|
|
||||||
# in first read, store all data into list
|
|
||||||
listCount = read_uint32(fmesh)
|
|
||||||
for i in range(listCount):
|
|
||||||
cache = read_3vector(fmesh)
|
|
||||||
# switch yz
|
|
||||||
vList.append((cache[0], cache[2], cache[1]))
|
|
||||||
listCount = read_uint32(fmesh)
|
|
||||||
for i in range(listCount):
|
|
||||||
cache = read_3vector(fmesh)
|
|
||||||
# switch yz
|
|
||||||
vnList.append((cache[0], cache[2], cache[1]))
|
|
||||||
|
|
||||||
listCount = read_uint32(fmesh)
|
|
||||||
for i in range(listCount):
|
|
||||||
faceData = read_component_face(fmesh)
|
|
||||||
|
|
||||||
# we need invert triangle sort
|
|
||||||
faceList.append((
|
|
||||||
faceData[4], faceData[5],
|
|
||||||
faceData[2], faceData[3],
|
|
||||||
faceData[0], faceData[1]
|
|
||||||
))
|
|
||||||
|
|
||||||
# then, we need add correspond count for vertices
|
|
||||||
mesh.vertices.add(len(vList))
|
|
||||||
mesh.loops.add(len(faceList)*3) # triangle face confirm
|
|
||||||
mesh.polygons.add(len(faceList))
|
|
||||||
mesh.create_normals_split()
|
|
||||||
|
|
||||||
# add vertices data
|
|
||||||
mesh.vertices.foreach_set("co", unpack_list(vList))
|
|
||||||
mesh.loops.foreach_set("vertex_index", unpack_list(flat_component_vertices_index(faceList)))
|
|
||||||
mesh.loops.foreach_set("normal", unpack_list(flat_component_vertices_normal(faceList, vnList)))
|
|
||||||
for i in range(len(faceList)):
|
|
||||||
mesh.polygons[i].loop_start = i * 3
|
|
||||||
mesh.polygons[i].loop_total = 3
|
|
||||||
|
|
||||||
mesh.polygons[i].use_smooth = True
|
|
||||||
|
|
||||||
mesh.validate(clean_customdata=False)
|
|
||||||
mesh.update(calc_edges=False, calc_edges_loose=False)
|
|
||||||
|
|
||||||
fmesh.close()
|
|
||||||
|
|
||||||
return mesh
|
|
||||||
|
|
||||||
def flat_vertices_index(faceList):
|
|
||||||
for item in faceList:
|
|
||||||
yield (item[0], )
|
|
||||||
yield (item[3], )
|
|
||||||
yield (item[6], )
|
|
||||||
|
|
||||||
def flat_vertices_normal(faceList, vnList):
|
|
||||||
for item in faceList:
|
|
||||||
yield vnList[item[2]]
|
|
||||||
yield vnList[item[5]]
|
|
||||||
yield vnList[item[8]]
|
|
||||||
|
|
||||||
def flat_vertices_uv(faceList, vtList):
|
|
||||||
for item in faceList:
|
|
||||||
yield vtList[item[1]]
|
|
||||||
yield vtList[item[4]]
|
|
||||||
yield vtList[item[7]]
|
|
||||||
|
|
||||||
def flat_component_vertices_index(faceList):
|
|
||||||
for item in faceList:
|
|
||||||
yield (item[0], )
|
|
||||||
yield (item[2], )
|
|
||||||
yield (item[4], )
|
|
||||||
|
|
||||||
def flat_component_vertices_normal(faceList, vnList):
|
|
||||||
for item in faceList:
|
|
||||||
yield vnList[item[1]]
|
|
||||||
yield vnList[item[3]]
|
|
||||||
yield vnList[item[5]]
|
|
||||||
|
|
||||||
# export
|
|
||||||
|
|
||||||
def is_component(name):
|
|
||||||
return get_component_id(name) != -1
|
|
||||||
|
|
||||||
def get_component_id(name):
|
|
||||||
for ind, comp in enumerate(config.component_list):
|
|
||||||
if name.startswith(comp):
|
|
||||||
return ind
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def is_external_texture(name):
|
|
||||||
if name in config.external_texture_list:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def mesh_triangulate(me):
|
|
||||||
bm = bmesh.new()
|
|
||||||
bm.from_mesh(me)
|
|
||||||
bmesh.ops.triangulate(bm, faces=bm.faces)
|
|
||||||
bm.to_mesh(me)
|
|
||||||
bm.free()
|
|
||||||
|
|
||||||
def try_get_custom_property(obj, field):
|
|
||||||
try:
|
|
||||||
return obj[field]
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_value_when_none(obj, newValue):
|
|
||||||
if obj is None:
|
|
||||||
return newValue
|
|
||||||
else:
|
|
||||||
return obj
|
|
||||||
|
|
||||||
# ======================================================================================= file io assistant
|
|
||||||
|
|
||||||
# import
|
|
||||||
|
|
||||||
def peek_stream(fs):
|
|
||||||
res = fs.read(1)
|
|
||||||
fs.seek(-1, os.SEEK_CUR)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def read_float(fs):
|
|
||||||
return struct.unpack("f", fs.read(4))[0]
|
|
||||||
|
|
||||||
def read_uint8(fs):
|
|
||||||
return struct.unpack("B", fs.read(1))[0]
|
|
||||||
|
|
||||||
def read_uint32(fs):
|
|
||||||
return struct.unpack("I", fs.read(4))[0]
|
|
||||||
|
|
||||||
def read_uint64(fs):
|
|
||||||
return struct.unpack("Q", fs.read(8))[0]
|
|
||||||
|
|
||||||
def read_string(fs):
|
|
||||||
count = read_uint32(fs)
|
|
||||||
return fs.read(count*4).decode("utf_32_le")
|
|
||||||
|
|
||||||
def read_bool(fs):
|
|
||||||
return read_uint8(fs) != 0
|
|
||||||
|
|
||||||
def read_worldMaterix(fs):
|
|
||||||
p = struct.unpack("ffffffffffffffff", fs.read(4*4*4))
|
|
||||||
res = mathutils.Matrix((
|
|
||||||
(p[0], p[2], p[1], p[3]),
|
|
||||||
(p[8], p[10], p[9], p[11]),
|
|
||||||
(p[4], p[6], p[5], p[7]),
|
|
||||||
(p[12], p[14], p[13], p[15])))
|
|
||||||
return res.transposed()
|
|
||||||
|
|
||||||
def read_3vector(fs):
|
|
||||||
return struct.unpack("fff", fs.read(3*4))
|
|
||||||
|
|
||||||
def read_2vector(fs):
|
|
||||||
return struct.unpack("ff", fs.read(2*4))
|
|
||||||
|
|
||||||
def read_face(fs):
|
|
||||||
return struct.unpack("IIIIIIIII", fs.read(4*9))
|
|
||||||
|
|
||||||
def read_component_face(fs):
|
|
||||||
return struct.unpack("IIIIII", fs.read(4*6))
|
|
||||||
|
|
||||||
# export
|
|
||||||
|
|
||||||
def write_string(fs,str):
|
|
||||||
count=len(str)
|
|
||||||
write_uint32(fs,count)
|
|
||||||
fs.write(str.encode("utf_32_le"))
|
|
||||||
|
|
||||||
def write_uint8(fs,num):
|
|
||||||
fs.write(struct.pack("B", num))
|
|
||||||
|
|
||||||
def write_uint32(fs,num):
|
|
||||||
fs.write(struct.pack("I", num))
|
|
||||||
|
|
||||||
def write_uint64(fs,num):
|
|
||||||
fs.write(struct.pack("Q", num))
|
|
||||||
|
|
||||||
def write_bool(fs,boolean):
|
|
||||||
if boolean:
|
|
||||||
write_uint8(fs, 1)
|
|
||||||
else:
|
|
||||||
write_uint8(fs, 0)
|
|
||||||
|
|
||||||
def write_float(fs,fl):
|
|
||||||
fs.write(struct.pack("f", fl))
|
|
||||||
|
|
||||||
def write_worldMatrix(fs, matt):
|
|
||||||
mat = matt.transposed()
|
|
||||||
fs.write(struct.pack("ffffffffffffffff",
|
|
||||||
mat[0][0],mat[0][2], mat[0][1], mat[0][3],
|
|
||||||
mat[2][0],mat[2][2], mat[2][1], mat[2][3],
|
|
||||||
mat[1][0],mat[1][2], mat[1][1], mat[1][3],
|
|
||||||
mat[3][0],mat[3][2], mat[3][1], mat[3][3]))
|
|
||||||
|
|
||||||
def write_3vector(fs, x, y ,z):
|
|
||||||
fs.write(struct.pack("fff", x, y ,z))
|
|
||||||
|
|
||||||
def write_color(fs, colors):
|
|
||||||
write_3vector(fs, colors[0], colors[1], colors[2])
|
|
||||||
|
|
||||||
def write_2vector(fs, u, v):
|
|
||||||
fs.write(struct.pack("ff", u, v))
|
|
||||||
|
|
||||||
def write_face(fs, v1, vt1, vn1, v2, vt2, vn2, v3, vt3, vn3):
|
|
||||||
fs.write(struct.pack("IIIIIIIII", v1, vt1, vn1, v2, vt2, vn2, v3, vt3, vn3))
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
|||||||
import bpy,bmesh
|
|
||||||
from . import utils
|
|
||||||
|
|
||||||
class BALLANCE_OT_no_uv_checker(bpy.types.Operator):
|
|
||||||
"""Check whether the currently selected object has UV"""
|
|
||||||
bl_idname = "ballance.no_uv_checker"
|
|
||||||
bl_label = "Check UV"
|
|
||||||
bl_options = {'UNDO'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(self, context):
|
|
||||||
return check_valid_target()
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
check_target()
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
# ====================== method
|
|
||||||
|
|
||||||
def check_valid_target():
|
|
||||||
return (len(bpy.context.selected_objects) > 0)
|
|
||||||
|
|
||||||
def check_target():
|
|
||||||
noUVObject = []
|
|
||||||
invalidObjectCount = 0
|
|
||||||
for obj in bpy.context.selected_objects:
|
|
||||||
if obj.type != 'MESH':
|
|
||||||
invalidObjectCount+=1
|
|
||||||
continue
|
|
||||||
if obj.mode != 'OBJECT':
|
|
||||||
invalidObjectCount+=1
|
|
||||||
continue
|
|
||||||
if obj.data.uv_layers.active is None:
|
|
||||||
noUVObject.append(obj.name)
|
|
||||||
|
|
||||||
if len(noUVObject) > 4:
|
|
||||||
print("Following object don't have UV:")
|
|
||||||
for item in noUVObject:
|
|
||||||
print(item)
|
|
||||||
|
|
||||||
utils.ShowMessageBox((
|
|
||||||
"All objects: {}".format(len(bpy.context.selected_objects)),
|
|
||||||
"Skipped: {}".format(invalidObjectCount),
|
|
||||||
"No UV Count: {}".format(len(noUVObject)),
|
|
||||||
"",
|
|
||||||
"Following object don't have UV: "
|
|
||||||
) + tuple(noUVObject[:4]) +
|
|
||||||
(("Too much objects don't have UV. Please open terminal to browse them." if len(noUVObject) > 4 else "") ,), "Check result", 'INFO')
|
|
@ -1,21 +0,0 @@
|
|||||||
import bpy
|
|
||||||
from bpy_extras.io_utils import unpack_list
|
|
||||||
|
|
||||||
def ShowMessageBox(message, title, icon):
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
for item in message:
|
|
||||||
layout.label(text=item, translate=False)
|
|
||||||
|
|
||||||
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
|
|
||||||
|
|
||||||
def AddSceneAndMove2Cursor(obj):
|
|
||||||
Move2Cursor(obj)
|
|
||||||
|
|
||||||
view_layer = bpy.context.view_layer
|
|
||||||
collection = view_layer.active_layer_collection.collection
|
|
||||||
collection.objects.link(obj)
|
|
||||||
|
|
||||||
def Move2Cursor(obj):
|
|
||||||
obj.location = bpy.context.scene.cursor.location
|
|
Loading…
Reference in New Issue
Block a user