From c614798d01dc8136f443915ff369275257d49340 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Tue, 13 Dec 2022 23:06:48 +0100 Subject: [PATCH 01/16] Added temp UI list - Added temporary Template UI list for the curve tools just to test and see how it looks - Added required attributes for the hierarchy_empty --- __init__.py | 2 ++ tools/generate_empty_on_curves.py | 49 +++++++++++++++++++++++++++++-- ui.py | 16 ++++++++-- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/__init__.py b/__init__.py index c6fce9b..2551eca 100644 --- a/__init__.py +++ b/__init__.py @@ -241,6 +241,7 @@ def get_all_curves(self, context): distance_curve: bpy.props.FloatProperty(name="Distance", description="Add empties along curve by said distance", default=0.2, min=0.01) use_amount: bpy.props.BoolProperty(name="Use Amount", description="When this is checked, it will use amount", default=True) use_distance: bpy.props.BoolProperty(name="Use Distance", description="When this is checked, it will use distance", default=False) + use_pose2: bpy.props.BoolProperty(name="Use Pose 2", description="When this is checked, you will be able to use another set of curves.", default=False) # Properties for UI in dropdowns UI_meshTools: bpy.props.BoolProperty(name="Mesh-Tools", default=False) @@ -272,6 +273,7 @@ def get_all_curves(self, context): mesh_tools.I3DEA_OT_xml_config, mesh_tools.I3DEA_OT_fill_volume, generate_empty_on_curves.I3DEA_OT_emties_along_curves, + generate_empty_on_curves.MATERIAL_UL_matslots_example, skeletons.I3DEA_OT_skeletons, material_tools.I3DEA_OT_mirror_material, material_tools.I3DEA_OT_remove_duplicate_material, diff --git a/tools/generate_empty_on_curves.py b/tools/generate_empty_on_curves.py index 3c60175..79fc915 100644 --- a/tools/generate_empty_on_curves.py +++ b/tools/generate_empty_on_curves.py @@ -1,4 +1,40 @@ import bpy +from ..helper_functions import check_i3d_exporter_type + +giants_i3d, stjerne_i3d, dcc, I3DRemoveAttributes = check_i3d_exporter_type() + + +class MATERIAL_UL_matslots_example(bpy.types.UIList): + # The draw_item function is called for each item of the collection that is visible in the list. + # data is the RNA object containing the collection, + # item is the current drawn item of the collection, + # icon is the "computed" icon for the item (as an integer, because some objects like materials or textures + # have custom icons ID, which are not available as enum items). + # active_data is the RNA object containing the active property for the collection (i.e. integer pointing to the + # active item of the collection). + # active_propname is the name of the active property (use 'getattr(active_data, active_propname)'). + # index is index of the current item in the collection. + # flt_flag is the result of the filtering process for this item. + # Note: as index and flt_flag are optional arguments, you do not have to use/declare them here if you don't + # need them. + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + ob = data + slot = item + ma = slot.material + # draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code. + if self.layout_type in {'DEFAULT', 'COMPACT'}: + # You should always start your row layout by a label (icon + text), or a non-embossed text field, + # this will also make the row easily selectable in the list! The later also enables ctrl-click rename. + # We use icon_value of label, as our given icon is an integer value, not an enum ID. + # Note "data" names should never be translated! + if ma: + layout.prop(ma, "name", text="", emboss=False, icon_value=icon) + else: + layout.label(text="", translate=False, icon_value=icon) + # 'GRID' layout type should be as compact as possible (typically a single icon!). + elif self.layout_type == 'GRID': + layout.alignment = 'CENTER' + layout.label(text="", icon_value=icon) class I3DEA_OT_emties_along_curves(bpy.types.Operator): @@ -30,13 +66,22 @@ def create_empties_on_curve(selected_curves, hierarchy_name, num_empties=10): # Create empty object "pose1" hierarchy_empty = create_empty(name=hierarchy_name) + if giants_i3d: + dcc.I3DSetAttrString(hierarchy_empty, 'I3D_objectDataFilePath', hierarchy_name + ".dds") + dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataHierarchicalSetup', True) + dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataHideFirstAndLastObject', True) + dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataExportPosition', True) + dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataExportOrientation', True) + dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataExportScale', True) + # Create empty object "pose1" pose1 = create_empty(name="pose1") pose1.parent = hierarchy_empty # Create empty object "pose2" - pose2 = create_empty(name="pose2") - pose2.parent = hierarchy_empty + if bpy.context.scene.i3dea.use_pose2: + pose2 = create_empty(name="pose2") + pose2.parent = hierarchy_empty num1, num2 = -1, -1 for obj in selected_curves: diff --git a/ui.py b/ui.py index b5513cf..a12560f 100644 --- a/ui.py +++ b/ui.py @@ -129,10 +129,22 @@ def draw(self, context): # expanded view if context.scene.i3dea.UI_curve_tools: row = box.row() - # if not context.scene.i3dea.use_distance: + obj = context.object + + # template_list now takes two new args. + # The first one is the identifier of the registered UIList to use (if you want only the default list, + # with no custom draw code, use "UI_UL_list"). + row.template_list("MATERIAL_UL_matslots_example", "", obj, "material_slots", obj, "active_material_index") + row = box.row() + # The second one can usually be left as an empty string. + # It's an additional ID used to distinguish lists in case you + # use the same list several times in a given area. + row.template_list("MATERIAL_UL_matslots_example", "compact", obj, "material_slots", obj, "active_material_index", type='COMPACT') + row = box.row() + row.prop(context.scene.i3dea, "use_pose2") + row = box.row() row.enabled = context.scene.i3dea.use_distance is False row.prop(context.scene.i3dea, "use_amount") - # not context.scene.i3dea.use_amount: row2 = row.row() row2.enabled = context.scene.i3dea.use_amount is False row2.prop(context.scene.i3dea, "use_distance") From 59bb1616fea2df8cd6e81995f806ad87d63837a6 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Fri, 16 Dec 2022 00:22:49 +0100 Subject: [PATCH 02/16] Added working UIlists - Added working UIlists for Pose1 and Pose 2 in curve tools - Removed commented code at bottom of ui.py - Changed function name in orientation_tools.py - Added Giants attributes for array parent --- __init__.py | 23 ++++- tools/generate_empty_on_curves.py | 160 +++++++++++++++++++++--------- tools/orientation_tools.py | 4 +- tools/properties_converter.py | 4 +- ui.py | 50 +++------- 5 files changed, 155 insertions(+), 86 deletions(-) diff --git a/__init__.py b/__init__.py index 2551eca..571b28d 100644 --- a/__init__.py +++ b/__init__.py @@ -58,6 +58,13 @@ import bpy +class I3DEA_custom_ObjectProps(bpy.types.PropertyGroup): + curve_ref: bpy.props.PointerProperty( + name="Object", + type=bpy.types.Object + ) + + class I3DEA_PG_List(bpy.types.PropertyGroup): # Dropdown for UV size size_dropdown: bpy.props.EnumProperty( @@ -243,6 +250,16 @@ def get_all_curves(self, context): use_distance: bpy.props.BoolProperty(name="Use Distance", description="When this is checked, it will use distance", default=False) use_pose2: bpy.props.BoolProperty(name="Use Pose 2", description="When this is checked, you will be able to use another set of curves.", default=False) + active_obj_index: bpy.props.IntProperty() + object_collection: bpy.props.CollectionProperty( + type=I3DEA_custom_ObjectProps + ) + + active_obj_index2: bpy.props.IntProperty() + object_collection2: bpy.props.CollectionProperty( + type=I3DEA_custom_ObjectProps + ) + # Properties for UI in dropdowns UI_meshTools: bpy.props.BoolProperty(name="Mesh-Tools", default=False) UI_user_attributes: bpy.props.BoolProperty(name="User Attributes", default=False) @@ -258,6 +275,7 @@ def get_all_curves(self, context): classes = [ + I3DEA_custom_ObjectProps, I3DEA_PG_List, ui.I3DEA_PT_panel, track_tools.I3DEA_OT_make_uvset, @@ -272,8 +290,9 @@ def get_all_curves(self, context): mesh_tools.I3DEA_OT_mirror_orientation, mesh_tools.I3DEA_OT_xml_config, mesh_tools.I3DEA_OT_fill_volume, - generate_empty_on_curves.I3DEA_OT_emties_along_curves, - generate_empty_on_curves.MATERIAL_UL_matslots_example, + generate_empty_on_curves.I3DEA_OT_empties_along_curves, + generate_empty_on_curves.I3DEA_UL_selected_curves, + generate_empty_on_curves.I3DEA_UL_selected_curves2, skeletons.I3DEA_OT_skeletons, material_tools.I3DEA_OT_mirror_material, material_tools.I3DEA_OT_remove_duplicate_material, diff --git a/tools/generate_empty_on_curves.py b/tools/generate_empty_on_curves.py index 79fc915..87db7fc 100644 --- a/tools/generate_empty_on_curves.py +++ b/tools/generate_empty_on_curves.py @@ -4,52 +4,122 @@ giants_i3d, stjerne_i3d, dcc, I3DRemoveAttributes = check_i3d_exporter_type() -class MATERIAL_UL_matslots_example(bpy.types.UIList): - # The draw_item function is called for each item of the collection that is visible in the list. - # data is the RNA object containing the collection, - # item is the current drawn item of the collection, - # icon is the "computed" icon for the item (as an integer, because some objects like materials or textures - # have custom icons ID, which are not available as enum items). - # active_data is the RNA object containing the active property for the collection (i.e. integer pointing to the - # active item of the collection). - # active_propname is the name of the active property (use 'getattr(active_data, active_propname)'). - # index is index of the current item in the collection. - # flt_flag is the result of the filtering process for this item. - # Note: as index and flt_flag are optional arguments, you do not have to use/declare them here if you don't - # need them. - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - ob = data - slot = item - ma = slot.material - # draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code. - if self.layout_type in {'DEFAULT', 'COMPACT'}: - # You should always start your row layout by a label (icon + text), or a non-embossed text field, - # this will also make the row easily selectable in the list! The later also enables ctrl-click rename. - # We use icon_value of label, as our given icon is an integer value, not an enum ID. - # Note "data" names should never be translated! - if ma: - layout.prop(ma, "name", text="", emboss=False, icon_value=icon) - else: - layout.label(text="", translate=False, icon_value=icon) - # 'GRID' layout type should be as compact as possible (typically a single icon!). - elif self.layout_type == 'GRID': - layout.alignment = 'CENTER' - layout.label(text="", icon_value=icon) +class I3DEA_UL_selected_curves(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + pg = data + curve_icon = 'OUTLINER_OB_CURVE' + layout.prop(item, "curve_ref", text="", emboss=False, translate=False, icon=curve_icon) + +class I3DEA_UL_selected_curves2(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + pg = data + curve_icon = 'OUTLINER_OB_CURVE' + layout.prop(item, "curve_ref", text="", emboss=False, translate=False, icon=curve_icon) -class I3DEA_OT_emties_along_curves(bpy.types.Operator): + +class I3DEA_OT_empties_along_curves(bpy.types.Operator): bl_label = "Create empties along curves" bl_idname = "i3dea.add_empties_curves" bl_description = "Create empties evenly spread along selected curves" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'UNDO'} + state: bpy.props.IntProperty() + + # Store the selected objects (curves) + selected_curves = [[], []] + + def load_curves(self, context, pose2=False): + for curve in context.selected_objects: + if curve.type == 'CURVE': + if not pose2: + if curve.name not in self.selected_curves[0]: + self.selected_curves[0].append(curve.name) + elif pose2: + if curve.name not in self.selected_curves[1]: + self.selected_curves[1].append(curve.name) def execute(self, context): - # Store the selected objects (curves) - selected_objects = [obj for obj in bpy.context.selected_objects if obj.type == 'CURVE'] + if self.state == 1: + self.load_curves(context) + + object_names = [item.name for item in context.scene.i3dea.object_collection] + for curve_name in self.selected_curves[0]: + curve = bpy.data.objects[curve_name] + if curve.type == 'CURVE' and curve.name not in object_names: + my_curve = context.scene.i3dea.object_collection.add() + my_curve.name = curve.name + my_curve.curve_ref = curve + + context.scene.i3dea.active_obj_index = len(context.scene.i3dea.object_collection) - 1 + + print(self.selected_curves[0]) + + elif self.state == 2: + context.scene.i3dea.object_collection.clear() + context.scene.i3dea.active_obj_index = -1 + + self.selected_curves[0].clear() + + elif self.state == 3: + if len(context.scene.i3dea.object_collection) > 0: + print(f"First print \n{self.selected_curves[0]}\n-------------------") + ob_list = context.scene.i3dea.object_collection + active = context.scene.i3dea.active_obj_index + active_obj = context.scene.i3dea.object_collection[context.scene.i3dea.active_obj_index].curve_ref.name + + self.selected_curves[0].remove(active_obj) + + ob_list.remove(active) + context.scene.i3dea.active_obj_index = min(max(0, active - 1), len(ob_list) - 1) + + # self.selected_curves[0].remove(context.scene.i3dea.obj_ref.name) + print(f"After print \n{self.selected_curves[0]}\n-------------------") + else: + pass + + if self.state == 4: + self.load_curves(context, pose2=True) + + object_names2 = [item.name for item in context.scene.i3dea.object_collection2] + for curve_name in self.selected_curves[1]: + curve = bpy.data.objects[curve_name] + if curve.type == 'CURVE' and curve.name not in object_names2: + my_curve = context.scene.i3dea.object_collection2.add() + my_curve.name = curve.name + my_curve.curve_ref = curve + + context.scene.i3dea.active_obj_index2 = len(context.scene.i3dea.object_collection2) - 1 + + print(self.selected_curves[1]) + + elif self.state == 5: + context.scene.i3dea.object_collection2.clear() + context.scene.i3dea.active_obj_index2 = -1 + + self.selected_curves[1].clear() + + elif self.state == 6: + if len(context.scene.i3dea.object_collection2) > 0: + print(f"First print \n{self.selected_curves[1]}\n-------------------") + ob_list = context.scene.i3dea.object_collection2 + active = context.scene.i3dea.active_obj_index2 + active_obj = context.scene.i3dea.object_collection2[context.scene.i3dea.active_obj_index2].curve_ref.name + + self.selected_curves[1].remove(active_obj) + + ob_list.remove(active) + context.scene.i3dea.active_obj_index2 = min(max(0, active - 1), len(ob_list) - 1) + + # self.selected_curves[0].remove(context.scene.i3dea.obj_ref.name) + print(f"After print \n{self.selected_curves[1]}\n-------------------") + else: + pass + + elif self.state == 7: + # Create empty objects along selected curves + create_empties_on_curve(self.selected_curves, context.scene.i3dea.curve_array_name, num_empties=context.scene.i3dea.amount_curve) + self.report({'INFO'}, "Generated empties a long selected curves") - # Create empty objects along selected curves - create_empties_on_curve(selected_objects, context.scene.i3dea.curve_array_name, num_empties=context.scene.i3dea.amount_curve) - self.report({'INFO'}, "Generated empties a long selected curves") return {'FINISHED'} @@ -67,12 +137,12 @@ def create_empties_on_curve(selected_curves, hierarchy_name, num_empties=10): hierarchy_empty = create_empty(name=hierarchy_name) if giants_i3d: - dcc.I3DSetAttrString(hierarchy_empty, 'I3D_objectDataFilePath', hierarchy_name + ".dds") - dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataHierarchicalSetup', True) - dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataHideFirstAndLastObject', True) - dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataExportPosition', True) - dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataExportOrientation', True) - dcc.I3DSetAttrBool(hierarchy_empty, 'I3D_objectDataExportScale', True) + dcc.I3DSetAttrString(hierarchy_empty.name, 'I3D_objectDataFilePath', hierarchy_name + ".dds") + dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataHierarchicalSetup', True) + dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataHideFirstAndLastObject', True) + dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataExportPosition', True) + dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataExportOrientation', True) + dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataExportScale', True) # Create empty object "pose1" pose1 = create_empty(name="pose1") @@ -84,7 +154,7 @@ def create_empties_on_curve(selected_curves, hierarchy_name, num_empties=10): pose2.parent = hierarchy_empty num1, num2 = -1, -1 - for obj in selected_curves: + for obj in selected_curves[0]: # Create Y empties inside pose1 & pose2 # Set empty name based on curve name if obj.name.startswith("pose1"): diff --git a/tools/orientation_tools.py b/tools/orientation_tools.py index 4221e75..43a899a 100644 --- a/tools/orientation_tools.py +++ b/tools/orientation_tools.py @@ -30,11 +30,11 @@ class I3DEA_OT_copy_orientation(bpy.types.Operator): state: bpy.props.IntProperty() def execute(self, context): - def bakeTransformMatrix(matrix): + def bake_transform_matrix(matrix): return mathutils.Matrix.Rotation(math.radians(-90), 4, "X") @ matrix @ mathutils.Matrix.Rotation(math.radians(90), 4, "X") obj = bpy.context.object - m = bakeTransformMatrix(obj.matrix_local) + m = bake_transform_matrix(obj.matrix_local) orientation = "0 0 0" if 1 == self.state: diff --git a/tools/properties_converter.py b/tools/properties_converter.py index dc39cf6..32c6cff 100644 --- a/tools/properties_converter.py +++ b/tools/properties_converter.py @@ -21,8 +21,8 @@ class I3DEA_OT_properties_converter(bpy.types.Operator): bl_idname = "i3dea.properties_converter" - bl_label = "Convert I3D properties" - bl_description = "Converts I3D properties from Stjerne exporter to Giants exporter" + bl_label = "Convert I3D properties.py" + bl_description = "Converts I3D properties.py from Stjerne exporter to Giants exporter" bl_options = {'REGISTER', 'UNDO'} properties = [('Giants', 'Stjerne')] diff --git a/ui.py b/ui.py index a12560f..eadb092 100644 --- a/ui.py +++ b/ui.py @@ -1,6 +1,7 @@ import bpy from .helper_functions import check_i3d_exporter_type +from .tools.generate_empty_on_curves import I3DEA_OT_empties_along_curves class I3DEA_PT_panel(bpy.types.Panel): @@ -128,20 +129,23 @@ def draw(self, context): row.prop(context.scene.i3dea, "UI_curve_tools", text="Curve-Tools", icon='TRIA_DOWN' if context.scene.i3dea.UI_curve_tools else 'TRIA_RIGHT', icon_only=False, emboss=False) # expanded view if context.scene.i3dea.UI_curve_tools: + scene = context.scene + pg = scene.i3dea row = box.row() - obj = context.object - - # template_list now takes two new args. - # The first one is the identifier of the registered UIList to use (if you want only the default list, - # with no custom draw code, use "UI_UL_list"). - row.template_list("MATERIAL_UL_matslots_example", "", obj, "material_slots", obj, "active_material_index") + row.template_list("I3DEA_UL_selected_curves", "", pg, "object_collection", pg, "active_obj_index") row = box.row() - # The second one can usually be left as an empty string. - # It's an additional ID used to distinguish lists in case you - # use the same list several times in a given area. - row.template_list("MATERIAL_UL_matslots_example", "compact", obj, "material_slots", obj, "active_material_index", type='COMPACT') + row.operator("i3dea.add_empties_curves", text="Load Selected").state = 1 + row.operator("i3dea.add_empties_curves", text="Remove All").state = 2 + row.operator("i3dea.add_empties_curves", text="Remove Active").state = 3 row = box.row() row.prop(context.scene.i3dea, "use_pose2") + if context.scene.i3dea.use_pose2: + row = box.row() + row.template_list("I3DEA_UL_selected_curves2", "", pg, "object_collection2", pg, "active_obj_index2") + row = box.row() + row.operator("i3dea.add_empties_curves", text="Load Selected").state = 4 + row.operator("i3dea.add_empties_curves", text="Remove All").state = 5 + row.operator("i3dea.add_empties_curves", text="Remove Active").state = 6 row = box.row() row.enabled = context.scene.i3dea.use_distance is False row.prop(context.scene.i3dea, "use_amount") @@ -154,7 +158,7 @@ def draw(self, context): row = box.row() row.prop(context.scene.i3dea, "curve_array_name", text="Array Name") row = box.row() - row.operator("i3dea.add_empties_curves", text="Create", icon='OUTLINER_DATA_CURVES') + row.operator("i3dea.add_empties_curves", text="Create", icon='OUTLINER_DATA_CURVES').state = 7 # --------------------------------------------------------------- # "Skeleton-Tools" Box box = layout.box() @@ -227,27 +231,3 @@ def draw(self, context): row = box.row() row.operator("i3dea.assets", text="Import Asset") # ----------------------------------------- - - -"""def get_attributes(obj_name): - m_attributes = [] - m_types = ["boolean", "string", "scriptCallback", "float"] - for key in obj_name.keys(): - if 0 == key.find("userAttribute_"): - try: - m_list = key.split("_", 2) - m_type = m_list[1] - m_name = m_list[2] - m_val = obj_name[key] - if m_type in m_types: - if "boolean" == m_type: - if m_val: - m_val = "true" - else: - m_val = "false" - m_val = "{}".format(m_val) - m_item = {"name": m_name, "type": m_type, "value": m_val} - m_attributes.append(m_item) - except: - pass - return m_attributes""" From 533dac3e88889a6e19c871c8ea12094f0626f1ea Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Fri, 16 Dec 2022 19:33:49 +0100 Subject: [PATCH 03/16] Generate_empty_on_currve + ui - Fixed so generate_empty_on_curve works as intended (pose 1 & 2 option) - Removed the use_distance from ui, as this currently doesn't work correctly --- tools/generate_empty_on_curves.py | 97 ++++++++++++++++++------------- ui.py | 6 +- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/tools/generate_empty_on_curves.py b/tools/generate_empty_on_curves.py index 87db7fc..6e08d1c 100644 --- a/tools/generate_empty_on_curves.py +++ b/tools/generate_empty_on_curves.py @@ -52,8 +52,6 @@ def execute(self, context): context.scene.i3dea.active_obj_index = len(context.scene.i3dea.object_collection) - 1 - print(self.selected_curves[0]) - elif self.state == 2: context.scene.i3dea.object_collection.clear() context.scene.i3dea.active_obj_index = -1 @@ -62,7 +60,6 @@ def execute(self, context): elif self.state == 3: if len(context.scene.i3dea.object_collection) > 0: - print(f"First print \n{self.selected_curves[0]}\n-------------------") ob_list = context.scene.i3dea.object_collection active = context.scene.i3dea.active_obj_index active_obj = context.scene.i3dea.object_collection[context.scene.i3dea.active_obj_index].curve_ref.name @@ -71,9 +68,6 @@ def execute(self, context): ob_list.remove(active) context.scene.i3dea.active_obj_index = min(max(0, active - 1), len(ob_list) - 1) - - # self.selected_curves[0].remove(context.scene.i3dea.obj_ref.name) - print(f"After print \n{self.selected_curves[0]}\n-------------------") else: pass @@ -100,7 +94,6 @@ def execute(self, context): elif self.state == 6: if len(context.scene.i3dea.object_collection2) > 0: - print(f"First print \n{self.selected_curves[1]}\n-------------------") ob_list = context.scene.i3dea.object_collection2 active = context.scene.i3dea.active_obj_index2 active_obj = context.scene.i3dea.object_collection2[context.scene.i3dea.active_obj_index2].curve_ref.name @@ -109,9 +102,6 @@ def execute(self, context): ob_list.remove(active) context.scene.i3dea.active_obj_index2 = min(max(0, active - 1), len(ob_list) - 1) - - # self.selected_curves[0].remove(context.scene.i3dea.obj_ref.name) - print(f"After print \n{self.selected_curves[1]}\n-------------------") else: pass @@ -149,43 +139,33 @@ def create_empties_on_curve(selected_curves, hierarchy_name, num_empties=10): pose1.parent = hierarchy_empty # Create empty object "pose2" - if bpy.context.scene.i3dea.use_pose2: + pose2 = None + if bpy.context.scene.i3dea.use_pose2 and len(selected_curves[1]) > 1: pose2 = create_empty(name="pose2") pose2.parent = hierarchy_empty num1, num2 = -1, -1 - for obj in selected_curves[0]: - # Create Y empties inside pose1 & pose2 + for curve1 in selected_curves[0]: + # Create Y empties inside pose1 # Set empty name based on curve name - if obj.name.startswith("pose1"): - num1 += 1 - empty_name = f"pose1_Y_{num1:03d}" - elif obj.name.startswith("pose2"): - num2 += 1 - empty_name = f"pose2_Y_{num2:03d}" - else: - empty_name = "empty" + num1 += 1 + empty_name = f"pose1_Y_{num1:03d}" + y_empty = create_empty(name=empty_name) - # Set object parent to the pose1 and pose2 empties - if obj.name.startswith("pose1"): - y_empty.parent = pose1 - elif obj.name.startswith("pose2"): - y_empty.parent = pose2 + # Set object parent to the pose1 empties + y_empty.parent = pose1 + + curve1_length = bpy.data.objects[curve1].data.splines[0].calc_length() for i in range(num_empties): # Set empty name based on curve name - if obj.name.startswith("pose1"): - empty_name = f"pose1_X_{i:03d}" - elif obj.name.startswith("pose2"): - empty_name = f"pose2_X_{i:03d}" - else: - empty_name = "empty" - x_empty = create_empty(empty_type='ARROWS', name=empty_name) + empty_x_name = f"pose1_X_{i:03d}" + x_empty = create_empty(empty_type='ARROWS', name=empty_x_name) # Set object constraint to follow curve x_empty.constraints.new('FOLLOW_PATH') - x_empty.constraints['Follow Path'].target = obj + x_empty.constraints['Follow Path'].target = bpy.data.objects[curve1] x_empty.constraints['Follow Path'].use_curve_radius = False x_empty.constraints['Follow Path'].use_fixed_location = True x_empty.constraints['Follow Path'].use_curve_follow = True @@ -193,16 +173,55 @@ def create_empties_on_curve(selected_curves, hierarchy_name, num_empties=10): x_empty.constraints['Follow Path'].up_axis = 'UP_Z' # Set object parent based on curve name - if obj.name.startswith("pose1"): - x_empty.parent = y_empty - elif obj.name.startswith("pose2"): - x_empty.parent = y_empty + x_empty.parent = y_empty # Set offset factor for empty along curve if bpy.context.scene.i3dea.use_amount: x_empty.constraints['Follow Path'].offset_factor = i / (num_empties - 1) elif bpy.context.scene.i3dea.use_distance: - x_empty.constraints['Follow Path'].offset_factor = i / (bpy.context.scene.i3dea.distance_curve - 1) + amount1 = bpy.context.scene.i3dea.distance_curve / curve1_length + x_empty.constraints['Follow Path'].offset_factor = i / (amount1 - 1) # Apply the constraint bpy.ops.constraint.apply({'constraint': x_empty.constraints["Follow Path"]}, constraint='Follow Path') + + if bpy.context.scene.i3dea.use_pose2: + for curve2 in selected_curves[1]: + # Create Y empties inside pose2 + # Set empty name based on curve name + num2 += 1 + empty2_name = f"pose2_Y_{num2:03d}" + + y_empty2 = create_empty(name=empty2_name) + + # Set object parent to pose2 empties + y_empty2.parent = pose2 + + curve2_length = bpy.data.objects[curve2].data.splines[0].calc_length() + amount2 = bpy.context.scene.i3dea.distance_curve / curve2_length + + for i in range(num_empties): + # Set empty name based on curve name + empty2_x_name = f"pose2_X_{i:03d}" + x_empty2 = create_empty(empty_type='ARROWS', name=empty2_x_name) + + # Set object constraint to follow curve + x_empty2.constraints.new('FOLLOW_PATH') + x_empty2.constraints['Follow Path'].target = bpy.data.objects[curve2] + x_empty2.constraints['Follow Path'].use_curve_radius = False + x_empty2.constraints['Follow Path'].use_fixed_location = True + x_empty2.constraints['Follow Path'].use_curve_follow = True + x_empty2.constraints['Follow Path'].forward_axis = 'FORWARD_Y' + x_empty2.constraints['Follow Path'].up_axis = 'UP_Z' + + # Set object parent based on curve name + x_empty2.parent = y_empty2 + + # Set offset factor for empty along curve + if bpy.context.scene.i3dea.use_amount: + x_empty2.constraints['Follow Path'].offset_factor = i / (num_empties - 1) + elif bpy.context.scene.i3dea.use_distance: + x_empty2.constraints['Follow Path'].offset_factor = amount2 + + # Apply the constraint + bpy.ops.constraint.apply({'constraint': x_empty2.constraints["Follow Path"]}, constraint='Follow Path') diff --git a/ui.py b/ui.py index eadb092..3b2fc19 100644 --- a/ui.py +++ b/ui.py @@ -149,9 +149,9 @@ def draw(self, context): row = box.row() row.enabled = context.scene.i3dea.use_distance is False row.prop(context.scene.i3dea, "use_amount") - row2 = row.row() - row2.enabled = context.scene.i3dea.use_amount is False - row2.prop(context.scene.i3dea, "use_distance") + # row2 = row.row() + # row2.enabled = context.scene.i3dea.use_amount is False + # row2.prop(context.scene.i3dea, "use_distance") row = box.row() row.prop(context.scene.i3dea, "amount_curve") row.prop(context.scene.i3dea, "distance_curve") From cf0c37bad3375d23f07caec474ead3df84b00bae Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Mon, 2 Jan 2023 00:29:10 +0100 Subject: [PATCH 04/16] Track visualization fixes - Fixed a better ui for selecting different track types to be visualized (caterpillar, rubber, bogie) - Fixed so get_curve_length function will apply scale if scale is not 1 1 1 - Changed couple class names --- __init__.py | 10 ++++++++-- tools/generate_empty_on_curves.py | 2 -- tools/track_tools.py | 31 +++++++++++++++++++++---------- ui.py | 6 +++--- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/__init__.py b/__init__.py index 571b28d..50458f1 100644 --- a/__init__.py +++ b/__init__.py @@ -229,6 +229,12 @@ def get_all_curves(self, context): advanced_mode: bpy.props.BoolProperty(name="Advanced Mode", description="Add more options for UVset2 creation and Track setup", default=False) all_curves: bpy.props.EnumProperty(items=get_all_curves, name="Select A Curve") add_empties: bpy.props.BoolProperty(name="Add Empties", description="If you check this it will add the amount of empties between each track link that's written bellow.", default=False) + track_type_method: bpy.props.EnumProperty(name="Track Method", + items=[('CATERPILLAR', 'Caterpillar', ""), + ('RUBBER', 'Rubber', "",), + ('BOGIE', 'Bogie', "")], + description="Track visualization method, caterpillar, rubber or bogie", + default='CATERPILLAR') # User Attribute properties user_attribute_name: bpy.props.StringProperty(name="Name", description="Name of the User Attribute.", default="") @@ -282,8 +288,8 @@ def get_all_curves(self, context): 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, + track_tools.I3DEA_OT_visualization, + track_tools.I3DEA_OT_visualization_del, mesh_tools.I3DEA_OT_remove_doubles, mesh_tools.I3DEA_OT_mesh_name, mesh_tools.I3DEA_OT_ignore, diff --git a/tools/generate_empty_on_curves.py b/tools/generate_empty_on_curves.py index 6e08d1c..db36f72 100644 --- a/tools/generate_empty_on_curves.py +++ b/tools/generate_empty_on_curves.py @@ -6,14 +6,12 @@ class I3DEA_UL_selected_curves(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - pg = data curve_icon = 'OUTLINER_OB_CURVE' layout.prop(item, "curve_ref", text="", emboss=False, translate=False, icon=curve_icon) class I3DEA_UL_selected_curves2(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - pg = data curve_icon = 'OUTLINER_OB_CURVE' layout.prop(item, "curve_ref", text="", emboss=False, translate=False, icon=curve_icon) diff --git a/tools/track_tools.py b/tools/track_tools.py index 3d79955..48b72cf 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -29,6 +29,9 @@ def get_curve_length(curve_obj): + if curve_obj.scale != Vector((1, 1, 1)): + print(f"{curve_obj.name} scale is not 1 1 1, scale will be applied.") + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) length = curve_obj.data.splines[0].calc_length(resolution=1024) return length @@ -74,7 +77,7 @@ def execute(self, context): self.report({'ERROR'}, "No active object in scene") return {'CANCELLED'} if not context.object.type == "CURVE": - self.report({'ERROR'}, "The active object [" + context.active_object.name + "] is not a curve") + self.report({'ERROR'}, f"The active object ({context.active_object.name}) is not a curve") return {'CANCELLED'} else: curve_length = get_curve_length(context.object) @@ -105,8 +108,8 @@ def execute(self, context): return {'FINISHED'} -class I3DEA_OT_track_on_curve(bpy.types.Operator): - bl_idname = "i3dea.track_on_curve" +class I3DEA_OT_visualization(bpy.types.Operator): + bl_idname = "i3dea.visualization" bl_label = "Add track to curve" bl_description = "Makes a full setup to see how the track will look along the curve" bl_options = {'REGISTER', 'UNDO'} @@ -126,17 +129,19 @@ def execute(self, context): if curve.type == 'CURVE': curve_list.append(curve) break + else: + self.report({'ERROR'}, "No curve in scene") for piece, curve in zip(piece_list, curve_list): hierarchy_name = 'track_visualization' space = bpy.context.scene.i3dea.piece_distance curve_length = get_curve_length(curve) - if not bpy.context.scene.i3dea.rubber_track: + if bpy.context.scene.i3dea.track_type_method == 'CATERPILLAR': if bpy.context.scene.i3dea.track_piece_amount > 1: piece_num = int(bpy.context.scene.i3dea.track_piece_amount) else: piece_num = 25 - self.report({'INFO'}, "No amount set in track piece amount, using default amount instead ({})".format(piece_num)) + self.report({'INFO'}, f"No amount set in track piece amount, using default amount instead ({piece_num})") bpy.ops.mesh.primitive_plane_add() plane = bpy.context.object @@ -159,10 +164,12 @@ def execute(self, context): plane.keyframe_insert("location", frame=1) plane.location[1] = curve_length plane.keyframe_insert("location", frame=250) - piece.parent = plane - piece.hide_set(True) + new = bpy.data.objects.new(piece.name+"_visual", bpy.data.objects[piece.name].data) + context.collection.objects.link(new) + new.parent = plane + new.hide_set(True) - if bpy.context.scene.i3dea.rubber_track: + elif bpy.context.scene.i3dea.track_type_method == 'RUBBER': obj = context.object # bpy.ops.object.duplicate() duplicate = bpy.data.objects.new(hierarchy_name, bpy.data.objects[obj.name].data) @@ -175,6 +182,9 @@ def execute(self, context): duplicate.keyframe_insert("location", frame=250) context.collection.objects.link(duplicate) + elif bpy.context.scene.i3dea.track_type_method == 'BOGIE': + self.report({'WARNING'}, "Bogie is not supported yet") + def stop_anim(scene): if scene.frame_current == 250: bpy.ops.screen.animation_cancel() @@ -185,8 +195,8 @@ def stop_anim(scene): return {'FINISHED'} -class I3DEA_OT_track_on_curve_delete(bpy.types.Operator): - bl_idname = "i3dea.track_on_curve_delete" +class I3DEA_OT_visualization_del(bpy.types.Operator): + bl_idname = "i3dea.visualization_del" bl_label = "Delete track visualization" bl_description = "Deletes the track visualization" bl_options = {'REGISTER', 'UNDO'} @@ -198,6 +208,7 @@ def execute(self, context): for obj in obj_list: for child in obj.children_recursive: child.hide_set(False) + bpy.data.objects.remove(child) bpy.data.objects.remove(obj) self.report({'INFO'}, "Track Visualization deleted") return {'FINISHED'} diff --git a/ui.py b/ui.py index 3b2fc19..23c445b 100644 --- a/ui.py +++ b/ui.py @@ -117,10 +117,10 @@ def draw(self, context): row.prop(context.scene.i3dea, "track_piece_amount", text="") box = col.box() row = box.row() - row.prop(context.scene.i3dea, "rubber_track") + row.prop(context.scene.i3dea, "track_type_method", expand=True) row = box.row() - row.operator("i3dea.track_on_curve", text="Add track to curve") - row.operator("i3dea.track_on_curve_delete", text="Delete") + row.operator("i3dea.visualization", text="Track Visualization") + row.operator("i3dea.visualization_del", text="Delete") # --------------------------------------------------------------- # "Curve-Tools" Box box = layout.box() From bf0cc0402358294b55000998bee3e86e5fb99660 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Fri, 13 Jan 2023 03:10:30 +0100 Subject: [PATCH 05/16] Moved properties -Moved properties from __init__.py to properties.py --- __init__.py | 231 +--------------------------------- properties.py | 223 ++++++++++++++++++++++++++++++++ tools/properties_converter.py | 4 +- tools/track_tools.py | 4 +- ui.py | 1 - 5 files changed, 232 insertions(+), 231 deletions(-) create mode 100644 properties.py diff --git a/__init__.py b/__init__.py index 50458f1..b8322b4 100644 --- a/__init__.py +++ b/__init__.py @@ -38,9 +38,11 @@ if "bpy" in locals(): import importlib + importlib.reload(properties) importlib.reload(ui) importlib.reload(tools) else: + from . import properties, ui from .tools import ( assets_importer, orientation_tools, @@ -52,237 +54,14 @@ verifier, generate_empty_on_curves, ) - from . import ui import bpy -class I3DEA_custom_ObjectProps(bpy.types.PropertyGroup): - curve_ref: bpy.props.PointerProperty( - name="Object", - type=bpy.types.Object - ) - - -class I3DEA_PG_List(bpy.types.PropertyGroup): - # Dropdown for UV size - size_dropdown: bpy.props.EnumProperty( - name="Size List", - description="List of UV size", - items=[ - ('four', '2x2', "Create UVset 2 2x2"), - ('sixteen', '4x4', "Create UVset 2 4x4")], - default='four') - - # Dropdown for skeletons - skeletons_dropdown: bpy.props.EnumProperty( - items=[ - ('create_base_vehicle', 'Tractor', "Add Tractor Skeleton"), - ('create_base_harvester', 'Combine', "Add Harvester Skeleton"), - ('create_base_tool', 'Tool', "Add Tool Skeleton"), - ('create_attacher_joints', 'Attacher Joints', "Add Attacher Joint Skeleton"), - ('create_player', "Player", 'Add Player Skeleton'), - ('create_lights', "Lights", 'Add Lights Skeleton'), - ('create_cameras_vehicle', 'Cameras (Tractor)', "Add Cameras (Tractor) Skeleton"), - ('create_cameras_harvester', 'Cameras (Combine)', "Add Cameras (Combine) Skeleton"), - ('create_traffic_vehicle', 'Traffic Vehicle', "Add Traffic Vehicle Skeleton"), - ('create_placeable', 'Placeable', "Add Placeable Skeleton"), - ('create_animal_husbandry', 'Husbandry', "Add Husbandry Skeleton") - ], - name="Skeletons List", - description="List of skeletons") - - # Dropdown for assets import - assets_dropdown: bpy.props.EnumProperty( - name="Assets List", - description="List of assets", - default='frontloaderAdapter', - items=[('', "Adapters", "Adapters",), - ('frontloaderAdapter', "frontloaderAdapter", "Import frontloaderAdapter",), - ('telehandlerAdapter', "telehandlerAdapter", "Import telehandlerAdapter",), - ('wheelloaderAdapter', "wheelloaderAdapter", "Import wheelloaderAdapter",), - ('', "Beacon Lights", "Beacon Lights",), - ('beaconLight01', "beaconLight01", "Import beaconLight01",), - ('beaconLight02', "beaconLight02", "Import beaconLight02",), - ('beaconLight03', "beaconLight03", "Import beaconLight03",), - ('beaconLight04', "beaconLight04", "Import beaconLight04",), - ('beaconLight05', "beaconLight05", "Import beaconLight05",), - ('beaconLight06', "beaconLight06", "Import beaconLight06",), - ('beaconLight07', "beaconLight07", "Import beaconLight07",), - ('beaconLight08', "beaconLight08", "Import beaconLight08",), - ('beaconLight09', "beaconLight09", "Import beaconLight09",), - ('beaconLight10', "beaconLight10", "Import beaconLight10",), - ('beaconLight11', "beaconLight11", "Import beaconLight11",), - ('', "Car Hitches", "Car Hitches",), - ('fifthWheel_decal', "fifthWheel_hitch", "Import fifthWheel_hitch",), - ('gooseneck_decal', "gooseneck_hitch", "Import gooseneck_hitch",), - ('', "Hitches", "Hitches",), - ('zetorHitch', "zetorHitch", "Import zetorHitch",), - ('', "Lower Link Balls", "Lower Link Balls",), - ('lowerLinkBallRight', "lowerLinkBalls", "Import lowerLinkBalls",), - ('', "Power Take Offs", "Power Take Offs",), - ('walterscheidW', "walterscheidW", "Import walterscheidW",), - ('walterscheidWWE', "walterscheidWWE", "Import walterscheidWWE",), - ('walterscheidWWZ', "walterscheidWWZ", "Import walterscheidWWZ",), - ('', "Reflectors", "Reflectors",), - ('bigTriangle', "bigTriangle", "Import bigTriangle",), - ('redOrangeRectangle_01', "redOrangeRectangle_01", "Import redOrangeRectangle_01",), - ('redRectangle_01', "redRectangle_01", "Import redRectangle_01",), - ('redRound_01', "redRound_01", "Import redRound_01",), - ('redRound_02', "redRound_02", "Import redRound_02",), - ('redRound_03', "redRound_03", "Import redRound_03",), - ('redRound_04', "redRound_04", "Import redRound_04",), - ('redTriangle_01', "redTriangle_01", "Import redTriangle_01",), - ('redTriangle_02', "redTriangle_02", "Import redTriangle_02",), - ('whiteRectangle_01', "whiteRectangle_01", "Import whiteRectangle_01",), - ('whiteRound_01', "whiteRound_01", "Import whiteRound_01",), - ('whiteRound_02', "whiteRound_02", "Import whiteRound_02",), - ('whiteRound_03', "whiteRound_03", "Import whiteRound_03",), - ('whiteRound_04', "whiteRound_04", "Import whiteRound_04",), - ('whiteTriangle_01', "whiteTriangle_01", "Import whiteTriangle_01",), - ('whiteTriangle_02', "whiteTriangle_02", "Import whiteTriangle_02",), - ('yellowRectangle_01', "yellowRectangle_01", "Import yellowRectangle_01",), - ('yellowRound_01', "yellowRound_01", "Import yellowRound_01",), - ('yellowRound_02', "yellowRound_02", "Import yellowRound_02",), - ('yellowRound_03', "yellowRound_03", "Import yellowRound_03",), - ('yellowRound_04', "yellowRound_04", "Import yellowRound_04",), - ('yellowTriangle_01', "yellowTriangle_01", "Import yellowTriangle_01",), - ('yellowTriangle_02', "yellowTriangle_02", "Import yellowTriangle_02",), - ('', "Skf Lincoln", "Skf Lincoln",), - ('skfLincoln', "skfLincoln", "Import skfLincoln",), - ('', "Upper Links", "Upper Links",), - ('johnDeere8RTUpperlink', "johnDeere8RTUpperlink", "Import johnDeere8RTUpperlink",), - ('johnDeereUpperlink', "johnDeereUpperlink", "Import johnDeereUpperlink",), - ('walterscheid01', "walterscheid01", "Import walterscheid01",), - ('walterscheid02', "walterscheid02", "Import walterscheid02",), - ('walterscheid03', "walterscheid03", "Import walterscheid03",), - ('walterscheid04', "walterscheid04", "Import walterscheid04",), - ('walterscheid05', "walterscheid05", "Import walterscheid05",), - ('', "Wheel Chocks", "Wheel Chocks",), - ('chockSupport', "chockSupport", "Import chockSupport",), - ('wheelChock01', "wheelChock01", "Import wheelChock01",), - ('wheelChock02', "wheelChock02", "Import Wheel Chock 02",), - ('wheelChock03', "wheelChock03", "Import wheelChock03",), - ('wheelChock04_parked', "wheelChock04", "Import wheelChock04",), - ('wheelChock05chain2', "wheelChock05", "Import Wheel Chock 05",), - ('', "Miscellaneous", "Miscellaneous",), - ('attacherTruckGeneric', "attacherTruckGeneric", "Import attacherTruckGeneric",), - ('glass', "camera_01", "Import camera_01",), - ('rotArm2', "camera_02", "Import camera_02",), - ('glass3', "camera_03", "Import camera_03",), - ('exhaustLicencePlateHolder', "exhaustLicencePlateHolder", "Import exhaustLicencePlateHolder",), - ('extinguisher', "extinguisher", "Import extinguisher",), - ('gps', "gps", "Import gps",), - ('rearHitch', "rearHitch", "Import rearHitch",), - ('smallWheel01', "smallWheel01", "Import smallWheel01",), - ('starFire', "starFire", "Import starFire",), - ('toolbar', "toolbar", "Import toolbar",), - ('toolbarWide', "toolbarWide", "Import toolbarWide",), - ('towBar', "towBar", "Import towBar",), - ('trailerLowAttacher', "trailerLowAttacher", "Import trailerLowAttacher",), - ('trailerToolBoxHolder', "trailerToolBox", "Import trailerToolBox",), - ('trailerToolBox02', "trailerToolBox02", "Import trailerToolBox02",), - ('triangleAdapter', "triangleAdapter", "Import triangleAdapter",), - ('upperLink', "upperLink", "Import upperLink",), - ('upperLinkSmall', "upperLinkSmall", "Import upperLinkSmall",), ]) - - # Properties for material_tools - material_name: bpy.props.StringProperty(name="Material name", description="Write name of the material you want to create", default="material_mat") - diffuse_box: bpy.props.BoolProperty(name="Add diffuse node", description="If checked it will create a image texture linked to Base Color", default=False) - alpha_box: bpy.props.BoolProperty(name="Alpha", description="If checked it will set alpha settings to diffuse node", default=False) - diffuse_texture_path: bpy.props.StringProperty(name="Diffuse", description="Add path to your diffuse texture.", subtype='FILE_PATH', default="") - spec_texture_path: bpy.props.StringProperty(name="Specular", description="Add path to your specular texture.", subtype='FILE_PATH', default="") - normal_texture_path: bpy.props.StringProperty(name="Normal", description="Add path to your normal map texture.", subtype='FILE_PATH', default="") - - # i3dio_material handler - shader_path: bpy.props.StringProperty(name="Path to shader location", description="Select path to the shader you want to apply", subtype='FILE_PATH', default="") - mask_map: bpy.props.StringProperty(name="Mask Map", description="Add mask map texture", subtype='FILE_PATH', default="") - dirt_diffuse: bpy.props.StringProperty(name="Dirt diffuse", description="Add dirt diffuse texture", subtype='FILE_PATH', default="") - shader_box: bpy.props.BoolProperty(name="Set shader path", description="If checked it will add the the path to the shader in material", default=True) - mask_map_box: bpy.props.BoolProperty(name="Set mask map path", description="If checked it will add the the path to mask map in material", default=True) - dirt_diffuse_box: bpy.props.BoolProperty(name="Set dirt diffuse path", description="If checked it add the the path to dirt diffuse in material", default=True) - - # Track-Tools - def get_all_curves(self, context): - """ Returns enum elements of all Curves of the current Scene. """ - # From Giants I3D Exporter - - curves = tuple() - curves += (("None", "None", "None", 0),) - try: - num = 1 - for curveName in [obj.name for obj in context.scene.objects if obj.type == 'CURVE']: - curves += ((curveName, curveName, curveName, num),) - num += 1 - return curves - except: - return curves - - 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.FloatProperty(name="Track pieces possible along curve", description="The amount of track links that will fit along the curve", min=1, max=400, default=1) - rubber_track: bpy.props.BoolProperty(name="Rubber Track", description="Check this if you want to visualize a rubber track", default=False) - advanced_mode: bpy.props.BoolProperty(name="Advanced Mode", description="Add more options for UVset2 creation and Track setup", default=False) - all_curves: bpy.props.EnumProperty(items=get_all_curves, name="Select A Curve") - add_empties: bpy.props.BoolProperty(name="Add Empties", description="If you check this it will add the amount of empties between each track link that's written bellow.", default=False) - track_type_method: bpy.props.EnumProperty(name="Track Method", - items=[('CATERPILLAR', 'Caterpillar', ""), - ('RUBBER', 'Rubber', "",), - ('BOGIE', 'Bogie', "")], - description="Track visualization method, caterpillar, rubber or bogie", - default='CATERPILLAR') - - # User Attribute properties - user_attribute_name: bpy.props.StringProperty(name="Name", description="Name of the User Attribute.", default="") - user_attribute_type: bpy.props.EnumProperty( - name="Type", - description="List of User Attributes", - items=[ - ('boolean', "boolean", ""), - ('float', "float", ""), - ('string', "string", ""), - ('scriptCallback', "scriptCallback", ""),], - default='boolean') - - # Properties for Curve-Tools - curve_array_name: bpy.props.StringProperty(name="Array Name", description="Set array name", default="curveArray") - amount_curve: bpy.props.IntProperty(name="Static amount", description="Add empties on all curves with this amount", default=32, min=1) - distance_curve: bpy.props.FloatProperty(name="Distance", description="Add empties along curve by said distance", default=0.2, min=0.01) - use_amount: bpy.props.BoolProperty(name="Use Amount", description="When this is checked, it will use amount", default=True) - use_distance: bpy.props.BoolProperty(name="Use Distance", description="When this is checked, it will use distance", default=False) - use_pose2: bpy.props.BoolProperty(name="Use Pose 2", description="When this is checked, you will be able to use another set of curves.", default=False) - - active_obj_index: bpy.props.IntProperty() - object_collection: bpy.props.CollectionProperty( - type=I3DEA_custom_ObjectProps - ) - - active_obj_index2: bpy.props.IntProperty() - object_collection2: bpy.props.CollectionProperty( - type=I3DEA_custom_ObjectProps - ) - - # Properties for UI in dropdowns - UI_meshTools: bpy.props.BoolProperty(name="Mesh-Tools", default=False) - UI_user_attributes: bpy.props.BoolProperty(name="User Attributes", default=False) - UI_track_tools: bpy.props.BoolProperty(name="UV-Tools", default=False) - UI_uvset: bpy.props.BoolProperty(name="UVset", default=False) - UI_skeletons: bpy.props.BoolProperty(name="Skeletons", default=False) - UI_curve_tools: bpy.props.BoolProperty(name="Curve-Tools", default=False) - UI_materialTools: bpy.props.BoolProperty(name="Material-Tools", default=False) - UI_create_mat: bpy.props.BoolProperty(name="Create material", default=False) - UI_paths: bpy.props.BoolProperty(name="Add paths to material", default=False) - UI_assets: bpy.props.BoolProperty(name="Assets Importer", default=False) - UI_active_obj: bpy.props.StringProperty(name="Active Object Name", default="") - - classes = [ - I3DEA_custom_ObjectProps, - I3DEA_PG_List, + properties.I3DEA_custom_ObjectProps, + properties.I3DEA_PG_List, ui.I3DEA_PT_panel, track_tools.I3DEA_OT_make_uvset, track_tools.I3DEA_OT_add_empty, @@ -314,7 +93,7 @@ def get_all_curves(self, context): def register(): for cls in classes: bpy.utils.register_class(cls) - bpy.types.Scene.i3dea = bpy.props.PointerProperty(type=I3DEA_PG_List) + bpy.types.Scene.i3dea = bpy.props.PointerProperty(type=properties.I3DEA_PG_List) def unregister(): diff --git a/properties.py b/properties.py new file mode 100644 index 0000000..effe96d --- /dev/null +++ b/properties.py @@ -0,0 +1,223 @@ +import bpy + + +class I3DEA_custom_ObjectProps(bpy.types.PropertyGroup): + curve_ref: bpy.props.PointerProperty( + name="Object", + type=bpy.types.Object + ) + + +class I3DEA_PG_List(bpy.types.PropertyGroup): + # Dropdown for UV size + size_dropdown: bpy.props.EnumProperty( + name="Size List", + description="List of UV size", + items=[ + ('four', '2x2', "Create UVset 2 2x2"), + ('sixteen', '4x4', "Create UVset 2 4x4")], + default='four') + + # Dropdown for skeletons + skeletons_dropdown: bpy.props.EnumProperty( + items=[ + ('create_base_vehicle', 'Tractor', "Add Tractor Skeleton"), + ('create_base_harvester', 'Combine', "Add Harvester Skeleton"), + ('create_base_tool', 'Tool', "Add Tool Skeleton"), + ('create_attacher_joints', 'Attacher Joints', "Add Attacher Joint Skeleton"), + ('create_player', "Player", 'Add Player Skeleton'), + ('create_lights', "Lights", 'Add Lights Skeleton'), + ('create_cameras_vehicle', 'Cameras (Tractor)', "Add Cameras (Tractor) Skeleton"), + ('create_cameras_harvester', 'Cameras (Combine)', "Add Cameras (Combine) Skeleton"), + ('create_traffic_vehicle', 'Traffic Vehicle', "Add Traffic Vehicle Skeleton"), + ('create_placeable', 'Placeable', "Add Placeable Skeleton"), + ('create_animal_husbandry', 'Husbandry', "Add Husbandry Skeleton") + ], + name="Skeletons List", + description="List of skeletons") + + # Dropdown for assets import + assets_dropdown: bpy.props.EnumProperty( + name="Assets List", + description="List of assets", + default='frontloaderAdapter', + items=[('', "Adapters", "Adapters",), + ('frontloaderAdapter', "frontloaderAdapter", "Import frontloaderAdapter",), + ('telehandlerAdapter', "telehandlerAdapter", "Import telehandlerAdapter",), + ('wheelloaderAdapter', "wheelloaderAdapter", "Import wheelloaderAdapter",), + ('', "Beacon Lights", "Beacon Lights",), + ('beaconLight01', "beaconLight01", "Import beaconLight01",), + ('beaconLight02', "beaconLight02", "Import beaconLight02",), + ('beaconLight03', "beaconLight03", "Import beaconLight03",), + ('beaconLight04', "beaconLight04", "Import beaconLight04",), + ('beaconLight05', "beaconLight05", "Import beaconLight05",), + ('beaconLight06', "beaconLight06", "Import beaconLight06",), + ('beaconLight07', "beaconLight07", "Import beaconLight07",), + ('beaconLight08', "beaconLight08", "Import beaconLight08",), + ('beaconLight09', "beaconLight09", "Import beaconLight09",), + ('beaconLight10', "beaconLight10", "Import beaconLight10",), + ('beaconLight11', "beaconLight11", "Import beaconLight11",), + ('', "Car Hitches", "Car Hitches",), + ('fifthWheel_decal', "fifthWheel_hitch", "Import fifthWheel_hitch",), + ('gooseneck_decal', "gooseneck_hitch", "Import gooseneck_hitch",), + ('', "Hitches", "Hitches",), + ('zetorHitch', "zetorHitch", "Import zetorHitch",), + ('', "Lower Link Balls", "Lower Link Balls",), + ('lowerLinkBallRight', "lowerLinkBalls", "Import lowerLinkBalls",), + ('', "Power Take Offs", "Power Take Offs",), + ('walterscheidW', "walterscheidW", "Import walterscheidW",), + ('walterscheidWWE', "walterscheidWWE", "Import walterscheidWWE",), + ('walterscheidWWZ', "walterscheidWWZ", "Import walterscheidWWZ",), + ('', "Reflectors", "Reflectors",), + ('bigTriangle', "bigTriangle", "Import bigTriangle",), + ('redOrangeRectangle_01', "redOrangeRectangle_01", "Import redOrangeRectangle_01",), + ('redRectangle_01', "redRectangle_01", "Import redRectangle_01",), + ('redRound_01', "redRound_01", "Import redRound_01",), + ('redRound_02', "redRound_02", "Import redRound_02",), + ('redRound_03', "redRound_03", "Import redRound_03",), + ('redRound_04', "redRound_04", "Import redRound_04",), + ('redTriangle_01', "redTriangle_01", "Import redTriangle_01",), + ('redTriangle_02', "redTriangle_02", "Import redTriangle_02",), + ('whiteRectangle_01', "whiteRectangle_01", "Import whiteRectangle_01",), + ('whiteRound_01', "whiteRound_01", "Import whiteRound_01",), + ('whiteRound_02', "whiteRound_02", "Import whiteRound_02",), + ('whiteRound_03', "whiteRound_03", "Import whiteRound_03",), + ('whiteRound_04', "whiteRound_04", "Import whiteRound_04",), + ('whiteTriangle_01', "whiteTriangle_01", "Import whiteTriangle_01",), + ('whiteTriangle_02', "whiteTriangle_02", "Import whiteTriangle_02",), + ('yellowRectangle_01', "yellowRectangle_01", "Import yellowRectangle_01",), + ('yellowRound_01', "yellowRound_01", "Import yellowRound_01",), + ('yellowRound_02', "yellowRound_02", "Import yellowRound_02",), + ('yellowRound_03', "yellowRound_03", "Import yellowRound_03",), + ('yellowRound_04', "yellowRound_04", "Import yellowRound_04",), + ('yellowTriangle_01', "yellowTriangle_01", "Import yellowTriangle_01",), + ('yellowTriangle_02', "yellowTriangle_02", "Import yellowTriangle_02",), + ('', "Skf Lincoln", "Skf Lincoln",), + ('skfLincoln', "skfLincoln", "Import skfLincoln",), + ('', "Upper Links", "Upper Links",), + ('johnDeere8RTUpperlink', "johnDeere8RTUpperlink", "Import johnDeere8RTUpperlink",), + ('johnDeereUpperlink', "johnDeereUpperlink", "Import johnDeereUpperlink",), + ('walterscheid01', "walterscheid01", "Import walterscheid01",), + ('walterscheid02', "walterscheid02", "Import walterscheid02",), + ('walterscheid03', "walterscheid03", "Import walterscheid03",), + ('walterscheid04', "walterscheid04", "Import walterscheid04",), + ('walterscheid05', "walterscheid05", "Import walterscheid05",), + ('', "Wheel Chocks", "Wheel Chocks",), + ('chockSupport', "chockSupport", "Import chockSupport",), + ('wheelChock01', "wheelChock01", "Import wheelChock01",), + ('wheelChock02', "wheelChock02", "Import Wheel Chock 02",), + ('wheelChock03', "wheelChock03", "Import wheelChock03",), + ('wheelChock04_parked', "wheelChock04", "Import wheelChock04",), + ('wheelChock05chain2', "wheelChock05", "Import Wheel Chock 05",), + ('', "Miscellaneous", "Miscellaneous",), + ('attacherTruckGeneric', "attacherTruckGeneric", "Import attacherTruckGeneric",), + ('glass', "camera_01", "Import camera_01",), + ('rotArm2', "camera_02", "Import camera_02",), + ('glass3', "camera_03", "Import camera_03",), + ('exhaustLicencePlateHolder', "exhaustLicencePlateHolder", "Import exhaustLicencePlateHolder",), + ('extinguisher', "extinguisher", "Import extinguisher",), + ('gps', "gps", "Import gps",), + ('rearHitch', "rearHitch", "Import rearHitch",), + ('smallWheel01', "smallWheel01", "Import smallWheel01",), + ('starFire', "starFire", "Import starFire",), + ('toolbar', "toolbar", "Import toolbar",), + ('toolbarWide', "toolbarWide", "Import toolbarWide",), + ('towBar', "towBar", "Import towBar",), + ('trailerLowAttacher', "trailerLowAttacher", "Import trailerLowAttacher",), + ('trailerToolBoxHolder', "trailerToolBox", "Import trailerToolBox",), + ('trailerToolBox02', "trailerToolBox02", "Import trailerToolBox02",), + ('triangleAdapter', "triangleAdapter", "Import triangleAdapter",), + ('upperLink', "upperLink", "Import upperLink",), + ('upperLinkSmall', "upperLinkSmall", "Import upperLinkSmall",), ]) + + # Properties for material_tools + material_name: bpy.props.StringProperty(name="Material name", description="Write name of the material you want to create", default="material_mat") + diffuse_box: bpy.props.BoolProperty(name="Add diffuse node", description="If checked it will create a image texture linked to Base Color", default=False) + alpha_box: bpy.props.BoolProperty(name="Alpha", description="If checked it will set alpha settings to diffuse node", default=False) + diffuse_texture_path: bpy.props.StringProperty(name="Diffuse", description="Add path to your diffuse texture.", subtype='FILE_PATH', default="") + spec_texture_path: bpy.props.StringProperty(name="Specular", description="Add path to your specular texture.", subtype='FILE_PATH', default="") + normal_texture_path: bpy.props.StringProperty(name="Normal", description="Add path to your normal map texture.", subtype='FILE_PATH', default="") + + # i3dio_material handler + shader_path: bpy.props.StringProperty(name="Path to shader location", description="Select path to the shader you want to apply", subtype='FILE_PATH', default="") + mask_map: bpy.props.StringProperty(name="Mask Map", description="Add mask map texture", subtype='FILE_PATH', default="") + dirt_diffuse: bpy.props.StringProperty(name="Dirt diffuse", description="Add dirt diffuse texture", subtype='FILE_PATH', default="") + shader_box: bpy.props.BoolProperty(name="Set shader path", description="If checked it will add the the path to the shader in material", default=True) + mask_map_box: bpy.props.BoolProperty(name="Set mask map path", description="If checked it will add the the path to mask map in material", default=True) + dirt_diffuse_box: bpy.props.BoolProperty(name="Set dirt diffuse path", description="If checked it add the the path to dirt diffuse in material", default=True) + + # Track-Tools + def get_all_curves(self, context): + """ Returns enum elements of all Curves of the current Scene. """ + # From Giants I3D Exporter + + curves = tuple() + curves += (("None", "None", "None", 0),) + try: + num = 1 + for curveName in [obj.name for obj in context.scene.objects if obj.type == 'CURVE']: + curves += ((curveName, curveName, curveName, num),) + num += 1 + return curves + except: + return curves + + 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.FloatProperty(name="Track pieces possible along curve", description="The amount of track links that will fit along the curve", min=1, max=400, default=1) + rubber_track: bpy.props.BoolProperty(name="Rubber Track", description="Check this if you want to visualize a rubber track", default=False) + advanced_mode: bpy.props.BoolProperty(name="Advanced Mode", description="Add more options for UVset2 creation and Track setup", default=False) + all_curves: bpy.props.EnumProperty(items=get_all_curves, name="Select A Curve") + add_empties: bpy.props.BoolProperty(name="Add Empties", description="If you check this it will add the amount of empties between each track link that's written bellow.", default=False) + track_type_method: bpy.props.EnumProperty(name="Track Method", + items=[('CATERPILLAR', 'Caterpillar', ""), + ('RUBBER', 'Rubber', "",), + ('BOGIE', 'Bogie', "")], + description="Track visualization method, caterpillar, rubber or bogie", + default='CATERPILLAR') + + # User Attribute properties.py + user_attribute_name: bpy.props.StringProperty(name="Name", description="Name of the User Attribute.", default="") + user_attribute_type: bpy.props.EnumProperty( + name="Type", + description="List of User Attributes", + items=[ + ('boolean', "boolean", ""), + ('float', "float", ""), + ('string', "string", ""), + ('scriptCallback', "scriptCallback", ""),], + default='boolean') + + # Properties for Curve-Tools + curve_array_name: bpy.props.StringProperty(name="Array Name", description="Set array name", default="curveArray") + amount_curve: bpy.props.IntProperty(name="Static amount", description="Add empties on all curves with this amount", default=32, min=1) + distance_curve: bpy.props.FloatProperty(name="Distance", description="Add empties along curve by said distance", default=0.2, min=0.01) + use_amount: bpy.props.BoolProperty(name="Use Amount", description="When this is checked, it will use amount", default=True) + use_distance: bpy.props.BoolProperty(name="Use Distance", description="When this is checked, it will use distance", default=False) + use_pose2: bpy.props.BoolProperty(name="Use Pose 2", description="When this is checked, you will be able to use another set of curves.", default=False) + + active_obj_index: bpy.props.IntProperty() + object_collection: bpy.props.CollectionProperty( + type=I3DEA_custom_ObjectProps + ) + + active_obj_index2: bpy.props.IntProperty() + object_collection2: bpy.props.CollectionProperty( + type=I3DEA_custom_ObjectProps + ) + + # Properties for UI in dropdowns + UI_meshTools: bpy.props.BoolProperty(name="Mesh-Tools", default=False) + UI_user_attributes: bpy.props.BoolProperty(name="User Attributes", default=False) + UI_track_tools: bpy.props.BoolProperty(name="UV-Tools", default=False) + UI_uvset: bpy.props.BoolProperty(name="UVset", default=False) + UI_skeletons: bpy.props.BoolProperty(name="Skeletons", default=False) + UI_curve_tools: bpy.props.BoolProperty(name="Curve-Tools", default=False) + UI_materialTools: bpy.props.BoolProperty(name="Material-Tools", default=False) + UI_create_mat: bpy.props.BoolProperty(name="Create material", default=False) + UI_paths: bpy.props.BoolProperty(name="Add paths to material", default=False) + UI_assets: bpy.props.BoolProperty(name="Assets Importer", default=False) + UI_active_obj: bpy.props.StringProperty(name="Active Object Name", default="") diff --git a/tools/properties_converter.py b/tools/properties_converter.py index 32c6cff..2cb4016 100644 --- a/tools/properties_converter.py +++ b/tools/properties_converter.py @@ -21,8 +21,8 @@ class I3DEA_OT_properties_converter(bpy.types.Operator): bl_idname = "i3dea.properties_converter" - bl_label = "Convert I3D properties.py" - bl_description = "Converts I3D properties.py from Stjerne exporter to Giants exporter" + bl_label = "Convert I3D properties.py.py" + bl_description = "Converts I3D properties.py.py from Stjerne exporter to Giants exporter" bl_options = {'REGISTER', 'UNDO'} properties = [('Giants', 'Stjerne')] diff --git a/tools/track_tools.py b/tools/track_tools.py index 48b72cf..8561677 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -249,12 +249,12 @@ def execute(self, context): elif context.scene.i3dea.advanced_mode and not context.scene.i3dea.all_curves == "None": vmask_bake = vmask_bake_objs(duplicated_obj, name) - bpy.ops.object.empty_add(radius=0, location=[0,0,0]) + bpy.ops.object.empty_add(radius=0, location=(0, 0, 0)) empty_parent = context.object empty_parent.name = name bpy.ops.object.empty_add(radius=0, location=original_loc) track_geo = bpy.context.object - track_geo.name = "{}Geo".format(name) + track_geo.name = f"{name}Geo" if giants_i3d: dcc.I3DSetAttrBool(track_geo.name, 'I3D_receiveShadows', True) dcc.I3DSetAttrBool(track_geo.name, 'I3D_castsShadows', True) diff --git a/ui.py b/ui.py index 23c445b..84cee85 100644 --- a/ui.py +++ b/ui.py @@ -1,7 +1,6 @@ import bpy from .helper_functions import check_i3d_exporter_type -from .tools.generate_empty_on_curves import I3DEA_OT_empties_along_curves class I3DEA_PT_panel(bpy.types.Panel): From 4d2e5e10c77d9856a2120b3020335b3edadb9c1a Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Sun, 15 Jan 2023 03:09:17 +0100 Subject: [PATCH 06/16] track_tools.py cleanup -Fixed a lot of messy functions in track_tools.py --- properties.py | 6 +- tools/track_tools.py | 206 +++++++++++++++++++------------------------ 2 files changed, 96 insertions(+), 116 deletions(-) diff --git a/properties.py b/properties.py index effe96d..e65147c 100644 --- a/properties.py +++ b/properties.py @@ -14,9 +14,9 @@ class I3DEA_PG_List(bpy.types.PropertyGroup): name="Size List", description="List of UV size", items=[ - ('four', '2x2', "Create UVset 2 2x2"), - ('sixteen', '4x4', "Create UVset 2 4x4")], - default='four') + ('4', '2x2', "Create UVset 2 2x2"), + ('16', '4x4', "Create UVset 2 4x4")], + default='4') # Dropdown for skeletons skeletons_dropdown: bpy.props.EnumProperty( diff --git a/tools/track_tools.py b/tools/track_tools.py index 8561677..7441811 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -19,8 +19,10 @@ # track_tools.py includes different tools for uv import bpy +import bmesh import math +import mathutils from mathutils import Vector from ..helper_functions import check_obj_type, check_i3d_exporter_type @@ -36,21 +38,15 @@ def get_curve_length(curve_obj): return length -def create_empties(objs): - for _ in range(bpy.context.scene.i3dea.add_empty_int): - for loop_obj in objs: - location = loop_obj.location - bpy.ops.object.empty_add(radius=0, location=location) - empty = bpy.context.active_object - empty.name = loop_obj.name + ".001" - if loop_obj.parent is not None: - empty.parent = loop_obj.parent - empty.matrix_parent_inverse = loop_obj.matrix_world.inverted() - - bpy.ops.object.select_all(action='DESELECT') +def create_empties(objs, amount): for obj in objs: - obj.select_set(True) - return + for _ in range(amount): + empty = bpy.data.objects.new(obj.name + ".001", None) + empty.location = obj.location + bpy.context.collection.objects.link(empty) + if obj.parent is not None: + empty.parent = obj.parent + empty.matrix_parent_inverse = obj.matrix_world.inverted() class I3DEA_OT_add_empty(bpy.types.Operator): @@ -60,8 +56,8 @@ class I3DEA_OT_add_empty(bpy.types.Operator): bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - selected_list = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] - create_empties(selected_list) + selected_list = [obj for obj in context.selected_objects if obj.type == 'MESH'] + create_empties(selected_list, context.scene.i3dea.add_empty_int) self.report({'INFO'}, "Empties added") return {'FINISHED'} @@ -103,7 +99,7 @@ def execute(self, context): elif context.object.type == 'CURVE': curve = bpy.context.object curve_length = get_curve_length(curve) - float_val = curve_length/bpy.context.scene.i3dea.piece_distance + float_val = curve_length / bpy.context.scene.i3dea.piece_distance bpy.context.scene.i3dea.track_piece_amount = float_val return {'FINISHED'} @@ -141,7 +137,8 @@ def execute(self, context): piece_num = int(bpy.context.scene.i3dea.track_piece_amount) else: piece_num = 25 - self.report({'INFO'}, f"No amount set in track piece amount, using default amount instead ({piece_num})") + self.report({'INFO'}, + f"No amount set in track piece amount, using default amount instead ({piece_num})") bpy.ops.mesh.primitive_plane_add() plane = bpy.context.object @@ -164,7 +161,7 @@ def execute(self, context): plane.keyframe_insert("location", frame=1) plane.location[1] = curve_length plane.keyframe_insert("location", frame=250) - new = bpy.data.objects.new(piece.name+"_visual", bpy.data.objects[piece.name].data) + new = bpy.data.objects.new(piece.name + "_visual", bpy.data.objects[piece.name].data) context.collection.objects.link(new) new.parent = plane new.hide_set(True) @@ -235,13 +232,10 @@ def execute(self, context): original_obj = context.object check_obj_type(original_obj) original_loc = original_obj.location - original_obj.name = "originalMesh" - duplicated_obj = create_second_uv(original_obj, name) - for obj in duplicated_obj: - obj.select_set(True) + duplicated_obj = create_second_uv(original_obj, name, int(context.scene.i3dea.size_dropdown)) if not context.scene.i3dea.advanced_mode: - if context.scene.i3dea.size_dropdown == 'four': + if context.scene.i3dea.size_dropdown == '4': self.report({'INFO'}, "UVset2 2x2 Created") else: self.report({'INFO'}, "UVset2 4x4 Created") @@ -264,16 +258,15 @@ def execute(self, context): dcc.I3DSetAttrBool(track_geo.name, 'I3D_objectDataExportPosition', True) obj_name = track_geo.name dim_x = original_obj.dimensions[0] - bbox = create_bbox(context.scene.i3dea.all_curves, name, obj_name, dim_x) - bbox.hide_set(True) - all_pieces = create_from_amount(duplicated_obj) + create_bbox(context.scene.i3dea.all_curves, name, obj_name, dim_x) + all_pieces = create_from_amount(duplicated_obj, int(context.scene.i3dea.track_piece_amount)) for obj in all_pieces: obj.select_set(True) obj.parent = track_geo obj.matrix_parent_inverse = track_geo.matrix_world.inverted() if context.scene.i3dea.add_empties: - create_empties(all_pieces) + create_empties(all_pieces, context.scene.i3dea.add_empty_int) track_geo.parent = empty_parent track_geo.matrix_parent_inverse = empty_parent.matrix_world.inverted() @@ -291,72 +284,64 @@ def execute(self, context): return {'FINISHED'} -def create_second_uv(original_obj, name): - # Create a copy/duplicate of the active object - obj = original_obj.copy() - obj.data = original_obj.data.copy() - bpy.context.collection.objects.link(obj) - obj.name = name - - # Check if UVset2 exist - if 'UVset2' not in obj.data.uv_layers: - obj.data.uv_layers.new(name="UVset2") - obj.data.uv_layers['UVset2'].active = True - bpy.ops.object.select_all(action='DESELECT') - bpy.context.view_layer.objects.active = None - - # UV cursor coordinates - values = ((0.25, 0.25), (0.75, 0.25), (0.75, 0.75), (0.25, 0.75)) - if bpy.context.scene.i3dea.size_dropdown == 'sixteen': - values = ((0.125, 0.125), (0.375, 0.125), (0.625, 0.125), (0.875, 0.125), (0.875, 0.375), (0.625, 0.375), (0.375, 0.375), (0.125, 0.375), (0.125, 0.625), (0.375, 0.625), - (0.625, 0.625), (0.875, 0.625), (0.875, 0.875), (0.625, 0.875), (0.375, 0.875), (0.125, 0.875)) - # list of objects created in for loop - duplicated_obj = [] - for i, value in enumerate(values): - duplicate = obj.copy() - duplicate.data = obj.data.copy() - bpy.context.collection.objects.link(duplicate) - duplicate.select_set(True) - bpy.context.view_layer.objects.active = duplicate - bpy.context.object.name = name + ".001" - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.object.mode_set(mode='OBJECT') - original_type = bpy.context.area.ui_type - bpy.context.area.ui_type = 'UV' - bpy.context.space_data.cursor_location = value - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.uv.select_all(action='SELECT') - bpy.context.space_data.pivot_point = 'CENTER' - bpy.ops.uv.snap_selected(target='CURSOR_OFFSET') - if bpy.context.scene.i3dea.size_dropdown == 'sixteen': - bpy.ops.transform.resize(value=[0.25, 0.25, 0.25]) - else: - bpy.ops.transform.resize(value=[0.5, 0.5, 0.5]) - bpy.ops.object.mode_set(mode='OBJECT') - duplicated_obj.append(duplicate) - duplicate.select_set(False) - # Set ui back to the ui started in - bpy.context.area.ui_type = original_type +def create_second_uv(original_obj, name, amount): + """ + Creates second UV set for the given object by copying it multiple times and transforming each copy's UV set. + + param original_obj: Active object that will be used when this function is called + param name: + """ + grid_size = math.ceil(math.sqrt(amount)) + ref_obj = original_obj.copy() + ref_obj.data = original_obj.data.copy() + if 'uvSet2' not in ref_obj.data.uv_layers: + ref_obj.data.uv_layers.new(name="uvSet2") + + new_objs = [] + for i, _ in enumerate(range(amount)): + new_obj = ref_obj.copy() + new_obj.data = ref_obj.data.copy() + + bpy.context.collection.objects.link(new_obj) + + new_obj.name = f"{name}_{i:03}" + + bm = bmesh.new() + bm.from_mesh(new_obj.data) + + uv2 = bm.loops.layers.uv["uvSet2"] + uv1 = bm.loops.layers.uv[0] - # Remove the first duplicated object - bpy.data.objects.remove(obj) - return duplicated_obj + for bm_vert in bm.verts: + for link_loop in bm_vert.link_loops: + uv2_data = link_loop[uv2] + uv1_data = link_loop[uv1] + scale_matrix = mathutils.Matrix.Diagonal((1 / grid_size, 1 / grid_size)) + uv2_data.uv = uv1_data.uv @ scale_matrix + pos = divmod(i, grid_size) + uv2_data.uv[0] = uv2_data.uv[0] + (pos[1] / grid_size) + uv2_data.uv[1] = uv2_data.uv[1] + (1 - ((pos[0] + 1) / grid_size)) + bm.to_mesh(new_obj.data) + new_objs.append(new_obj) + + bpy.data.objects.remove(ref_obj, do_unlink=True) + return new_objs def vmask_bake_objs(objs, name): - bpy.ops.object.empty_add(radius=0, location=[0, 0, 0]) - vmask_empty = bpy.context.object + vmask_empty = bpy.data.objects.new("objsForBake", None) + bpy.context.collection.objects.link(vmask_empty) + vmask_empty.empty_display_size = 0 vmask_empty.name = "objsForBake" location = 0 - for obj in objs: + for i, obj in enumerate(objs): location += 1 vmask_obj = obj.copy() vmask_obj.data = obj.data.copy() bpy.context.collection.objects.link(vmask_obj) vmask_obj.select_set(True) - vmask_obj.name = name + "_vmask.001" + vmask_obj.name = f"{name}_vmask_{i:03}" vmask_obj.location[1] = location vmask_obj.parent = vmask_empty vmask_obj.data.uv_layers.remove(vmask_obj.data.uv_layers[0]) @@ -364,49 +349,44 @@ def vmask_bake_objs(objs, name): def create_bbox(curve_name, name, obj_name, dim_x): - prev_sel = bpy.context.selected_objects - prev_cursor = Vector(bpy.context.scene.cursor.location) curve = bpy.data.objects[curve_name] - curve.select_set(True) - bpy.context.view_layer.objects.active = curve - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.curve.select_all(action='SELECT') - bpy.ops.view3d.snap_cursor_to_selected() - bpy.ops.object.mode_set(mode='OBJECT') - curve_dim = curve.dimensions - # Create bbox - bpy.ops.mesh.primitive_cube_add() + bbox = [Vector(b) for b in curve.bound_box] + center = sum(bbox, Vector()) / 8 + center = curve.matrix_world @ center + bpy.ops.mesh.primitive_cube_add(location=center) bbox = bpy.context.object bbox.name = "zzz_bbox_{}Geo".format(name) - dim = curve_dim + Vector((1.0, 1.0, 1.0)) + dim = curve.dimensions + Vector((1.0, 1.0, 1.0)) bbox.dimensions = dim bpy.context.view_layer.update() bbox.dimensions[0] = dim_x + 1 bpy.context.view_layer.update() - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + matrix = bbox.matrix_world.copy() + for vert in bbox.data.vertices: + vert.co = matrix @ vert.co + bbox.matrix_world.identity() + if giants_i3d: dcc.I3DSetAttrString(bbox.name, 'I3D_boundingVolume', obj_name) - bbox.select_set(False) - bpy.context.scene.cursor.location = prev_cursor - - for obj in prev_sel: - obj.select_set(True) - bpy.context.view_layer.objects.active = obj return bbox -def create_from_amount(objects): - amount = int(bpy.context.scene.i3dea.track_piece_amount) - obj_list = objects +def create_from_amount(objects, amount): + """ + Takes in a list of objects and duplicates them until the desired amount is reached. + The new objects will have names that are incremented from the original objects. - index = 0 - while len(obj_list) < amount: - bpy.ops.object.select_all(action='DESELECT') - old_object = obj_list[index] - old_object.select_set(True) - bpy.context.view_layer.objects.active = old_object - bpy.ops.object.duplicate_move() - new_object = bpy.context.object + param objects: List with all the objects to be duplicated x times + param amount: Amount of times the loop will be run + """ + obj_list = objects + last_suffix = int(obj_list[-1].name.split("_")[-1]) + for i in range(amount - len(obj_list)): + old_object = obj_list[i % len(obj_list)] + new_object = old_object.copy() + new_object.data = old_object.data.copy() + new_object.name = "{}_{:03d}".format(old_object.name.split("_")[0], last_suffix+i+1) + bpy.context.collection.objects.link(new_object) obj_list.append(new_object) - index += 1 return obj_list + From cb7287fb9cc44caa9f65590d3cd66aaef907de5d Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Sun, 15 Jan 2023 22:26:09 +0100 Subject: [PATCH 07/16] User attributes + descriptions - Added a new operator: I3DEA_OT_delete_user_attribute, this operator will delete the current user attribute thats selected - Added more descriptions in track_tools.py --- __init__.py | 1 + properties.py | 4 ++-- tools/track_tools.py | 30 +++++++++++++++++++++++++++--- tools/user_attributes.py | 18 ++++++++++++++++++ ui.py | 4 ++-- 5 files changed, 50 insertions(+), 7 deletions(-) diff --git a/__init__.py b/__init__.py index b8322b4..10f6288 100644 --- a/__init__.py +++ b/__init__.py @@ -86,6 +86,7 @@ orientation_tools.I3DEA_OT_copy_orientation, assets_importer.I3DEA_OT_assets, user_attributes.I3DEA_OT_create_user_attribute, + user_attributes.I3DEA_OT_delete_user_attribute, verifier.I3DEA_OT_verify_scene, ] diff --git a/properties.py b/properties.py index e65147c..2fde505 100644 --- a/properties.py +++ b/properties.py @@ -180,7 +180,7 @@ def get_all_curves(self, context): default='CATERPILLAR') # User Attribute properties.py - user_attribute_name: bpy.props.StringProperty(name="Name", description="Name of the User Attribute.", default="") + user_attribute_name: bpy.props.StringProperty(name="Name", description="Name of the User Attribute.") user_attribute_type: bpy.props.EnumProperty( name="Type", description="List of User Attributes", @@ -188,7 +188,7 @@ def get_all_curves(self, context): ('boolean', "boolean", ""), ('float', "float", ""), ('string', "string", ""), - ('scriptCallback', "scriptCallback", ""),], + ('scriptCallback', "scriptCallback", "")], default='boolean') # Properties for Curve-Tools diff --git a/tools/track_tools.py b/tools/track_tools.py index 7441811..25da548 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -31,6 +31,9 @@ def get_curve_length(curve_obj): + """ + Returns length of curve and if the scale is not 1 1 1, it will be applied first to get the correct result + """ if curve_obj.scale != Vector((1, 1, 1)): print(f"{curve_obj.name} scale is not 1 1 1, scale will be applied.") bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) @@ -39,6 +42,12 @@ def get_curve_length(curve_obj): def create_empties(objs, amount): + """ + It will add x amount of empties in between each object + + param objs: The objects the empties will be added in between + param amount: The amount of empties that will be added between each object + """ for obj in objs: for _ in range(amount): empty = bpy.data.objects.new(obj.name + ".001", None) @@ -77,7 +86,7 @@ def execute(self, context): return {'CANCELLED'} else: curve_length = get_curve_length(context.object) - bpy.context.scene.i3dea.curve_length_disp = curve_length + context.scene.i3dea.curve_length_disp = curve_length return {'FINISHED'} @@ -226,8 +235,8 @@ def execute(self, context): return {'CANCELLED'} name = "track" - if bpy.context.scene.i3dea.custom_text_box: - name = bpy.context.scene.i3dea.custom_text + if context.scene.i3dea.custom_text_box: + name = context.scene.i3dea.custom_text original_obj = context.object check_obj_type(original_obj) @@ -329,6 +338,12 @@ def create_second_uv(original_obj, name, amount): def vmask_bake_objs(objs, name): + """ + Input objects will be spread out in a line and 1st uv will be removed so its ready for bake + + param objs: objects to be added to vmask bake + param name: + """ vmask_empty = bpy.data.objects.new("objsForBake", None) bpy.context.collection.objects.link(vmask_empty) vmask_empty.empty_display_size = 0 @@ -349,6 +364,14 @@ def vmask_bake_objs(objs, name): def create_bbox(curve_name, name, obj_name, dim_x): + """ + Creates a bounding box/volume around the curve + + param curve_name: Name of the curve + param name: name of the bbox + param obj_name: Name of object + param dim_x: X dimension + """ curve = bpy.data.objects[curve_name] bbox = [Vector(b) for b in curve.bound_box] center = sum(bbox, Vector()) / 8 @@ -368,6 +391,7 @@ def create_bbox(curve_name, name, obj_name, dim_x): if giants_i3d: dcc.I3DSetAttrString(bbox.name, 'I3D_boundingVolume', obj_name) + bbox.hide_set(True) return bbox diff --git a/tools/user_attributes.py b/tools/user_attributes.py index b4ed633..6235842 100644 --- a/tools/user_attributes.py +++ b/tools/user_attributes.py @@ -55,3 +55,21 @@ def execute(self, context): if attr_type == 'scriptCallback': obj[create_attr_name] = "" return {'FINISHED'} + + +class I3DEA_OT_delete_user_attribute(bpy.types.Operator): + bl_idname = "i3dea.delete_user_attribute" + bl_label = "Delete User Attribute" + bl_description = "Delete selected user attribute" + bl_options = {'UNDO'} + + attribute_name: bpy.props.StringProperty() + + def execute(self, context): + obj = context.object + + if obj and self.attribute_name in obj: + del obj[self.attribute_name] + + return {'FINISHED'} + diff --git a/ui.py b/ui.py index 84cee85..d0d860c 100644 --- a/ui.py +++ b/ui.py @@ -48,8 +48,7 @@ def draw(self, context): row = box.row() obj = context.object if obj: - row.label(text="Object Name: ") - row.label(text=obj.name) + row.label(text=f"Object Name: {obj.name}") row = box.row() attributes = [k for k in obj.keys() if 0 == k.find("userAttribute_")] @@ -63,6 +62,7 @@ def draw(self, context): m_list = k.split("_", 2) name = m_list[2] row2.prop(obj, f'["{k}"]', text=name) + row2.operator("i3dea.delete_user_attribute", text="", icon='X').attribute_name = k row2 = box2.row() row = box.row() From d23662d1a3063d8105860c6880f0cb5674355024 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Sun, 22 Jan 2023 03:53:54 +0100 Subject: [PATCH 08/16] New UI - Added new ui (wip) - Cleaned a lot of file --- __init__.py | 27 +++---- helper_functions.py | 39 ++++++--- new_ui.py | 102 +++++++++++++++++++++++ tools/generate_empty_on_curves.py | 18 ++--- tools/material_tools.py | 12 +-- tools/skeletons.py | 130 +++++++++++++++--------------- tools/track_tools.py | 22 ++--- tools/verifier.py | 8 ++ ui.py | 29 +++---- 9 files changed, 253 insertions(+), 134 deletions(-) create mode 100644 new_ui.py diff --git a/__init__.py b/__init__.py index 10f6288..32ede3b 100644 --- a/__init__.py +++ b/__init__.py @@ -15,15 +15,6 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### - - -""" -TODO: - - See if it's possible to do more with fill volume checker - - Make it possible to scale the curve to get an rounded value for the track amount -""" - - bl_info = { "name": "I3D Exporter Additionals", "author": "T-Bone", @@ -35,14 +26,17 @@ "category": "Game Engine" } - if "bpy" in locals(): import importlib + importlib.reload(helper_functions) importlib.reload(properties) importlib.reload(ui) + importlib.reload(new_ui) importlib.reload(tools) else: - from . import properties, ui + import bpy + from .helper_functions import Singleton + from . import properties, ui, new_ui from .tools import ( assets_importer, orientation_tools, @@ -55,14 +49,13 @@ generate_empty_on_curves, ) - -import bpy - - classes = [ properties.I3DEA_custom_ObjectProps, properties.I3DEA_PG_List, ui.I3DEA_PT_panel, + new_ui.I3DEA_PT_MainPanel, + new_ui.I3DEA_PT_GeneralTools, + new_ui.I3DEA_PT_UserAttributes, track_tools.I3DEA_OT_make_uvset, track_tools.I3DEA_OT_add_empty, track_tools.I3DEA_OT_curve_length, @@ -101,3 +94,7 @@ def unregister(): del bpy.types.Scene.i3dea for cls in classes: bpy.utils.unregister_class(cls) + + +if __name__ == "__main__": + register() diff --git a/helper_functions.py b/helper_functions.py index 0101576..200c2bc 100644 --- a/helper_functions.py +++ b/helper_functions.py @@ -1,20 +1,33 @@ import bpy -def check_i3d_exporter_type(): - giants_i3d = False - I3DRemoveAttributes: any = {} - dcc: any = {} - stjerne_i3d = False +class Singleton: + __instance = None - for a in bpy.context.preferences.addons: - if a.module == "io_export_i3d": - giants_i3d = True - from io_export_i3d.dcc import dccBlender as dcc - from io_export_i3d.dcc import I3DRemoveAttributes - if a.module == "i3dio": - stjerne_i3d = True - return giants_i3d, stjerne_i3d, dcc, I3DRemoveAttributes + def __init__(self): + if Singleton.__instance is not None: + raise Exception("This class is a singleton!") + else: + Singleton.__instance = self + self.giants_i3d = False + self.stjerne_i3d = False + self.check_i3d_exporter_type() + + @classmethod + def get_instance(cls): + if Singleton.__instance is None: + Singleton() + return Singleton.__instance + + def check_i3d_exporter_type(self): + if "io_export_i3d" in bpy.context.preferences.addons: + self.giants_i3d = True + else: + self.giants_i3d = False + if "i3dio" in bpy.context.preferences.addons: + self.stjerne_i3d = True + else: + self.stjerne_i3d = False def check_obj_type(obj): diff --git a/new_ui.py b/new_ui.py new file mode 100644 index 0000000..f1b5be2 --- /dev/null +++ b/new_ui.py @@ -0,0 +1,102 @@ +import bpy + +from bpy.types import Panel, UIList +from .helper_functions import Singleton + +singleton_instance = Singleton.get_instance() + + +class I3DEA_UL_pose_curves(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + curve_ob = item.curve + curve_icon = 'OUTLINER_OB_CURVE' + if self.layout_type in {'DEFAULT', 'COMPACT'}: + layout.prop(curve_ob, "name", text="", emboss=False, icon=curve_icon) + elif self.layout_type == 'GRID': + layout.alignment = 'CENTER' + layout.label(text="", icon_value=icon) + + +class I3deaPanel: + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'GIANTS I3D Exporter NEW' + + +class I3DEA_PT_MainPanel(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_MainPanel' + bl_label = 'I3D Exporter Additionals' + + def draw(self, context): + layout = self.layout + if singleton_instance.giants_i3d and singleton_instance.stjerne_i3d: + # "Exporter selection" box + layout.label(text="Both Giants & Stjerne I3D exporter is enabled", icon='ERROR') + layout.label(text="Recommend to disable one of them as it can cause some issues") + + +class I3DEA_PT_GeneralTools(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_GeneralTools' + bl_label = 'General Tools' + bl_parent_id = 'I3DEA_PT_MainPanel' + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + row = col.row(align=True) + row.operator("i3dea.copy_orientation", text="Copy Location").state = 1 + row.operator("i3dea.copy_orientation", text="Copy Rotation").state = 2 + row = col.row(align=True) + row.operator("i3dea.mirror_orientation", text="Set mirror orientation") + row.operator("i3dea.remove_doubles", text="Clean Meshes") + row = col.row(align=True) + row.operator("i3dea.mesh_name", text="Set Mesh Name") + row.operator("i3dea.fill_volume", text="Check Fill Volume") + if singleton_instance.giants_i3d: + row = col.row(align=True) + row.operator("i3dea.xml_config", text="Enable export to i3dMappings") + row.operator("i3dea.ignore", text="Add Suffix _ignore") + row = col.row(align=True) + row.operator("i3dea.verify_scene", text="Verify Scene") + + +class I3DEA_PT_UserAttributes(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_UserAttributes' + bl_label = 'User Attributes' + bl_parent_id = 'I3DEA_PT_MainPanel' + + @classmethod + def poll(cls, context): + return singleton_instance.giants_i3d + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + row = col.row(align=True) + obj = context.object + if obj: + row.label(text=f"Object Name: {obj.name}") + row = col.row() + + attributes = [k for k in obj.keys() if 0 == k.find("userAttribute_")] + if attributes: + col2 = row.column() + box2 = col2.box() + row2 = box2.row() + row2.label(text="Attributes:") + row2 = box2.row() + for k in attributes: + m_list = k.split("_", 2) + name = m_list[2] + row2.prop(obj, f'["{k}"]', text=name) + row2.operator("i3dea.delete_user_attribute", text="", icon='X').attribute_name = k + row2 = box2.row() + row = col.row() + + row.label(text="Add new attributes:") + row = col.row() + row.prop(context.scene.i3dea, "user_attribute_name", text="Name") + row = col.row() + row.prop(context.scene.i3dea, "user_attribute_type", text="Type") + row = col.row() + row.operator("i3dea.create_user_attribute", text="Add") diff --git a/tools/generate_empty_on_curves.py b/tools/generate_empty_on_curves.py index db36f72..42f5974 100644 --- a/tools/generate_empty_on_curves.py +++ b/tools/generate_empty_on_curves.py @@ -1,7 +1,7 @@ import bpy -from ..helper_functions import check_i3d_exporter_type +from ..helper_functions import Singleton -giants_i3d, stjerne_i3d, dcc, I3DRemoveAttributes = check_i3d_exporter_type() +singleton_instance = Singleton.get_instance() class I3DEA_UL_selected_curves(bpy.types.UIList): @@ -124,13 +124,13 @@ def create_empties_on_curve(selected_curves, hierarchy_name, num_empties=10): # Create empty object "pose1" hierarchy_empty = create_empty(name=hierarchy_name) - if giants_i3d: - dcc.I3DSetAttrString(hierarchy_empty.name, 'I3D_objectDataFilePath', hierarchy_name + ".dds") - dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataHierarchicalSetup', True) - dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataHideFirstAndLastObject', True) - dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataExportPosition', True) - dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataExportOrientation', True) - dcc.I3DSetAttrBool(hierarchy_empty.name, 'I3D_objectDataExportScale', True) + if singleton_instance.giants_i3d: + hierarchy_empty['I3D_objectDataFilePath'] = hierarchy_name + ".dds" + hierarchy_empty['I3D_objectDataHierarchicalSetup'] = True + hierarchy_empty['I3D_objectDataHideFirstAndLastObject'] = True + hierarchy_empty['I3D_objectDataExportPosition'] = True + hierarchy_empty['I3D_objectDataExportOrientation'] = True + hierarchy_empty['I3D_objectDataExportScale'] = True # Create empty object "pose1" pose1 = create_empty(name="pose1") diff --git a/tools/material_tools.py b/tools/material_tools.py index 0fee4f9..1641e4c 100644 --- a/tools/material_tools.py +++ b/tools/material_tools.py @@ -20,9 +20,9 @@ import bpy -from ..helper_functions import check_i3d_exporter_type +from ..helper_functions import Singleton -giants_i3d, stjerne_i3d, dcc, I3DRemoveAttributes = check_i3d_exporter_type() +singleton_instance = Singleton.get_instance() class I3DEA_OT_mirror_material(bpy.types.Operator): @@ -31,7 +31,7 @@ class I3DEA_OT_mirror_material(bpy.types.Operator): bl_description = "Adds mirror_mat to materials" def execute(self, context): - if giants_i3d: + if singleton_instance.giants_i3d: if context.scene.I3D_UIexportSettings.I3D_shaderFolderLocation == "": self.report({'ERROR'}, "Shader Folder location is not set!") return {'CANCELLED'} @@ -57,7 +57,7 @@ def execute(self, context): for i in range(len(obj.material_slots)): bpy.ops.object.material_slot_remove() obj.data.materials.append(mirror_mat) - if giants_i3d: + if singleton_instance.giants_i3d: bpy.context.object.active_material['customShader'] = "$data\\shaders\\mirrorShader.xml" bpy.context.object.active_material['shadingRate'] = "1x1" self.report({'INFO'}, "Created material: mirror_mat") @@ -119,9 +119,9 @@ def execute(self, context): except Exception as e: print(e) - if giants_i3d: + if singleton_instance.giants_i3d: links.new(img_tex_spec.outputs["Color"], principled.inputs["Specular"]) - if stjerne_i3d: + if singleton_instance.stjerne_i3d: sep_rgb = nodes.new("ShaderNodeSeparateRGB") sep_rgb.location = (-210, 90) links.new(img_tex_spec.outputs["Color"], sep_rgb.inputs["Image"]) diff --git a/tools/skeletons.py b/tools/skeletons.py index abb5639..9da23a9 100644 --- a/tools/skeletons.py +++ b/tools/skeletons.py @@ -25,9 +25,9 @@ import bpy import math -from ..helper_functions import check_i3d_exporter_type +from ..helper_functions import Singleton -giants_i3d, stjerne_i3d, dcc, I3DRemoveAttributes = check_i3d_exporter_type() +singleton_instance = Singleton.get_instance() class I3DEA_OT_skeletons(bpy.types.Operator): @@ -344,14 +344,14 @@ def create_animal_husbandry(self): nav_root_node = self.create_skel_node("1_8_navigationRootNode", animal_husbandry) self.create_plane("navigationMesh", nav_root_node) walking_plane = self.create_plane("walkingPlane", nav_root_node) - if giants_i3d: - dcc.I3DSetAttrBool(walking_plane, 'I3D_collision', True) - dcc.I3DSetAttrBool(walking_plane, 'I3D_static', True) - dcc.I3DSetAttrBool(walking_plane, 'I3D_nonRenderable', True) - dcc.I3DSetAttrBool(walking_plane, 'I3D_castsShadows', True) - dcc.I3DSetAttrBool(walking_plane, 'I3D_receiveShadows', True) - dcc.I3DSetAttrFloat(walking_plane, 'I3D_collisionMask', 131072) - if stjerne_i3d: + if singleton_instance.giants_i3d: + walking_plane['I3D_collision'] = True + walking_plane['I3D_static'] = True + walking_plane['I3D_nonRenderable'] = True + walking_plane['I3D_castsShadows'] = True + walking_plane['I3D_receiveShadows'] = True + walking_plane['I3D_collisionMask'] = 131072 + if singleton_instance.stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'static' bpy.context.object.i3d_attributes.collision_mask = "20000" bpy.context.object.data.i3d_attributes.casts_shadows = True @@ -420,13 +420,13 @@ def create_placeable_elements(self, parent): self.create_collision("collision", 255, "ff", collisions) self.create_collision("tipCollision", 524288, "80000", collisions) self.create_collision("tipCollisionWall", 524288, "80000", collisions) - if giants_i3d: + if singleton_instance.giants_i3d: prop_name = 'userAttribute_float_collisionHeight' bpy.context.object[prop_name] = 4.0 bpy.context.object.id_properties_ensure() # Make sure the manager is updated property_manager = bpy.context.object.id_properties_ui(prop_name) property_manager.update(min=-200, max=200) - if stjerne_i3d: + if singleton_instance.stjerne_i3d: bpy.ops.i3dio_user_attribute_list.new_item() bpy.context.object.i3d_user_attributes.attribute_list[0].name = "collisionHeight" bpy.context.object.i3d_user_attributes.attribute_list[0].type = 'data_float' @@ -508,15 +508,15 @@ def create_light(self, name, parent, cone_angle, light_range, drop_off, rgb, tra light_transform.parent = parent light_transform.name = name light = light_transform.name - if giants_i3d: - dcc.I3DSetAttrBool(light, 'I3D_collision', False) - dcc.I3DSetAttrBool(light, 'I3D_static', False) - dcc.I3DSetAttrFloat(light, 'I3D_clipDistance', 75) - if stjerne_i3d: + if singleton_instance.giants_i3d: + light['I3D_collision'] = False + light['I3D_static'] = False + light['I3D_clipDistance'] = 75.00 + if singleton_instance.stjerne_i3d: bpy.context.object.i3d_attributes.clip_distance = 75 return light_transform - def create_point_light(self, name, parent, light_range=18, rgb=(0.44, 0.4, 0.4), clip_distance=75): + def create_point_light(self, name, parent, light_range=18, rgb=(0.44, 0.4, 0.4), clip_distance=75.00): bpy.ops.object.light_add(type='POINT') light = bpy.context.object.data light.color = rgb @@ -526,11 +526,11 @@ def create_point_light(self, name, parent, light_range=18, rgb=(0.44, 0.4, 0.4), light.parent = parent light.name = name light = light.name - if giants_i3d: - dcc.I3DSetAttrBool(light, 'I3D_collision', False) - dcc.I3DSetAttrBool(light, 'I3D_static', False) - dcc.I3DSetAttrFloat(light, 'I3D_clipDistance', clip_distance) - if stjerne_i3d: + if singleton_instance.giants_i3d: + light['I3D_collision'] = False + light['I3D_static'] = False + light['I3D_clipDistance'] = clip_distance + if singleton_instance.stjerne_i3d: bpy.context.object.i3d_attributes.clip_distance = clip_distance return light @@ -550,7 +550,7 @@ def create_spot_light(self, name, parent, cone_angle=math.radians(120), light_ra dcc.I3DSetAttrBool(light, 'I3D_collision', False) dcc.I3DSetAttrBool(light, 'I3D_static', False) dcc.I3DSetAttrFloat(light, 'I3D_clipDistance', clip_distance) - if stjerne_i3d: + if singleton_instance.stjerne_i3d: bpy.context.object.i3d_attributes.clip_distance = clip_distance return light @@ -562,15 +562,15 @@ def create_collision(self, name, col_mask, col_mask_stjerne, parent, size=(1.0, bpy.ops.object.transform_apply(scale=True) bpy.context.active_object.parent = parent trigger = bpy.context.active_object.name - if giants_i3d: - dcc.I3DSetAttrBool(trigger, 'I3D_collision', True) - dcc.I3DSetAttrBool(trigger, 'I3D_static', True) - dcc.I3DSetAttrBool(trigger, 'I3D_trigger', True) - dcc.I3DSetAttrBool(trigger, 'I3D_nonRenderable', True) - dcc.I3DSetAttrBool(trigger, 'I3D_castsShadows', True) - dcc.I3DSetAttrBool(trigger, 'I3D_receiveShadows', True) - dcc.I3DSetAttrBool(trigger, 'I3D_collisionMask', col_mask) - if stjerne_i3d: + if singleton_instance.giants_i3d: + trigger['I3D_collision'] = True + trigger['I3D_static'] = True + trigger['I3D_trigger'] = True + trigger['I3D_nonRenderable'] = True + trigger['I3D_castsShadows'] = True + trigger['I3D_receiveShadows'] = True + trigger['I3D_collisionMask'] = col_mask + if singleton_instance.stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'static' bpy.context.object.i3d_attributes.trigger = True bpy.context.object.i3d_attributes.collision_mask = col_mask_stjerne @@ -587,15 +587,15 @@ def create_trigger(self, name, col_mask, col_mask_stjerne, parent, size=(1.0, 1. bpy.ops.object.transform_apply(scale=True) bpy.context.active_object.parent = parent trigger = bpy.context.active_object.name - if giants_i3d: - dcc.I3DSetAttrBool(trigger, 'i3D_collision', True) - dcc.I3DSetAttrBool(trigger, 'I3D_static', True) - dcc.I3DSetAttrBool(trigger, 'I3D_trigger', True) - dcc.I3DSetAttrBool(trigger, 'I3D_nonRenderable', True) - dcc.I3DSetAttrBool(trigger, 'I3D_castsShadows', True) - dcc.I3DSetAttrBool(trigger, 'I3D_receiveShadows', True) - dcc.I3DSetAttrFloat(trigger, 'I3D_collisionMask', col_mask) - if stjerne_i3d: + if singleton_instance.giants_i3d: + trigger['I3D_collision'] = True + trigger['I3D_static'] = True + trigger['I3D_trigger'] = True + trigger['I3D_nonRenderable'] = True + trigger['I3D_castsShadows'] = True + trigger['I3D_receiveShadows'] = True + trigger['I3D_collisionMask'] = col_mask + if singleton_instance.stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'static' bpy.context.object.i3d_attributes.trigger = True bpy.context.object.i3d_attributes.collision_mask = col_mask_stjerne @@ -612,23 +612,23 @@ def create_exact_fill_root_node(self, name, parent, size=(1.0, 1.0, 1.0)): bpy.ops.object.transform_apply(scale=True) bpy.context.active_object.parent = parent bpy.ops.object.parent_set(type='OBJECT', keep_transform=False) - exact_fill_root_node = bpy.context.active_object.name - if giants_i3d: - dcc.I3DSetAttrBool(exact_fill_root_node, 'I3D_collision', True) - dcc.I3DSetAttrBool(exact_fill_root_node, 'I3D_static', True) - dcc.I3DSetAttrBool(exact_fill_root_node, 'I3D_trigger', True) - dcc.I3DSetAttrBool(exact_fill_root_node, 'I3D_nonRenderable', True) - dcc.I3DSetAttrBool(exact_fill_root_node, 'I3D_castsShadows', True) - dcc.I3DSetAttrBool(exact_fill_root_node, 'I3D_receiveShadows', True) - dcc.I3DSetAttrFloat(exact_fill_root_node, 'I3D_collisionMask', 1073741824) - if stjerne_i3d: + exact_fill_root_node = bpy.context.active_object + if singleton_instance.giants_i3d: + exact_fill_root_node['I3D_collision'] = True + exact_fill_root_node['I3D_static'] = True + exact_fill_root_node['I3D_trigger'] = True + exact_fill_root_node['I3D_nonRenderable'] = True + exact_fill_root_node['I3D_castsShadows'] = True + exact_fill_root_node['I3D_receiveShadows'] = True + exact_fill_root_node['I3D_collisionMask'] = 1073741824 + if singleton_instance.stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'static' bpy.context.object.i3d_attributes.collision_mask = "40000000" bpy.context.object.i3d_attributes.trigger = True bpy.context.object.data.i3d_attributes.casts_shadows = True bpy.context.object.data.i3d_attributes.receive_shadows = True bpy.context.object.data.i3d_attributes.non_renderable = True - return exact_fill_root_node + return exact_fill_root_node.name def create_plane(self, name, parent, size=(1.0, 1.0, 1.0)): bpy.ops.mesh.primitive_plane_add() @@ -638,8 +638,6 @@ def create_plane(self, name, parent, size=(1.0, 1.0, 1.0)): bpy.ops.object.transform_apply(scale=True) bpy.context.active_object.parent = parent plane = bpy.context.active_object.name - if giants_i3d: - I3DRemoveAttributes(plane) return plane def create_vehicle_component(self, name, data_name, size=(1.0, 1.0, 1.0), translate=(0, 0, 0)): @@ -651,17 +649,17 @@ def create_vehicle_component(self, name, data_name, size=(1.0, 1.0, 1.0), transl bpy.ops.object.transform_apply(scale=True) bpy.context.active_object.location = translate translate = (0, 0, 0) - component = bpy.context.active_object.name - if giants_i3d: - dcc.I3DSetAttrBool(component, 'I3D_dynamic', True) - dcc.I3DSetAttrBool(component, 'I3D_collision', True) - dcc.I3DSetAttrBool(component, 'I3D_compound', True) - dcc.I3DSetAttrFloat(component, 'I3D_collisionMask', 2109442) - dcc.I3DSetAttrFloat(component, 'I3D_clipDistance', 300.0) - dcc.I3DSetAttrBool(component, 'I3D_castsShadows', True) - dcc.I3DSetAttrBool(component, 'I3D_receiveShadows', True) - dcc.I3DSetAttrBool(component, 'I3D_nonRenderable', True) - if stjerne_i3d: + component = bpy.context.active_object + if singleton_instance.giants_i3d: + component['I3D_dynamic'] = True + component['I3D_collision'] = True + component['I3D_compound'] = True + component['I3D_collisionMask'] = 2109442.0 + component['I3D_clipDistance'] = 300.0 + component['I3D_castsShadows'] = True + component['I3D_receiveShadows'] = True + component['I3D_nonRenderable'] = True + if singleton_instance.stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'dynamic' bpy.context.object.i3d_attributes.compound = True bpy.context.object.i3d_attributes.collision_mask = "203002" diff --git a/tools/track_tools.py b/tools/track_tools.py index 25da548..270f703 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -25,9 +25,9 @@ import mathutils from mathutils import Vector -from ..helper_functions import check_obj_type, check_i3d_exporter_type +from ..helper_functions import Singleton -giants_i3d, stjerne_i3d, dcc, I3DRemoveAttributes = check_i3d_exporter_type() +singleton_instance = Singleton.get_instance() def get_curve_length(curve_obj): @@ -258,13 +258,13 @@ def execute(self, context): bpy.ops.object.empty_add(radius=0, location=original_loc) track_geo = bpy.context.object track_geo.name = f"{name}Geo" - if giants_i3d: - dcc.I3DSetAttrBool(track_geo.name, 'I3D_receiveShadows', True) - dcc.I3DSetAttrBool(track_geo.name, 'I3D_castsShadows', True) - dcc.I3DSetAttrBool(track_geo.name, 'I3D_clipDistance', 300) - dcc.I3DSetAttrBool(track_geo.name, 'I3D_mergeChildren', True) - dcc.I3DSetAttrBool(track_geo.name, 'I3D_objectDataExportOrientation', True) - dcc.I3DSetAttrBool(track_geo.name, 'I3D_objectDataExportPosition', True) + if singleton_instance.giants_i3d: + track_geo['I3D_receiveShadows'] = True + track_geo['I3D_castsShadows'] = True + track_geo['I3D_clipDistance'] = 300.00 + track_geo['I3D_mergeChildren'] = True + track_geo['I3D_objectDataExportOrientation'] = True + track_geo['I3D_objectDataExportPosition'] = True obj_name = track_geo.name dim_x = original_obj.dimensions[0] create_bbox(context.scene.i3dea.all_curves, name, obj_name, dim_x) @@ -389,8 +389,8 @@ def create_bbox(curve_name, name, obj_name, dim_x): vert.co = matrix @ vert.co bbox.matrix_world.identity() - if giants_i3d: - dcc.I3DSetAttrString(bbox.name, 'I3D_boundingVolume', obj_name) + if singleton_instance.giants_i3d: + bbox['I3D_boundingVolume'] = obj_name bbox.hide_set(True) return bbox diff --git a/tools/verifier.py b/tools/verifier.py index 6d3f3e7..2dc2649 100644 --- a/tools/verifier.py +++ b/tools/verifier.py @@ -18,6 +18,14 @@ # Test the whole scene to check if there is any issues in the setup before export to i3d + +""" +TODO: + - See if it's possible to do more with fill volume checker + - Make it possible to scale the curve to get an rounded value for the track amount +""" + + import bpy from mathutils import Vector diff --git a/ui.py b/ui.py index d0d860c..443d8c3 100644 --- a/ui.py +++ b/ui.py @@ -1,6 +1,8 @@ import bpy -from .helper_functions import check_i3d_exporter_type +from .helper_functions import Singleton + +singleton_instance = Singleton.get_instance() class I3DEA_PT_panel(bpy.types.Panel): @@ -13,8 +15,7 @@ class I3DEA_PT_panel(bpy.types.Panel): def draw(self, context): layout = self.layout - giants_i3d, stjerne_i3d, dcc, I3DRemoveAttributes = check_i3d_exporter_type() - if giants_i3d and stjerne_i3d: + if singleton_instance.giants_i3d and singleton_instance.stjerne_i3d: # "Exporter selection" box layout.label(text="Both Giants & Stjerne I3D exporter is enabled", icon='ERROR') layout.label(text="Recommend to disable one of them as it can cause some issues") @@ -26,20 +27,20 @@ def draw(self, context): # expanded view if context.scene.i3dea.UI_meshTools: row = box.row() - row.operator("i3dea.copy_orientation", text="Copy Location").state = 1 - row.operator("i3dea.copy_orientation", text="Copy Rotation").state = 2 + # row.operator("i3dea.copy_orientation", text="Copy Location").state = 1 + # row.operator("i3dea.copy_orientation", text="Copy Rotation").state = 2 row = box.row() - row.operator("i3dea.remove_doubles", text="Clean Meshes") - row.operator("i3dea.mesh_name", text="Set Mesh Name") + # row.operator("i3dea.remove_doubles", text="Clean Meshes") + # row.operator("i3dea.mesh_name", text="Set Mesh Name") row = box.row() - row.operator("i3dea.mirror_orientation", text="Set mirror orientation") - row.operator("i3dea.fill_volume", text="Check Fill Volume") - if giants_i3d: + # row.operator("i3dea.mirror_orientation", text="Set mirror orientation") + # row.operator("i3dea.fill_volume", text="Check Fill Volume") + if singleton_instance.giants_i3d: row = box.row() - row.operator("i3dea.ignore", text="Add Suffix _ignore") - row.operator("i3dea.xml_config", text="Enable export to i3dMappings") + # row.operator("i3dea.ignore", text="Add Suffix _ignore") + # row.operator("i3dea.xml_config", text="Enable export to i3dMappings") row = box.row() - row.operator("i3dea.verify_scene", text="Verify Scene") + # row.operator("i3dea.verify_scene", text="Verify Scene") col = box.column() box = col.box() row = box.row() @@ -201,7 +202,7 @@ def draw(self, context): row.prop(context.scene.i3dea, "normal_texture_path", text="Normal") row = box.row() row.operator("i3dea.setup_material", text="Create " + bpy.context.scene.i3dea.material_name) - if stjerne_i3d: + if singleton_instance.stjerne_i3d: box = col.box() row = box.row() row.prop(context.scene.i3dea, "UI_paths", text="Add paths to material", icon='TRIA_DOWN' if context.scene.i3dea.UI_paths else 'TRIA_RIGHT', icon_only=False, emboss=False) From 8213da35230859e150e91a6baf4be1819a758195 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Sun, 22 Jan 2023 04:12:44 +0100 Subject: [PATCH 09/16] Changed check exporter type function --- __init__.py | 1 - helper_functions.py | 36 +++++++------------------ new_ui.py | 13 ++++----- tools/generate_empty_on_curves.py | 6 ++--- tools/material_tools.py | 12 ++++----- tools/skeletons.py | 45 +++++++++++++++---------------- tools/track_tools.py | 4 +-- ui.py | 10 +++---- 8 files changed, 54 insertions(+), 73 deletions(-) diff --git a/__init__.py b/__init__.py index 32ede3b..7043311 100644 --- a/__init__.py +++ b/__init__.py @@ -35,7 +35,6 @@ importlib.reload(tools) else: import bpy - from .helper_functions import Singleton from . import properties, ui, new_ui from .tools import ( assets_importer, diff --git a/helper_functions.py b/helper_functions.py index 200c2bc..49ba29c 100644 --- a/helper_functions.py +++ b/helper_functions.py @@ -1,33 +1,15 @@ import bpy -class Singleton: - __instance = None - - def __init__(self): - if Singleton.__instance is not None: - raise Exception("This class is a singleton!") - else: - Singleton.__instance = self - self.giants_i3d = False - self.stjerne_i3d = False - self.check_i3d_exporter_type() - - @classmethod - def get_instance(cls): - if Singleton.__instance is None: - Singleton() - return Singleton.__instance - - def check_i3d_exporter_type(self): - if "io_export_i3d" in bpy.context.preferences.addons: - self.giants_i3d = True - else: - self.giants_i3d = False - if "i3dio" in bpy.context.preferences.addons: - self.stjerne_i3d = True - else: - self.stjerne_i3d = False +def check_i3d_exporter_type(): + giants_i3d = False + stjerne_i3d = False + for a in bpy.context.preferences.addons: + if a.module == "io_export_i3d": + giants_i3d = True + if a.module == "i3dio": + stjerne_i3d = True + return giants_i3d, stjerne_i3d def check_obj_type(obj): diff --git a/new_ui.py b/new_ui.py index f1b5be2..3d55455 100644 --- a/new_ui.py +++ b/new_ui.py @@ -1,9 +1,7 @@ import bpy from bpy.types import Panel, UIList -from .helper_functions import Singleton - -singleton_instance = Singleton.get_instance() +from .helper_functions import check_i3d_exporter_type class I3DEA_UL_pose_curves(UIList): @@ -28,8 +26,9 @@ class I3DEA_PT_MainPanel(I3deaPanel, Panel): bl_label = 'I3D Exporter Additionals' def draw(self, context): + giants_i3d, stjerne_i3d = check_i3d_exporter_type() layout = self.layout - if singleton_instance.giants_i3d and singleton_instance.stjerne_i3d: + if giants_i3d and stjerne_i3d: # "Exporter selection" box layout.label(text="Both Giants & Stjerne I3D exporter is enabled", icon='ERROR') layout.label(text="Recommend to disable one of them as it can cause some issues") @@ -41,6 +40,7 @@ class I3DEA_PT_GeneralTools(I3deaPanel, Panel): bl_parent_id = 'I3DEA_PT_MainPanel' def draw(self, context): + giants_i3d, stjerne_i3d = check_i3d_exporter_type() layout = self.layout col = layout.column(align=True) row = col.row(align=True) @@ -52,7 +52,7 @@ def draw(self, context): row = col.row(align=True) row.operator("i3dea.mesh_name", text="Set Mesh Name") row.operator("i3dea.fill_volume", text="Check Fill Volume") - if singleton_instance.giants_i3d: + if giants_i3d: row = col.row(align=True) row.operator("i3dea.xml_config", text="Enable export to i3dMappings") row.operator("i3dea.ignore", text="Add Suffix _ignore") @@ -67,7 +67,8 @@ class I3DEA_PT_UserAttributes(I3deaPanel, Panel): @classmethod def poll(cls, context): - return singleton_instance.giants_i3d + giants_i3d, stjerne_i3d = check_i3d_exporter_type() + return giants_i3d def draw(self, context): layout = self.layout diff --git a/tools/generate_empty_on_curves.py b/tools/generate_empty_on_curves.py index 42f5974..f705c87 100644 --- a/tools/generate_empty_on_curves.py +++ b/tools/generate_empty_on_curves.py @@ -1,7 +1,7 @@ import bpy -from ..helper_functions import Singleton +from ..helper_functions import check_i3d_exporter_type -singleton_instance = Singleton.get_instance() +giants_i3d, stjerne_i3d = check_i3d_exporter_type() class I3DEA_UL_selected_curves(bpy.types.UIList): @@ -124,7 +124,7 @@ def create_empties_on_curve(selected_curves, hierarchy_name, num_empties=10): # Create empty object "pose1" hierarchy_empty = create_empty(name=hierarchy_name) - if singleton_instance.giants_i3d: + if giants_i3d: hierarchy_empty['I3D_objectDataFilePath'] = hierarchy_name + ".dds" hierarchy_empty['I3D_objectDataHierarchicalSetup'] = True hierarchy_empty['I3D_objectDataHideFirstAndLastObject'] = True diff --git a/tools/material_tools.py b/tools/material_tools.py index 1641e4c..3677617 100644 --- a/tools/material_tools.py +++ b/tools/material_tools.py @@ -20,9 +20,9 @@ import bpy -from ..helper_functions import Singleton +from ..helper_functions import check_i3d_exporter_type -singleton_instance = Singleton.get_instance() +giants_i3d, stjerne_i3d = check_i3d_exporter_type() class I3DEA_OT_mirror_material(bpy.types.Operator): @@ -31,7 +31,7 @@ class I3DEA_OT_mirror_material(bpy.types.Operator): bl_description = "Adds mirror_mat to materials" def execute(self, context): - if singleton_instance.giants_i3d: + if giants_i3d: if context.scene.I3D_UIexportSettings.I3D_shaderFolderLocation == "": self.report({'ERROR'}, "Shader Folder location is not set!") return {'CANCELLED'} @@ -57,7 +57,7 @@ def execute(self, context): for i in range(len(obj.material_slots)): bpy.ops.object.material_slot_remove() obj.data.materials.append(mirror_mat) - if singleton_instance.giants_i3d: + if giants_i3d: bpy.context.object.active_material['customShader'] = "$data\\shaders\\mirrorShader.xml" bpy.context.object.active_material['shadingRate'] = "1x1" self.report({'INFO'}, "Created material: mirror_mat") @@ -119,9 +119,9 @@ def execute(self, context): except Exception as e: print(e) - if singleton_instance.giants_i3d: + if giants_i3d: links.new(img_tex_spec.outputs["Color"], principled.inputs["Specular"]) - if singleton_instance.stjerne_i3d: + if stjerne_i3d: sep_rgb = nodes.new("ShaderNodeSeparateRGB") sep_rgb.location = (-210, 90) links.new(img_tex_spec.outputs["Color"], sep_rgb.inputs["Image"]) diff --git a/tools/skeletons.py b/tools/skeletons.py index 9da23a9..d90295f 100644 --- a/tools/skeletons.py +++ b/tools/skeletons.py @@ -25,9 +25,9 @@ import bpy import math -from ..helper_functions import Singleton +from ..helper_functions import check_i3d_exporter_type -singleton_instance = Singleton.get_instance() +giants_i3d, stjerne_i3d = check_i3d_exporter_type() class I3DEA_OT_skeletons(bpy.types.Operator): @@ -344,14 +344,14 @@ def create_animal_husbandry(self): nav_root_node = self.create_skel_node("1_8_navigationRootNode", animal_husbandry) self.create_plane("navigationMesh", nav_root_node) walking_plane = self.create_plane("walkingPlane", nav_root_node) - if singleton_instance.giants_i3d: + if giants_i3d: walking_plane['I3D_collision'] = True walking_plane['I3D_static'] = True walking_plane['I3D_nonRenderable'] = True walking_plane['I3D_castsShadows'] = True walking_plane['I3D_receiveShadows'] = True walking_plane['I3D_collisionMask'] = 131072 - if singleton_instance.stjerne_i3d: + if stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'static' bpy.context.object.i3d_attributes.collision_mask = "20000" bpy.context.object.data.i3d_attributes.casts_shadows = True @@ -420,13 +420,13 @@ def create_placeable_elements(self, parent): self.create_collision("collision", 255, "ff", collisions) self.create_collision("tipCollision", 524288, "80000", collisions) self.create_collision("tipCollisionWall", 524288, "80000", collisions) - if singleton_instance.giants_i3d: + if giants_i3d: prop_name = 'userAttribute_float_collisionHeight' bpy.context.object[prop_name] = 4.0 bpy.context.object.id_properties_ensure() # Make sure the manager is updated property_manager = bpy.context.object.id_properties_ui(prop_name) property_manager.update(min=-200, max=200) - if singleton_instance.stjerne_i3d: + if stjerne_i3d: bpy.ops.i3dio_user_attribute_list.new_item() bpy.context.object.i3d_user_attributes.attribute_list[0].name = "collisionHeight" bpy.context.object.i3d_user_attributes.attribute_list[0].type = 'data_float' @@ -508,11 +508,11 @@ def create_light(self, name, parent, cone_angle, light_range, drop_off, rgb, tra light_transform.parent = parent light_transform.name = name light = light_transform.name - if singleton_instance.giants_i3d: + if giants_i3d: light['I3D_collision'] = False light['I3D_static'] = False light['I3D_clipDistance'] = 75.00 - if singleton_instance.stjerne_i3d: + if stjerne_i3d: bpy.context.object.i3d_attributes.clip_distance = 75 return light_transform @@ -526,11 +526,11 @@ def create_point_light(self, name, parent, light_range=18, rgb=(0.44, 0.4, 0.4), light.parent = parent light.name = name light = light.name - if singleton_instance.giants_i3d: + if giants_i3d: light['I3D_collision'] = False light['I3D_static'] = False light['I3D_clipDistance'] = clip_distance - if singleton_instance.stjerne_i3d: + if stjerne_i3d: bpy.context.object.i3d_attributes.clip_distance = clip_distance return light @@ -545,12 +545,11 @@ def create_spot_light(self, name, parent, cone_angle=math.radians(120), light_ra light = bpy.context.active_object light.parent = parent light.name = name - light = light.name if giants_i3d: - dcc.I3DSetAttrBool(light, 'I3D_collision', False) - dcc.I3DSetAttrBool(light, 'I3D_static', False) - dcc.I3DSetAttrFloat(light, 'I3D_clipDistance', clip_distance) - if singleton_instance.stjerne_i3d: + light['I3D_collision'] = False + light['I3D_static'] = False + light['I3D_clipDistance'] = clip_distance + if stjerne_i3d: bpy.context.object.i3d_attributes.clip_distance = clip_distance return light @@ -562,7 +561,7 @@ def create_collision(self, name, col_mask, col_mask_stjerne, parent, size=(1.0, bpy.ops.object.transform_apply(scale=True) bpy.context.active_object.parent = parent trigger = bpy.context.active_object.name - if singleton_instance.giants_i3d: + if giants_i3d: trigger['I3D_collision'] = True trigger['I3D_static'] = True trigger['I3D_trigger'] = True @@ -570,7 +569,7 @@ def create_collision(self, name, col_mask, col_mask_stjerne, parent, size=(1.0, trigger['I3D_castsShadows'] = True trigger['I3D_receiveShadows'] = True trigger['I3D_collisionMask'] = col_mask - if singleton_instance.stjerne_i3d: + if stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'static' bpy.context.object.i3d_attributes.trigger = True bpy.context.object.i3d_attributes.collision_mask = col_mask_stjerne @@ -587,7 +586,7 @@ def create_trigger(self, name, col_mask, col_mask_stjerne, parent, size=(1.0, 1. bpy.ops.object.transform_apply(scale=True) bpy.context.active_object.parent = parent trigger = bpy.context.active_object.name - if singleton_instance.giants_i3d: + if giants_i3d: trigger['I3D_collision'] = True trigger['I3D_static'] = True trigger['I3D_trigger'] = True @@ -595,7 +594,7 @@ def create_trigger(self, name, col_mask, col_mask_stjerne, parent, size=(1.0, 1. trigger['I3D_castsShadows'] = True trigger['I3D_receiveShadows'] = True trigger['I3D_collisionMask'] = col_mask - if singleton_instance.stjerne_i3d: + if stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'static' bpy.context.object.i3d_attributes.trigger = True bpy.context.object.i3d_attributes.collision_mask = col_mask_stjerne @@ -613,7 +612,7 @@ def create_exact_fill_root_node(self, name, parent, size=(1.0, 1.0, 1.0)): bpy.context.active_object.parent = parent bpy.ops.object.parent_set(type='OBJECT', keep_transform=False) exact_fill_root_node = bpy.context.active_object - if singleton_instance.giants_i3d: + if giants_i3d: exact_fill_root_node['I3D_collision'] = True exact_fill_root_node['I3D_static'] = True exact_fill_root_node['I3D_trigger'] = True @@ -621,7 +620,7 @@ def create_exact_fill_root_node(self, name, parent, size=(1.0, 1.0, 1.0)): exact_fill_root_node['I3D_castsShadows'] = True exact_fill_root_node['I3D_receiveShadows'] = True exact_fill_root_node['I3D_collisionMask'] = 1073741824 - if singleton_instance.stjerne_i3d: + if stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'static' bpy.context.object.i3d_attributes.collision_mask = "40000000" bpy.context.object.i3d_attributes.trigger = True @@ -650,7 +649,7 @@ def create_vehicle_component(self, name, data_name, size=(1.0, 1.0, 1.0), transl bpy.context.active_object.location = translate translate = (0, 0, 0) component = bpy.context.active_object - if singleton_instance.giants_i3d: + if giants_i3d: component['I3D_dynamic'] = True component['I3D_collision'] = True component['I3D_compound'] = True @@ -659,7 +658,7 @@ def create_vehicle_component(self, name, data_name, size=(1.0, 1.0, 1.0), transl component['I3D_castsShadows'] = True component['I3D_receiveShadows'] = True component['I3D_nonRenderable'] = True - if singleton_instance.stjerne_i3d: + if stjerne_i3d: bpy.context.object.i3d_attributes.rigid_body_type = 'dynamic' bpy.context.object.i3d_attributes.compound = True bpy.context.object.i3d_attributes.collision_mask = "203002" diff --git a/tools/track_tools.py b/tools/track_tools.py index 270f703..0c72d00 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -25,9 +25,9 @@ import mathutils from mathutils import Vector -from ..helper_functions import Singleton +from ..helper_functions import check_i3d_exporter_type -singleton_instance = Singleton.get_instance() +giants_i3d, stjerne_i3d = check_i3d_exporter_type() def get_curve_length(curve_obj): diff --git a/ui.py b/ui.py index 443d8c3..4dd6615 100644 --- a/ui.py +++ b/ui.py @@ -1,8 +1,8 @@ import bpy -from .helper_functions import Singleton +from .helper_functions import check_i3d_exporter_type -singleton_instance = Singleton.get_instance() +giants_i3d, stjerne_i3d = check_i3d_exporter_type() class I3DEA_PT_panel(bpy.types.Panel): @@ -15,7 +15,7 @@ class I3DEA_PT_panel(bpy.types.Panel): def draw(self, context): layout = self.layout - if singleton_instance.giants_i3d and singleton_instance.stjerne_i3d: + if giants_i3d and stjerne_i3d: # "Exporter selection" box layout.label(text="Both Giants & Stjerne I3D exporter is enabled", icon='ERROR') layout.label(text="Recommend to disable one of them as it can cause some issues") @@ -35,7 +35,7 @@ def draw(self, context): row = box.row() # row.operator("i3dea.mirror_orientation", text="Set mirror orientation") # row.operator("i3dea.fill_volume", text="Check Fill Volume") - if singleton_instance.giants_i3d: + if giants_i3d: row = box.row() # row.operator("i3dea.ignore", text="Add Suffix _ignore") # row.operator("i3dea.xml_config", text="Enable export to i3dMappings") @@ -202,7 +202,7 @@ def draw(self, context): row.prop(context.scene.i3dea, "normal_texture_path", text="Normal") row = box.row() row.operator("i3dea.setup_material", text="Create " + bpy.context.scene.i3dea.material_name) - if singleton_instance.stjerne_i3d: + if stjerne_i3d: box = col.box() row = box.row() row.prop(context.scene.i3dea, "UI_paths", text="Add paths to material", icon='TRIA_DOWN' if context.scene.i3dea.UI_paths else 'TRIA_RIGHT', icon_only=False, emboss=False) From ec0a0a4d5840c56a122d5271bb593e3b09ef4fab Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Mon, 23 Jan 2023 00:58:17 +0100 Subject: [PATCH 10/16] Getting closer to a proper new UI - Changed the Category from being in Giants I3D Exporter to be in its own "I3D Exporter Additionals" category - Started changing the track_tools.py to support the new layout - Cleaned up some properties - Moved UI classes to the bottom of the class list --- __init__.py | 16 ++++-- new_ui.py | 107 ++++++++++++++++++++++++++++++++++- properties.py | 129 ++++++++++++++++++++++++------------------- tools/track_tools.py | 56 ++++++++++++++----- ui.py | 36 ++++++------ 5 files changed, 249 insertions(+), 95 deletions(-) diff --git a/__init__.py b/__init__.py index 7043311..79cc390 100644 --- a/__init__.py +++ b/__init__.py @@ -18,10 +18,10 @@ bl_info = { "name": "I3D Exporter Additionals", "author": "T-Bone", - "description": "Additionals For Giants I3D Exporter", + "description": "Additionals For I3D Exporter", "blender": (3, 0, 0), "version": (2, 0, 8), - "location": "View3D > UI > GIANTS I3D Exporter > I3D Exporter Additionals", + "location": "View3D > UI > I3D Exporter Additionals > I3D Exporter Additionals", "warning": "", "category": "Game Engine" } @@ -51,10 +51,6 @@ classes = [ properties.I3DEA_custom_ObjectProps, properties.I3DEA_PG_List, - ui.I3DEA_PT_panel, - new_ui.I3DEA_PT_MainPanel, - new_ui.I3DEA_PT_GeneralTools, - new_ui.I3DEA_PT_UserAttributes, track_tools.I3DEA_OT_make_uvset, track_tools.I3DEA_OT_add_empty, track_tools.I3DEA_OT_curve_length, @@ -80,6 +76,14 @@ user_attributes.I3DEA_OT_create_user_attribute, user_attributes.I3DEA_OT_delete_user_attribute, verifier.I3DEA_OT_verify_scene, + ui.I3DEA_PT_panel, + new_ui.I3DEA_PT_MainPanel, + new_ui.I3DEA_PT_GeneralTools, + new_ui.I3DEA_PT_UserAttributes, + new_ui.I3DEA_PT_TrackTools, + new_ui.I3DEA_PT_TrackSetup, + new_ui.I3DEA_PT_TrackVisualization, + new_ui.I3DEA_PT_ArrayHierarchy, ] diff --git a/new_ui.py b/new_ui.py index 3d55455..a9e83d2 100644 --- a/new_ui.py +++ b/new_ui.py @@ -18,7 +18,7 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn class I3deaPanel: bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'GIANTS I3D Exporter NEW' + bl_category = 'I3D Exporter Additionals' class I3DEA_PT_MainPanel(I3deaPanel, Panel): @@ -38,6 +38,7 @@ class I3DEA_PT_GeneralTools(I3deaPanel, Panel): bl_idname = 'I3DEA_PT_GeneralTools' bl_label = 'General Tools' bl_parent_id = 'I3DEA_PT_MainPanel' + bl_options = {'DEFAULT_CLOSED'} def draw(self, context): giants_i3d, stjerne_i3d = check_i3d_exporter_type() @@ -64,6 +65,7 @@ class I3DEA_PT_UserAttributes(I3deaPanel, Panel): bl_idname = 'I3DEA_PT_UserAttributes' bl_label = 'User Attributes' bl_parent_id = 'I3DEA_PT_MainPanel' + bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): @@ -101,3 +103,106 @@ def draw(self, context): row.prop(context.scene.i3dea, "user_attribute_type", text="Type") row = col.row() row.operator("i3dea.create_user_attribute", text="Add") + + +class I3DEA_PT_TrackTools(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_TrackTools' + bl_label = 'Track Tools' + bl_parent_id = 'I3DEA_PT_MainPanel' + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + giants_i3d, stjerne_i3d = check_i3d_exporter_type() + layout = self.layout + col = layout.column(align=True) + row = col.row(align=True) + row.label(text="Hey") + + +class I3DEA_PT_TrackSetup(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_TrackSetup' + bl_label = 'Track Setup' + bl_parent_id = 'I3DEA_PT_TrackTools' + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + i3dea = context.scene.i3dea + layout = self.layout + + col = layout.column(align=True) + row = col.row(align=True) + row.prop(i3dea, "track_mode", expand=True) + + if i3dea.track_mode == 'MANUAL': + box = layout.box() + box_col = box.column(align=True) + box_col.label(text="Create Second UV") + box_row = box_col.row(align=True) + + box_row.prop(i3dea, "size_dropdown", text="") + box_row.operator("i3dea.make_uvset", text="Create UVset 2", icon="UV") + + box = layout.box() + box_col = box.column(align=True) + box_col.label(text="Add empties between selected objects") + box_row = box_col.row(align=True) + + box_row.prop(i3dea, "add_empty_int", text="") + box_row.operator("i3dea.add_empty", text="Add", icon='EMPTY_DATA') + + box = layout.box() + box_col = box.column(align=True) + box_col.label(text="Get length of selected curve") + box_row = box_col.row(align=True) + + box_row.prop(i3dea, "curve_length_disp", text="") + box_row.operator("i3dea.curve_length", text="Get Curve Length", icon='MOD_LENGTH') + + box = layout.box() + box_col = box.column(align=True) + box_col.label(text="Get length of selected curve") + box_row = box_col.row(align=True) + + box_row.prop(i3dea, "piece_distance", text="") + box_row.operator("i3dea.calculate_amount", text="Calculate Amount") + box_row = box_col.row(align=True) + box_row.prop(i3dea, "track_piece_amount", text="") + + +class I3DEA_PT_TrackVisualization(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_TrackVisualization' + bl_label = 'Track Visualization' + bl_parent_id = 'I3DEA_PT_TrackTools' + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + row = col.row(align=True) + row.prop(context.scene.i3dea, "track_type_method", expand=True) + + layout.use_property_split = True + layout.use_property_decorate = False + if context.scene.i3dea.track_type_method == 'CATERPILLAR': + col = layout.column(heading="Track Settings", align=True) + col.prop(context.scene.i3dea, "track_vis_amount") + col.prop(context.scene.i3dea, "track_vis_distance") + row = layout.row(align=True) + row.operator("i3dea.visualization", text="Track Visualization") + row.operator("i3dea.visualization_del", text="Delete") + + +class I3DEA_PT_ArrayHierarchy(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_ArrayHierarchy' + bl_label = 'Motion Path From Curves' + bl_parent_id = 'I3DEA_PT_MainPanel' + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + i3dea = context.scene.i3dea + layout = self.layout + + col = layout.column(align=True) + row = col.row(align=True) + row.label(text="Test") + # row.prop(i3dea, "track_mode", expand=True) diff --git a/properties.py b/properties.py index 2fde505..9c970e3 100644 --- a/properties.py +++ b/properties.py @@ -1,8 +1,16 @@ import bpy - +from bpy.props import ( + StringProperty, + BoolProperty, + IntProperty, + FloatProperty, + EnumProperty, + CollectionProperty, + PointerProperty +) class I3DEA_custom_ObjectProps(bpy.types.PropertyGroup): - curve_ref: bpy.props.PointerProperty( + curve_ref: PointerProperty( name="Object", type=bpy.types.Object ) @@ -10,7 +18,7 @@ class I3DEA_custom_ObjectProps(bpy.types.PropertyGroup): class I3DEA_PG_List(bpy.types.PropertyGroup): # Dropdown for UV size - size_dropdown: bpy.props.EnumProperty( + size_dropdown: EnumProperty( name="Size List", description="List of UV size", items=[ @@ -19,7 +27,7 @@ class I3DEA_PG_List(bpy.types.PropertyGroup): default='4') # Dropdown for skeletons - skeletons_dropdown: bpy.props.EnumProperty( + skeletons_dropdown: EnumProperty( items=[ ('create_base_vehicle', 'Tractor', "Add Tractor Skeleton"), ('create_base_harvester', 'Combine', "Add Harvester Skeleton"), @@ -37,7 +45,7 @@ class I3DEA_PG_List(bpy.types.PropertyGroup): description="List of skeletons") # Dropdown for assets import - assets_dropdown: bpy.props.EnumProperty( + assets_dropdown: EnumProperty( name="Assets List", description="List of assets", default='frontloaderAdapter', @@ -131,20 +139,20 @@ class I3DEA_PG_List(bpy.types.PropertyGroup): ('upperLinkSmall', "upperLinkSmall", "Import upperLinkSmall",), ]) # Properties for material_tools - material_name: bpy.props.StringProperty(name="Material name", description="Write name of the material you want to create", default="material_mat") - diffuse_box: bpy.props.BoolProperty(name="Add diffuse node", description="If checked it will create a image texture linked to Base Color", default=False) - alpha_box: bpy.props.BoolProperty(name="Alpha", description="If checked it will set alpha settings to diffuse node", default=False) - diffuse_texture_path: bpy.props.StringProperty(name="Diffuse", description="Add path to your diffuse texture.", subtype='FILE_PATH', default="") - spec_texture_path: bpy.props.StringProperty(name="Specular", description="Add path to your specular texture.", subtype='FILE_PATH', default="") - normal_texture_path: bpy.props.StringProperty(name="Normal", description="Add path to your normal map texture.", subtype='FILE_PATH', default="") + material_name: StringProperty(name="Material name", description="Write name of the material you want to create", default="material_mat") + diffuse_box: BoolProperty(name="Add diffuse node", description="If checked it will create a image texture linked to Base Color", default=False) + alpha_box: BoolProperty(name="Alpha", description="If checked it will set alpha settings to diffuse node", default=False) + diffuse_texture_path: StringProperty(name="Diffuse", description="Add path to your diffuse texture.", subtype='FILE_PATH', default="") + spec_texture_path: StringProperty(name="Specular", description="Add path to your specular texture.", subtype='FILE_PATH', default="") + normal_texture_path: StringProperty(name="Normal", description="Add path to your normal map texture.", subtype='FILE_PATH', default="") # i3dio_material handler - shader_path: bpy.props.StringProperty(name="Path to shader location", description="Select path to the shader you want to apply", subtype='FILE_PATH', default="") - mask_map: bpy.props.StringProperty(name="Mask Map", description="Add mask map texture", subtype='FILE_PATH', default="") - dirt_diffuse: bpy.props.StringProperty(name="Dirt diffuse", description="Add dirt diffuse texture", subtype='FILE_PATH', default="") - shader_box: bpy.props.BoolProperty(name="Set shader path", description="If checked it will add the the path to the shader in material", default=True) - mask_map_box: bpy.props.BoolProperty(name="Set mask map path", description="If checked it will add the the path to mask map in material", default=True) - dirt_diffuse_box: bpy.props.BoolProperty(name="Set dirt diffuse path", description="If checked it add the the path to dirt diffuse in material", default=True) + shader_path: StringProperty(name="Path to shader location", description="Select path to the shader you want to apply", subtype='FILE_PATH', default="") + mask_map: StringProperty(name="Mask Map", description="Add mask map texture", subtype='FILE_PATH', default="") + dirt_diffuse: StringProperty(name="Dirt diffuse", description="Add dirt diffuse texture", subtype='FILE_PATH', default="") + shader_box: BoolProperty(name="Set shader path", description="If checked it will add the the path to the shader in material", default=True) + mask_map_box: BoolProperty(name="Set mask map path", description="If checked it will add the the path to mask map in material", default=True) + dirt_diffuse_box: BoolProperty(name="Set dirt diffuse path", description="If checked it add the the path to dirt diffuse in material", default=True) # Track-Tools def get_all_curves(self, context): @@ -162,26 +170,33 @@ def get_all_curves(self, context): except: return curves - 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.FloatProperty(name="Track pieces possible along curve", description="The amount of track links that will fit along the curve", min=1, max=400, default=1) - rubber_track: bpy.props.BoolProperty(name="Rubber Track", description="Check this if you want to visualize a rubber track", default=False) - advanced_mode: bpy.props.BoolProperty(name="Advanced Mode", description="Add more options for UVset2 creation and Track setup", default=False) - all_curves: bpy.props.EnumProperty(items=get_all_curves, name="Select A Curve") - add_empties: bpy.props.BoolProperty(name="Add Empties", description="If you check this it will add the amount of empties between each track link that's written bellow.", default=False) - track_type_method: bpy.props.EnumProperty(name="Track Method", - items=[('CATERPILLAR', 'Caterpillar', ""), - ('RUBBER', 'Rubber', "",), - ('BOGIE', 'Bogie', "")], - description="Track visualization method, caterpillar, rubber or bogie", - default='CATERPILLAR') + track_mode: EnumProperty(name="Track Mode", + items=[('MANUAL', 'Manual Tools', ""), + ('AUTOMATIC', 'Automatic', "",)], + description="Track Mode", + default='MANUAL') + custom_text_box: BoolProperty(name="Custom name", description="If checked you will be able to add custom name for the track pieces", default=False) + custom_text: StringProperty(name="Custom track name", description="Set custom name", default="trackPiece") + add_empty_int: IntProperty(name="Number of empties add: ", description="Place your number", default=1, min=1, max=5) + piece_distance: FloatProperty(name="Track piece distance: ", description="Add track piece distance", default=0.2, precision=10, min=0.0001) + curve_length_disp: StringProperty(name="curve_length", default="0.0") + track_piece_amount: FloatProperty(name="Track pieces possible along curve", description="The amount of track links that will fit along the curve", min=1, max=400, default=1) + rubber_track: BoolProperty(name="Rubber Track", description="Check this if you want to visualize a rubber track", default=False) + advanced_mode: BoolProperty(name="Advanced Mode", description="Add more options for UVset2 creation and Track setup", default=False) + all_curves: EnumProperty(items=get_all_curves, name="Select A Curve") + add_empties: BoolProperty(name="Add Empties", description="If you check this it will add the amount of empties between each track link that's written bellow.", default=False) + track_type_method: EnumProperty(name="Track Method", + items=[('CATERPILLAR', 'Caterpillar', ""), + ('RUBBER', 'Rubber', "",), + ('BOGIE', 'Bogie', "")], + description="Track visualization method, caterpillar, rubber or bogie", + default='CATERPILLAR') + track_vis_amount: IntProperty(name="Amount of pieces", description="Amount of track pieces to use along the curve", default=1, min=1, max=200) + track_vis_distance: FloatProperty(name="Distance between links", description="Distance between each link", default=0.2, precision=6, min=0.0001, max=5, unit='LENGTH') # User Attribute properties.py - user_attribute_name: bpy.props.StringProperty(name="Name", description="Name of the User Attribute.") - user_attribute_type: bpy.props.EnumProperty( + user_attribute_name: StringProperty(name="Name", description="Name of the User Attribute.") + user_attribute_type: EnumProperty( name="Type", description="List of User Attributes", items=[ @@ -192,32 +207,32 @@ def get_all_curves(self, context): default='boolean') # Properties for Curve-Tools - curve_array_name: bpy.props.StringProperty(name="Array Name", description="Set array name", default="curveArray") - amount_curve: bpy.props.IntProperty(name="Static amount", description="Add empties on all curves with this amount", default=32, min=1) - distance_curve: bpy.props.FloatProperty(name="Distance", description="Add empties along curve by said distance", default=0.2, min=0.01) - use_amount: bpy.props.BoolProperty(name="Use Amount", description="When this is checked, it will use amount", default=True) - use_distance: bpy.props.BoolProperty(name="Use Distance", description="When this is checked, it will use distance", default=False) - use_pose2: bpy.props.BoolProperty(name="Use Pose 2", description="When this is checked, you will be able to use another set of curves.", default=False) - - active_obj_index: bpy.props.IntProperty() - object_collection: bpy.props.CollectionProperty( + curve_array_name: StringProperty(name="Array Name", description="Set array name", default="curveArray") + amount_curve: IntProperty(name="Static amount", description="Add empties on all curves with this amount", default=32, min=1) + distance_curve: FloatProperty(name="Distance", description="Add empties along curve by said distance", default=0.2, min=0.01) + use_amount: BoolProperty(name="Use Amount", description="When this is checked, it will use amount", default=True) + use_distance: BoolProperty(name="Use Distance", description="When this is checked, it will use distance", default=False) + use_pose2: BoolProperty(name="Use Pose 2", description="When this is checked, you will be able to use another set of curves.", default=False) + + active_obj_index: IntProperty() + object_collection: CollectionProperty( type=I3DEA_custom_ObjectProps ) - active_obj_index2: bpy.props.IntProperty() - object_collection2: bpy.props.CollectionProperty( + active_obj_index2: IntProperty() + object_collection2: CollectionProperty( type=I3DEA_custom_ObjectProps ) # Properties for UI in dropdowns - UI_meshTools: bpy.props.BoolProperty(name="Mesh-Tools", default=False) - UI_user_attributes: bpy.props.BoolProperty(name="User Attributes", default=False) - UI_track_tools: bpy.props.BoolProperty(name="UV-Tools", default=False) - UI_uvset: bpy.props.BoolProperty(name="UVset", default=False) - UI_skeletons: bpy.props.BoolProperty(name="Skeletons", default=False) - UI_curve_tools: bpy.props.BoolProperty(name="Curve-Tools", default=False) - UI_materialTools: bpy.props.BoolProperty(name="Material-Tools", default=False) - UI_create_mat: bpy.props.BoolProperty(name="Create material", default=False) - UI_paths: bpy.props.BoolProperty(name="Add paths to material", default=False) - UI_assets: bpy.props.BoolProperty(name="Assets Importer", default=False) - UI_active_obj: bpy.props.StringProperty(name="Active Object Name", default="") + UI_meshTools: BoolProperty(name="Mesh-Tools", default=False) + UI_user_attributes: BoolProperty(name="User Attributes", default=False) + UI_track_tools: BoolProperty(name="UV-Tools", default=False) + UI_uvset: BoolProperty(name="UVset", default=False) + UI_skeletons: BoolProperty(name="Skeletons", default=False) + UI_curve_tools: BoolProperty(name="Curve-Tools", default=False) + UI_materialTools: BoolProperty(name="Material-Tools", default=False) + UI_create_mat: BoolProperty(name="Create material", default=False) + UI_paths: BoolProperty(name="Add paths to material", default=False) + UI_assets: BoolProperty(name="Assets Importer", default=False) + UI_active_obj: StringProperty(name="Active Object Name", default="") diff --git a/tools/track_tools.py b/tools/track_tools.py index 0c72d00..e961418 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -25,7 +25,7 @@ import mathutils from mathutils import Vector -from ..helper_functions import check_i3d_exporter_type +from ..helper_functions import check_i3d_exporter_type, check_obj_type giants_i3d, stjerne_i3d = check_i3d_exporter_type() @@ -86,7 +86,7 @@ def execute(self, context): return {'CANCELLED'} else: curve_length = get_curve_length(context.object) - context.scene.i3dea.curve_length_disp = curve_length + context.scene.i3dea.curve_length_disp = str(round(curve_length, 6)) return {'FINISHED'} @@ -139,16 +139,9 @@ def execute(self, context): for piece, curve in zip(piece_list, curve_list): hierarchy_name = 'track_visualization' - space = bpy.context.scene.i3dea.piece_distance + space = context.scene.i3dea.track_vis_distance curve_length = get_curve_length(curve) if bpy.context.scene.i3dea.track_type_method == 'CATERPILLAR': - if bpy.context.scene.i3dea.track_piece_amount > 1: - piece_num = int(bpy.context.scene.i3dea.track_piece_amount) - else: - piece_num = 25 - self.report({'INFO'}, - f"No amount set in track piece amount, using default amount instead ({piece_num})") - bpy.ops.mesh.primitive_plane_add() plane = bpy.context.object plane.name = hierarchy_name @@ -161,7 +154,7 @@ def execute(self, context): 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["Array"].count = context.scene.i3dea.track_vis_amount plane.modifiers.new("Curve", 'CURVE') plane.modifiers["Curve"].object = curve plane.modifiers["Curve"].deform_axis = 'NEG_Y' @@ -190,6 +183,8 @@ def execute(self, context): elif bpy.context.scene.i3dea.track_type_method == 'BOGIE': self.report({'WARNING'}, "Bogie is not supported yet") + return {'CANCELLED'} + def stop_anim(scene): if scene.frame_current == 250: @@ -258,7 +253,7 @@ def execute(self, context): bpy.ops.object.empty_add(radius=0, location=original_loc) track_geo = bpy.context.object track_geo.name = f"{name}Geo" - if singleton_instance.giants_i3d: + if giants_i3d: track_geo['I3D_receiveShadows'] = True track_geo['I3D_castsShadows'] = True track_geo['I3D_clipDistance'] = 300.00 @@ -389,7 +384,7 @@ def create_bbox(curve_name, name, obj_name, dim_x): vert.co = matrix @ vert.co bbox.matrix_world.identity() - if singleton_instance.giants_i3d: + if giants_i3d: bbox['I3D_boundingVolume'] = obj_name bbox.hide_set(True) return bbox @@ -414,3 +409,38 @@ def create_from_amount(objects, amount): obj_list.append(new_object) return obj_list + +"""import bpy + +def scale_curve_to_integer(curve_object): + # Enter edit mode + bpy.ops.object.editmode_toggle() + + # Select all control points + bpy.ops.curve.select_all(action='SELECT') + + # Get the total length of the curve + length = curve_object.data.splines[0].calc_length(resolution=10000) + print(length) + + # Round the length to the nearest whole number + # rounded_length = round(length) + + # Calculate the scale factor + scale_factor = round(length) / length + + # Apply the scale factor to the curve object + bpy.ops.transform.resize(value=(scale_factor, scale_factor, scale_factor)) + + # Exit edit mode + bpy.ops.object.editmode_toggle() + +# Example usage: +# Select the curve object in Blender's 3D View +# then run: +bpy.ops.object.select_all(action='DESELECT') +bpy.context.object.select_set(True) +scale_curve_to_integer(bpy.context.object)""" + + + diff --git a/ui.py b/ui.py index 4dd6615..6703d5b 100644 --- a/ui.py +++ b/ui.py @@ -23,7 +23,7 @@ def draw(self, context): box = layout.box() row = box.row() # extend button for - row.prop(context.scene.i3dea, "UI_meshTools", text="Mesh-Tools", icon='TRIA_DOWN' if context.scene.i3dea.UI_meshTools else 'TRIA_RIGHT', icon_only=False, emboss=False) + """row.prop(context.scene.i3dea, "UI_meshTools", text="Mesh-Tools", icon='TRIA_DOWN' if context.scene.i3dea.UI_meshTools else 'TRIA_RIGHT', icon_only=False, emboss=False) # expanded view if context.scene.i3dea.UI_meshTools: row = box.row() @@ -73,7 +73,7 @@ def draw(self, context): row = box.row() row.prop(context.scene.i3dea, "user_attribute_type", text="Type") row = box.row() - row.operator("i3dea.create_user_attribute", text="Add") + row.operator("i3dea.create_user_attribute", text="Add")""" # "Track-Tools" Box box = layout.box() @@ -99,28 +99,28 @@ def draw(self, context): row = box.row() row.prop(context.scene.i3dea, "add_empties") row = box.row() - row.prop(context.scene.i3dea, "size_dropdown", text="") - row.operator("i3dea.make_uvset", text="Create UVset 2") - box = col.box() - row = box.row() - row.prop(context.scene.i3dea, "add_empty_int", text="") - 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="") + # row.prop(context.scene.i3dea, "size_dropdown", text="") + # row.operator("i3dea.make_uvset", text="Create UVset 2") + # box = col.box() + # row = box.row() + # row.prop(context.scene.i3dea, "add_empty_int", text="") + # 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.prop(context.scene.i3dea, "track_type_method", expand=True) - row = box.row() - row.operator("i3dea.visualization", text="Track Visualization") - row.operator("i3dea.visualization_del", text="Delete") + # box = col.box() + # row = box.row() + # row.prop(context.scene.i3dea, "track_type_method", expand=True) + # row = box.row() + # row.operator("i3dea.visualization", text="Track Visualization") + # row.operator("i3dea.visualization_del", text="Delete") # --------------------------------------------------------------- # "Curve-Tools" Box box = layout.box() From b3a5889a9c7eb8424ece7c6db3bc74f0fd7c8578 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Tue, 24 Jan 2023 01:24:33 +0100 Subject: [PATCH 11/16] Basically finished Array creator --- __init__.py | 12 +- helper_functions.py | 13 +- new_ui.py | 72 ++++++- properties.py | 34 +-- tools/generate_empty_on_curves.py | 337 ++++++++++++------------------ tools/track_tools.py | 13 +- ui.py | 74 +++---- 7 files changed, 279 insertions(+), 276 deletions(-) diff --git a/__init__.py b/__init__.py index 79cc390..2c1df98 100644 --- a/__init__.py +++ b/__init__.py @@ -49,7 +49,8 @@ ) classes = [ - properties.I3DEA_custom_ObjectProps, + properties.SubPoseItem, + properties.PoseItem, properties.I3DEA_PG_List, track_tools.I3DEA_OT_make_uvset, track_tools.I3DEA_OT_add_empty, @@ -63,9 +64,11 @@ mesh_tools.I3DEA_OT_mirror_orientation, mesh_tools.I3DEA_OT_xml_config, mesh_tools.I3DEA_OT_fill_volume, + generate_empty_on_curves.PoseAddOperator, + generate_empty_on_curves.PoseRemoveOperator, + generate_empty_on_curves.AddCurveOperator, + generate_empty_on_curves.RemoveCurveOperator, generate_empty_on_curves.I3DEA_OT_empties_along_curves, - generate_empty_on_curves.I3DEA_UL_selected_curves, - generate_empty_on_curves.I3DEA_UL_selected_curves2, skeletons.I3DEA_OT_skeletons, material_tools.I3DEA_OT_mirror_material, material_tools.I3DEA_OT_remove_duplicate_material, @@ -83,7 +86,10 @@ new_ui.I3DEA_PT_TrackTools, new_ui.I3DEA_PT_TrackSetup, new_ui.I3DEA_PT_TrackVisualization, + new_ui.I3DEA_UL_PoseList, + new_ui.I3DEA_UL_SubPoseCurveList, new_ui.I3DEA_PT_ArrayHierarchy, + new_ui.I3DEA_PT_SubArrayHierarchy, ] diff --git a/helper_functions.py b/helper_functions.py index 49ba29c..120d151 100644 --- a/helper_functions.py +++ b/helper_functions.py @@ -1,5 +1,5 @@ import bpy - +from mathutils import Vector def check_i3d_exporter_type(): giants_i3d = False @@ -20,3 +20,14 @@ def check_obj_type(obj): continue if not mode == 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT') + + +def get_curve_length(curve_obj): + """ + Returns length of curve and if the scale is not 1 1 1, it will be applied first to get the correct result + """ + if curve_obj.scale != Vector((1, 1, 1)): + print(f"{curve_obj.name} scale is not 1 1 1, scale will be applied.") + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + length = curve_obj.data.splines[0].calc_length(resolution=1024) + return length diff --git a/new_ui.py b/new_ui.py index a9e83d2..76e99af 100644 --- a/new_ui.py +++ b/new_ui.py @@ -112,11 +112,7 @@ class I3DEA_PT_TrackTools(I3deaPanel, Panel): bl_options = {'DEFAULT_CLOSED'} def draw(self, context): - giants_i3d, stjerne_i3d = check_i3d_exporter_type() - layout = self.layout - col = layout.column(align=True) - row = col.row(align=True) - row.label(text="Hey") + pass class I3DEA_PT_TrackSetup(I3deaPanel, Panel): @@ -192,6 +188,26 @@ def draw(self, context): row.operator("i3dea.visualization_del", text="Delete") +class I3DEA_UL_PoseList(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + if self.layout_type in {'DEFAULT', 'COMPACT'}: + layout.label(text=item.name) + elif self.layout_type == 'GRID': + layout.alignment = 'CENTER' + layout.label(text=item.name) + + +class I3DEA_UL_SubPoseCurveList(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + curve_ob = item.curve + curve_icon = 'OUTLINER_OB_CURVE' + if self.layout_type in {'DEFAULT', 'COMPACT'}: + layout.prop(curve_ob, "name", text="", emboss=False, icon=curve_icon) + elif self.layout_type == 'GRID': + layout.alignment = 'CENTER' + layout.label(text="", icon_value=icon) + + class I3DEA_PT_ArrayHierarchy(I3deaPanel, Panel): bl_idname = 'I3DEA_PT_ArrayHierarchy' bl_label = 'Motion Path From Curves' @@ -204,5 +220,47 @@ def draw(self, context): col = layout.column(align=True) row = col.row(align=True) - row.label(text="Test") - # row.prop(i3dea, "track_mode", expand=True) + row.template_list("I3DEA_UL_PoseList", "", i3dea, "pose_list", i3dea, "pose_count", rows=1) + col = row.column(align=True) + col.operator("i3dea.add_pose", text="", icon='ADD') + col.operator("i3dea.remove_pose", text="", icon='REMOVE') + + +class I3DEA_PT_SubArrayHierarchy(I3deaPanel, Panel): + bl_label = "Sub Pose List" + bl_parent_id = "I3DEA_PT_ArrayHierarchy" + + @classmethod + def poll(cls, context): + return context.scene.i3dea.pose_list + + def draw(self, context): + i3dea = context.scene.i3dea + layout = self.layout + pose_list = context.scene.i3dea.pose_list + if pose_list: + selected_pose = pose_list[context.scene.i3dea.pose_count] + layout.label(text=str(pose_list[context.scene.i3dea.pose_count].name)) + row = layout.row() + row.template_list("I3DEA_UL_SubPoseCurveList", "", selected_pose, "sub_pose_list", selected_pose, "sub_pose_count", rows=1) + row = layout.row(align=True) + row.operator("i3dea.add_curve", text="Add Curves", icon='ADD') + row.operator("i3dea.remove_curve", text="Remove All", icon='CANCEL').remove_all = True + row.operator("i3dea.remove_curve", text="Remove", icon='REMOVE').remove_all = False + + box = layout.box() + box_col = box.column(align=True) + box_col.label(text="Settings for array creation") + box_row = box_col.row(align=True) + + box_row.prop(i3dea, "motion_type", expand=True) + box_row = box_col.row(align=True) + box_row.prop(i3dea, "motion_amount_rel") + box_row.prop(i3dea, "motion_amount_fix") + box_row.prop(i3dea, "motion_distance") + box_row = box_col.row() + box_row.label(text="") + box_row = box_col.row(align=True) + box_row.prop(i3dea, "motion_hierarchy_name") + box_row = box_col.row(align=True) + box_row.operator("i3dea.add_empties_curves", text="Create") diff --git a/properties.py b/properties.py index 9c970e3..3646243 100644 --- a/properties.py +++ b/properties.py @@ -9,11 +9,15 @@ PointerProperty ) -class I3DEA_custom_ObjectProps(bpy.types.PropertyGroup): - curve_ref: PointerProperty( - name="Object", - type=bpy.types.Object - ) + +class SubPoseItem(bpy.types.PropertyGroup): + curve: bpy.props.PointerProperty(type=bpy.types.Object) + + +class PoseItem(bpy.types.PropertyGroup): + name: bpy.props.StringProperty(name="Name", default="Pose") + sub_pose_list: bpy.props.CollectionProperty(type=SubPoseItem) + sub_pose_count: bpy.props.IntProperty(default=0) class I3DEA_PG_List(bpy.types.PropertyGroup): @@ -214,15 +218,17 @@ def get_all_curves(self, context): use_distance: BoolProperty(name="Use Distance", description="When this is checked, it will use distance", default=False) use_pose2: BoolProperty(name="Use Pose 2", description="When this is checked, you will be able to use another set of curves.", default=False) - active_obj_index: IntProperty() - object_collection: CollectionProperty( - type=I3DEA_custom_ObjectProps - ) - - active_obj_index2: IntProperty() - object_collection2: CollectionProperty( - type=I3DEA_custom_ObjectProps - ) + # Motion Path From Curves + pose_list: bpy.props.CollectionProperty(type=PoseItem) + pose_count: bpy.props.IntProperty(default=0) + motion_type: EnumProperty(name="Motion Types", + items=[('AMOUNT_REL', 'Fixed Amount', "Places empties on every curve in equal distances per curve"), + ('AMOUNT_FIX', 'Distance from Amount', "Places empties on longest curve in equal distance. Apply this distance on other curves",), + ('DISTANCE', 'Fixed Distance', "Places Objects in fixed equal distance")]) + motion_amount_rel: IntProperty(name="AmountRel", default=32) + motion_amount_fix: IntProperty(name="AmountFix", default=32) + motion_distance: FloatProperty(name="AmountFix", default=0.2) + motion_hierarchy_name: StringProperty(name="Array Name", default="curveArray") # Properties for UI in dropdowns UI_meshTools: BoolProperty(name="Mesh-Tools", default=False) diff --git a/tools/generate_empty_on_curves.py b/tools/generate_empty_on_curves.py index f705c87..d1ba625 100644 --- a/tools/generate_empty_on_curves.py +++ b/tools/generate_empty_on_curves.py @@ -1,225 +1,158 @@ import bpy -from ..helper_functions import check_i3d_exporter_type +from ..helper_functions import check_i3d_exporter_type, get_curve_length giants_i3d, stjerne_i3d = check_i3d_exporter_type() -class I3DEA_UL_selected_curves(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - curve_icon = 'OUTLINER_OB_CURVE' - layout.prop(item, "curve_ref", text="", emboss=False, translate=False, icon=curve_icon) - - -class I3DEA_UL_selected_curves2(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - curve_icon = 'OUTLINER_OB_CURVE' - layout.prop(item, "curve_ref", text="", emboss=False, translate=False, icon=curve_icon) - - -class I3DEA_OT_empties_along_curves(bpy.types.Operator): - bl_label = "Create empties along curves" - bl_idname = "i3dea.add_empties_curves" - bl_description = "Create empties evenly spread along selected curves" - bl_options = {'UNDO'} - state: bpy.props.IntProperty() - - # Store the selected objects (curves) - selected_curves = [[], []] - - def load_curves(self, context, pose2=False): - for curve in context.selected_objects: - if curve.type == 'CURVE': - if not pose2: - if curve.name not in self.selected_curves[0]: - self.selected_curves[0].append(curve.name) - elif pose2: - if curve.name not in self.selected_curves[1]: - self.selected_curves[1].append(curve.name) +class PoseAddOperator(bpy.types.Operator): + bl_label = "Add Pose" + bl_idname = "i3dea.add_pose" def execute(self, context): - if self.state == 1: - self.load_curves(context) - - object_names = [item.name for item in context.scene.i3dea.object_collection] - for curve_name in self.selected_curves[0]: - curve = bpy.data.objects[curve_name] - if curve.type == 'CURVE' and curve.name not in object_names: - my_curve = context.scene.i3dea.object_collection.add() - my_curve.name = curve.name - my_curve.curve_ref = curve - - context.scene.i3dea.active_obj_index = len(context.scene.i3dea.object_collection) - 1 - - elif self.state == 2: - context.scene.i3dea.object_collection.clear() - context.scene.i3dea.active_obj_index = -1 + pose_count = context.scene.i3dea.pose_count + pose_list = context.scene.i3dea.pose_list + while True: + pose_count += 1 + name = "pose" + str(pose_count) + existing = any(item.name == name for item in pose_list) + if not existing: + break + item = pose_list.add() + item.name = name + return {'FINISHED'} - self.selected_curves[0].clear() - elif self.state == 3: - if len(context.scene.i3dea.object_collection) > 0: - ob_list = context.scene.i3dea.object_collection - active = context.scene.i3dea.active_obj_index - active_obj = context.scene.i3dea.object_collection[context.scene.i3dea.active_obj_index].curve_ref.name +class PoseRemoveOperator(bpy.types.Operator): + bl_label = "Remove Pose" + bl_idname = "i3dea.remove_pose" - self.selected_curves[0].remove(active_obj) + @classmethod + def poll(cls, context): + return context.scene.i3dea.pose_list - ob_list.remove(active) - context.scene.i3dea.active_obj_index = min(max(0, active - 1), len(ob_list) - 1) - else: - pass + def execute(self, context): + pose_list = context.scene.i3dea.pose_list + index = max(0, len(pose_list) - 1) + pose_list.remove(index) + context.scene.i3dea.pose_count = max(0, context.scene.i3dea.pose_count - 1) + return {'FINISHED'} - if self.state == 4: - self.load_curves(context, pose2=True) - object_names2 = [item.name for item in context.scene.i3dea.object_collection2] - for curve_name in self.selected_curves[1]: - curve = bpy.data.objects[curve_name] - if curve.type == 'CURVE' and curve.name not in object_names2: - my_curve = context.scene.i3dea.object_collection2.add() - my_curve.name = curve.name - my_curve.curve_ref = curve +class AddCurveOperator(bpy.types.Operator): + bl_label = "Add Curve" + bl_idname = "i3dea.add_curve" - context.scene.i3dea.active_obj_index2 = len(context.scene.i3dea.object_collection2) - 1 + def execute(self, context): + pose_list = context.scene.i3dea.pose_list + selected_pose = pose_list[context.scene.i3dea.pose_count] + sub_pose_list = selected_pose.sub_pose_list + for obj in context.selected_objects: + if obj.type == 'CURVE': + if not any(sub_pose.curve == obj for sub_pose in sub_pose_list): + sub_pose = sub_pose_list.add() + sub_pose.curve = obj + return {'FINISHED'} - print(self.selected_curves[1]) - elif self.state == 5: - context.scene.i3dea.object_collection2.clear() - context.scene.i3dea.active_obj_index2 = -1 +class RemoveCurveOperator(bpy.types.Operator): + bl_label = "Remove Curve" + bl_idname = "i3dea.remove_curve" + remove_all: bpy.props.BoolProperty() - self.selected_curves[1].clear() + @classmethod + def poll(cls, context): + pose_list = context.scene.i3dea.pose_list + selected_pose = pose_list[context.scene.i3dea.pose_count] + return selected_pose.sub_pose_list - elif self.state == 6: - if len(context.scene.i3dea.object_collection2) > 0: - ob_list = context.scene.i3dea.object_collection2 - active = context.scene.i3dea.active_obj_index2 - active_obj = context.scene.i3dea.object_collection2[context.scene.i3dea.active_obj_index2].curve_ref.name + def execute(self, context): + pose_list = context.scene.i3dea.pose_list + selected_pose = pose_list[context.scene.i3dea.pose_count] + + if self.remove_all: + selected_pose.sub_pose_list.clear() + else: + sub_pose_list = selected_pose.sub_pose_list + index = selected_pose.sub_pose_count + sub_pose_list.remove(index) + selected_pose.sub_pose_count = min(max(0, index - 1), len(sub_pose_list) - 1) + return {'FINISHED'} - self.selected_curves[1].remove(active_obj) - ob_list.remove(active) - context.scene.i3dea.active_obj_index2 = min(max(0, active - 1), len(ob_list) - 1) - else: - pass +class I3DEA_OT_empties_along_curves(bpy.types.Operator): + bl_label = "Create empties along curves" + bl_idname = "i3dea.add_empties_curves" + bl_description = "Create empties evenly spread along selected curves" + bl_options = {'UNDO'} - elif self.state == 7: - # Create empty objects along selected curves - create_empties_on_curve(self.selected_curves, context.scene.i3dea.curve_array_name, num_empties=context.scene.i3dea.amount_curve) - self.report({'INFO'}, "Generated empties a long selected curves") + def __create_empty(self, empty_type='PLAIN_AXES', location=(0, 0, 0), name="empty"): + """Creates an empty object at the origin""" + bpy.ops.object.empty_add(type=empty_type, radius=0.25, location=location) + empty = bpy.context.object + empty.name = name + return empty + + def __create_empties_on_curve(self, hierarchy=""): + """Creates empty objects along each selected curve object""" + # Create empty object "pose1" + hierarchy_empty = self.__create_empty(name=hierarchy) + + if giants_i3d: + hierarchy_empty['I3D_objectDataFilePath'] = hierarchy + ".dds" + hierarchy_empty['I3D_objectDataHierarchicalSetup'] = True + hierarchy_empty['I3D_objectDataHideFirstAndLastObject'] = True + hierarchy_empty['I3D_objectDataExportPosition'] = True + hierarchy_empty['I3D_objectDataExportOrientation'] = True + hierarchy_empty['I3D_objectDataExportScale'] = True + + # for curve in bpy.context.scene.i3dea.pose_list.sub_pose_list: + # pass + + for pose in bpy.context.scene.i3dea.pose_list: + pose_empty = self.__create_empty(name=pose.name) + pose_empty.parent = hierarchy_empty + longest_curve_length = 0 + for curve in pose.sub_pose_list: + curve_length = get_curve_length(curve.curve) + if curve_length > longest_curve_length: + longest_curve_length = curve_length + + for curve in pose.sub_pose_list: + curve_empty = self.__create_empty(name=curve.curve.name + "_Y") + curve_empty.parent = pose_empty + amount = 0 + if bpy.context.scene.i3dea.motion_type == "AMOUNT_REL": + amount = bpy.context.scene.i3dea.motion_amount_rel + if bpy.context.scene.i3dea.motion_type == "DISTANCE": + c_length = get_curve_length(curve.curve) + amount = int(c_length / bpy.context.scene.i3dea.motion_distance) + if bpy.context.scene.i3dea.motion_type == "AMOUNT_FIX": + c_length = get_curve_length(curve.curve) + distance = longest_curve_length / bpy.context.scene.i3dea.motion_amount_fix + amount = int(c_length / distance) + for i in range(amount): + x_empty = self.__create_empty(empty_type='ARROWS', name=f"{curve.curve.name}_X_{i:03d}") + # Set object constraint to follow curve + x_empty.constraints.new('FOLLOW_PATH') + x_empty.constraints['Follow Path'].target = bpy.data.objects[curve.curve.name] + x_empty.constraints['Follow Path'].use_curve_radius = False + x_empty.constraints['Follow Path'].use_fixed_location = True + x_empty.constraints['Follow Path'].use_curve_follow = True + x_empty.constraints['Follow Path'].forward_axis = 'FORWARD_Y' + x_empty.constraints['Follow Path'].up_axis = 'UP_Z' + + # Set object parent based on curve name + x_empty.parent = curve_empty + + # Set offset factor for empty along curve + x_empty.constraints['Follow Path'].offset_factor = i / (amount - 1) + + # Apply the constraint + bpy.ops.constraint.apply({'constraint': x_empty.constraints["Follow Path"]}, + constraint='Follow Path') + def execute(self, context): + i3dea = context.scene.i3dea + self.__create_empties_on_curve(hierarchy=i3dea.motion_hierarchy_name) + self.report({'INFO'}, "Generated empties a long selected curves") return {'FINISHED'} - - -def create_empty(empty_type='PLAIN_AXES', location=(0, 0, 0), name="empty"): - """Creates an empty object at the origin""" - bpy.ops.object.empty_add(type=empty_type, radius=0.25, location=location) - empty = bpy.context.object - empty.name = name - return empty - - -def create_empties_on_curve(selected_curves, hierarchy_name, num_empties=10): - """Creates empty objects along each selected curve object""" - # Create empty object "pose1" - hierarchy_empty = create_empty(name=hierarchy_name) - - if giants_i3d: - hierarchy_empty['I3D_objectDataFilePath'] = hierarchy_name + ".dds" - hierarchy_empty['I3D_objectDataHierarchicalSetup'] = True - hierarchy_empty['I3D_objectDataHideFirstAndLastObject'] = True - hierarchy_empty['I3D_objectDataExportPosition'] = True - hierarchy_empty['I3D_objectDataExportOrientation'] = True - hierarchy_empty['I3D_objectDataExportScale'] = True - - # Create empty object "pose1" - pose1 = create_empty(name="pose1") - pose1.parent = hierarchy_empty - - # Create empty object "pose2" - pose2 = None - if bpy.context.scene.i3dea.use_pose2 and len(selected_curves[1]) > 1: - pose2 = create_empty(name="pose2") - pose2.parent = hierarchy_empty - - num1, num2 = -1, -1 - for curve1 in selected_curves[0]: - # Create Y empties inside pose1 - # Set empty name based on curve name - num1 += 1 - empty_name = f"pose1_Y_{num1:03d}" - - y_empty = create_empty(name=empty_name) - - # Set object parent to the pose1 empties - y_empty.parent = pose1 - - curve1_length = bpy.data.objects[curve1].data.splines[0].calc_length() - - for i in range(num_empties): - # Set empty name based on curve name - empty_x_name = f"pose1_X_{i:03d}" - x_empty = create_empty(empty_type='ARROWS', name=empty_x_name) - - # Set object constraint to follow curve - x_empty.constraints.new('FOLLOW_PATH') - x_empty.constraints['Follow Path'].target = bpy.data.objects[curve1] - x_empty.constraints['Follow Path'].use_curve_radius = False - x_empty.constraints['Follow Path'].use_fixed_location = True - x_empty.constraints['Follow Path'].use_curve_follow = True - x_empty.constraints['Follow Path'].forward_axis = 'FORWARD_Y' - x_empty.constraints['Follow Path'].up_axis = 'UP_Z' - - # Set object parent based on curve name - x_empty.parent = y_empty - - # Set offset factor for empty along curve - if bpy.context.scene.i3dea.use_amount: - x_empty.constraints['Follow Path'].offset_factor = i / (num_empties - 1) - elif bpy.context.scene.i3dea.use_distance: - amount1 = bpy.context.scene.i3dea.distance_curve / curve1_length - x_empty.constraints['Follow Path'].offset_factor = i / (amount1 - 1) - - # Apply the constraint - bpy.ops.constraint.apply({'constraint': x_empty.constraints["Follow Path"]}, constraint='Follow Path') - - if bpy.context.scene.i3dea.use_pose2: - for curve2 in selected_curves[1]: - # Create Y empties inside pose2 - # Set empty name based on curve name - num2 += 1 - empty2_name = f"pose2_Y_{num2:03d}" - - y_empty2 = create_empty(name=empty2_name) - - # Set object parent to pose2 empties - y_empty2.parent = pose2 - - curve2_length = bpy.data.objects[curve2].data.splines[0].calc_length() - amount2 = bpy.context.scene.i3dea.distance_curve / curve2_length - - for i in range(num_empties): - # Set empty name based on curve name - empty2_x_name = f"pose2_X_{i:03d}" - x_empty2 = create_empty(empty_type='ARROWS', name=empty2_x_name) - - # Set object constraint to follow curve - x_empty2.constraints.new('FOLLOW_PATH') - x_empty2.constraints['Follow Path'].target = bpy.data.objects[curve2] - x_empty2.constraints['Follow Path'].use_curve_radius = False - x_empty2.constraints['Follow Path'].use_fixed_location = True - x_empty2.constraints['Follow Path'].use_curve_follow = True - x_empty2.constraints['Follow Path'].forward_axis = 'FORWARD_Y' - x_empty2.constraints['Follow Path'].up_axis = 'UP_Z' - - # Set object parent based on curve name - x_empty2.parent = y_empty2 - - # Set offset factor for empty along curve - if bpy.context.scene.i3dea.use_amount: - x_empty2.constraints['Follow Path'].offset_factor = i / (num_empties - 1) - elif bpy.context.scene.i3dea.use_distance: - x_empty2.constraints['Follow Path'].offset_factor = amount2 - - # Apply the constraint - bpy.ops.constraint.apply({'constraint': x_empty2.constraints["Follow Path"]}, constraint='Follow Path') diff --git a/tools/track_tools.py b/tools/track_tools.py index e961418..748c0cd 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -25,22 +25,11 @@ import mathutils from mathutils import Vector -from ..helper_functions import check_i3d_exporter_type, check_obj_type +from ..helper_functions import check_i3d_exporter_type, check_obj_type, get_curve_length giants_i3d, stjerne_i3d = check_i3d_exporter_type() -def get_curve_length(curve_obj): - """ - Returns length of curve and if the scale is not 1 1 1, it will be applied first to get the correct result - """ - if curve_obj.scale != Vector((1, 1, 1)): - print(f"{curve_obj.name} scale is not 1 1 1, scale will be applied.") - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) - length = curve_obj.data.splines[0].calc_length(resolution=1024) - return length - - def create_empties(objs, amount): """ It will add x amount of empties in between each object diff --git a/ui.py b/ui.py index 6703d5b..147756c 100644 --- a/ui.py +++ b/ui.py @@ -109,12 +109,12 @@ def draw(self, context): # 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.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.prop(context.scene.i3dea, "track_type_method", expand=True) @@ -123,42 +123,42 @@ def draw(self, context): # row.operator("i3dea.visualization_del", text="Delete") # --------------------------------------------------------------- # "Curve-Tools" Box - box = layout.box() - row = box.row() + # box = layout.box() + # row = box.row() # expand button for "Curve Tools" - row.prop(context.scene.i3dea, "UI_curve_tools", text="Curve-Tools", icon='TRIA_DOWN' if context.scene.i3dea.UI_curve_tools else 'TRIA_RIGHT', icon_only=False, emboss=False) + # row.prop(context.scene.i3dea, "UI_curve_tools", text="Curve-Tools", icon='TRIA_DOWN' if context.scene.i3dea.UI_curve_tools else 'TRIA_RIGHT', icon_only=False, emboss=False) # expanded view - if context.scene.i3dea.UI_curve_tools: - scene = context.scene - pg = scene.i3dea - row = box.row() - row.template_list("I3DEA_UL_selected_curves", "", pg, "object_collection", pg, "active_obj_index") - row = box.row() - row.operator("i3dea.add_empties_curves", text="Load Selected").state = 1 - row.operator("i3dea.add_empties_curves", text="Remove All").state = 2 - row.operator("i3dea.add_empties_curves", text="Remove Active").state = 3 - row = box.row() - row.prop(context.scene.i3dea, "use_pose2") - if context.scene.i3dea.use_pose2: - row = box.row() - row.template_list("I3DEA_UL_selected_curves2", "", pg, "object_collection2", pg, "active_obj_index2") - row = box.row() - row.operator("i3dea.add_empties_curves", text="Load Selected").state = 4 - row.operator("i3dea.add_empties_curves", text="Remove All").state = 5 - row.operator("i3dea.add_empties_curves", text="Remove Active").state = 6 - row = box.row() - row.enabled = context.scene.i3dea.use_distance is False - row.prop(context.scene.i3dea, "use_amount") + # if context.scene.i3dea.UI_curve_tools: + # scene = context.scene + # pg = scene.i3dea + # row = box.row() + # row.template_list("I3DEA_UL_selected_curves", "", pg, "object_collection", pg, "active_obj_index") + # row = box.row() + # row.operator("i3dea.add_empties_curves", text="Load Selected").state = 1 + # row.operator("i3dea.add_empties_curves", text="Remove All").state = 2 + # row.operator("i3dea.add_empties_curves", text="Remove Active").state = 3 + # row = box.row() + # row.prop(context.scene.i3dea, "use_pose2") + # if context.scene.i3dea.use_pose2: + # row = box.row() + # row.template_list("I3DEA_UL_selected_curves2", "", pg, "object_collection2", pg, "active_obj_index2") + # row = box.row() + # row.operator("i3dea.add_empties_curves", text="Load Selected").state = 4 + # row.operator("i3dea.add_empties_curves", text="Remove All").state = 5 + # row.operator("i3dea.add_empties_curves", text="Remove Active").state = 6 + # row = box.row() + # row.enabled = context.scene.i3dea.use_distance is False + # row.prop(context.scene.i3dea, "use_amount") # row2 = row.row() # row2.enabled = context.scene.i3dea.use_amount is False # row2.prop(context.scene.i3dea, "use_distance") - row = box.row() - row.prop(context.scene.i3dea, "amount_curve") - row.prop(context.scene.i3dea, "distance_curve") - row = box.row() - row.prop(context.scene.i3dea, "curve_array_name", text="Array Name") - row = box.row() - row.operator("i3dea.add_empties_curves", text="Create", icon='OUTLINER_DATA_CURVES').state = 7 + # row = box.row() + # row.prop(context.scene.i3dea, "amount_curve") + # row.prop(context.scene.i3dea, "distance_curve") + # row = box.row() + # row.prop(context.scene.i3dea, "curve_array_name", text="Array Name") + # row = box.row() + # row.operator("i3dea.add_empties_curves", text="Create", icon='OUTLINER_DATA_CURVES').state = 7 # --------------------------------------------------------------- # "Skeleton-Tools" Box box = layout.box() From e6faa5552c935a24d342b0ed4227efe212f7e381 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Wed, 25 Jan 2023 01:27:15 +0100 Subject: [PATCH 12/16] UI is done - Removed old UI from class list - Added the rest of the panel sub cat - Done a whole lot of changes to the mirror orientation operator, there was a lot of unnecessary code and functions - Done last adjustments for the Create empties along curves operator - Removed old UI props from PG list - Removed automatic apply scale for curve in get_curve_length func as it used bpy.ops.object.transform_apply, which isnt reallly ideal to use - Done a couple changes to the skeletons script as there was a couple issues with some oof the items (this whole script will get a proper update at same time, it really needs a clean up) --- __init__.py | 9 +- helper_functions.py | 9 +- new_ui.py | 76 ++++++++++++++- properties.py | 9 -- tools/generate_empty_on_curves.py | 23 +++-- tools/mesh_tools.py | 148 +++++++++++++----------------- tools/skeletons.py | 14 ++- tools/track_tools.py | 6 +- ui.py | 120 ++++++++++++------------ 9 files changed, 228 insertions(+), 186 deletions(-) diff --git a/__init__.py b/__init__.py index 2c1df98..b2117eb 100644 --- a/__init__.py +++ b/__init__.py @@ -30,12 +30,11 @@ import importlib importlib.reload(helper_functions) importlib.reload(properties) - importlib.reload(ui) importlib.reload(new_ui) importlib.reload(tools) else: import bpy - from . import properties, ui, new_ui + from . import properties, new_ui from .tools import ( assets_importer, orientation_tools, @@ -79,10 +78,14 @@ user_attributes.I3DEA_OT_create_user_attribute, user_attributes.I3DEA_OT_delete_user_attribute, verifier.I3DEA_OT_verify_scene, - ui.I3DEA_PT_panel, + + # UI classes new_ui.I3DEA_PT_MainPanel, new_ui.I3DEA_PT_GeneralTools, new_ui.I3DEA_PT_UserAttributes, + new_ui.I3DEA_PT_Skeletons, + new_ui.I3DEA_PT_MaterialTools, + new_ui.I3DEA_PT_AssetImporter, new_ui.I3DEA_PT_TrackTools, new_ui.I3DEA_PT_TrackSetup, new_ui.I3DEA_PT_TrackVisualization, diff --git a/helper_functions.py b/helper_functions.py index 120d151..dd7be70 100644 --- a/helper_functions.py +++ b/helper_functions.py @@ -22,12 +22,11 @@ def check_obj_type(obj): bpy.ops.object.mode_set(mode='OBJECT') -def get_curve_length(curve_obj): +def get_curve_length(curve_name): """ Returns length of curve and if the scale is not 1 1 1, it will be applied first to get the correct result """ - if curve_obj.scale != Vector((1, 1, 1)): - print(f"{curve_obj.name} scale is not 1 1 1, scale will be applied.") - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) - length = curve_obj.data.splines[0].calc_length(resolution=1024) + if bpy.data.objects[curve_name].scale != Vector((1, 1, 1)): + print(f"{curve_name} scale is not 1 1 1, this can lead to wrong result.") + length = bpy.data.objects[curve_name].data.splines[0].calc_length(resolution=1024) return length diff --git a/new_ui.py b/new_ui.py index 76e99af..17c51f4 100644 --- a/new_ui.py +++ b/new_ui.py @@ -31,7 +31,7 @@ def draw(self, context): if giants_i3d and stjerne_i3d: # "Exporter selection" box layout.label(text="Both Giants & Stjerne I3D exporter is enabled", icon='ERROR') - layout.label(text="Recommend to disable one of them as it can cause some issues") + layout.label(text="Recommend to disable one of them as it can cause unexpected issues") class I3DEA_PT_GeneralTools(I3deaPanel, Panel): @@ -105,6 +105,78 @@ def draw(self, context): row.operator("i3dea.create_user_attribute", text="Add") +class I3DEA_PT_Skeletons(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_Skeletons' + bl_label = 'Skeletons' + bl_parent_id = 'I3DEA_PT_MainPanel' + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + i3dea = context.scene.i3dea + layout = self.layout + + col = layout.column(align=True) + row = col.row(align=True) + row.prop(i3dea, "skeletons_dropdown", text="") + row.operator("i3dea.skeletons", text="Create", icon='BONE_DATA') + + +class I3DEA_PT_MaterialTools(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_MaterialTools' + bl_label = 'Material Tools' + bl_parent_id = 'I3DEA_PT_MainPanel' + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + i3dea = context.scene.i3dea + layout = self.layout + + box = layout.box() + box_col = box.column(align=True) + box_col.label(text="Material operators") + box_col.label(text="Mirror material is currently not possible to export with I3D Exporter") + box_row = box_col.row(align=True) + + box_row.operator("i3dea.mirror_material", text="Add Mirror Material") + box_row.operator("i3dea.remove_duplicate_material", text="Remove Duplicate Materials") + + box = layout.box() + box_col = box.column(align=True) + box_col.label(text="Create a material") + box_row = box_col.row(align=True) + + box_row.prop(i3dea, "diffuse_box", text="Diffuse") + if i3dea.diffuse_box: + box_row.prop(i3dea, "alpha_box", text="Alpha") + box_row = box_col.row(align=True) + box_row.prop(i3dea, "material_name", text="") + if i3dea.diffuse_box: + box_row = box_col.row(align=True) + box_row.prop(i3dea, "diffuse_texture_path", text="Diffuse") + box_row = box_col.row(align=True) + box_row.prop(i3dea, "spec_texture_path", text="Specular") + box_row = box_col.row(align=True) + box_row.prop(i3dea, "normal_texture_path", text="Normal") + box_row = box_col.row(align=True) + box_row.operator("i3dea.setup_material", text="Create " + i3dea.material_name) + + +class I3DEA_PT_AssetImporter(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_AssetImporter' + bl_label = 'Asset Importer' + bl_parent_id = 'I3DEA_PT_MainPanel' + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + i3dea = context.scene.i3dea + layout = self.layout + + col = layout.column(align=True) + row = col.row(align=True) + row.prop(i3dea, "assets_dropdown", text="") + row.operator("i3dea.assets", text="Import Asset") + + class I3DEA_PT_TrackTools(I3deaPanel, Panel): bl_idname = 'I3DEA_PT_TrackTools' bl_label = 'Track Tools' @@ -156,7 +228,7 @@ def draw(self, context): box = layout.box() box_col = box.column(align=True) - box_col.label(text="Get length of selected curve") + box_col.label(text="Calculate the distance between 2 track pieces") box_row = box_col.row(align=True) box_row.prop(i3dea, "piece_distance", text="") diff --git a/properties.py b/properties.py index 3646243..97002c3 100644 --- a/properties.py +++ b/properties.py @@ -231,14 +231,5 @@ def get_all_curves(self, context): motion_hierarchy_name: StringProperty(name="Array Name", default="curveArray") # Properties for UI in dropdowns - UI_meshTools: BoolProperty(name="Mesh-Tools", default=False) - UI_user_attributes: BoolProperty(name="User Attributes", default=False) UI_track_tools: BoolProperty(name="UV-Tools", default=False) UI_uvset: BoolProperty(name="UVset", default=False) - UI_skeletons: BoolProperty(name="Skeletons", default=False) - UI_curve_tools: BoolProperty(name="Curve-Tools", default=False) - UI_materialTools: BoolProperty(name="Material-Tools", default=False) - UI_create_mat: BoolProperty(name="Create material", default=False) - UI_paths: BoolProperty(name="Add paths to material", default=False) - UI_assets: BoolProperty(name="Assets Importer", default=False) - UI_active_obj: StringProperty(name="Active Object Name", default="") diff --git a/tools/generate_empty_on_curves.py b/tools/generate_empty_on_curves.py index d1ba625..2fe5cb6 100644 --- a/tools/generate_empty_on_curves.py +++ b/tools/generate_empty_on_curves.py @@ -1,3 +1,5 @@ +import math + import bpy from ..helper_functions import check_i3d_exporter_type, get_curve_length @@ -111,9 +113,11 @@ def __create_empties_on_curve(self, hierarchy=""): for pose in bpy.context.scene.i3dea.pose_list: pose_empty = self.__create_empty(name=pose.name) pose_empty.parent = hierarchy_empty + + # For AMOUNT_FIX longest_curve_length = 0 for curve in pose.sub_pose_list: - curve_length = get_curve_length(curve.curve) + curve_length = get_curve_length(curve.curve.name) if curve_length > longest_curve_length: longest_curve_length = curve_length @@ -123,13 +127,13 @@ def __create_empties_on_curve(self, hierarchy=""): amount = 0 if bpy.context.scene.i3dea.motion_type == "AMOUNT_REL": amount = bpy.context.scene.i3dea.motion_amount_rel - if bpy.context.scene.i3dea.motion_type == "DISTANCE": - c_length = get_curve_length(curve.curve) - amount = int(c_length / bpy.context.scene.i3dea.motion_distance) - if bpy.context.scene.i3dea.motion_type == "AMOUNT_FIX": - c_length = get_curve_length(curve.curve) + elif bpy.context.scene.i3dea.motion_type == "DISTANCE": + c_length = get_curve_length(curve.curve.name) + amount = math.ceil(c_length / bpy.context.scene.i3dea.motion_distance) + elif bpy.context.scene.i3dea.motion_type == "AMOUNT_FIX": + c_length = get_curve_length(curve.curve.name) distance = longest_curve_length / bpy.context.scene.i3dea.motion_amount_fix - amount = int(c_length / distance) + amount = math.ceil(c_length / distance) for i in range(amount): x_empty = self.__create_empty(empty_type='ARROWS', name=f"{curve.curve.name}_X_{i:03d}") # Set object constraint to follow curve @@ -138,7 +142,7 @@ def __create_empties_on_curve(self, hierarchy=""): x_empty.constraints['Follow Path'].use_curve_radius = False x_empty.constraints['Follow Path'].use_fixed_location = True x_empty.constraints['Follow Path'].use_curve_follow = True - x_empty.constraints['Follow Path'].forward_axis = 'FORWARD_Y' + x_empty.constraints['Follow Path'].forward_axis = 'TRACK_NEGATIVE_Y' x_empty.constraints['Follow Path'].up_axis = 'UP_Z' # Set object parent based on curve name @@ -148,8 +152,7 @@ def __create_empties_on_curve(self, hierarchy=""): x_empty.constraints['Follow Path'].offset_factor = i / (amount - 1) # Apply the constraint - bpy.ops.constraint.apply({'constraint': x_empty.constraints["Follow Path"]}, - constraint='Follow Path') + bpy.ops.constraint.apply({'constraint': x_empty.constraints["Follow Path"]}, constraint='Follow Path') def execute(self, context): i3dea = context.scene.i3dea diff --git a/tools/mesh_tools.py b/tools/mesh_tools.py index 6282f90..323c421 100644 --- a/tools/mesh_tools.py +++ b/tools/mesh_tools.py @@ -18,8 +18,8 @@ # mesh_tools.py includes different tools for mesh -import bmesh import bpy +import bmesh import math from mathutils import Matrix, Vector @@ -216,99 +216,75 @@ def execute(self, context): 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_label = "Set Mirror Orientation" + bl_description = "Sets mirror orientation based on camera and ref empty" 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 + camera = None + mirror = None + target = None + for obj in bpy.context.selected_objects: + if obj.type == 'CAMERA': + camera = obj + elif obj.type == 'MESH': + mirror = obj + elif obj.type == 'EMPTY': + target = obj + + if camera and mirror and target: + mirror_parent = mirror.parent if mirror.parent else None + + mirror_axis_target = bpy.data.objects.new("mirror_axis_target", None) + bpy.context.collection.objects.link(mirror_axis_target) + target_mirror = bpy.data.objects.new("target_mirror", None) + bpy.context.collection.objects.link(target_mirror) + + v1 = (mirror.location - camera.location).normalized() + v2 = (mirror.location - target.location).normalized() + + v3 = v1 + v2 + + target_mirror.location = mirror.location + mirror_axis_target.location = mirror.location - v3 + + 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 + bpy.ops.object.select_all(action='DESELECT') + 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: + 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.transform_apply(location=False, rotation=True, scale=False) + bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') 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) -# + 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/skeletons.py b/tools/skeletons.py index d90295f..c697728 100644 --- a/tools/skeletons.py +++ b/tools/skeletons.py @@ -98,7 +98,7 @@ def create_base_vec(self, is_harvester): self.create_skel_node("playerLeftFootTarget", character_targets, True) self.create_skel_node("9_mirrors_cabin", cabin) self.create_skel_node("10_visuals_cabin", cabin) - attacher_joints = "" + attacher_joints = None if is_harvester: attacher_joints = self.create_vehicle_attacher_joints(True) else: @@ -507,11 +507,10 @@ def create_light(self, name, parent, cone_angle, light_range, drop_off, rgb, tra light_transform = bpy.context.active_object light_transform.parent = parent light_transform.name = name - light = light_transform.name if giants_i3d: - light['I3D_collision'] = False - light['I3D_static'] = False - light['I3D_clipDistance'] = 75.00 + light_transform['I3D_collision'] = False + light_transform['I3D_static'] = False + light_transform['I3D_clipDistance'] = 75.00 if stjerne_i3d: bpy.context.object.i3d_attributes.clip_distance = 75 return light_transform @@ -525,7 +524,6 @@ def create_point_light(self, name, parent, light_range=18, rgb=(0.44, 0.4, 0.4), light = bpy.context.active_object light.parent = parent light.name = name - light = light.name if giants_i3d: light['I3D_collision'] = False light['I3D_static'] = False @@ -560,7 +558,7 @@ def create_collision(self, name, col_mask, col_mask_stjerne, parent, size=(1.0, bpy.context.object.dimensions = size bpy.ops.object.transform_apply(scale=True) bpy.context.active_object.parent = parent - trigger = bpy.context.active_object.name + trigger = bpy.context.active_object if giants_i3d: trigger['I3D_collision'] = True trigger['I3D_static'] = True @@ -585,7 +583,7 @@ def create_trigger(self, name, col_mask, col_mask_stjerne, parent, size=(1.0, 1. bpy.context.object.dimensions = size bpy.ops.object.transform_apply(scale=True) bpy.context.active_object.parent = parent - trigger = bpy.context.active_object.name + trigger = bpy.context.active_object if giants_i3d: trigger['I3D_collision'] = True trigger['I3D_static'] = True diff --git a/tools/track_tools.py b/tools/track_tools.py index 748c0cd..82f974e 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -74,7 +74,7 @@ def execute(self, context): self.report({'ERROR'}, f"The active object ({context.active_object.name}) is not a curve") return {'CANCELLED'} else: - curve_length = get_curve_length(context.object) + curve_length = get_curve_length(context.object.name) context.scene.i3dea.curve_length_disp = str(round(curve_length, 6)) return {'FINISHED'} @@ -96,7 +96,7 @@ def execute(self, context): context.scene.i3dea.piece_distance = abs(obj1[1] - obj2[1]) elif context.object.type == 'CURVE': curve = bpy.context.object - curve_length = get_curve_length(curve) + curve_length = get_curve_length(curve.name) float_val = curve_length / bpy.context.scene.i3dea.piece_distance bpy.context.scene.i3dea.track_piece_amount = float_val return {'FINISHED'} @@ -129,7 +129,7 @@ def execute(self, context): for piece, curve in zip(piece_list, curve_list): hierarchy_name = 'track_visualization' space = context.scene.i3dea.track_vis_distance - curve_length = get_curve_length(curve) + curve_length = get_curve_length(curve.name) if bpy.context.scene.i3dea.track_type_method == 'CATERPILLAR': bpy.ops.mesh.primitive_plane_add() plane = bpy.context.object diff --git a/ui.py b/ui.py index 147756c..f964f74 100644 --- a/ui.py +++ b/ui.py @@ -20,8 +20,8 @@ def draw(self, context): layout.label(text="Both Giants & Stjerne I3D exporter is enabled", icon='ERROR') layout.label(text="Recommend to disable one of them as it can cause some issues") # "Mesh-Tools" box - box = layout.box() - row = box.row() + # box = layout.box() + # row = box.row() # extend button for """row.prop(context.scene.i3dea, "UI_meshTools", text="Mesh-Tools", icon='TRIA_DOWN' if context.scene.i3dea.UI_meshTools else 'TRIA_RIGHT', icon_only=False, emboss=False) # expanded view @@ -161,73 +161,73 @@ def draw(self, context): # row.operator("i3dea.add_empties_curves", text="Create", icon='OUTLINER_DATA_CURVES').state = 7 # --------------------------------------------------------------- # "Skeleton-Tools" Box - box = layout.box() - row = box.row() + # box = layout.box() + # row = box.row() # expand button for "Skeletons" - row.prop(context.scene.i3dea, "UI_skeletons", text="Skeletons", icon='TRIA_DOWN' if context.scene.i3dea.UI_skeletons else 'TRIA_RIGHT', icon_only=False, emboss=False) + # row.prop(context.scene.i3dea, "UI_skeletons", text="Skeletons", icon='TRIA_DOWN' if context.scene.i3dea.UI_skeletons else 'TRIA_RIGHT', icon_only=False, emboss=False) # expanded view - if context.scene.i3dea.UI_skeletons: - row = box.row() - row.prop(context.scene.i3dea, "skeletons_dropdown", text="") - row.operator("i3dea.skeletons", text="Create", icon='BONE_DATA') + # if context.scene.i3dea.UI_skeletons: + # row = box.row() + # row.prop(context.scene.i3dea, "skeletons_dropdown", text="") + # row.operator("i3dea.skeletons", text="Create", icon='BONE_DATA') # --------------------------------------------------------------- # "Material-Tools" box - box = layout.box() - row = box.row() + # box = layout.box() + # row = box.row() # extend button for "Material-Tools" - row.prop(context.scene.i3dea, "UI_materialTools", text="Material-Tools", icon='TRIA_DOWN' if context.scene.i3dea.UI_materialTools else 'TRIA_RIGHT', icon_only=False, emboss=False) + # row.prop(context.scene.i3dea, "UI_materialTools", text="Material-Tools", icon='TRIA_DOWN' if context.scene.i3dea.UI_materialTools else 'TRIA_RIGHT', icon_only=False, emboss=False) # expanded view - if context.scene.i3dea.UI_materialTools: - row = box.row() - row.operator("i3dea.mirror_material", text="Add Mirror Material") - row.operator("i3dea.remove_duplicate_material", text="Remove Duplicate Materials") - col = box.column() - box = col.box() - row = box.row() - row.prop(context.scene.i3dea, "UI_create_mat", text="Create a material", icon='TRIA_DOWN' if context.scene.i3dea.UI_create_mat else 'TRIA_RIGHT', icon_only=False, emboss=False) - if context.scene.i3dea.UI_create_mat: + # if context.scene.i3dea.UI_materialTools: + # row = box.row() + # row.operator("i3dea.mirror_material", text="Add Mirror Material") + # row.operator("i3dea.remove_duplicate_material", text="Remove Duplicate Materials") + # col = box.column() + # box = col.box() + # row = box.row() + # row.prop(context.scene.i3dea, "UI_create_mat", text="Create a material", icon='TRIA_DOWN' if context.scene.i3dea.UI_create_mat else 'TRIA_RIGHT', icon_only=False, emboss=False) + # if context.scene.i3dea.UI_create_mat: # row.label(text="Create a material") - row = box.row() - row.prop(context.scene.i3dea, "diffuse_box", text="Diffuse") - if context.scene.i3dea.diffuse_box: - row.prop(context.scene.i3dea, "alpha_box", text="Alpha") - row = box.row() - row.prop(context.scene.i3dea, "material_name", text="") - if context.scene.i3dea.diffuse_box: - row = box.row() - row.prop(context.scene.i3dea, "diffuse_texture_path", text="Diffuse") - row = box.row() - row.prop(context.scene.i3dea, "spec_texture_path", text="Specular") - row = box.row() - row.prop(context.scene.i3dea, "normal_texture_path", text="Normal") - row = box.row() - row.operator("i3dea.setup_material", text="Create " + bpy.context.scene.i3dea.material_name) - if stjerne_i3d: - box = col.box() - row = box.row() - row.prop(context.scene.i3dea, "UI_paths", text="Add paths to material", icon='TRIA_DOWN' if context.scene.i3dea.UI_paths else 'TRIA_RIGHT', icon_only=False, emboss=False) - if context.scene.i3dea.UI_paths: - row = box.row() - row.prop(context.scene.i3dea, "shader_box", text="") - row.prop(context.scene.i3dea, "shader_path", text="Shader path") - row = box.row() - row.prop(context.scene.i3dea, "mask_map_box", text="") - row.prop(context.scene.i3dea, "mask_map", text="Mask texture") - row = box.row() - row.prop(context.scene.i3dea, "dirt_diffuse_box", text="") - row.prop(context.scene.i3dea, "dirt_diffuse", text="Dirt texture") - row = box.row() - row.operator("i3dea.i3dio_material", text="Run") + # row = box.row() + # row.prop(context.scene.i3dea, "diffuse_box", text="Diffuse") + # if context.scene.i3dea.diffuse_box: + # row.prop(context.scene.i3dea, "alpha_box", text="Alpha") + # row = box.row() + # row.prop(context.scene.i3dea, "material_name", text="") + # if context.scene.i3dea.diffuse_box: + # row = box.row() + # row.prop(context.scene.i3dea, "diffuse_texture_path", text="Diffuse") + # row = box.row() + # row.prop(context.scene.i3dea, "spec_texture_path", text="Specular") + # row = box.row() + # row.prop(context.scene.i3dea, "normal_texture_path", text="Normal") + # row = box.row() + # row.operator("i3dea.setup_material", text="Create " + bpy.context.scene.i3dea.material_name) + # if stjerne_i3d: + # box = col.box() + # row = box.row() + # row.prop(context.scene.i3dea, "UI_paths", text="Add paths to material", icon='TRIA_DOWN' if context.scene.i3dea.UI_paths else 'TRIA_RIGHT', icon_only=False, emboss=False) + # if context.scene.i3dea.UI_paths: + # row = box.row() + # row.prop(context.scene.i3dea, "shader_box", text="") + # row.prop(context.scene.i3dea, "shader_path", text="Shader path") + # row = box.row() + # row.prop(context.scene.i3dea, "mask_map_box", text="") + # row.prop(context.scene.i3dea, "mask_map", text="Mask texture") + # row = box.row() + # row.prop(context.scene.i3dea, "dirt_diffuse_box", text="") + # row.prop(context.scene.i3dea, "dirt_diffuse", text="Dirt texture") + # row = box.row() + # row.operator("i3dea.i3dio_material", text="Run") # ----------------------------------------- # "Assets Importer" box - box = layout.box() - row = box.row() + # box = layout.box() + # row = box.row() # extend button for "Assets Importer" - row.prop(context.scene.i3dea, "UI_assets", text="Assets Importer", icon='TRIA_DOWN' if context.scene.i3dea.UI_assets else 'TRIA_RIGHT', icon_only=False, emboss=False) + # row.prop(context.scene.i3dea, "UI_assets", text="Assets Importer", icon='TRIA_DOWN' if context.scene.i3dea.UI_assets else 'TRIA_RIGHT', icon_only=False, emboss=False) # expanded view - if context.scene.i3dea.UI_assets: - row = box.row() - row.prop(context.scene.i3dea, "assets_dropdown", text="") - row = box.row() - row.operator("i3dea.assets", text="Import Asset") + # if context.scene.i3dea.UI_assets: + # row = box.row() + # row.prop(context.scene.i3dea, "assets_dropdown", text="") + # row = box.row() + # row.operator("i3dea.assets", text="Import Asset") # ----------------------------------------- From 9d7104c3f14cfb38387bea0619f389a898fa3898 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Thu, 26 Jan 2023 00:51:04 +0100 Subject: [PATCH 13/16] Started adding properties for automatic track creation --- __init__.py | 1 + helper_functions.py | 50 ++++++++++++++++++++++++++++++++++++++-- new_ui.py | 23 ++++++++++++++++++ properties.py | 13 +++++++---- tools/mesh_tools.py | 6 +---- tools/skeletons.py | 2 +- tools/track_tools.py | 38 +++++++++++++++++++++++++++++- tools/user_attributes.py | 2 +- 8 files changed, 121 insertions(+), 14 deletions(-) diff --git a/__init__.py b/__init__.py index b2117eb..314afdf 100644 --- a/__init__.py +++ b/__init__.py @@ -57,6 +57,7 @@ track_tools.I3DEA_OT_calculate_amount, track_tools.I3DEA_OT_visualization, track_tools.I3DEA_OT_visualization_del, + track_tools.I3DEA_OT_automatic_track_creation, mesh_tools.I3DEA_OT_remove_doubles, mesh_tools.I3DEA_OT_mesh_name, mesh_tools.I3DEA_OT_ignore, diff --git a/helper_functions.py b/helper_functions.py index dd7be70..ba4f0f8 100644 --- a/helper_functions.py +++ b/helper_functions.py @@ -1,5 +1,6 @@ import bpy -from mathutils import Vector +from mathutils import Vector, Matrix + def check_i3d_exporter_type(): giants_i3d = False @@ -22,11 +23,56 @@ def check_obj_type(obj): bpy.ops.object.mode_set(mode='OBJECT') +def apply_transforms(ob_name, use_loc=False, use_rot=False, use_scale=False, apply_all=False): + """ + Applies scale for object + + https://blender.stackexchange.com/questions/159538/how-to-apply-all-transformations-to-an-object-at-low-level + """ + if apply_all: + use_loc = True + use_rot = True + use_scale = True + + ob = bpy.data.objects[ob_name] + mb = ob.matrix_basis + I = Matrix() + loc, rot, scale = mb.decompose() + + # rotation + t = Matrix.Translation(loc) + r = mb.to_3x3().normalized().to_4x4() + s = Matrix.Diagonal(scale).to_4x4() + + transform = [I] * 3 + basis = [t, r, s] + + def swap(i): + transform[i], basis[i] = basis[i], transform[i] + + if use_loc: + swap(0) + if use_rot: + swap(1) + if use_scale: + swap(2) + + M = transform[0] @ transform[1] @ transform[2] + if hasattr(ob.data, "transform"): + ob.data.transform(M) + for c in ob.children: + c.matrix_local = M @ c.matrix_local + + ob.matrix_basis = basis[0] @ basis[1] @ basis[2] + return + + def get_curve_length(curve_name): """ Returns length of curve and if the scale is not 1 1 1, it will be applied first to get the correct result """ if bpy.data.objects[curve_name].scale != Vector((1, 1, 1)): - print(f"{curve_name} scale is not 1 1 1, this can lead to wrong result.") + print(f"{curve_name} scale is not 1 1 1, applying scale automatically.") + apply_transforms(curve_name, use_scale=True) length = bpy.data.objects[curve_name].data.splines[0].calc_length(resolution=1024) return length diff --git a/new_ui.py b/new_ui.py index 17c51f4..0342809 100644 --- a/new_ui.py +++ b/new_ui.py @@ -236,6 +236,29 @@ def draw(self, context): box_row = box_col.row(align=True) box_row.prop(i3dea, "track_piece_amount", text="") + elif i3dea.track_mode == 'AUTOMATIC': + box = layout.box() + box_col = box.column(align=True) + box_col.label(text="Settings") + box_row = box_col.row(align=True) + + box_row.prop(i3dea, "auto_uvset", text="Create 2nd UV", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_uvset else 'CHECKBOX_DEHLT') + box_row = box_col.row(align=True) + box_row.prop(i3dea, "auto_vmask", text="Create Vmask Objects", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_vmask else 'CHECKBOX_DEHLT') + box_row = box_col.row(align=True) + box_row.prop(i3dea, "auto_amount", text="Auto Calc Amount", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_amount else 'CHECKBOX_DEHLT') + if not i3dea.auto_amount: + box_row = box_col.row(align=True) + box_row.prop(i3dea, "auto_fxd_amount", text="") + box_row = box_col.row(align=True) + box_row.prop(i3dea, "auto_allow_curve_scale", text="Allow Curve Scale", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_allow_curve_scale else 'CHECKBOX_DEHLT') + box_row = box_col.row(align=True) + box_row.prop(i3dea, "auto_empty", text="Add empties", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_empty else 'CHECKBOX_DEHLT') + if i3dea.auto_empty: + box_row = box_col.row(align=True) + box_row.prop(i3dea, "auto_empty_int", text="") + + class I3DEA_PT_TrackVisualization(I3deaPanel, Panel): bl_idname = 'I3DEA_PT_TrackVisualization' diff --git a/properties.py b/properties.py index 97002c3..bdd9433 100644 --- a/properties.py +++ b/properties.py @@ -198,6 +198,15 @@ def get_all_curves(self, context): track_vis_amount: IntProperty(name="Amount of pieces", description="Amount of track pieces to use along the curve", default=1, min=1, max=200) track_vis_distance: FloatProperty(name="Distance between links", description="Distance between each link", default=0.2, precision=6, min=0.0001, max=5, unit='LENGTH') + # Automatic mode settings + auto_uvset: BoolProperty(name="Create 2nd UV", description="If checked it will create 2nd UVset", default=False) + auto_vmask: BoolProperty(name="Add Vmask", description="Adds pieces ready for AO bake", default=False) + auto_amount: BoolProperty(name="Allow Auto Amount", description="Automatically calculates the amount of pieces", default=False) + auto_fxd_amount: IntProperty(name="Piece Amount", description="Fixed number of amount of pieces that will be added", default=False) + auto_allow_curve_scale: BoolProperty(name="Allow Curve Scale", description="If checked it will try to scale the curve so it perfectly fits a whole iteger amount of pieces", default=False) + auto_empty: BoolProperty(name="Add Empties", description="If checked it will add the amount of empties bellow", default=False) + auto_empty_int: IntProperty(name="Empty amount", description="Amount of empties that will be added between each track piece", default=False, min=1, max=5) + # User Attribute properties.py user_attribute_name: StringProperty(name="Name", description="Name of the User Attribute.") user_attribute_type: EnumProperty( @@ -229,7 +238,3 @@ def get_all_curves(self, context): motion_amount_fix: IntProperty(name="AmountFix", default=32) motion_distance: FloatProperty(name="AmountFix", default=0.2) motion_hierarchy_name: StringProperty(name="Array Name", default="curveArray") - - # Properties for UI in dropdowns - UI_track_tools: BoolProperty(name="UV-Tools", default=False) - UI_uvset: BoolProperty(name="UVset", default=False) diff --git a/tools/mesh_tools.py b/tools/mesh_tools.py index 323c421..2d323d0 100644 --- a/tools/mesh_tools.py +++ b/tools/mesh_tools.py @@ -237,21 +237,18 @@ def execute(self, context): mirror_axis_target = bpy.data.objects.new("mirror_axis_target", None) bpy.context.collection.objects.link(mirror_axis_target) - target_mirror = bpy.data.objects.new("target_mirror", None) - bpy.context.collection.objects.link(target_mirror) v1 = (mirror.location - camera.location).normalized() v2 = (mirror.location - target.location).normalized() v3 = v1 + v2 - target_mirror.location = mirror.location mirror_axis_target.location = mirror.location - v3 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.constraints['Track To'].target = mirror bpy.ops.object.select_all(action='DESELECT') bpy.context.view_layer.objects.active = mirror_axis_target mirror_axis_target.select_set(True) @@ -283,7 +280,6 @@ def execute(self, context): 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)") diff --git a/tools/skeletons.py b/tools/skeletons.py index c697728..ed1117b 100644 --- a/tools/skeletons.py +++ b/tools/skeletons.py @@ -17,7 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### # -------------------------------------------------------------------------------------- -# Converted/inspired from skeletons script in maya i3d exporter, plugins/Skeletons.py +# Converted from skeletons script in maya i3d exporter, plugins/Skeletons.py # -------------------------------------------------------------------------------------- # skeletons.py contains skeleton setup for vehicles, tools and placeables diff --git a/tools/track_tools.py b/tools/track_tools.py index 82f974e..9d49717 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -277,6 +277,17 @@ def execute(self, context): return {'FINISHED'} +class I3DEA_OT_automatic_track_creation(bpy.types.Operator): + bl_label = "Generate UVset 2" + bl_idname = "i3dea.automatic_track_creation" + bl_description = "Create track setup depending on the above settings." + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + + pass + + def create_second_uv(original_obj, name, amount): """ Creates second UV set for the given object by copying it multiple times and transforming each copy's UV set. @@ -399,6 +410,31 @@ def create_from_amount(objects, amount): return obj_list +def scale_curve_to_integer(curve_name): + obj = bpy.data.objects[curve_name] + # Enter edit mode + bpy.ops.object.editmode_toggle() + + # Select all control points + bpy.ops.curve.select_all(action='SELECT') + + # Get the total length of the curve + length = get_curve_length(curve_name) + print(length) + + # Round the length to the nearest whole number + # rounded_length = round(length) + + # Calculate the scale factor + scale_factor = round(length) / length + + # Apply the scale factor to the curve object + bpy.ops.transform.resize(value=(scale_factor, scale_factor, scale_factor)) + + # Exit edit mode + bpy.ops.object.editmode_toggle() + + """import bpy def scale_curve_to_integer(curve_object): @@ -409,7 +445,7 @@ def scale_curve_to_integer(curve_object): bpy.ops.curve.select_all(action='SELECT') # Get the total length of the curve - length = curve_object.data.splines[0].calc_length(resolution=10000) + length = curve_object.data.splines[0].calc_length(resolution=1024) print(length) # Round the length to the nearest whole number diff --git a/tools/user_attributes.py b/tools/user_attributes.py index 6235842..7cfa27f 100644 --- a/tools/user_attributes.py +++ b/tools/user_attributes.py @@ -31,7 +31,7 @@ class I3DEA_OT_create_user_attribute(bpy.types.Operator): def execute(self, context): attr_type = context.scene.i3dea.user_attribute_type attr_name = context.scene.i3dea.user_attribute_name - create_attr_name = "userAttribute_{}_{}".format(attr_type, attr_name) + create_attr_name = f"userAttribute_{attr_type}_{attr_name}" obj = context.object if obj: From 6c488e8c50c2cc732d9f28308416a2d1fb39b539 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Fri, 27 Jan 2023 02:22:34 +0100 Subject: [PATCH 14/16] Automatic ui almost done - Changed get curve objs function - Added a if statement to check if user attribute name already exists --- __init__.py | 4 ++ new_ui.py | 116 +++++++++++++++++++++++++++++++++------ properties.py | 50 +++++++---------- tools/track_tools.py | 36 ------------ tools/user_attributes.py | 42 +++++++------- 5 files changed, 147 insertions(+), 101 deletions(-) diff --git a/__init__.py b/__init__.py index 314afdf..765c47a 100644 --- a/__init__.py +++ b/__init__.py @@ -89,6 +89,10 @@ new_ui.I3DEA_PT_AssetImporter, new_ui.I3DEA_PT_TrackTools, new_ui.I3DEA_PT_TrackSetup, + new_ui.I3DEA_PT_CreateUvSet, + new_ui.I3DEA_PT_CalcAmount, + new_ui.I3DEA_PT_AddEmpties, + new_ui.I3DEA_PT_CreateAutoTrack, new_ui.I3DEA_PT_TrackVisualization, new_ui.I3DEA_UL_PoseList, new_ui.I3DEA_UL_SubPoseCurveList, diff --git a/new_ui.py b/new_ui.py index 0342809..0c950a8 100644 --- a/new_ui.py +++ b/new_ui.py @@ -21,6 +21,15 @@ class I3deaPanel: bl_category = 'I3D Exporter Additionals' +class I3deaTrackSetupAuto(I3deaPanel): + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + i3dea = context.scene.i3dea + return i3dea.track_mode == 'AUTOMATIC' + + class I3DEA_PT_MainPanel(I3deaPanel, Panel): bl_idname = 'I3DEA_PT_MainPanel' bl_label = 'I3D Exporter Additionals' @@ -236,28 +245,103 @@ def draw(self, context): box_row = box_col.row(align=True) box_row.prop(i3dea, "track_piece_amount", text="") - elif i3dea.track_mode == 'AUTOMATIC': - box = layout.box() - box_col = box.column(align=True) - box_col.label(text="Settings") - box_row = box_col.row(align=True) - - box_row.prop(i3dea, "auto_uvset", text="Create 2nd UV", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_uvset else 'CHECKBOX_DEHLT') - box_row = box_col.row(align=True) - box_row.prop(i3dea, "auto_vmask", text="Create Vmask Objects", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_vmask else 'CHECKBOX_DEHLT') - box_row = box_col.row(align=True) - box_row.prop(i3dea, "auto_amount", text="Auto Calc Amount", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_amount else 'CHECKBOX_DEHLT') - if not i3dea.auto_amount: - box_row = box_col.row(align=True) - box_row.prop(i3dea, "auto_fxd_amount", text="") - box_row = box_col.row(align=True) + """ box_row.prop(i3dea, "auto_allow_curve_scale", text="Allow Curve Scale", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_allow_curve_scale else 'CHECKBOX_DEHLT') box_row = box_col.row(align=True) box_row.prop(i3dea, "auto_empty", text="Add empties", toggle=True, icon='CHECKBOX_HLT' if i3dea.auto_empty else 'CHECKBOX_DEHLT') if i3dea.auto_empty: box_row = box_col.row(align=True) - box_row.prop(i3dea, "auto_empty_int", text="") + box_row.prop(i3dea, "auto_empty_int", text="")""" + + +class I3DEA_PT_CreateUvSet(I3deaTrackSetupAuto, Panel): + bl_idname = 'I3DEA_PT_CreateUvSet' + bl_label = 'Create UV set' + bl_parent_id = 'I3DEA_PT_TrackSetup' + + def draw_header(self, context): + i3dea = context.scene.i3dea + self.layout.prop(i3dea, "auto_use_uvset", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + i3dea = context.scene.i3dea + + col = layout.column() + col.active = i3dea.auto_use_uvset + col.prop(i3dea, "auto_uvset_dropdown", text="2nd uv size") + col.prop(i3dea, "auto_add_vmask") + + +class I3DEA_PT_CalcAmount(I3deaTrackSetupAuto, Panel): + bl_idname = 'I3DEA_PT_CalcAmount' + bl_label = 'Fixed Amount' + bl_parent_id = 'I3DEA_PT_TrackSetup' + + def draw_header(self, context): + i3dea = context.scene.i3dea + self.layout.prop(i3dea, "auto_calc_amount", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + i3dea = context.scene.i3dea + + col = layout.column() + col.active = i3dea.auto_calc_amount + col.prop(i3dea, "auto_fxd_amount") + + +class I3DEA_PT_AddEmpties(I3deaTrackSetupAuto, Panel): + bl_idname = 'I3DEA_PT_AddEmpties' + bl_label = 'Add Empties' + bl_parent_id = 'I3DEA_PT_TrackSetup' + + def draw_header(self, context): + i3dea = context.scene.i3dea + self.layout.prop(i3dea, "auto_add_empties", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + i3dea = context.scene.i3dea + + col = layout.column() + col.active = i3dea.auto_add_empties + col.prop(i3dea, "auto_empty_int") + + +class I3DEA_PT_CreateAutoTrack(I3deaPanel, Panel): + bl_idname = 'I3DEA_PT_CreateAutoTrack' + bl_label = 'Manage and Create' + bl_parent_id = 'I3DEA_PT_TrackSetup' + + @classmethod + def poll(cls, context): + i3dea = context.scene.i3dea + return i3dea.track_mode == 'AUTOMATIC' + + def draw(self, context): + layout = self.layout + + layout.use_property_split = True + layout.use_property_decorate = False + + i3dea = context.scene.i3dea + col = layout.column() + col.prop(i3dea, "auto_all_curves") + if i3dea.auto_all_curves: + col.prop(i3dea, "auto_allow_curve_scale") + col.prop(i3dea, "auto_create_bbox") + col.operator("i3dea.automatic_track_creation", text="Create") class I3DEA_PT_TrackVisualization(I3deaPanel, Panel): diff --git a/properties.py b/properties.py index bdd9433..41e8491 100644 --- a/properties.py +++ b/properties.py @@ -10,6 +10,11 @@ ) +def get_curve_objects(self, context): + """ Returns enum elements of all Curves of the current Scene. """ + return [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'CURVE'] if bpy.data.objects else [] + + class SubPoseItem(bpy.types.PropertyGroup): curve: bpy.props.PointerProperty(type=bpy.types.Object) @@ -159,21 +164,6 @@ class I3DEA_PG_List(bpy.types.PropertyGroup): dirt_diffuse_box: BoolProperty(name="Set dirt diffuse path", description="If checked it add the the path to dirt diffuse in material", default=True) # Track-Tools - def get_all_curves(self, context): - """ Returns enum elements of all Curves of the current Scene. """ - # From Giants I3D Exporter - - curves = tuple() - curves += (("None", "None", "None", 0),) - try: - num = 1 - for curveName in [obj.name for obj in context.scene.objects if obj.type == 'CURVE']: - curves += ((curveName, curveName, curveName, num),) - num += 1 - return curves - except: - return curves - track_mode: EnumProperty(name="Track Mode", items=[('MANUAL', 'Manual Tools', ""), ('AUTOMATIC', 'Automatic', "",)], @@ -187,7 +177,6 @@ def get_all_curves(self, context): track_piece_amount: FloatProperty(name="Track pieces possible along curve", description="The amount of track links that will fit along the curve", min=1, max=400, default=1) rubber_track: BoolProperty(name="Rubber Track", description="Check this if you want to visualize a rubber track", default=False) advanced_mode: BoolProperty(name="Advanced Mode", description="Add more options for UVset2 creation and Track setup", default=False) - all_curves: EnumProperty(items=get_all_curves, name="Select A Curve") add_empties: BoolProperty(name="Add Empties", description="If you check this it will add the amount of empties between each track link that's written bellow.", default=False) track_type_method: EnumProperty(name="Track Method", items=[('CATERPILLAR', 'Caterpillar', ""), @@ -199,13 +188,22 @@ def get_all_curves(self, context): track_vis_distance: FloatProperty(name="Distance between links", description="Distance between each link", default=0.2, precision=6, min=0.0001, max=5, unit='LENGTH') # Automatic mode settings - auto_uvset: BoolProperty(name="Create 2nd UV", description="If checked it will create 2nd UVset", default=False) - auto_vmask: BoolProperty(name="Add Vmask", description="Adds pieces ready for AO bake", default=False) - auto_amount: BoolProperty(name="Allow Auto Amount", description="Automatically calculates the amount of pieces", default=False) - auto_fxd_amount: IntProperty(name="Piece Amount", description="Fixed number of amount of pieces that will be added", default=False) + auto_use_uvset: BoolProperty(name="Create 2nd UV", description="If checked it will create 2nd UVset", default=False) + auto_uvset_dropdown: EnumProperty( + name="Size List", + description="List of UV size", + items=[ + ('4', '2x2', "Create UVset 2 2x2"), + ('16', '4x4', "Create UVset 2 4x4")], + default='4') + auto_add_vmask: BoolProperty(name="Add Vmask Objects", description="Adds pieces ready for AO bake", default=False) + auto_calc_amount: BoolProperty(name="Fixed Amount", description="If checked you will need to add the amount manually", default=False) + auto_fxd_amount: IntProperty(name="Piece Amount", description="Fixed number of amount of pieces that will be added", default=1, min=1, max=200) + auto_add_empties: BoolProperty(name="Empty amount", description="If checked it will add the amount of empties bellow", default=False) + auto_empty_int: IntProperty(name="Empty amount", description="Amount of empties that will be added between each track piece", default=1, min=1, max=5) auto_allow_curve_scale: BoolProperty(name="Allow Curve Scale", description="If checked it will try to scale the curve so it perfectly fits a whole iteger amount of pieces", default=False) - auto_empty: BoolProperty(name="Add Empties", description="If checked it will add the amount of empties bellow", default=False) - auto_empty_int: IntProperty(name="Empty amount", description="Amount of empties that will be added between each track piece", default=False, min=1, max=5) + auto_all_curves: EnumProperty(items=get_curve_objects, name="Select A Curve") + auto_create_bbox: BoolProperty(name="Add BoundingVolume", description="Creates a BV around track", default=False) # User Attribute properties.py user_attribute_name: StringProperty(name="Name", description="Name of the User Attribute.") @@ -219,14 +217,6 @@ def get_all_curves(self, context): ('scriptCallback', "scriptCallback", "")], default='boolean') - # Properties for Curve-Tools - curve_array_name: StringProperty(name="Array Name", description="Set array name", default="curveArray") - amount_curve: IntProperty(name="Static amount", description="Add empties on all curves with this amount", default=32, min=1) - distance_curve: FloatProperty(name="Distance", description="Add empties along curve by said distance", default=0.2, min=0.01) - use_amount: BoolProperty(name="Use Amount", description="When this is checked, it will use amount", default=True) - use_distance: BoolProperty(name="Use Distance", description="When this is checked, it will use distance", default=False) - use_pose2: BoolProperty(name="Use Pose 2", description="When this is checked, you will be able to use another set of curves.", default=False) - # Motion Path From Curves pose_list: bpy.props.CollectionProperty(type=PoseItem) pose_count: bpy.props.IntProperty(default=0) diff --git a/tools/track_tools.py b/tools/track_tools.py index 9d49717..e39db52 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -433,39 +433,3 @@ def scale_curve_to_integer(curve_name): # Exit edit mode bpy.ops.object.editmode_toggle() - - -"""import bpy - -def scale_curve_to_integer(curve_object): - # Enter edit mode - bpy.ops.object.editmode_toggle() - - # Select all control points - bpy.ops.curve.select_all(action='SELECT') - - # Get the total length of the curve - length = curve_object.data.splines[0].calc_length(resolution=1024) - print(length) - - # Round the length to the nearest whole number - # rounded_length = round(length) - - # Calculate the scale factor - scale_factor = round(length) / length - - # Apply the scale factor to the curve object - bpy.ops.transform.resize(value=(scale_factor, scale_factor, scale_factor)) - - # Exit edit mode - bpy.ops.object.editmode_toggle() - -# Example usage: -# Select the curve object in Blender's 3D View -# then run: -bpy.ops.object.select_all(action='DESELECT') -bpy.context.object.select_set(True) -scale_curve_to_integer(bpy.context.object)""" - - - diff --git a/tools/user_attributes.py b/tools/user_attributes.py index 7cfa27f..9ee572f 100644 --- a/tools/user_attributes.py +++ b/tools/user_attributes.py @@ -36,25 +36,29 @@ def execute(self, context): if obj: if context.scene.i3dea.user_attribute_name: - if attr_type == 'boolean': - obj[create_attr_name] = False - ui = obj.id_properties_ui(create_attr_name) - ui.update(description=create_attr_name) - ui.update(default=False) - ui.update(min=False) - ui.update(max=True) - if attr_type == 'float': - obj[create_attr_name] = 0.0 - ui = obj.id_properties_ui(create_attr_name) - ui.update(description=create_attr_name) - ui.update(default=0.0) - ui.update(min=-200) - ui.update(max=200) - if attr_type == 'string': - obj[create_attr_name] = "" - if attr_type == 'scriptCallback': - obj[create_attr_name] = "" - return {'FINISHED'} + if attr_name in [k.split("_")[-1] for k in obj.keys() if k.startswith("userAttribute_")]: + self.report({'WARNING'}, f"Attribute {attr_name} already exist.") + return {'CANCELLED'} + else: + if attr_type == 'boolean': + obj[create_attr_name] = False + ui = obj.id_properties_ui(create_attr_name) + ui.update(description=create_attr_name) + ui.update(default=False) + ui.update(min=False) + ui.update(max=True) + elif attr_type == 'float': + obj[create_attr_name] = 0.0 + ui = obj.id_properties_ui(create_attr_name) + ui.update(description=create_attr_name) + ui.update(default=0.0) + ui.update(min=-200) + ui.update(max=200) + elif attr_type == 'string': + obj[create_attr_name] = "" + elif attr_type == 'scriptCallback': + obj[create_attr_name] = "" + return {'FINISHED'} class I3DEA_OT_delete_user_attribute(bpy.types.Operator): From 224f40a465154c8bf55e86305652cccd7a43a6d3 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Sat, 28 Jan 2023 03:19:39 +0100 Subject: [PATCH 15/16] Fixed get_curve_objects - Fixed get_curve_objects never returning none if there is no curves in scene --- properties.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/properties.py b/properties.py index 41e8491..cd944a5 100644 --- a/properties.py +++ b/properties.py @@ -12,7 +12,11 @@ def get_curve_objects(self, context): """ Returns enum elements of all Curves of the current Scene. """ - return [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'CURVE'] if bpy.data.objects else [] + curve_objects = [obj for obj in bpy.data.objects if obj.type == 'CURVE'] + if curve_objects: + return [(obj.name, obj.name, "") for obj in curve_objects] + else: + return [("None", "None", "")] class SubPoseItem(bpy.types.PropertyGroup): From 7555da26b10480e4afd0b42c89cd095ef8eeef69 Mon Sep 17 00:00:00 2001 From: Kristian <57712777+NMC-TBone@users.noreply.github.com> Date: Sun, 29 Jan 2023 21:07:42 +0100 Subject: [PATCH 16/16] Release version - Fixed the rest of the automatic track operator - Bumped version to 3.0.0 --- __init__.py | 4 +- new_ui.py | 16 ++-- properties.py | 11 ++- tools/track_tools.py | 191 +++++++++++++++++++++++-------------------- 4 files changed, 125 insertions(+), 97 deletions(-) diff --git a/__init__.py b/__init__.py index 765c47a..e7516e7 100644 --- a/__init__.py +++ b/__init__.py @@ -20,8 +20,8 @@ "author": "T-Bone", "description": "Additionals For I3D Exporter", "blender": (3, 0, 0), - "version": (2, 0, 8), - "location": "View3D > UI > I3D Exporter Additionals > I3D Exporter Additionals", + "version": (3, 0, 0), + "location": "View3D > UI > I3D Exporter Additionals", "warning": "", "category": "Game Engine" } diff --git a/new_ui.py b/new_ui.py index 0c950a8..ffb2435 100644 --- a/new_ui.py +++ b/new_ui.py @@ -283,7 +283,7 @@ class I3DEA_PT_CalcAmount(I3deaTrackSetupAuto, Panel): def draw_header(self, context): i3dea = context.scene.i3dea - self.layout.prop(i3dea, "auto_calc_amount", text="") + self.layout.prop(i3dea, "auto_fixed_amount", text="") def draw(self, context): layout = self.layout @@ -293,8 +293,8 @@ def draw(self, context): i3dea = context.scene.i3dea col = layout.column() - col.active = i3dea.auto_calc_amount - col.prop(i3dea, "auto_fxd_amount") + col.active = i3dea.auto_fixed_amount + col.prop(i3dea, "auto_fxd_amount_int") class I3DEA_PT_AddEmpties(I3deaTrackSetupAuto, Panel): @@ -337,10 +337,16 @@ def draw(self, context): i3dea = context.scene.i3dea col = layout.column() - col.prop(i3dea, "auto_all_curves") - if i3dea.auto_all_curves: + # col.prop(i3dea, "auto_all_curves") + col.prop(i3dea, "auto_curve_object", text="Curve object", icon='OUTLINER_OB_CURVE') + if i3dea.auto_curve_object and not i3dea.auto_fixed_amount: col.prop(i3dea, "auto_allow_curve_scale") + else: + i3dea.property_unset("auto_allow_curve_scale") col.prop(i3dea, "auto_create_bbox") + col.prop(i3dea, "auto_name") + if not i3dea.auto_fixed_amount: + col.prop(i3dea, "auto_distance") col.operator("i3dea.automatic_track_creation", text="Create") diff --git a/properties.py b/properties.py index cd944a5..a06cab8 100644 --- a/properties.py +++ b/properties.py @@ -201,13 +201,20 @@ class I3DEA_PG_List(bpy.types.PropertyGroup): ('16', '4x4', "Create UVset 2 4x4")], default='4') auto_add_vmask: BoolProperty(name="Add Vmask Objects", description="Adds pieces ready for AO bake", default=False) - auto_calc_amount: BoolProperty(name="Fixed Amount", description="If checked you will need to add the amount manually", default=False) - auto_fxd_amount: IntProperty(name="Piece Amount", description="Fixed number of amount of pieces that will be added", default=1, min=1, max=200) + auto_fixed_amount: BoolProperty(name="Fixed Amount", description="If checked you will need to set the amount manually", default=False) + auto_fxd_amount_int: IntProperty(name="Piece Amount", description="Fixed number of amount of pieces that will be added", default=1, min=1, max=200) auto_add_empties: BoolProperty(name="Empty amount", description="If checked it will add the amount of empties bellow", default=False) auto_empty_int: IntProperty(name="Empty amount", description="Amount of empties that will be added between each track piece", default=1, min=1, max=5) auto_allow_curve_scale: BoolProperty(name="Allow Curve Scale", description="If checked it will try to scale the curve so it perfectly fits a whole iteger amount of pieces", default=False) auto_all_curves: EnumProperty(items=get_curve_objects, name="Select A Curve") auto_create_bbox: BoolProperty(name="Add BoundingVolume", description="Creates a BV around track", default=False) + auto_name: StringProperty(name="Custom name", description="Name of the track setup", default="myTrack") + auto_distance: FloatProperty(name="Distance between links", description="Distance between each link", default=0.2) + auto_curve_object: PointerProperty( + type=bpy.types.Object, + name="Curve Object", + description="Select a curve object", + poll=lambda self, obj: obj.type == 'CURVE') # User Attribute properties.py user_attribute_name: StringProperty(name="Name", description="Name of the User Attribute.") diff --git a/tools/track_tools.py b/tools/track_tools.py index e39db52..e379240 100644 --- a/tools/track_tools.py +++ b/tools/track_tools.py @@ -16,14 +16,12 @@ # # ##### END GPL LICENSE BLOCK ##### -# track_tools.py includes different tools for uv - import bpy import bmesh import math import mathutils -from mathutils import Vector +from mathutils import Vector, Matrix from ..helper_functions import check_i3d_exporter_type, check_obj_type, get_curve_length @@ -40,6 +38,7 @@ def create_empties(objs, amount): for obj in objs: for _ in range(amount): empty = bpy.data.objects.new(obj.name + ".001", None) + empty.empty_display_size = 0 empty.location = obj.location bpy.context.collection.objects.link(empty) if obj.parent is not None: @@ -174,7 +173,6 @@ def execute(self, context): self.report({'WARNING'}, "Bogie is not supported yet") return {'CANCELLED'} - def stop_anim(scene): if scene.frame_current == 250: bpy.ops.screen.animation_cancel() @@ -218,63 +216,14 @@ def execute(self, context): self.report({'ERROR'}, "Selected object is not a mesh!") return {'CANCELLED'} - name = "track" - if context.scene.i3dea.custom_text_box: - name = context.scene.i3dea.custom_text - original_obj = context.object - check_obj_type(original_obj) - original_loc = original_obj.location - duplicated_obj = create_second_uv(original_obj, name, int(context.scene.i3dea.size_dropdown)) + create_second_uv(original_obj, original_obj.name + "_UVset2", int(context.scene.i3dea.size_dropdown)) - if not context.scene.i3dea.advanced_mode: - if context.scene.i3dea.size_dropdown == '4': - self.report({'INFO'}, "UVset2 2x2 Created") - else: - self.report({'INFO'}, "UVset2 4x4 Created") - return {'FINISHED'} - - elif context.scene.i3dea.advanced_mode and not context.scene.i3dea.all_curves == "None": - vmask_bake = vmask_bake_objs(duplicated_obj, name) - bpy.ops.object.empty_add(radius=0, location=(0, 0, 0)) - empty_parent = context.object - empty_parent.name = name - bpy.ops.object.empty_add(radius=0, location=original_loc) - track_geo = bpy.context.object - track_geo.name = f"{name}Geo" - if giants_i3d: - track_geo['I3D_receiveShadows'] = True - track_geo['I3D_castsShadows'] = True - track_geo['I3D_clipDistance'] = 300.00 - track_geo['I3D_mergeChildren'] = True - track_geo['I3D_objectDataExportOrientation'] = True - track_geo['I3D_objectDataExportPosition'] = True - obj_name = track_geo.name - dim_x = original_obj.dimensions[0] - create_bbox(context.scene.i3dea.all_curves, name, obj_name, dim_x) - all_pieces = create_from_amount(duplicated_obj, int(context.scene.i3dea.track_piece_amount)) - for obj in all_pieces: - obj.select_set(True) - obj.parent = track_geo - obj.matrix_parent_inverse = track_geo.matrix_world.inverted() - - if context.scene.i3dea.add_empties: - create_empties(all_pieces, context.scene.i3dea.add_empty_int) - - track_geo.parent = empty_parent - track_geo.matrix_parent_inverse = empty_parent.matrix_world.inverted() - - if "zzz_data_ignore" not in bpy.data.objects: - bpy.ops.object.empty_add(radius=0, location=[0, 0, 0]) - data_ignore = bpy.context.object - data_ignore.name = "zzz_data_ignore" - data_ignore = bpy.data.objects["zzz_data_ignore"] - - bpy.data.objects[context.scene.i3dea.all_curves].parent = data_ignore - original_obj.parent = data_ignore - vmask_bake.parent = data_ignore - self.report({'INFO'}, "Full track setup created and ready for export!") - return {'FINISHED'} + if context.scene.i3dea.size_dropdown == '4': + self.report({'INFO'}, "UVset2 2x2 Created") + else: + self.report({'INFO'}, "UVset2 4x4 Created") + return {'FINISHED'} class I3DEA_OT_automatic_track_creation(bpy.types.Operator): @@ -284,11 +233,84 @@ class I3DEA_OT_automatic_track_creation(bpy.types.Operator): bl_options = {'REGISTER', 'UNDO'} def execute(self, context): + i3dea = context.scene.i3dea + if not context.object: + self.report({'ERROR'}, "No selected object!") + return {'CANCELLED'} + if not context.object.type == 'MESH': + self.report({'ERROR'}, "Selected object is not a mesh!") + return {'CANCELLED'} + if not i3dea.auto_curve_object: + self.report({'ERROR'}, "No curve chosen!") + return {'CANCELLED'} - pass + if i3dea.auto_name is "": + name = "myTrack" + else: + name = i3dea.auto_name + original_obj = context.object -def create_second_uv(original_obj, name, amount): + if "zzz_data_ignore" not in bpy.data.objects: + data_ignore = bpy.data.objects.new("zzz_data_ignore", None) + bpy.context.collection.objects.link(data_ignore) + data_ignore.empty_display_size = 0 + data_ignore = bpy.data.objects["zzz_data_ignore"] + + track_main_parent = bpy.data.objects.new(name, None) + bpy.context.collection.objects.link(track_main_parent) + track_main_parent.empty_display_size = 0 + track_geo = bpy.data.objects.new(f"{name}Geo", None) + bpy.context.collection.objects.link(track_geo) + track_geo.empty_display_size = 0 + track_geo.location = original_obj.location + track_geo.parent = track_main_parent + track_geo.matrix_parent_inverse = track_main_parent.matrix_world.inverted() + + if giants_i3d: + track_geo['I3D_receiveShadows'] = True + track_geo['I3D_castsShadows'] = True + track_geo['I3D_clipDistance'] = 300.00 + track_geo['I3D_mergeChildren'] = True + track_geo['I3D_objectDataExportOrientation'] = True + track_geo['I3D_objectDataExportPosition'] = True + + if i3dea.auto_create_bbox: + create_bbox(i3dea.auto_curve_object.name, name, track_geo.name, original_obj.dimensions[0]) + + if i3dea.auto_use_uvset: + second_uv = create_second_uv(original_obj, name, int(i3dea.auto_uvset_dropdown)) + if i3dea.auto_add_vmask: + vmask = vmask_bake_objs(second_uv, name) + vmask.parent = data_ignore + else: + second_uv = create_second_uv(original_obj, name, int(i3dea.auto_uvset_dropdown), existing_uv=True) + + if not i3dea.auto_allow_curve_scale: + if i3dea.auto_fixed_amount: + amount = i3dea.auto_fxd_amount_int + else: + amount = get_curve_length(i3dea.auto_curve_object.name) / i3dea.auto_distance + amount = round(amount) + else: + amount = scale_curve_to_fit_distance(i3dea.auto_curve_object.name, i3dea.auto_distance) + + all_pieces = create_from_amount(second_uv, amount) + for obj in all_pieces: + obj.parent = track_geo + obj.matrix_parent_inverse = track_geo.matrix_world.inverted() + + if i3dea.auto_add_empties: + create_empties(all_pieces, i3dea.auto_empty_int) + + i3dea.auto_curve_object.parent = data_ignore + original_obj.parent = data_ignore + original_obj.matrix_parent_inverse = data_ignore.matrix_world.inverted() + self.report({'INFO'}, "Full track setup created and ready for export!") + return {'FINISHED'} + + +def create_second_uv(original_obj, name, amount, existing_uv=False): """ Creates second UV set for the given object by copying it multiple times and transforming each copy's UV set. @@ -298,8 +320,12 @@ def create_second_uv(original_obj, name, amount): grid_size = math.ceil(math.sqrt(amount)) ref_obj = original_obj.copy() ref_obj.data = original_obj.data.copy() - if 'uvSet2' not in ref_obj.data.uv_layers: - ref_obj.data.uv_layers.new(name="uvSet2") + if not existing_uv: + if 'uvSet2' not in ref_obj.data.uv_layers: + ref_obj.data.uv_layers.new(name="uvSet2") + else: + if len(ref_obj.data.uv_layers) < 2: + ref_obj.data.uv_layers.new(name="uvSet2") new_objs = [] for i, _ in enumerate(range(amount)): @@ -313,7 +339,7 @@ def create_second_uv(original_obj, name, amount): bm = bmesh.new() bm.from_mesh(new_obj.data) - uv2 = bm.loops.layers.uv["uvSet2"] + uv2 = bm.loops.layers.uv[1] uv1 = bm.loops.layers.uv[0] for bm_vert in bm.verts: @@ -342,7 +368,6 @@ def vmask_bake_objs(objs, name): vmask_empty = bpy.data.objects.new("objsForBake", None) bpy.context.collection.objects.link(vmask_empty) vmask_empty.empty_display_size = 0 - vmask_empty.name = "objsForBake" location = 0 for i, obj in enumerate(objs): @@ -410,26 +435,16 @@ def create_from_amount(objects, amount): return obj_list -def scale_curve_to_integer(curve_name): - obj = bpy.data.objects[curve_name] - # Enter edit mode - bpy.ops.object.editmode_toggle() - - # Select all control points - bpy.ops.curve.select_all(action='SELECT') - - # Get the total length of the curve - length = get_curve_length(curve_name) - print(length) - - # Round the length to the nearest whole number - # rounded_length = round(length) - - # Calculate the scale factor - scale_factor = round(length) / length - - # Apply the scale factor to the curve object - bpy.ops.transform.resize(value=(scale_factor, scale_factor, scale_factor)) - - # Exit edit mode - bpy.ops.object.editmode_toggle() +def scale_curve_to_fit_distance(curve_name, distance): + i = 0 + while True: + length = get_curve_length(curve_name) + amount = length / distance + if math.isclose(round(amount, 4) % 1, 0) or i > 250: + break + rounded_amount = round(amount) + scale_factor = rounded_amount / amount + scale_matrix = Matrix.Scale(scale_factor, 4) + bpy.data.objects[curve_name].data.transform(scale_matrix) + i += 1 + return round(amount)