diff --git a/auto_Convex/add_bounding_auto_convex.py b/auto_Convex/add_bounding_auto_convex.py index 2a1f762..ef4e4f0 100644 --- a/auto_Convex/add_bounding_auto_convex.py +++ b/auto_Convex/add_bounding_auto_convex.py @@ -7,54 +7,7 @@ from bpy.types import Operator from ..collider_shapes.add_bounding_primitive import OBJECT_OT_add_bounding_object - - -def bmesh_join(list_of_bmeshes, list_of_matrices, normal_update=False): - # sourcery skip: use-contextlib-suppress - """ takes as input a list of bm references and outputs a single merged bmesh - allows an additional 'normal_update=True' to force _normal_ calculations. - """ - bm = bmesh.new() - add_vert = bm.verts.new - add_face = bm.faces.new - add_edge = bm.edges.new - - for bm_to_add, matrix in zip(list_of_bmeshes, list_of_matrices): - bm_to_add.transform(matrix) - - for bm_to_add in list_of_bmeshes: - offset = len(bm.verts) - - for v in bm_to_add.verts: - add_vert(v.co) - - bm.verts.index_update() - bm.verts.ensure_lookup_table() - - if bm_to_add.faces: - for face in bm_to_add.faces: - add_face(tuple(bm.verts[i.index + offset] for i in face.verts)) - bm.faces.index_update() - - if bm_to_add.edges: - for edge in bm_to_add.edges: - edge_seq = tuple(bm.verts[i.index + offset] - for i in edge.verts) - try: - add_edge(edge_seq) - except ValueError: - # edge exists! - pass - bm.edges.index_update() - - if normal_update: - bm.normal_update() - - me = bpy.data.meshes.new("joined_mesh") - bm.to_mesh(me) - - return me - +from ..bmesh_operations.mesh_edit import bmesh_join class VHACD_OT_convex_decomposition(OBJECT_OT_add_bounding_object, Operator): bl_idname = 'collision.vhacd' @@ -118,8 +71,7 @@ def execute(self, context): # CLEANUP super().execute(context) - overwrite_path = self.overwrite_executable_path( - self.prefs.executable_path) + overwrite_path = self.overwrite_executable_path(self.prefs.executable_path) vhacd_exe = self.prefs.default_executable_path if not overwrite_path else overwrite_path data_path = self.set_temp_data_path(self.prefs.data_path) @@ -139,15 +91,27 @@ def execute(self, context): meshes = [] matrices = [] - for obj in self.selected_objects: + for base_ob in self.selected_objects: # skip if invalid object - if not self.is_valid_object(obj): + if not self.is_valid_object(base_ob): continue + if base_ob and base_ob.type in self.valid_object_types: + if base_ob.type == 'MESH': + obj = base_ob + + else: + # store initial state for operation cancel + user_collections = base_ob.users_collection + self.original_obj_data.append(self.store_initial_obj_state(base_ob, user_collections)) + # convert meshes + obj = self.convert_to_mesh(context, base_ob, use_modifiers=self.my_use_modifier_stack) + self.tmp_meshes.append(obj) + context.view_layer.objects.active = obj - if self.obj_mode == "EDIT": + if self.obj_mode == "EDIT" and base_ob.type == 'MESH' and self.active_obj.type == 'MESH': new_mesh = self.get_mesh_Edit( obj, use_modifiers=self.my_use_modifier_stack) else: # mode == "OBJECT": @@ -159,7 +123,8 @@ def execute(self, context): if self.creation_mode[self.creation_mode_idx] == 'INDIVIDUAL': convex_collision_data = {} - convex_collision_data['parent'] = obj + convex_collision_data['parent'] = base_ob + convex_collision_data['mtx_world'] = base_ob.matrix_world.copy() convex_collision_data['mesh'] = new_mesh collider_data.append(convex_collision_data) @@ -171,6 +136,7 @@ def execute(self, context): if self.creation_mode[self.creation_mode_idx] == 'SELECTION': convex_collision_data = {} convex_collision_data['parent'] = self.active_obj + convex_collision_data['mtx_world'] = self.active_obj.matrix_world.copy() bmeshes = [] @@ -190,6 +156,7 @@ def execute(self, context): for convex_collision_data in collider_data: parent = convex_collision_data['parent'] mesh = convex_collision_data['mesh'] + mtx_world = convex_collision_data['mtx_world'] joined_obj = bpy.data.objects.new('debug_joined_mesh', mesh.copy()) bpy.context.scene.collection.objects.link(joined_obj) @@ -302,6 +269,7 @@ def execute(self, context): convex_collisions_data = {} convex_collisions_data['colliders'] = imported convex_collisions_data['parent'] = parent + convex_collisions_data['mtx_world'] = parent.matrix_world.copy() convex_decomposition_data.append(convex_collisions_data) context.view_layer.objects.active = self.active_obj @@ -309,18 +277,18 @@ def execute(self, context): for convex_collisions_data in convex_decomposition_data: convex_collision = convex_collisions_data['colliders'] parent = convex_collisions_data['parent'] + mtx_world = convex_collisions_data['mtx_world'] for new_collider in convex_collision: new_collider.name = super().collider_name(basename=parent.name) - self.custom_set_parent(context, parent, new_collider) - if self.creation_mode[self.creation_mode_idx] == 'INDIVIDUAL': - new_collider.matrix_world = parent.matrix_world - # Apply rotation and scale for custom origin to work. + new_collider.matrix_world = mtx_world self.apply_transform( new_collider, rotation=True, scale=True) + self.custom_set_parent(context, parent, new_collider) + collections = parent.users_collection self.primitive_postprocessing( context, new_collider, collections) diff --git a/bmesh_operations/__init__.py b/bmesh_operations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bmesh_operations/box_creation.py b/bmesh_operations/box_creation.py new file mode 100644 index 0000000..8c70b91 --- /dev/null +++ b/bmesh_operations/box_creation.py @@ -0,0 +1,63 @@ +import bmesh +import bpy + +from bpy_extras.object_utils import object_data_add + + +tmp_name = 'box_collider' + +# vertex indizes defining the faces of the cube +face_order = [ + (0, 1, 2, 3), + (4, 7, 6, 5), + (0, 4, 5, 1), + (1, 5, 6, 2), + (2, 6, 7, 3), + (4, 0, 3, 7), +] + + +def add_box_object(context, vertices): + """Generate a new object from the given vertices""" + + global tmp_name + + verts = vertices + edges = [] + faces = [[0, 1, 2, 3], [7, 6, 5, 4], [5, 6, 2, 1], [0, 3, 7, 4], [3, 2, 6, 7], [4, 5, 1, 0]] + + mesh = bpy.data.meshes.new(name=tmp_name) + mesh.from_pydata(verts, edges, faces) + + return object_data_add(context, mesh, operator=None, name=None) + + +def verts_faces_to_bbox_collider(self, context, verts_loc): + """Create box collider for selected mesh area in edit mode""" + + global tmp_name + + # add new mesh + mesh = bpy.data.meshes.new(tmp_name) + bm = bmesh.new() + + # create mesh vertices + for v_co in verts_loc: + bm.verts.new(v_co) + + # connect vertices to faces + bm.verts.ensure_lookup_table() + for f_idx in face_order: + bm.faces.new([bm.verts[i] for i in f_idx]) + + # update bmesh to draw properly in viewport + bm.to_mesh(mesh) + mesh.update() + + # create new object from mesh and link it to collection + new_collider = bpy.data.objects.new(tmp_name, mesh) + + root_collection = context.scene.collection + root_collection.objects.link(new_collider) + + return new_collider \ No newline at end of file diff --git a/collider_shapes/capsule_generation.py b/bmesh_operations/capsule_generation.py similarity index 100% rename from collider_shapes/capsule_generation.py rename to bmesh_operations/capsule_generation.py diff --git a/collider_shapes/capsule_height_radius.py b/bmesh_operations/capsule_height_radius.py similarity index 100% rename from collider_shapes/capsule_height_radius.py rename to bmesh_operations/capsule_height_radius.py diff --git a/bmesh_operations/mesh_edit.py b/bmesh_operations/mesh_edit.py new file mode 100644 index 0000000..49d4a6d --- /dev/null +++ b/bmesh_operations/mesh_edit.py @@ -0,0 +1,47 @@ +import bpy, bmesh + +def bmesh_join(list_of_bmeshes, list_of_matrices, normal_update=False): + # sourcery skip: use-contextlib-suppress + """ takes as input a list of bm references and outputs a single merged bmesh + allows an additional 'normal_update=True' to force _normal_ calculations. + """ + bm = bmesh.new() + add_vert = bm.verts.new + add_face = bm.faces.new + add_edge = bm.edges.new + + for bm_to_add, matrix in zip(list_of_bmeshes, list_of_matrices): + bm_to_add.transform(matrix) + + for bm_to_add in list_of_bmeshes: + offset = len(bm.verts) + + for v in bm_to_add.verts: + add_vert(v.co) + + bm.verts.index_update() + bm.verts.ensure_lookup_table() + + if bm_to_add.faces: + for face in bm_to_add.faces: + add_face(tuple(bm.verts[i.index + offset] for i in face.verts)) + bm.faces.index_update() + + if bm_to_add.edges: + for edge in bm_to_add.edges: + edge_seq = tuple(bm.verts[i.index + offset] + for i in edge.verts) + try: + add_edge(edge_seq) + except ValueError: + # edge exists! + pass + bm.edges.index_update() + + if normal_update: + bm.normal_update() + + me = bpy.data.meshes.new("joined_mesh") + bm.to_mesh(me) + + return me \ No newline at end of file diff --git a/collider_conversion/__init__.py b/collider_conversion/__init__.py index be50811..8410911 100644 --- a/collider_conversion/__init__.py +++ b/collider_conversion/__init__.py @@ -1,9 +1,11 @@ -from . import conversion_operators +from . import convert_to_collider +from . import convert_to_mesh +from . import regenerate_name classes = ( - conversion_operators.OBJECT_OT_convert_to_collider, - conversion_operators.OBJECT_OT_convert_to_mesh, - conversion_operators.OBJECT_OT_regenerate_name, + convert_to_collider.OBJECT_OT_convert_to_collider, + convert_to_mesh.OBJECT_OT_convert_to_mesh, + regenerate_name.OBJECT_OT_regenerate_name, ) diff --git a/collider_conversion/convert_to_collider.py b/collider_conversion/convert_to_collider.py new file mode 100644 index 0000000..f11bb0c --- /dev/null +++ b/collider_conversion/convert_to_collider.py @@ -0,0 +1,156 @@ +import bpy +from bpy.types import Operator + +from ..collider_shapes.add_bounding_primitive import OBJECT_OT_add_bounding_object +default_shape = 'box_shape' +default_group = 'USER_01' + + +class OBJECT_OT_convert_to_collider(OBJECT_OT_add_bounding_object, Operator): + """Convert existing objects to be a collider""" + bl_idname = "object.convert_to_collider" + bl_label = "Object to Collider" + bl_description = 'Convert selected meshes to colliders' + + @classmethod + def poll(cls, context): + # Convert is only supported in object mode + return False if context.mode != 'OBJECT' else super().poll(context) + + def __init__(self): + super().__init__() + self.use_shape_change = True + self.use_decimation = True + self.is_mesh_to_collider = True + self.shape = 'mesh_shape' + self.use_keep_original_materials = True + self.use_modifier_stack = True + + def invoke(self, context, event): + super().invoke(context, event) + + self.collider_shapes_idx = 3 + self.collider_shapes = ['box_shape', 'sphere_shape', 'capsule_shape', 'convex_shape', + 'mesh_shape'] + + self.shape = self.collider_shapes[self.collider_shapes_idx] + + return {'RUNNING_MODAL'} + + def modal(self, context, event): + status = super().modal(context, event) + if status == {'FINISHED'}: + return {'FINISHED'} + if status == {'CANCELLED'}: + return {'CANCELLED'} + if status == {'PASS_THROUGH'}: + return {'PASS_THROUGH'} + + if event.type == 'P' and event.value == 'RELEASE': + self.my_use_modifier_stack = not self.my_use_modifier_stack + self.execute(context) + + elif event.type == 'Q' and event.value == 'RELEASE': + # toggle through display modes + self.collider_shapes_idx = (self.collider_shapes_idx + 1) % len(self.collider_shapes) + self.shape = self.collider_shapes[self.collider_shapes_idx] + for collider in self.new_colliders_list: + if collider: + collider['collider_shape'] = self.shape + self.update_names() + + return {'RUNNING_MODAL'} + + def execute(self, context): + # CLEANUP and INIT + super().execute(context) + + collider_data = [] + user_collections = [] + + # Create the bounding geometry, depending on edit or object mode. + for base_ob in self.selected_objects: + + # skip if invalid object + if not self.is_valid_object(base_ob): + continue + + if base_ob and base_ob.type in self.valid_object_types: + if base_ob.type == 'MESH': + obj = base_ob + mods = self.store_obj_mod_in_dic(obj) + + for mod in obj.modifiers: + mod.show_viewport = self.use_modifier_stack + mod.show_in_editmode = self.use_modifier_stack + + self.restore_obj_mod_from_dic(mods) + + else: # other object types like curves, surfaces, texts ... + # store initial state for operation cancel + user_collections = base_ob.users_collection + self.original_obj_data.append(self.store_initial_obj_state(base_ob, user_collections)) + # convert meshes + obj = self.convert_to_mesh(context, base_ob, use_modifiers=self.my_use_modifier_stack) + self.tmp_meshes.append(obj) + + new_collider = obj.copy() + new_collider.data = obj.data.copy() + user_collections = base_ob.users_collection + + # New collider to scene + bpy.context.collection.objects.link(new_collider) + + # store initial state for operation cancel + self.original_obj_data.append(self.store_initial_obj_state(obj, user_collections)) + + + for collection in obj.users_collection: + collection.objects.unlink(obj) + + prefs = context.preferences.addons[__package__.split('.')[0]].preferences + + if prefs.replace_name: + basename = prefs.obj_basename + elif obj.parent: + basename = obj.parent.name + else: + basename = obj.name + + mesh_collider_data = {} + mesh_collider_data['basename'] = basename + mesh_collider_data['new_collider'] = new_collider + collider_data.append(mesh_collider_data) + + if self.creation_mode[self.creation_mode_idx] == 'INDIVIDUAL': + for mesh_collider_data in collider_data: + basename = mesh_collider_data['basename'] + new_collider = mesh_collider_data['new_collider'] + + self.primitive_postprocessing(context, new_collider, user_collections) + self.new_colliders_list.append(new_collider) + super().set_collider_name(new_collider, basename) + + else: # self.creation_mode[self.creation_mode_idx] == 'SELECTION': + bpy.ops.object.select_all(action='DESELECT') + last_selected = None + for mesh_collider_data in collider_data: + basename = mesh_collider_data['basename'] + new_collider = mesh_collider_data['new_collider'] + new_collider.select_set(True) + + context.view_layer.objects.active = new_collider + bpy.ops.object.join() + + self.primitive_postprocessing(context, new_collider, user_collections) + self.new_colliders_list.append(new_collider) + super().set_collider_name(new_collider, basename) + + + elapsed_time = self.get_time_elapsed() + super().print_generation_time("Convert to Collider", elapsed_time) + self.report({'INFO'}, f"Convert to Collider: {float(elapsed_time)}") + + return {'RUNNING_MODAL'} + + diff --git a/collider_conversion/convert_to_mesh.py b/collider_conversion/convert_to_mesh.py new file mode 100644 index 0000000..00f1af3 --- /dev/null +++ b/collider_conversion/convert_to_mesh.py @@ -0,0 +1,88 @@ +import bpy +from bpy.types import Operator + +from ..collider_shapes.add_bounding_primitive import OBJECT_OT_add_bounding_object +from ..pyshics_materials.material_functions import assign_physics_material, create_material, remove_materials + +default_shape = 'box_shape' +default_group = 'USER_01' + +class OBJECT_OT_convert_to_mesh(Operator): + """Convert selected colliders to mesh objects""" + bl_idname = "object.convert_to_mesh" + bl_label = "Collider to Mesh" + bl_description = 'Convert selected colliders to meshes' + + my_string: bpy.props.StringProperty(name="Mesh Name", default='Mesh') + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) + + def draw(self, context): + scene = context.scene + layout = self.layout + col = layout.column() + + row = col.row() + row.prop(self, "my_string") + + row = col.row() + row.prop(scene, "DefaultMeshMaterial", text='Material') + + @classmethod + def poll(cls, context): + + # Convert is only supported in object mode + if context.mode != 'OBJECT': + return False + + # Objects need to be selected + count = 0 + for obj in context.selected_objects: + if obj.type == 'MESH': + count = count + 1 + return count > 0 + + def execute(self, context): + colSettings = context.scene.collider_tools + count = 0 + + for obj in bpy.context.selected_objects.copy(): + if obj.get('isCollider'): + count += 1 + # Reste object properties to regular mesh + obj['isCollider'] = False + obj.color = (1, 1, 1, 1) + obj.name = OBJECT_OT_add_bounding_object.unique_name(self.my_string) + obj.display_type = 'TEXTURED' + + # replace collision material + remove_materials(obj) + if colSettings.defaultMeshMaterial: + assign_physics_material(obj, colSettings.defaultMeshMaterial.name) + else: + default_material = create_material('Material', (1, 1, 1, 1)) + colSettings.defaultMeshMaterial = default_material + assign_physics_material(obj, default_material.name) + + # remove from collision collection + prefs = context.preferences.addons[__package__.split('.')[0]].preferences + collection_name = prefs.col_collection_name + + # remove from collision collection + for collection in bpy.data.collections: + if collection.name == collection_name: + if obj.name in collection.objects: + collection.objects.unlink(obj) + + # add to default scene collection if the object is not part of any collection anymore + if len(obj.users_collection) == 0: + bpy.context.scene.collection.objects.link(obj) + + if count == 0: + self.report({'WARNING'}, 'No collider selected for conversion') + else: + self.report({'INFO'}, f"{count} colliders have been converted") + + return {'FINISHED'} \ No newline at end of file diff --git a/collider_conversion/regenerate_name.py b/collider_conversion/regenerate_name.py new file mode 100644 index 0000000..1603fa9 --- /dev/null +++ b/collider_conversion/regenerate_name.py @@ -0,0 +1,53 @@ +from bpy.types import Operator + +from ..collider_shapes.add_bounding_primitive import OBJECT_OT_add_bounding_object + +default_shape = 'box_shape' +default_group = 'USER_01' + + +class OBJECT_OT_regenerate_name(Operator): + """Regenerate selected collider names based on preset""" + bl_idname = "object.regenerate_name" + bl_label = "Regenerate Name" + bl_description = 'Regenerate selected collider names based on preset' + + @classmethod + def poll(cls, context): + + # Convert is only supported in object mode + if context.mode != 'OBJECT': + return False + + count = 0 + for obj in context.selected_objects: + if obj.type == 'MESH': + count = count + 1 + return count > 0 + + def execute(self, context): + prefs = context.preferences.addons[__package__.split('.')[0]].preferences + + for obj in context.selected_objects.copy(): + + # skip if invalid object + if obj is None or obj.type != "MESH": + continue + + if prefs.replace_name: + basename = prefs.obj_basename + elif obj.parent: + basename = obj.parent.name + else: + basename = obj.name + + # get collider shape and group and set to default there is no previous data + shape_identifier = default_shape if obj.get('collider_shape') is None else obj.get('collider_shape') + user_group = default_group if obj.get('collider_group') is None else obj.get('collider_group') + + new_name = OBJECT_OT_add_bounding_object.class_collider_name(shape_identifier, user_group, + basename=basename) + obj.name = new_name + OBJECT_OT_add_bounding_object.set_data_name(obj, new_name, "_data") + + return {'FINISHED'} diff --git a/collider_shapes/add_bounding_box.py b/collider_shapes/add_bounding_box.py index 4d6b5f0..2372061 100644 --- a/collider_shapes/add_bounding_box.py +++ b/collider_shapes/add_bounding_box.py @@ -1,67 +1,11 @@ -import bmesh import bpy from bpy.types import Operator -from bpy_extras.object_utils import object_data_add from .add_bounding_primitive import OBJECT_OT_add_bounding_object - +from ..bmesh_operations.box_creation import verts_faces_to_bbox_collider tmp_name = 'box_collider' -# vertex indizes defining the faces of the cube -face_order = [ - (0, 1, 2, 3), - (4, 7, 6, 5), - (0, 4, 5, 1), - (1, 5, 6, 2), - (2, 6, 7, 3), - (4, 0, 3, 7), -] - - -def add_box_object(context, vertices): - """Generate a new object from the given vertices""" - - global tmp_name - - verts = vertices - edges = [] - faces = [[0, 1, 2, 3], [7, 6, 5, 4], [5, 6, 2, 1], [0, 3, 7, 4], [3, 2, 6, 7], [4, 5, 1, 0]] - - mesh = bpy.data.meshes.new(name=tmp_name) - mesh.from_pydata(verts, edges, faces) - - return object_data_add(context, mesh, operator=None, name=None) - - -def verts_faces_to_bbox_collider(self, context, verts_loc, faces): - """Create box collider for selected mesh area in edit mode""" - - global tmp_name - - # add new mesh - mesh = bpy.data.meshes.new(tmp_name) - bm = bmesh.new() - - # create mesh vertices - for v_co in verts_loc: - bm.verts.new(v_co) - # connect vertices to faces - bm.verts.ensure_lookup_table() - for f_idx in faces: - bm.faces.new([bm.verts[i] for i in f_idx]) - - # update bmesh to draw properly in viewport - bm.to_mesh(mesh) - mesh.update() - - # create new object from mesh and link it to collection - new_collider = bpy.data.objects.new(tmp_name, mesh) - - root_collection = context.scene.collection - root_collection.objects.link(new_collider) - - return new_collider class OBJECT_OT_add_bounding_box(OBJECT_OT_add_bounding_object, Operator): @@ -92,8 +36,6 @@ def modal(self, context, event): if status == {'PASS_THROUGH'}: return {'PASS_THROUGH'} - colSettings = context.scene.collider_tools - # change bounding object settings if event.type == 'G' and event.value == 'RELEASE': self.my_space = 'GLOBAL' @@ -114,25 +56,36 @@ def execute(self, context): # CLEANUP and INIT super().execute(context) - colSettings = context.scene.collider_tools - # List for storing dictionaries of data used to generate the collision meshes collider_data = [] verts_co = [] # Create the bounding geometry, depending on edit or object mode. - for obj in self.selected_objects: + for base_ob in self.selected_objects: # skip if invalid object - if not self.is_valid_object(obj): + if not self.is_valid_object(base_ob): continue + if base_ob and base_ob.type in self.valid_object_types: + if base_ob.type == 'MESH': + obj = base_ob + + else: + # store initial state for operation cancel + user_collections = base_ob.users_collection + self.original_obj_data.append(self.store_initial_obj_state(base_ob, user_collections)) + # convert meshes + obj = self.convert_to_mesh(context, base_ob, use_modifiers=self.my_use_modifier_stack) + self.tmp_meshes.append(obj) + + context.view_layer.objects.active = obj bounding_box_data = {} - if self.obj_mode == "EDIT": + # EDIT is only supported for 'MESH' type objects and only if the active object is a 'MESH' + if self.obj_mode == "EDIT" and base_ob.type == 'MESH' and self.active_obj.type == 'MESH': used_vertices = self.get_vertices_Edit(obj, use_modifiers=self.my_use_modifier_stack) - else: # self.obj_mode == "OBJECT": used_vertices = self.get_vertices_Object(obj, use_modifiers=self.my_use_modifier_stack) @@ -145,7 +98,8 @@ def execute(self, context): verts_loc, center_point = self.generate_bounding_box(co) # store data needed to generate a bounding box in a dictionary - bounding_box_data['parent'] = obj + bounding_box_data['parent'] = base_ob + bounding_box_data['mtx_world'] = base_ob.matrix_world.copy() bounding_box_data['verts_loc'] = verts_loc bounding_box_data['center_point'] = center_point @@ -158,6 +112,7 @@ def execute(self, context): if self.creation_mode[self.creation_mode_idx] == 'SELECTION': collider_data = self.selection_bbox_data(verts_co) + bpy.ops.object.mode_set(mode='OBJECT') for bounding_box_data in collider_data: @@ -165,15 +120,15 @@ def execute(self, context): parent = bounding_box_data['parent'] verts_loc = bounding_box_data['verts_loc'] center_point = bounding_box_data['center_point'] + mtx_world = bounding_box_data['mtx_world'] - global face_order - new_collider = verts_faces_to_bbox_collider(self, context, verts_loc, face_order) + new_collider = verts_faces_to_bbox_collider(self, context, verts_loc) scene = context.scene if self.my_space == 'LOCAL': - new_collider.parent = parent + new_collider.matrix_world = mtx_world # align collider with parent - new_collider.matrix_world = parent.matrix_world + self.custom_set_parent(context, parent, new_collider) self.use_recenter_origin = False else: # self.my_space == 'GLOBAL': @@ -204,7 +159,8 @@ def selection_bbox_data(self, verts_co): verts_co = self.transform_vertex_space(ws_vtx_co, self.active_obj) bbox_verts, center_point = self.generate_bounding_box(verts_co) + mtx_world = self.active_obj.matrix_world - bounding_box_data = {'parent': self.active_obj, 'verts_loc': bbox_verts, 'center_point': center_point} + bounding_box_data = {'parent': self.active_obj, 'verts_loc': bbox_verts, 'center_point': center_point, 'mtx_world': mtx_world} return [bounding_box_data] diff --git a/collider_shapes/add_bounding_capsule.py b/collider_shapes/add_bounding_capsule.py index 51fd4c5..b34b56d 100644 --- a/collider_shapes/add_bounding_capsule.py +++ b/collider_shapes/add_bounding_capsule.py @@ -1,7 +1,7 @@ import bpy from bpy.types import Operator -from mathutils import Matrix, Vector -from . import capsule_generation as Capsule +from mathutils import Vector +from ..bmesh_operations import capsule_generation as Capsule from math import radians from .utilities import get_sca_matrix, get_rot_matrix, get_loc_matrix @@ -81,16 +81,28 @@ def execute(self, context): verts_co = [] # Create the bounding geometry, depending on edit or object mode. - for obj in self.selected_objects: + for base_ob in self.selected_objects: # skip if invalid object - if not self.is_valid_object(obj): + if not self.is_valid_object(base_ob): continue + if base_ob and base_ob.type in self.valid_object_types: + if base_ob.type == 'MESH': + obj = base_ob + + else: + # store initial state for operation cancel + user_collections = base_ob.users_collection + self.original_obj_data.append(self.store_initial_obj_state(base_ob, user_collections)) + # convert meshes + obj = self.convert_to_mesh(context, base_ob, use_modifiers=self.my_use_modifier_stack) + self.tmp_meshes.append(obj) + context.view_layer.objects.active = obj bounding_capsule_data = {} - if self.obj_mode == "EDIT": + if self.obj_mode == "EDIT" and base_ob.type == 'MESH' and self.active_obj.type == 'MESH': used_vertices = self.get_vertices_Edit(obj, use_modifiers=self.my_use_modifier_stack) else: # self.obj_mode == "OBJECT": @@ -129,7 +141,7 @@ def execute(self, context): # store data needed to generate a bounding box in a dictionary - bounding_capsule_data['parent'] = obj + bounding_capsule_data['parent'] = base_ob bounding_capsule_data['verts_loc'] = coordinates bounding_capsule_data['center_point'] = [center[0], center[1], center[2]] collider_data.append(bounding_capsule_data) diff --git a/collider_shapes/add_bounding_convex_hull.py b/collider_shapes/add_bounding_convex_hull.py index 0ebd5e2..9ccb450 100644 --- a/collider_shapes/add_bounding_convex_hull.py +++ b/collider_shapes/add_bounding_convex_hull.py @@ -49,15 +49,27 @@ def execute(self, context): verts_co = [] # Duplicate original meshes to convert to collider - for obj in self.selected_objects: + for base_ob in self.selected_objects: # skip if invalid object - if not self.is_valid_object(obj): + if not self.is_valid_object(base_ob): continue + if base_ob and base_ob.type in self.valid_object_types: + if base_ob.type == 'MESH': + obj = base_ob + + else: + # store initial state for operation cancel + user_collections = base_ob.users_collection + self.original_obj_data.append(self.store_initial_obj_state(base_ob, user_collections)) + # convert meshes + obj = self.convert_to_mesh(context, base_ob, use_modifiers=self.my_use_modifier_stack) + self.tmp_meshes.append(obj) + convex_collision_data = {} - if self.obj_mode == "EDIT": + if self.obj_mode == "EDIT" and base_ob.type == 'MESH' and self.active_obj.type == 'MESH': used_vertices = self.get_vertices_Edit(obj, use_modifiers=self.my_use_modifier_stack) else: # self.obj_mode == "OBJECT": @@ -70,7 +82,7 @@ def execute(self, context): if self.creation_mode[self.creation_mode_idx] == 'INDIVIDUAL': # duplicate object - convex_collision_data['parent'] = obj + convex_collision_data['parent'] = base_ob convex_collision_data['verts_loc'] = ws_vtx_co collider_data.append(convex_collision_data) diff --git a/collider_shapes/add_bounding_cylinder.py b/collider_shapes/add_bounding_cylinder.py index 8206a70..2608309 100644 --- a/collider_shapes/add_bounding_cylinder.py +++ b/collider_shapes/add_bounding_cylinder.py @@ -258,15 +258,27 @@ def execute(self, context): collider_data = [] verts_co = [] - for obj in context.selected_objects.copy(): + for base_ob in self.selected_objects: # skip if invalid object - if not self.is_valid_object(obj): + if not self.is_valid_object(base_ob): continue + if base_ob and base_ob.type in self.valid_object_types: + if base_ob.type == 'MESH': + obj = base_ob + + else: + # store initial state for operation cancel + user_collections = base_ob.users_collection + self.original_obj_data.append(self.store_initial_obj_state(base_ob, user_collections)) + # convert meshes + obj = self.convert_to_mesh(context, base_ob, use_modifiers=self.my_use_modifier_stack) + self.tmp_meshes.append(obj) + bounding_cylinder_data = {} - if self.obj_mode == 'EDIT': + if self.obj_mode == "EDIT" and base_ob.type == 'MESH' and self.active_obj.type == 'MESH': used_vertices = self.get_vertices_Edit( obj, use_modifiers=self.my_use_modifier_stack) else: @@ -317,7 +329,7 @@ def execute(self, context): nsphere = welzl(np.array(coordinates)) radius = np.sqrt(nsphere.sqradius) - bounding_cylinder_data['parent'] = obj + bounding_cylinder_data['parent'] = base_ob bounding_cylinder_data['radius'] = radius bounding_cylinder_data['depth'] = depth bounding_cylinder_data['center_point'] = [ diff --git a/collider_shapes/add_bounding_primitive.py b/collider_shapes/add_bounding_primitive.py index 3a0ad3e..f307668 100644 --- a/collider_shapes/add_bounding_primitive.py +++ b/collider_shapes/add_bounding_primitive.py @@ -614,7 +614,9 @@ def set_collisions_wire_preview(self, mode): @staticmethod def remove_objects(list): - '''Remove previously created collisions''' + '''Remove list of objects''' + print(str(list)) + if len(list) > 0: for ob in list: if ob: @@ -784,28 +786,37 @@ def mesh_from_selection(obj, use_modifiers=False): return mesh - @staticmethod - def is_valid_object(obj): + def is_valid_object(self, obj): """Is the object valid to be used as a base mesh for collider generation""" - if obj is None or obj.type != "MESH": + if obj is None or obj.type not in self.valid_object_types: return False return True # Collections @staticmethod - def add_to_collections(obj, collection_name): + def add_to_collections(obj, collection_name, hide=False): """Add an object to a collection""" if collection_name not in bpy.data.collections: collection = bpy.data.collections.new(collection_name) bpy.context.scene.collection.children.link(collection) col = bpy.data.collections[collection_name] - + if hide: + col.hide_viewport = True + col.hide_render = True try: col.objects.link(obj) except RuntimeError as err: pass + return col + + @staticmethod + def remove_empty_collection(collection_name): + if collection_name in bpy.data.collections: + collection = bpy.data.collections[collection_name] + if len(collection.objects) == 0: + bpy.data.collections.remove(collection) @staticmethod def set_collections(obj, collections): """link an object to a collection""" @@ -859,6 +870,51 @@ def print_generation_time(shape, time): print(shape) print("Time elapsed: ", str(time)) + @staticmethod + def store_initial_obj_state(obj, collections): + dic = {} + dic['obj'] = obj + col_list = [col.name for col in collections] + dic['users_collection'] = col_list + + return dic + + @staticmethod + def store_obj_mod_in_dic(object): + mods = [] + + for mod in object.modifiers: + mods.append({"mod":mod, "show_viewport":mod.show_viewport, "show_in_editmode": mod.show_in_editmode}) + + return mods + + @staticmethod + def restore_obj_mod_from_dic(modifier_dic): + for mod_entry in modifier_dic: + modifier = mod_entry["mod"] + modifier.show_viewport = mod_entry["show_viewport"] + modifier.show_in_editmode = mod_entry["show_in_editmode"] + + @classmethod + def convert_to_mesh(cls, context, object, use_modifiers = False): + mods = cls.store_obj_mod_in_dic(object) + + for mod in object.modifiers: + mod.show_viewport = use_modifiers + mod.show_in_editmode = use_modifiers + + deg = context.evaluated_depsgraph_get() + me = bpy.data.meshes.new_from_object(object.evaluated_get(deg), depsgraph=deg) + new_obj = bpy.data.objects.new(object.name + "_mesh", me) + col = cls.add_to_collections(new_obj, 'tmp_mesh', hide=False) + col.color_tag = 'COLOR_03' + + cls.restore_obj_mod_from_dic(mods) + + new_obj.matrix_world = object.matrix_world + context.view_layer.objects.active = new_obj + return new_obj + def primitive_postprocessing(self, context, bounding_object, base_object_collections): colSettings = context.scene.collider_tools @@ -1012,6 +1068,22 @@ def cancel_cleanup(self, context): objs = bpy.data.objects objs.remove(obj, do_unlink=True) + # Delete temporary objects + if self.prefs.debug == False: + self.remove_objects(self.tmp_meshes) + self.remove_empty_collection('tmp_mesh') + + # delete original data + for data in self.original_obj_data: + # Assign unlinked data to user groups + original_obj = data['obj'] + original_user_groups = data['users_collection'] + + if self.is_mesh_to_collider: + bpy.context.collection.objects.link(original_obj) + for col in original_user_groups: + self.add_to_collections(original_obj, col) + context.space_data.shading.color_type = self.original_color_type try: @@ -1069,11 +1141,13 @@ def __init__(self): self.use_recenter_origin = False self.use_custom_rotation = False + self.valid_object_types = ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META'] + @classmethod def poll(cls, context): count = 0 for obj in context.selected_objects: - if obj.type == 'MESH': + if obj.type in ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META']: count = count + 1 return count > 0 @@ -1104,6 +1178,7 @@ def invoke(self, context, event): # General init settings self.new_colliders_list = [] + self.tmp_meshes = [] self.col_rotation_matrix_list = [] self.col_center_loc_list = [] @@ -1164,7 +1239,7 @@ def invoke(self, context, event): self.collision_groups = collider_groups self.collision_group_idx = self.collision_groups.index(colSettings.default_user_group) - # Mesh to Collider + # Object to Collider self.original_obj_data = [] # display settings @@ -1258,6 +1333,11 @@ def modal(self, context, event): else: obj.show_wire = False + # Delete temporary generated meshes + if self.prefs.debug == False: + self.remove_objects(self.tmp_meshes) + self.remove_empty_collection('tmp_mesh') + try: bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') except ValueError: @@ -1335,6 +1415,7 @@ def modal(self, context, event): pass self.execute(context) + elif event.type == 'N' and event.value == 'RELEASE' and self.use_keep_original_name == True: self.keep_original_name = not self.keep_original_name self.execute(context) @@ -1494,8 +1575,15 @@ def execute(self, context): print("AttributeError: bug #328") # Remove objects from previous generation + self.remove_objects(self.tmp_meshes) self.remove_objects(self.new_colliders_list) + self.remove_empty_collection('tmp_mesh') self.new_colliders_list = [] + self.original_obj_data = [] + self.tmp_meshes = [] + + # original data to be restored on cancelation or deleted on accept + self.original_obj_data = [] # reset previously stored displace modifiers when creating a new object self.displace_modifiers = [] diff --git a/collider_shapes/add_bounding_sphere.py b/collider_shapes/add_bounding_sphere.py index fcdde0d..f1a2f79 100644 --- a/collider_shapes/add_bounding_sphere.py +++ b/collider_shapes/add_bounding_sphere.py @@ -168,17 +168,29 @@ def execute(self, context): verts_co = [] # Create the bounding geometry, depending on edit or object mode. - for obj in self.selected_objects: + for base_ob in self.selected_objects: # skip if invalid object - if not self.is_valid_object(obj): + if not self.is_valid_object(base_ob): continue + if base_ob and base_ob.type in self.valid_object_types: + if base_ob.type == 'MESH': + obj = base_ob + + else: + # store initial state for operation cancel + user_collections = base_ob.users_collection + self.original_obj_data.append(self.store_initial_obj_state(base_ob, user_collections)) + # convert meshes + obj = self.convert_to_mesh(context, base_ob, use_modifiers=self.my_use_modifier_stack) + self.tmp_meshes.append(obj) + initial_mod_state = {} context.view_layer.objects.active = obj scene = context.scene - if self.obj_mode == "EDIT": + if self.obj_mode == "EDIT" and base_ob.type == 'MESH' and self.active_obj.type == 'MESH': used_vertices = self.get_vertices_Edit(obj, use_modifiers=self.my_use_modifier_stack) else: # mode == "OBJECT": @@ -192,7 +204,7 @@ def execute(self, context): if self.creation_mode[self.creation_mode_idx] == 'INDIVIDUAL': bounding_sphere_data['mid_point'], bounding_sphere_data['radius'] = self.calculate_bounding_sphere(obj, used_vertices) - bounding_sphere_data['parent'] = obj + bounding_sphere_data['parent'] = base_ob collider_data.append(bounding_sphere_data) else: # if self.creation_mode[self.creation_mode_idx] == 'SELECTION': diff --git a/collider_shapes/add_collision_mesh.py b/collider_shapes/add_collision_mesh.py index 5f9868a..cd77975 100644 --- a/collider_shapes/add_collision_mesh.py +++ b/collider_shapes/add_collision_mesh.py @@ -54,15 +54,27 @@ def execute(self, context): collider_data = [] - for obj in self.selected_objects: + for base_ob in self.selected_objects: # skip if invalid object - if not self.is_valid_object(obj): + if not self.is_valid_object(base_ob): continue + if base_ob and base_ob.type in self.valid_object_types: + if base_ob.type == 'MESH': + obj = base_ob + + else: + # store initial state for operation cancel + user_collections = base_ob.users_collection + self.original_obj_data.append(self.store_initial_obj_state(base_ob, user_collections)) + # convert meshes + obj = self.convert_to_mesh(context, base_ob, use_modifiers=self.my_use_modifier_stack) + self.tmp_meshes.append(obj) + mesh_collider_data = {} - if self.obj_mode == "EDIT": + if self.obj_mode == "EDIT" and base_ob.type == 'MESH' and self.active_obj.type == 'MESH': new_mesh = self.get_mesh_Edit(obj, use_modifiers=self.my_use_modifier_stack) new_collider = bpy.data.objects.new("", new_mesh) @@ -75,7 +87,8 @@ def execute(self, context): continue scene = context.scene - mesh_collider_data['parent'] = obj + mesh_collider_data['parent'] = base_ob + mesh_collider_data['mtx_world'] = base_ob.matrix_world.copy() mesh_collider_data['new_collider'] = new_collider collider_data.append(mesh_collider_data) @@ -85,16 +98,18 @@ def execute(self, context): for mesh_collider_data in collider_data: parent = mesh_collider_data['parent'] new_collider = mesh_collider_data['new_collider'] + mtx_world = mesh_collider_data['mtx_world'] context.scene.collection.objects.link(new_collider) self.shape_suffix = self.prefs.mesh_shape # create collision meshes + new_collider.matrix_world = mtx_world self.custom_set_parent(context, parent, new_collider) self.remove_all_modifiers(context, new_collider) # align objects - new_collider.matrix_world = parent.matrix_world + #new_collider.matrix_world = parent.matrix_world super().set_collider_name(new_collider, parent.name) diff --git a/collider_shapes/add_minimum_bounding_box.py b/collider_shapes/add_minimum_bounding_box.py index 6226943..fd7ae95 100644 --- a/collider_shapes/add_minimum_bounding_box.py +++ b/collider_shapes/add_minimum_bounding_box.py @@ -148,15 +148,27 @@ def execute(self, context): verts_co = [] # Create the bounding geometry, depending on edit or object mode. - for obj in self.selected_objects: + for base_ob in self.selected_objects: # skip if invalid object - if not self.is_valid_object(obj): + if not self.is_valid_object(base_ob): continue + if base_ob and base_ob.type in self.valid_object_types: + if base_ob.type == 'MESH': + obj = base_ob + + else: + # store initial state for operation cancel + user_collections = base_ob.users_collection + self.original_obj_data.append(self.store_initial_obj_state(base_ob, user_collections)) + # convert meshes + obj = self.convert_to_mesh(context, base_ob, use_modifiers=self.my_use_modifier_stack) + self.tmp_meshes.append(obj) + bounding_box_data = {} - if self.obj_mode == "EDIT": + if self.obj_mode == "EDIT" and base_ob.type == 'MESH' and self.active_obj.type == 'MESH': used_vertices = self.get_vertices_Edit(obj, use_modifiers=self.my_use_modifier_stack) else: # self.obj_mode == "OBJECT": @@ -175,7 +187,7 @@ def execute(self, context): # used_vertices uses local space. # store data needed to generate a bounding box in a dictionary - bounding_box_data['parent'] = obj + bounding_box_data['parent'] = base_ob bounding_box_data['verts_loc'] = ws_vtx_co collider_data.append(bounding_box_data) diff --git a/pyshics_materials/__init__.py b/pyshics_materials/__init__.py index 5055944..f188c1e 100644 --- a/pyshics_materials/__init__.py +++ b/pyshics_materials/__init__.py @@ -13,17 +13,13 @@ physics_materials.MATERIAL_OT_physics_material_random_color, ) -# def defaultActive(): -# mat = material_functions.create_default_material() -# return mat + def register(): scene = bpy.types.Scene scene.use_physics_tag = bpy.props.BoolProperty(name="Filter", default=False) - # scene.active_physics_material = bpy.props.StringProperty(name="Active Physics Material", default="") materialType = bpy.types.Material - #scene.active_physics_material = bpy.props.PointerProperty(name="My Node", default=defaultActive(), type=materialType) scene.active_physics_material = bpy.props.PointerProperty(name="My Node", type=materialType) materialType.edit = bpy.props.BoolProperty(name="Manipulate", default=False) @@ -36,7 +32,6 @@ def register(): def unregister(): - wm = bpy.types.WindowManager materialType = bpy.types.Material from bpy.utils import unregister_class diff --git a/ui/properties_panels.py b/ui/properties_panels.py index 2a63e46..43b9df9 100644 --- a/ui/properties_panels.py +++ b/ui/properties_panels.py @@ -519,7 +519,7 @@ class BUTTON_OT_auto_convex(bpy.types.Operator): def poll(cls, context): count = 0 for obj in context.selected_objects: - if obj.type == 'MESH': + if obj.type in ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META']: count = count + 1 return count > 0