diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 959e515..041b106 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman)", - "version": (1, 7, "40fb83b"), + "version": (1, 8, "2926820"), "blender": (2, 78, 0), "location": "File > Import-Export", "wiki_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/exp/pim/exporter.py b/addon/io_scs_tools/exp/pim/exporter.py index 6b6df49..affcad5 100644 --- a/addon/io_scs_tools/exp/pim/exporter.py +++ b/addon/io_scs_tools/exp/pim/exporter.py @@ -42,6 +42,7 @@ from io_scs_tools.utils.convert import change_to_scs_uv_coordinates as _change_to_scs_uv_coordinates from io_scs_tools.utils.convert import get_scs_transformation_components as _get_scs_transformation_components from io_scs_tools.utils.convert import scs_to_blend_matrix as _scs_to_blend_matrix +from io_scs_tools.utils.convert import hookup_name_to_hookup_id as _hookup_name_to_hookup_id from io_scs_tools.utils.printout import lprint @@ -74,7 +75,7 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec print("\n************************************") print("** SCS PIM Exporter **") - print("** (c)2015 SCS Software **") + print("** (c)2017 SCS Software **") print("************************************\n") scs_globals = _get_scs_globals() @@ -102,6 +103,7 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec objects_with_default_material = {} # stores object names which has no material set missing_mappings_data = {} # indicates if material doesn't have set any uv layer for export + invalid_objects_for_tangents = set() # stores object names which tangents calculation failed because of N-gons existence bones = skin = skin_stream = None if is_skin_used: @@ -150,17 +152,34 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec # calculate transformation matrix for current object (root object transforms are always subtracted!) mesh_transf_mat = root_object.matrix_world.inverted() * mesh_obj.matrix_world + """:type: mathutils.Matrix""" - # calculate transformation matrices for this object + # calculate vertex position transformation matrix for this object pos_transf_mat = (Matrix.Scale(scs_globals.export_scale, 4) * _scs_to_blend_matrix().inverted()) - - nor_transf_mat = _scs_to_blend_matrix().inverted() + """:type: mathutils.Matrix""" + + # calculate vertex normals transformation matrix for this object + # NOTE: as normals will be read from none export prepared mesh we have to add rotation and scale from mesh transformation matrix + _, rot, scale = mesh_transf_mat.decompose() + scale_matrix_x = Matrix.Scale(scale.x, 3, Vector((1, 0, 0))).to_4x4() + scale_matrix_y = Matrix.Scale(scale.y, 3, Vector((0, 1, 0))).to_4x4() + scale_matrix_z = Matrix.Scale(scale.z, 3, Vector((0, 0, 1))).to_4x4() + nor_transf_mat = (_scs_to_blend_matrix().inverted() * + rot.to_matrix().to_4x4() * + scale_matrix_x * scale_matrix_y * scale_matrix_z) + """:type: mathutils.Matrix""" + + tangent_transf_mat = _scs_to_blend_matrix().inverted() + """:type: mathutils.Matrix""" # get initial mesh and vertex groups for it mesh = _object_utils.get_mesh(mesh_obj) - _mesh_utils.bm_prepare_mesh_for_export(mesh, mesh_transf_mat, face_flip) - mesh.calc_normals_split() + _mesh_utils.bm_prepare_mesh_for_export(mesh, mesh_transf_mat) + + # get extra mesh only for normals + mesh_for_normals = _object_utils.get_mesh(mesh_obj) + mesh_for_normals.calc_normals_split() missing_uv_layers = {} # stores missing uvs specified by materials of this object missing_vcolor = False # indicates if object is missing vertex color layer @@ -197,7 +216,10 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec if nmap_uv_layer: if nmap_uv_layer in mesh.uv_layers: - mesh.calc_tangents(uvmap=nmap_uv_layer) + try: + mesh.calc_tangents(uvmap=nmap_uv_layer) + except RuntimeError: + invalid_objects_for_tangents.add(mesh_obj.name) else: lprint("W Unable to calculate normal map tangents for object %r,\n\t " "as it's missing UV layer with name: %r, expect problems!", @@ -206,8 +228,9 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec mesh_piece = mesh_pieces[pim_mat_name] """:type: Piece""" - piece_vert_indices = [] - for loop_i in poly.loop_indices: + first_loop_pvert_i = None # storing first loop piece vertex index for usage as first vertex of each triangle of the polygon + prev_tris_last_pvert_i = None # storing last piece vertex index for usage as second vertex of each next triangle of the polygon + for i, loop_i in enumerate(poly.loop_indices): loop = mesh.loops[loop_i] """:type: bpy.types.MeshLoop""" @@ -218,7 +241,8 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec position = tuple(pos_transf_mat * mesh.vertices[vert_i].co) # 2. normal -> loop.normal -> calc_normals_split() has to be called before - normal = nor_transf_mat * loop.normal + # NOTE: we are using normals from original mesh + normal = nor_transf_mat * mesh_for_normals.loops[loop_i].normal normal = tuple(Vector(normal).normalized()) # 3. uvs -> uv_lay = mesh.uv_layers[0].data; uv_lay[loop_i].uv @@ -277,18 +301,17 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec # 5. tangent -> loop.tangent; loop.bitangent_sign -> calc_tangents() has to be called before if pim_materials[pim_mat_name].get_nmap_uv_name(): # calculate tangents only if needed - tangent = tuple(nor_transf_mat * loop.tangent) + tangent = tuple(tangent_transf_mat * loop.tangent) tangent = tuple(Vector(tangent).normalized()) tangent = (tangent[0], tangent[1], tangent[2], loop.bitangent_sign) else: tangent = None - # save internal vertex index to array to be able to construct triangle afterwards + # 6. There we go, vertex data collected! Now create internal vertex index, for triangle and skin stream construction piece_vert_index = mesh_piece.add_vertex(vert_i, position, normal, uvs, uvs_aliases, vcol, tangent) - piece_vert_indices.append(piece_vert_index) + # 7. Get skinning data for vertex and save it to skin stream if is_skin_used: - # get skinning data for vertex and save it to skin stream bone_weights = {} bone_weights_sum = 0 for v_group_entry in mesh.vertices[vert_i].groups: @@ -307,7 +330,33 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec if bone_weights_sum <= 0: missing_skinned_verts.add(vert_i) - # save to terrain points storage if present in correct vertex group + # 8. Triangle construction! + # + # We are using totally naive method for triangulation, taking polygon loops one by one + # and once we have enough vertices, we create triangle. + # + if i < 2: # on start only save first two loops piece vertex indices + + if i == 0: + first_loop_pvert_i = piece_vert_index + else: + prev_tris_last_pvert_i = piece_vert_index + + else: # each next loop requires triangle creation + + # 1. construct vertices of triangle: + tris_pvert_indices = [first_loop_pvert_i, prev_tris_last_pvert_i, piece_vert_index] + + # 2. save current piece vertex index as last triangle vertex for possible next triangles + prev_tris_last_pvert_i = piece_vert_index + + # 3. Triangle creation, at last! + if face_flip: + mesh_piece.add_triangle(tuple(tris_pvert_indices)) + else: + mesh_piece.add_triangle(tuple(tris_pvert_indices[::-1])) # yep it's weird but it simply works vice versa + + # Addition - Terrain Points: save vertex to terrain points storage, if present in correct vertex group for group in mesh.vertices[vert_i].groups: # if current object doesn't have vertex group found in mesh data, then ignore that group @@ -348,10 +397,9 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec used_terrain_points.add(variant_i, node_index, position, normal) break - mesh_piece.add_triangle(tuple(piece_vert_indices[::-1])) # invert indices because of normals flip - # free normals calculations _mesh_utils.cleanup_mesh(mesh) + _mesh_utils.cleanup_mesh(mesh_for_normals) # create part if it doesn't exists yet part_name = used_parts.ensure_part(mesh_obj) @@ -360,6 +408,12 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec mesh_pieces = mesh_pieces.values() for piece in mesh_pieces: + + # now as pieces are created we can check for it's flaws + if piece.get_vertex_count() > 65536: + lprint("E Object %r has exceeded maximum vertex count (65536), expect errors during conversion!", + (mesh_obj.name,)) + # put pieces of current mesh to global list pim_pieces.append(piece) @@ -370,7 +424,7 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec # report missing data for each object if len(missing_uv_layers) > 0: for uv_lay_name in missing_uv_layers: - lprint("W Object '%s' is missing UV layer '%s' specified by materials: %s\n", + lprint("W Object %r is missing UV layer %r specified by materials: %r", (mesh_obj.name, uv_lay_name, missing_uv_layers[uv_lay_name])) if missing_vcolor: lprint("W Object %r is missing vertex color layer with name %r! Default RGB color will be exported (0.5, 0.5, 0.5)!", @@ -391,6 +445,11 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec lprint("W Some objects don't use any material. Default material and UV mapping is used on them:\n\t %s", (list(objects_with_default_material.keys()),)) + if len(invalid_objects_for_tangents) > 0: + lprint("E N-gons present in some objects, thus normal map tangent calculation failed.\n\t " + "Visualization in game will be distorted for this objects:\n\t %s", + (list(invalid_objects_for_tangents),)) + # create locators data sections for loc_obj in model_locators: @@ -403,15 +462,14 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec name = _name_utils.tokenize_name(loc_obj.name) hookup_string = loc_obj.scs_props.locator_model_hookup - if hookup_string != "" and ":" in hookup_string: - hookup = hookup_string.split(':', 1)[1].strip() - else: - if hookup_string != "": - lprint("W The Hookup %r has no expected value!", hookup_string) - hookup = None + hookup_id = None + if hookup_string != "": + hookup_id = _hookup_name_to_hookup_id(hookup_string) + if hookup_id is None: + lprint("W Model locator %r has unexpected hookup value %r.", (loc_obj.name, loc_obj.scs_props.locator_model_hookup)) # create locator object for export - locator = Locator(len(pim_locators), name, hookup) + locator = Locator(len(pim_locators), name, hookup_id) locator.set_position(pos) locator.set_rotation(qua) locator.set_scale(sca) diff --git a/addon/io_scs_tools/exp/pim/piece.py b/addon/io_scs_tools/exp/pim/piece.py index f8aceb4..beaa008 100644 --- a/addon/io_scs_tools/exp/pim/piece.py +++ b/addon/io_scs_tools/exp/pim/piece.py @@ -59,7 +59,7 @@ def get_global_triangle_count(): return Piece.__global_triangle_count @staticmethod - def __calc_vertex_hash(index, uvs, rgba): + def __calc_vertex_hash(index, uvs, rgba, tangent): """Calculates vertex hash from original vertex index, uvs components and vertex color. :param index: original index from Blender mesh :type index: int @@ -67,6 +67,8 @@ def __calc_vertex_hash(index, uvs, rgba): :type uvs: list of (tuple | mathutils.Vector) :param rgba: rgba representation of vertex color in SCS values :type rgba: tuple | mathutils.Color + :param tangent: vertex tangent in SCS coordinates or none, if piece doesn't have tangents + :type tangent: tuple | None :return: calculated vertex hash :rtype: str """ @@ -79,6 +81,9 @@ def __calc_vertex_hash(index, uvs, rgba): vertex_hash += frmt % rgba[0] + frmt % rgba[1] + frmt % rgba[2] + frmt % rgba[3] + if tangent: + vertex_hash += frmt % tangent[0] + frmt % tangent[1] + frmt % tangent[2] + frmt % tangent[3] + return vertex_hash def __init__(self, index, material): @@ -140,16 +145,20 @@ def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tange :type normal: tuple | mathutils.Vector :param uvs: list of uvs used on vertex (each uv must be in SCS coordinates) :type uvs: list of (tuple | mathutils.Vector) + :param uvs_aliases: list of uv aliases names per uv layer + :type uvs_aliases: list[list[str]] :param rgba: rgba representation of vertex color in SCS values :type rgba: tuple | mathutils.Color + :param tangent: tuple representation of vertex tangent in SCS values or None if piece doesn't have tangents + :type tangent: tuple | None :return: vertex index inside piece streams ( use it for adding triangles ) :rtype: int """ - vertex_hash = self.__calc_vertex_hash(vert_index, uvs, rgba) + vertex_hash = self.__calc_vertex_hash(vert_index, uvs, rgba, tangent) # save vertex if the vertex with the same properties doesn't exists yet in streams - if not vertex_hash in self.__vertices_hash: + if vertex_hash not in self.__vertices_hash: stream = self.__streams[Stream.Types.POSITION] stream.add_entry(position) @@ -195,6 +204,9 @@ def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tange def get_index(self): return self.__index + def get_vertex_count(self): + return self.__streams[Stream.Types.POSITION].get_size() + def get_as_section(self): """Gets piece represented with SectionData structure class. :return: packed piece as section data diff --git a/addon/io_scs_tools/imp/pim.py b/addon/io_scs_tools/imp/pim.py index 9fd136e..d19fcce 100644 --- a/addon/io_scs_tools/imp/pim.py +++ b/addon/io_scs_tools/imp/pim.py @@ -21,6 +21,7 @@ import bpy import bmesh import array +from re import match from mathutils import Vector from bpy_extras import object_utils as bpy_object_utils from io_scs_tools.consts import Mesh as _MESH_consts @@ -380,7 +381,7 @@ def _get_locator_properties(section): elif prop[0] == "Name": loc_name = prop[1] elif prop[0] == "Hookup": - loc_hookup = _search_hookup_name(prop[1]) + loc_hookup = _convert_utils.hookup_id_to_hookup_name(prop[1]) elif prop[0] == "Position": loc_position = prop[1] elif prop[0] == "Rotation": @@ -459,24 +460,6 @@ def _get_skin_properties(section): return skin_stream_cnt, skin_streams -def _search_hookup_name(hookup_id): - """Takes a Hookup ID string and returns the whole Hookup Name - or original ID string if it doesn't exists in Hookup inventory. - - :param hookup_id: Hookup ID (as saved in PIM) - :type hookup_id: str - :return: Hookup Name (as used in Blender UI) - :rtype: str - """ - hookup_name = hookup_id - for rec in _get_scs_globals().scs_hookup_inventory: - rec_id = rec.name.split(':', 1)[1].strip() - if rec_id == hookup_id: - hookup_name = rec.name - break - return hookup_name - - def _create_5_piece( context, preview_model, @@ -1119,6 +1102,26 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa objects = [] skinned_objects = [] for obj_i in objects_data: + + # PARTS - search part first so preview model can possibly ignore objects with prescribed variant + part_name = None + for part in parts_data: + if parts_data[part][0] is not None and obj_i in parts_data[part][0]: + part_name = part.lower() + + if preview_model: + + # ignore pieces with "coll" parts + if match(r'^coll([0-9]?|_.*)$', part_name): + lprint("I Ignoring piece with part collision part name %r for preview model!", (part_name,)) + continue + + # ignore pieces with shadow and none material effects + used_mat_effect = materials_data[objects_data[obj_i][2]][1] + if ".shadowonly" in used_mat_effect or ".fakeshadow" in used_mat_effect or used_mat_effect.startswith("eut2.none"): + lprint("I Ignoring piece with material having shadow or none effect for preview model!") + continue + # print('objects_data[obj_i]: %s' % str(objects_data[obj_i])) if format_version in (5, 6): obj = _create_5_piece( @@ -1179,12 +1182,8 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa lprint('I Created Object "%s"...', (obj.name,)) # PARTS - for part in parts_data: - # print('parts_data["%s"]: %s' % (str(part), str(parts_data[part]))) - if parts_data[part][0] is not None: - if obj_i in parts_data[part][0]: - # print(' obj_i: %s - part: %s - parts_data[part][0]: %s' % (obj_i, part, parts_data[part][0])) - obj.scs_props.scs_part = part.lower() + if part_name: + obj.scs_props.scs_part = part_name else: lprint('E "%s" - Object creation FAILED!', piece_name) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel.py index ae9d75b..75201d8 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel.py @@ -67,19 +67,19 @@ def __create_fresnel_group__(): subtract_dot_n = fresnel_g.nodes.new("ShaderNodeMath") subtract_dot_n.location = (185 * 2, 100) subtract_dot_n.operation = "SUBTRACT" - subtract_dot_n.use_clamp = True + subtract_dot_n.use_clamp = False subtract_dot_n.inputs[0].default_value = 1.0 mult_subtract_n = fresnel_g.nodes.new("ShaderNodeMath") mult_subtract_n.location = (185 * 3, 50) mult_subtract_n.operation = "MULTIPLY" - mult_subtract_n.use_clamp = True + mult_subtract_n.use_clamp = False mult_subtract_n.inputs[0].default_value = 1.0 add_mult_n = fresnel_g.nodes.new("ShaderNodeMath") add_mult_n.location = (185 * 4, 0) add_mult_n.operation = "ADD" - add_mult_n.use_clamp = True + add_mult_n.use_clamp = False add_mult_n.inputs[0].default_value = 1.0 # group links diff --git a/addon/io_scs_tools/operators/material.py b/addon/io_scs_tools/operators/material.py index b09e1d4..9c28475 100644 --- a/addon/io_scs_tools/operators/material.py +++ b/addon/io_scs_tools/operators/material.py @@ -73,6 +73,7 @@ def execute(self, context): if not mat.node_tree: continue + # also check none blender tools materials just to remove possible nodes usage of our groups if mat.scs_props.active_shader_preset_name == "": nodes_to_remove = [] @@ -113,8 +114,10 @@ def execute(self, context): bpy.data.node_groups.remove(bpy.data.node_groups[ng_name], do_unlink=True) # 4. finally set preset to material again, which will update nodes and possible input interface changes + scs_roots = _object_utils.gather_scs_roots(bpy.data.objects) for mat in bpy.data.materials: + # ignore none blender tools materials if mat.scs_props.active_shader_preset_name == "": continue @@ -130,6 +133,11 @@ def execute(self, context): if preset_section: _material_utils.set_shader_data_to_material(mat, preset_section) + # sync shader types on all scs roots by updating looks on them + # without this call we might end up with outdated looks raising errors once user will switch to them + for scs_root in scs_roots: + _looks.update_look_from_material(scs_root, mat, True) + return {'FINISHED'} class SearchShaderPreset(bpy.types.Operator): diff --git a/addon/io_scs_tools/operators/object.py b/addon/io_scs_tools/operators/object.py index baee64c..f6d11e9 100755 --- a/addon/io_scs_tools/operators/object.py +++ b/addon/io_scs_tools/operators/object.py @@ -37,6 +37,7 @@ from io_scs_tools.operators.bases.view import View as _BaseViewOperator from io_scs_tools.properties.object import ObjectSCSTools as _ObjectSCSTools from io_scs_tools.utils.printout import lprint +from io_scs_tools.utils import convert as _convert_utils from io_scs_tools.utils import object as _object_utils from io_scs_tools.utils import name as _name_utils from io_scs_tools.utils import material as _material_utils @@ -1137,6 +1138,35 @@ def execute(self, context): obj.select = False return {'FINISHED'} + class SelectLocatorsWithSameHookup(bpy.types.Operator): + bl_label = "Select Locators With Same Hookup" + bl_idname = "object.select_model_locators_with_same_hookup" + bl_description = "Select all visible model locators with the same hookup value as this one." + + source_object = StringProperty( + default="" + ) + + def execute(self, context): + lprint("D " + self.bl_label + "...") + + if self.source_object in bpy.data.objects: + src_obj = bpy.data.objects[self.source_object] + else: + src_obj = context.active_object + + if src_obj.scs_props.locator_type != "Model": + self.report({"ERRROR"}, "Active object is not model locator, aborting selection!") + return {"CANCELLED"} + + for obj in context.visible_objects: + if obj.scs_props.locator_type == 'Model' and obj.scs_props.locator_model_hookup == src_obj.scs_props.locator_model_hookup: + obj.select = True + else: + obj.select = False + + return {"FINISHED"} + class ViewModelLocators(bpy.types.Operator, _BaseViewOperator): """Switch visibility of model locators.""" bl_label = "Switch Visibility of Model Locators" @@ -1152,6 +1182,46 @@ def execute(self, context): hide_state=actual_hide_state, view_only=self.view_type == self.VIEWONLY) return {'FINISHED'} + class FixHookups(bpy.types.Operator): + bl_label = "Fix SCS Hookup Names on Model Locators" + bl_idname = "object.scs_fix_model_locator_hookups" + bl_description = "Tries to convert existing pure hookup ids to valid hookup name (valid Hookup Library is required)." + + def execute(self, context): + lprint("D " + self.bl_label + "...") + + if not _path_utils.is_valid_hookup_library_rel_path(): + self.report({"ERROR"}, "Aborting hookup fix operator, 'Hookup Library' is not initialized (has invalid path)!") + return {"CANCELLED"} + + model_locators_count = 0 + fixed_model_locators_count = 0 + for obj in context.scene.objects: + + # ignore none model locator objects + if obj.type != "EMPTY" or obj.scs_props.empty_object_type != "Locator" or obj.scs_props.locator_type != "Model": + continue + + # ignore model locators with empty hookup or valid hookups + if obj.scs_props.locator_model_hookup == "" or " : " in obj.scs_props.locator_model_hookup: + continue + + model_locators_count += 1 + + hookup_id = obj.scs_props.locator_model_hookup + hookup_name = _convert_utils.hookup_id_to_hookup_name(hookup_id) + + # if id is different from returned name, it means hookup was properly found in library + if hookup_id != hookup_name: + obj.scs_props.locator_model_hookup = hookup_name + fixed_model_locators_count += 1 + + message = "Successfully fixed %s/%s model locator with invalid hookups!" % (fixed_model_locators_count, model_locators_count) + lprint("I " + message) + self.report({"INFO"}, message) + + return {'FINISHED'} + class Prefab: """ Wrapper class for better navigation in file diff --git a/addon/io_scs_tools/operators/scene.py b/addon/io_scs_tools/operators/scene.py index 7ec1637..816ae10 100644 --- a/addon/io_scs_tools/operators/scene.py +++ b/addon/io_scs_tools/operators/scene.py @@ -131,6 +131,9 @@ def __init__(self): 2. if only root is selected -> select all children 3. if root and some children are selected -> don't change selection """ + + lprint("D Gathering object which visibility should be altered for export ...") + self.layers_visibilities = _view3d_utils.switch_layers_visibility([], True) self.last_active_obj = bpy.context.active_object self.altered_objs = [] @@ -163,11 +166,15 @@ def __init__(self): bpy.data.objects[obj_name].hide = False bpy.data.objects[obj_name].select = True + lprint("D Gathering object visibility done!") + def __del__(self): """Revert altered initial selection and layers visibilites """ - # saftey check if it's not deleting last instance + lprint("D Recovering object visibility after export ...") + + # safety check if it's not deleting last instance if hasattr(self, "altered_objs"): i = 0 for obj_name in self.altered_objs: @@ -185,6 +192,8 @@ def __del__(self): _view3d_utils.switch_layers_visibility(self.layers_visibilities, False) + lprint("D Recovering object visibility done!") + def execute_export(self, context, disable_local_view): """Actually executes export of current selected objects (bpy.context.selected_objects) diff --git a/addon/io_scs_tools/operators/wm.py b/addon/io_scs_tools/operators/wm.py index 89d3c74..0a08015 100644 --- a/addon/io_scs_tools/operators/wm.py +++ b/addon/io_scs_tools/operators/wm.py @@ -95,6 +95,8 @@ class Show3DViewReport(bpy.types.Operator): """Used for saving message inside class to be able to retrieve it on open gl draw.""" __static_progress_message_l = [] """Used for saving progress message inside class to be able to retrieve it on open gl draw.""" + __static_abort = False + """Used to propage abort message to all instances, so when abort is requested all instances will kill itself.""" esc_abort = 0 """Used for staging ESC key press in operator: @@ -215,6 +217,10 @@ def __del__(self): def modal(self, context, event): + # if global abort was requested finish this modal operator instance + if Show3DViewReport.__static_abort: + return {'FINISHED'} + # if operator doesn't have controls, then it can not be cancelled by user, # so we should simply pass trough if not Show3DViewReport.has_controls(): @@ -297,6 +303,9 @@ def cancel(self, context): def invoke(self, context, event): + # propagate abort to all instances trough static variable + Show3DViewReport.__static_abort = self.abort + # if abort is requested just cancel operator if self.abort: diff --git a/addon/io_scs_tools/ui/object.py b/addon/io_scs_tools/ui/object.py index dbd8e63..982afee 100644 --- a/addon/io_scs_tools/ui/object.py +++ b/addon/io_scs_tools/ui/object.py @@ -111,6 +111,8 @@ def _draw_locator_panel(layout, context, scene, obj, enabled=True): row = col2.row(align=True) props = row.operator('scene.scs_reload_library', icon='FILE_REFRESH', text="") props.library_path_attr = "hookup_library_rel_path" + props = row.operator("object.select_model_locators_with_same_hookup", icon="ZOOM_SELECTED", text="") + props.source_object = obj.name row.prop_search(obj.scs_props, 'locator_model_hookup', _get_scs_globals(), 'scs_hookup_inventory', text="") # (MODEL) LOCATOR PREVIEW PANEL _draw_locator_preview_panel(layout_box, obj) diff --git a/addon/io_scs_tools/utils/convert.py b/addon/io_scs_tools/utils/convert.py index 425d705..f2ccb33 100644 --- a/addon/io_scs_tools/utils/convert.py +++ b/addon/io_scs_tools/utils/convert.py @@ -346,3 +346,37 @@ def str_to_int(str_value): value = None return value + + +def hookup_id_to_hookup_name(hookup_id): + """Takes a Hookup ID string and returns the whole Hookup Name + or original ID string if it doesn't exists in Hookup inventory. + + :param hookup_id: Hookup ID (as saved in PIM) + :type hookup_id: str + :return: Hookup Name (as used in Blender UI) + :rtype: str + """ + hookup_name = hookup_id + for rec in _get_scs_globals().scs_hookup_inventory: + rec_id = rec.name.split(':', 1)[1].strip() + if rec_id == hookup_id: + hookup_name = rec.name + break + + return hookup_name + + +def hookup_name_to_hookup_id(hookup_string): + """Takes hookup name from model locator hookup property and returns hookup id used for export. + + :param hookup_string: hookup name string from model locator hookup property "flare_vehicle : flare.vehicle.high_beam" + :type hookup_string: str + :return: hookup id + :rtype: str | None + """ + hookup = None + if hookup_string != "" and ":" in hookup_string: + hookup = hookup_string.split(':', 1)[1].strip() + + return hookup diff --git a/addon/io_scs_tools/utils/mesh.py b/addon/io_scs_tools/utils/mesh.py index a22def2..5c9062a 100644 --- a/addon/io_scs_tools/utils/mesh.py +++ b/addon/io_scs_tools/utils/mesh.py @@ -451,18 +451,23 @@ def bm_delete_loose(mesh): bm.free() -def bm_prepare_mesh_for_export(mesh, transformation_matrix, flip=False): +def bm_prepare_mesh_for_export(mesh, transformation_matrix, triangulate=False, flip=False): """Triangulates given mesh with bmesh module. Data are then saved back into original mesh! :param mesh: mesh data block to be triangulated and transformed :type mesh: bpy.types.Mesh :param transformation_matrix: transformation matrix which should be applied to mesh (usually root.matrix_world.inverted * obj.matrix_world) :type transformation_matrix: mathutils.Matrix + :param triangulate: flag indicating if triangulation should be executed + :type triangulate: bool :param flip: flag indicating if faces vertex order should be flipped :type flip: bool """ bm = bmesh.new() bm.from_mesh(mesh) - bmesh.ops.triangulate(bm, faces=bm.faces) + + if triangulate: + bmesh.ops.triangulate(bm, faces=bm.faces) + bmesh.ops.transform(bm, matrix=transformation_matrix, verts=bm.verts) if flip: