diff --git a/__init__.py b/__init__.py index 6e74b29..a4eb182 100644 --- a/__init__.py +++ b/__init__.py @@ -22,7 +22,7 @@ "author": "T-Bone", "description": "Additionals For Giants I3D Exporter", "blender": (3, 0, 0), - "version": (2, 0, 5), + "version": (2, 0, 6), "location": "View3D > UI > GIANTS I3D Exporter > I3D Exporter Additionals", "warning": "", "category": "Game Engine" @@ -183,6 +183,9 @@ class I3DEA_PG_List(bpy.types.PropertyGroup): custom_text_box: bpy.props.BoolProperty(name="Custom name", description="If checked you will be able to add custom name for the track pieces", default=False) custom_text: bpy.props.StringProperty(name="Custom track name", description="Set custom name", default="trackPiece") add_empty_int: bpy.props.IntProperty(name="Number of empties add: ", description="Place your number", default=1, min=1, max=5) + piece_distance: bpy.props.FloatProperty(name="Track piece distance: ", description="Add track piece distance", default=0.2, precision=10, min=0.0001) + curve_length_disp: bpy.props.FloatProperty(name="curve_length", default=0.0, precision=10) + track_piece_amount: bpy.props.StringProperty(name="Track pieces possible along curve", description="The amount of track links that will fit along the curve") UI_meshTools: bpy.props.BoolProperty(name="Mesh-Tools", default=False) UI_track_tools: bpy.props.BoolProperty(name="UV-Tools", default=False) @@ -223,9 +226,9 @@ def draw(self, context): row.operator("i3dea.remove_doubles", text="Clean Meshes") row.operator("i3dea.mesh_name", text="Set Mesh Name") row = box.row() - row.operator("i3dea.curve_length", text="Get Curve Length") - + row.operator("i3dea.mirror_orientation", text="Set mirror orientation") if giants_i3d: + row = box.row() row.operator("i3dea.ignore", text="Add Suffix _ignore") # "Track-Tools" Box box = layout.box() @@ -250,7 +253,22 @@ def draw(self, context): box = col.box() row = box.row() row.prop(context.scene.i3dea, "add_empty_int", text="") - row.operator("i3dea.add_empty", text="Add Empty") + row.operator("i3dea.add_empty", text="Add Empty", icon='EMPTY_DATA') + box = col.box() + row = box.row() + row.operator("i3dea.curve_length", text="Get Curve Length", icon='MOD_LENGTH') + row.prop(context.scene.i3dea, "curve_length_disp", text="") + box = col.box() + row = box.row() + row.prop(context.scene.i3dea, "piece_distance", text="") + row.operator("i3dea.calculate_amount", text="Calculate Amount") + row = box.row() + row.prop(context.scene.i3dea, "track_piece_amount", text="") + box = col.box() + row = box.row() + row.operator("i3dea.track_on_curve", text="Add track along curve") + row.operator("i3dea.track_on_curve_delete", text="Delete") + # row.prop(context.scene.i3dea, "curve_length_disp", text="") # --------------------------------------------------------------- # "Skeleton-Tools" Box box = layout.box() @@ -261,7 +279,7 @@ def draw(self, context): if context.scene.i3dea.UI_skeletons: row = box.row() row.prop(context.scene.i3dea, "skeletons_dropdown", text="") - row.operator("i3dea.skeletons", text="Create") + row.operator("i3dea.skeletons", text="Create", icon='BONE_DATA') # --------------------------------------------------------------- # "Material-Tools" box box = layout.box() @@ -351,10 +369,14 @@ def execute(self, context): I3DEA_OT_addon_disable_stjerne, track_tools.I3DEA_OT_make_uvset, track_tools.I3DEA_OT_add_empty, + track_tools.I3DEA_OT_curve_length, + track_tools.I3DEA_OT_calculate_amount, + track_tools.I3DEA_OT_track_on_curve, + track_tools.I3DEA_OT_track_on_curve_delete, mesh_tools.I3DEA_OT_remove_doubles, mesh_tools.I3DEA_OT_mesh_name, - mesh_tools.I3DEA_OT_curve_length, mesh_tools.I3DEA_OT_ignore, + mesh_tools.I3DEA_OT_mirror_orientation, skeletons.I3DEA_OT_skeletons, material_tools.I3DEA_OT_mirror_material, material_tools.I3DEA_OT_remove_duplicate_material, diff --git a/tools/mesh_tools.py b/tools/mesh_tools.py index 28e35eb..2e4c1a5 100644 --- a/tools/mesh_tools.py +++ b/tools/mesh_tools.py @@ -20,7 +20,9 @@ import bmesh import bpy -from math import radians +import math + +from mathutils import Matrix class I3DEA_OT_remove_doubles(bpy.types.Operator): @@ -32,7 +34,7 @@ class I3DEA_OT_remove_doubles(bpy.types.Operator): def execute(self, context): smooth_angle = 180 merge_threshold = .0001 - smooth_radians = radians(smooth_angle) + smooth_radians = math.radians(smooth_angle) sel_obj = bpy.context.selected_objects act_obj = bpy.context.active_object @@ -83,26 +85,6 @@ def execute(self, context): return {'FINISHED'} -class I3DEA_OT_curve_length(bpy.types.Operator): - bl_idname = "i3dea.curve_length" - bl_label = "Get Curve Length" - bl_description = "Measure length of the selected curve" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return context.object is not None - - def execute(self, context): - if bpy.context.active_object.type == "CURVE": - curve_length = bpy.context.active_object.data.splines[0].calc_length(resolution=1024) - self.report({'INFO'}, bpy.utils.units.to_string(bpy.context.scene.unit_settings.system, 'LENGTH', curve_length)) - else: - self.report({'WARNING'}, "Active object is not a curve") - return {'CANCELLED'} - return {'FINISHED'} - - class I3DEA_OT_ignore(bpy.types.Operator): bl_idname = "i3dea.ignore" bl_label = "Suffix _ignore" @@ -114,3 +96,103 @@ def execute(self, context): for (i, o) in enumerate(objects): o.name = "{}_ignore".format(o.name) return {'FINISHED'} + + +class I3DEA_OT_mirror_orientation(bpy.types.Operator): + bl_idname = "i3dea.mirror_orientation" + bl_label = "Calculate Amount" + bl_description = "Calculates how many track pieces that fit from given track piece length and curve length" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + types = ['MESH', 'CAMERA', 'EMPTY'] + + selected_list = [obj for obj in bpy.context.selected_objects if obj.type in types] + camera_list = [mesh for mesh in selected_list if mesh.type == 'CAMERA'] + mesh_list = [mesh for mesh in selected_list if mesh.type == 'MESH'] + empty_list = [mesh for mesh in selected_list if mesh.type == 'EMPTY'] + + if len(bpy.context.selected_objects) == 3: + for camera, mirror, target in zip(camera_list, mesh_list, empty_list): + + mirror_parent = get_parent(mirror.name) + mirror_axis_target = bpy.ops.object.empty_add(type='ARROWS') + mirror_axis_target = bpy.context.active_object + mirror_axis_target.name = "mirror_axis_target" + target_mirror = bpy.ops.object.empty_add(type='ARROWS') + target_mirror = bpy.context.active_object + target_mirror.name = "target_mirror" + + camera_pos = camera.location + mirror_pos = mirror.location + target_pos = target.location + + v1 = vector_norm([mirror_pos[0]-camera_pos[0], mirror_pos[1]-camera_pos[1], mirror_pos[2]-camera_pos[2]]) + v2 = vector_norm([mirror_pos[0]-target_pos[0], mirror_pos[1]-target_pos[1], mirror_pos[2]-target_pos[2]]) + + v3 = [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]] + + bpy.data.objects[target_mirror.name].location = (mirror_pos[0], mirror_pos[1], mirror_pos[2]) + bpy.data.objects[mirror_axis_target.name].location = (mirror_pos[0] - v3[0], mirror_pos[1] - v3[1], mirror_pos[2] - v3[2]) + + mirror_axis_target.constraints.new('TRACK_TO') + mirror_axis_target.constraints['Track To'].track_axis = 'TRACK_NEGATIVE_Z' + mirror_axis_target.constraints['Track To'].up_axis = 'UP_Y' + mirror_axis_target.constraints['Track To'].target = target_mirror + # mirror_axis_target.rotation_euler = (0, math.radians(-180), 0) + bpy.context.view_layer.objects.active = mirror_axis_target + mirror_axis_target.select_set(True) + bpy.ops.constraint.apply(constraint="Track To", owner='OBJECT') + mirror_axis_target.select_set(False) + + matrix_world = mirror.matrix_world.copy() + mirror.parent = mirror_axis_target + mirror.matrix_parent_inverse = Matrix.Identity(4) + mirror.matrix_world = matrix_world + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = mirror + mirror.select_set(True) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) + mirror.select_set(False) + + if mirror_parent is not None: + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = mirror_parent + mirror_parent.select_set(True) + mirror.select_set(True) + bpy.ops.object.parent_set(type='OBJECT', keep_transform=True) + mirror_parent.select_set(False) + else: + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = mirror + mirror.select_set(True) + bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') + mirror.select_set(False) + + bpy.data.objects.remove(mirror_axis_target) + bpy.data.objects.remove(target_mirror) +# + else: + self.report({'ERROR'}, "You need to select 3 objects (camera, mirror, empty)") + return {'FINISHED'} + + +def get_parent(node): + parents = bpy.data.objects[node].parent + parent = None + if parents is None: + pass + else: + parent = parents + return parent + + +def vector_length(v): + return math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]) + + +def vector_norm(v): + length = vector_length(v) + if length == 0: + length = 1 + return [v[0]/length, v[1]/length, v[2]/length] diff --git a/tools/track_tools.py b/tools/track_tools.py index d9dd367..497b95f 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -17,8 +17,147 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### - import bpy +import math + + +class I3DEA_OT_add_empty(bpy.types.Operator): + bl_label = "Create empties" + bl_idname = "i3dea.add_empty" + bl_description = "Create empties between selected objects" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + selected_list = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] + for _ in range(context.scene.i3dea.add_empty_int): + for loop_obj in selected_list: + bpy.ops.object.empty_add(radius=0) + empty = bpy.context.active_object + empty.name = loop_obj.name + ".001" + if loop_obj.parent is not None: + empty.parent = loop_obj.parent + + bpy.ops.object.select_all(action='DESELECT') + for loop_obj in selected_list: + bpy.data.objects[loop_obj.name].select_set(True) + self.report({'INFO'}, "Empties added") + return {'FINISHED'} + + +def getCurveLength(length): + length = bpy.context.active_object.data.splines[0].calc_length(resolution=1024) + return length + + +class I3DEA_OT_curve_length(bpy.types.Operator): + bl_idname = "i3dea.curve_length" + bl_label = "Get Curve Length" + bl_description = "Measure length of the selected curve" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, length): + if bpy.context.view_layer.objects.active is None: + self.report({'ERROR'}, "No active object in scene") + return {'CANCELLED'} + if not bpy.context.active_object.type == "CURVE": + self.report({'ERROR'}, "The active object [" + bpy.context.active_object.name + "] is not a curve") + return {'CANCELLED'} + else: + curve_length = getCurveLength(length) + bpy.context.scene.i3dea.curve_length_disp = curve_length + return {'FINISHED'} + + +class I3DEA_OT_calculate_amount(bpy.types.Operator): + bl_idname = "i3dea.calculate_amount" + bl_label = "Calculate Amount" + bl_description = "Calculates how many track pieces that fit from given track piece length and curve length" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, length): + if bpy.context.view_layer.objects.active is None: + self.report({'ERROR'}, "No active object in scene") + return {'CANCELLED'} + # if not bpy.context.active_object.type == "CURVE": + # self.report({'ERROR'}, "The active object [" + bpy.context.active_object.name + "] is not a curve") + # return {'CANCELLED'} + if len(bpy.context.selected_objects) == 2: + obj1 = bpy.context.selected_objects[0].location + obj2 = bpy.context.selected_objects[1].location + # print(math.dist(obj1, obj2)) + bpy.context.scene.i3dea.piece_distance = math.dist(obj1, obj2) + # curve_length = getCurveLength(length) + if bpy.context.active_object.type == 'CURVE': + curve_length = getCurveLength(length) + float_val = curve_length/bpy.context.scene.i3dea.piece_distance + # print(float_val) + rounded_val = round(float_val) + string = str(float_val and rounded_val) + bpy.context.scene.i3dea.track_piece_amount = string + + return {'FINISHED'} + + +class I3DEA_OT_track_on_curve(bpy.types.Operator): + bl_idname = "i3dea.track_on_curve" + bl_label = "Add track along curve" + bl_description = "Makes a full setup to see how the track will look along the curve" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + types = ['MESH', 'CURVE'] + selected_list = [obj for obj in bpy.context.selected_objects if obj.type in types] + piece_list = [mesh for mesh in selected_list if mesh.type == 'MESH'] + curve_list = [curve for curve in selected_list if curve.type == 'CURVE'] + + if len(bpy.context.selected_objects) == 2: + for piece, curve in zip(piece_list, curve_list): + + hierarchy_name = 'track_visualization' + space = bpy.context.scene.i3dea.piece_distance + if bpy.context.scene.i3dea.track_piece_amount: + piece_num = int(bpy.context.scene.i3dea.track_piece_amount) + else: + piece_num = 30 + self.report({'INFO'}, "No amount set in track piece amount, using default instead") + + bpy.ops.mesh.primitive_plane_add() + plane = bpy.context.object + plane.name = hierarchy_name + plane.dimensions[1] = space + bpy.ops.object.transform_apply(scale=True) + plane.instance_type = 'FACES' + plane.show_instancer_for_viewport = False + plane.modifiers.new("Array", 'ARRAY') + plane.modifiers["Array"].use_relative_offset = False + plane.modifiers["Array"].use_constant_offset = True + plane.modifiers["Array"].constant_offset_displace[0] = 0 + plane.modifiers["Array"].constant_offset_displace[1] = space + plane.modifiers["Array"].count = piece_num + plane.modifiers.new("Curve", 'CURVE') + plane.modifiers["Curve"].object = curve + plane.modifiers["Curve"].deform_axis = 'NEG_Y' + + piece.parent = plane + + return {'FINISHED'} + + +class I3DEA_OT_track_on_curve_delete(bpy.types.Operator): + bl_idname = "i3dea.track_on_curve_delete" + bl_label = "Delete track visualization" + bl_description = "Deletes the track visualization" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + obj_list = [obj for obj in bpy.data.objects if obj.name.startswith("track_visualization")] + + if obj_list: + for obj in obj_list: + # for children in obj.children_recursive: + # bpy.data.objects.remove(children) + bpy.data.objects.remove(obj) + return {'FINISHED'} class I3DEA_OT_make_uvset(bpy.types.Operator): @@ -257,31 +396,3 @@ def execute(self, context): self.sixteen(context) return {'FINISHED'} return {'FINISHED'} - - -class I3DEA_OT_add_empty(bpy.types.Operator): - bl_label = "Create empties" - bl_idname = "i3dea.add_empty" - bl_description = "Create empties between selected objects" - bl_options = {'REGISTER', 'UNDO'} - - def execute(self, context): - selected_list = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] - for _ in range(context.scene.i3dea.add_empty_int): - for loop_obj in selected_list: - # print(loop_obj) - bpy.ops.object.empty_add(radius=0) - empty = bpy.context.active_object - empty.name = loop_obj.name + ".001" - - if loop_obj.parent is not None: - empty.parent = loop_obj.parent - - bpy.ops.object.select_all(action='DESELECT') - for loop_obj in selected_list: - bpy.data.objects[loop_obj.name].select_set(True) - - # attrs.select_set(True) - - self.report({'INFO'}, "Empties added") - return {'FINISHED'}