Procházet zdrojové kódy

minor refactoring on the exporter and mesh builder scripts.

Signed-off-by: Jason Dela Cruz <[email protected]>
Jason Dela Cruz před 2 roky
rodič
revize
49bd795dd4

+ 11 - 42
Gems/O3DE/GeomNodes/External/Scripts/exporter/fbx_exporter.py

@@ -5,31 +5,6 @@ from materials import mat_helper
 # import logging as _logging
 # _LOGGER = _logging.getLogger('GeomNodes.External.Scripts.exporter.fbx_exporter')
 
-def get_geomnodes_objects(geomnodes_obj, collection):
-    depsgraph = bpy.context.evaluated_depsgraph_get()        
-    eval_geomnodes_data = geomnodes_obj.evaluated_get(depsgraph).data
-
-    scene_scale_len = bpy.context.scene.unit_settings.scale_length
-
-    geomnodes_objects = []
-    if geomnodes_obj.type == 'MESH':
-        new_obj = bpy.data.objects.new(name=utils.remove_special_chars(eval_geomnodes_data.name), object_data=eval_geomnodes_data.copy())
-        new_obj.matrix_world = geomnodes_obj.matrix_world.copy() * scene_scale_len
-        new_obj.instance_type = geomnodes_obj.instance_type
-        geomnodes_objects.append(new_obj)
-        collection.objects.link(new_obj)
-
-    for instance in depsgraph.object_instances:
-        if instance.instance_object and instance.parent and instance.parent.original == geomnodes_obj:
-            if instance.object.type == 'MESH':
-                new_obj = bpy.data.objects.new(name=utils.remove_special_chars(instance.object.name), object_data=instance.object.data.copy())
-                new_obj.matrix_world = instance.matrix_world.copy() * scene_scale_len
-                new_obj.instance_type = instance.object.instance_type
-                geomnodes_objects.append(new_obj)
-                collection.objects.link(new_obj)
-
-    return geomnodes_objects
-
 def group_objects_by_name(objects):
     groups = {}
     for obj in objects:
@@ -49,8 +24,6 @@ def fbx_file_exporter(obj_name, fbx_file_path, mesh_to_triangles):
     @param file_name A custom file name string
     """
 
-    print(fbx_file_path)
-
     # Lets create a dictionary to store all the source paths to place back after export
     stored_image_source_paths = {}
     source_file_path = Path(fbx_file_path) # Covert string to path
@@ -59,33 +32,29 @@ def fbx_file_exporter(obj_name, fbx_file_path, mesh_to_triangles):
     # Deselect all objects
     bpy.ops.object.select_all(action='DESELECT')
 
-    geomnodes_obj = bpy.data.objects.get(obj_name)
-    if geomnodes_obj == None:
-        geomnodes_obj = utils.get_geomnodes_obj()
-    
-    if geomnodes_obj.hide_get():
-        geomnodes_obj.hide_set(False, view_layer=bpy.context.view_layer)
-
     # Create a new collection to hold the duplicated objects
     collection = bpy.data.collections.new(name="Export Collection")
     bpy.context.scene.collection.children.link(collection)
 
-    geomnodes_objects = get_geomnodes_objects(geomnodes_obj, collection)
-    
+    # create copies of objects/meshes produces by geometry nodes modifier
+    geomnodes_objects = utils.create_geomnodes_copies(obj_name)
+
+    # add geomnode objects to the collection. Need this since we will be joining them later and the objects needs to be in the scene
+    for obj in geomnodes_objects:
+        collection.objects.link(obj)
+
     groups = group_objects_by_name(geomnodes_objects)
-    for group_name, objects  in groups.items():
+    for _, objects  in groups.items():
         if len(objects) > 2: # join 2 or more objects, if the group only has one it will be included in the collection and in turn will be exported
             # Select all the objects that use this material
             bpy.ops.object.select_all(action='DESELECT')
             bpy.context.view_layer.objects.active = objects[0]
-            for obj in objects:
-                obj.select_set(True)
-
+            utils.select_set_objects(objects, True)
+            
             # Join the objects
             bpy.ops.object.join()
     
-    for obj in collection.objects:
-        obj.select_set(True)
+    utils.select_set_objects(collection.objects, True)
     
     if mesh_to_triangles:
         utils.add_remove_modifier("TRIANGULATE", True)

+ 21 - 60
Gems/O3DE/GeomNodes/External/Scripts/mesh_data_builder.py

@@ -2,11 +2,10 @@ import bpy
 import bpy_types
 import numpy as np
 from mathutils import Matrix
-from utils import get_prop_collection
+import utils
 
 import json
 import datetime
-from utils import get_geomnodes_obj
 from lib_loader import MapWriterSize, MapWriter
 import logging as _logging
 _LOGGER = _logging.getLogger('GeomNodes.External.Scripts.mesh_data_builder')
@@ -16,10 +15,10 @@ def get_mesh_colors_data(mesh):
     has_vertex_indexed_colors = False
 
     if len(mesh.vertex_colors) > 0:
-        colors = get_prop_collection(mesh.vertex_colors[0].data, 'color', 4, np.float32)
+        colors = utils.get_prop_collection(mesh.vertex_colors[0].data, 'color', 4, np.float32)
     elif mesh.attributes.get('Col') and len(mesh.attributes.get('Col').data) > 0:
         attribute_colors = mesh.attributes.get('Col')
-        colors = get_prop_collection(attribute_colors.data, 'color', 4, np.float32)
+        colors = utils.get_prop_collection(attribute_colors.data, 'color', 4, np.float32)
         has_vertex_indexed_colors = attribute_colors.domain == 'POINT'
     else:
         colors = np.zeros(len(mesh.loops)*4, dtype=np.float32)
@@ -37,15 +36,15 @@ def get_mesh_uv_data(mesh):
         uv_size = len(attribute_uvmap.data[0].vector)
         convert_uvs = uv_size == 3
         has_vertex_indexed_uvs = attribute_uvmap.domain == 'POINT'
-        uvs = get_prop_collection(attribute_uvmap.data, 'vector', uv_size, np.float32)
+        uvs = utils.get_prop_collection(attribute_uvmap.data, 'vector', uv_size, np.float32)
     elif mesh.attributes.get('uv_map') and len(mesh.attributes.get('uv_map').data) > 0:
         attribute_uvmap = mesh.attributes.get('uv_map')
         uv_size = len(attribute_uvmap.data[0].vector)
         convert_uvs = uv_size == 3
         has_vertex_indexed_uvs = attribute_uvmap.domain == 'POINT'
-        uvs = get_prop_collection(attribute_uvmap.data, 'vector', uv_size, np.float32)
+        uvs = utils.get_prop_collection(attribute_uvmap.data, 'vector', uv_size, np.float32)
     elif len(mesh.uv_layers) > 0:
-        uvs = get_prop_collection(mesh.uv_layers[0].data, 'uv', 2, np.float32)
+        uvs = utils.get_prop_collection(mesh.uv_layers[0].data, 'uv', 2, np.float32)
     else:
         uvs = np.zeros(len(mesh.loops)*2, dtype=np.float32)
     
@@ -69,7 +68,7 @@ def get_mesh_materials(mesh):
     return names
 
 def get_materials_data(mesh):
-    material_indices = get_prop_collection(mesh.loop_triangles, 'material_index', 1, np.int32)
+    material_indices = utils.get_prop_collection(mesh.loop_triangles, 'material_index', 1, np.int32)
     material_names = np.frombuffer(bytes(json.dumps({'Materials' : get_mesh_materials(mesh)}), "UTF-8"), np.byte)
 
     return material_indices, material_names
@@ -78,11 +77,11 @@ def get_mesh_data(mesh):
     mesh.calc_loop_triangles()
     mesh.calc_normals_split()
 
-    vertices = get_prop_collection(mesh.vertices, 'co', 3, np.float32)
-    normals = get_prop_collection(mesh.loop_triangles, 'split_normals', 3*3, np.float32)
-    indices = get_prop_collection(mesh.loop_triangles, 'vertices', 3, np.int32)
-    triangle_loops = get_prop_collection(mesh.loop_triangles, 'loops', 3, np.int32)
-    loops = get_prop_collection(mesh.loops, 'vertex_index', 1, np.int32)
+    vertices = utils.get_prop_collection(mesh.vertices, 'co', 3, np.float32)
+    normals = utils.get_prop_collection(mesh.loop_triangles, 'split_normals', 3*3, np.float32)
+    indices = utils.get_prop_collection(mesh.loop_triangles, 'vertices', 3, np.int32)
+    triangle_loops = utils.get_prop_collection(mesh.loop_triangles, 'loops', 3, np.int32)
+    loops = utils.get_prop_collection(mesh.loops, 'vertex_index', 1, np.int32)
 
     mesh_hash = np.asarray(hash(mesh), np.int64)
 
@@ -100,76 +99,38 @@ def to_instance_arrays(mesh, local_matrix, world_matrix):
     return mesh_hash, local_matrix, world_matrix
 
 def build_mesh_data(obj_name):
-    geomnodes_obj = bpy.data.objects.get(obj_name)
-    if geomnodes_obj == None:
-        geomnodes_obj = get_geomnodes_obj()
-
-    if geomnodes_obj.hide_get():
-        geomnodes_obj.hide_set(False, view_layer=bpy.context.view_layer)
-
     #_LOGGER.debug('Started building Mesh Data')
     start = datetime.datetime.now()
-    depsgraph = bpy.context.evaluated_depsgraph_get()        
-    eval_geomnodes_data = geomnodes_obj.evaluated_get(depsgraph).data
-
-    mesh_arr = []
-    hash_arr = []
-    instance_arr = []
-    instance_matrix_loc_arr = []
-    instance_matrix_world_arr = []
     
-    scene_scale_len = bpy.context.scene.unit_settings.scale_length
-
-    # Get number of meshes and instances
-    if geomnodes_obj.type == 'MESH':
-        hash_arr = [hash(geomnodes_obj)]
-        mesh_arr = [eval_geomnodes_data]
-        instance_arr = [eval_geomnodes_data]
-        instance_matrix_loc_arr = [Matrix() * scene_scale_len]
-        instance_matrix_world_arr = [geomnodes_obj.matrix_world.copy() * scene_scale_len]
-
-    for instance in depsgraph.object_instances:
-        if instance.instance_object and instance.parent and instance.parent.original == geomnodes_obj:
-            if instance.object.type == 'MESH':
-                # add only if the instance is not in the hash array
-                if hash(instance.object.data) not in hash_arr:
-                    hash_arr += [hash(instance.object.data)]
-                    mesh_arr += [instance.object.data]
-                # save the instance data and transforms
-                if hash(instance.object.data) in hash_arr:
-                    instance_arr += [instance.object.data]
-                    local_matrix = instance.parent.matrix_world.inverted() @ instance.matrix_world                        
-                    instance_matrix_loc_arr += [local_matrix * scene_scale_len]
-                    instance_matrix_world_arr += [instance.matrix_world.copy() * scene_scale_len]
-                    
+    meshes, instances, local_matrices, world_matrices = utils.get_geomnodes_data_arrays(obj_name)
     
     total_bytes = 0
-    total_bytes += MapWriterSize(np.int32).from_value(len(mesh_arr))
-    total_bytes += MapWriterSize(np.int32).from_value(len(instance_arr))
+    total_bytes += MapWriterSize(np.int32).from_value(len(meshes))
+    total_bytes += MapWriterSize(np.int32).from_value(len(instances))
     
     # Export meshes
-    for mesh in mesh_arr:
+    for mesh in meshes:
         for array in get_mesh_data(mesh):
             total_bytes += MapWriterSize().from_array(array)
 
     # Export instances
-    for instance, local_matrix, world_matrix in zip(instance_arr, instance_matrix_loc_arr, instance_matrix_world_arr):
+    for instance, local_matrix, world_matrix in zip(instances, local_matrices, world_matrices):
         for array in to_instance_arrays(instance, local_matrix, world_matrix):
             total_bytes += MapWriterSize().from_array(array)
 
     from lib_loader import GNLibs
     map_id = GNLibs.RequestSHM(total_bytes)
     #_LOGGER.debug('map_id = ' + str(map_id) + ' total_bytes = ' + str(total_bytes))
-    MapWriter(map_id, np.int32).from_value(len(mesh_arr))
-    MapWriter(map_id, np.int32).from_value(len(instance_arr))
+    MapWriter(map_id, np.int32).from_value(len(meshes))
+    MapWriter(map_id, np.int32).from_value(len(instances))
     
     # Export meshes
-    for mesh in mesh_arr:
+    for mesh in meshes:
         for array in get_mesh_data(mesh):
             MapWriter(map_id).from_array(array)
     
     # Export Instances
-    for instance, local_matrix, world_matrix in zip(instance_arr, instance_matrix_loc_arr, instance_matrix_world_arr):
+    for instance, local_matrix, world_matrix in zip(instances, local_matrices, world_matrices):
         for array in to_instance_arrays(instance, local_matrix, world_matrix):
             MapWriter(map_id).from_array(array)
 

+ 90 - 1
Gems/O3DE/GeomNodes/External/Scripts/utils.py

@@ -2,6 +2,7 @@ import bpy
 import numpy as np
 from pathlib import Path
 import re
+from mathutils import Matrix
 
 def get_geomnodes_obj():
     for obj in bpy.data.objects:
@@ -9,6 +10,86 @@ def get_geomnodes_obj():
             if modifier.type == 'NODES':
                 return obj
 
+def create_geomnodes_copies(obj_name):
+    '''! This function creates copies of objects/meshes produced by the Geometry Nodes modifier. Depending on the current parameters set this could vary.
+        Note that since objects are created you have to remove the objects later after using them.
+    @param obj_name is the currently selected object name.
+    @return copies of objects of meshes
+    '''
+    geomnodes_obj = bpy.data.objects.get(obj_name)
+    if geomnodes_obj == None:
+        geomnodes_obj = get_geomnodes_obj()
+    
+    if geomnodes_obj.hide_get():
+        geomnodes_obj.hide_set(False, view_layer=bpy.context.view_layer)
+
+    depsgraph = bpy.context.evaluated_depsgraph_get()        
+    eval_geomnodes_data = geomnodes_obj.evaluated_get(depsgraph).data
+
+    scene_scale_len = bpy.context.scene.unit_settings.scale_length
+
+    geomnodes_objects = []
+    if geomnodes_obj.type == 'MESH':
+        new_obj = bpy.data.objects.new(name=remove_special_chars(eval_geomnodes_data.name), object_data=eval_geomnodes_data.copy())
+        new_obj.matrix_world = geomnodes_obj.matrix_world.copy() * scene_scale_len
+        new_obj.instance_type = geomnodes_obj.instance_type
+        geomnodes_objects.append(new_obj)
+
+    for instance in depsgraph.object_instances:
+        if instance.instance_object and instance.parent and instance.parent.original == geomnodes_obj:
+            if instance.object.type == 'MESH':
+                new_obj = bpy.data.objects.new(name=remove_special_chars(instance.object.name), object_data=instance.object.data.copy())
+                new_obj.matrix_world = instance.matrix_world.copy() * scene_scale_len
+                new_obj.instance_type = instance.object.instance_type
+                geomnodes_objects.append(new_obj)
+
+    return geomnodes_objects
+
+def get_geomnodes_data_arrays(obj_name):
+    """! Produce the data arrays needed for sending to the gem. This are based on the objects, meshes, instances produced by the Geometry Nodes modifier depending on the current parameters and object selected.
+    @param obj_name is the currently selected object name.
+    @return data arrays
+    """
+    meshes = []
+    hashes = []
+    instance_meshes = []
+    local_matrices = []
+    world_matrices = []
+
+    geomnodes_obj = bpy.data.objects.get(obj_name)
+    if geomnodes_obj == None:
+        geomnodes_obj = get_geomnodes_obj()
+
+    if geomnodes_obj.hide_get():
+        geomnodes_obj.hide_set(False, view_layer=bpy.context.view_layer)
+
+    depsgraph = bpy.context.evaluated_depsgraph_get()        
+    eval_geomnodes_data = geomnodes_obj.evaluated_get(depsgraph).data
+    
+    scene_scale_len = bpy.context.scene.unit_settings.scale_length
+
+    # Get number of meshes and instances
+    if geomnodes_obj.type == 'MESH':
+        hashes = [hash(geomnodes_obj)]
+        meshes = [eval_geomnodes_data]
+        instance_meshes = [eval_geomnodes_data]
+        local_matrices = [Matrix() * scene_scale_len]
+        world_matrices = [geomnodes_obj.matrix_world.copy() * scene_scale_len]
+
+    for instance in depsgraph.object_instances:
+        if instance.instance_object and instance.parent and instance.parent.original == geomnodes_obj:
+            if instance.object.type == 'MESH':
+                # add only if the instance is not in the hash array
+                if hash(instance.object.data) not in hashes:
+                    hashes += [hash(instance.object.data)]
+                    meshes += [instance.object.data]
+                # save the instance data and transforms
+                if hash(instance.object.data) in hashes:
+                    instance_meshes += [instance.object.data]
+                    local_matrices += [(instance.parent.matrix_world.inverted() @ instance.matrix_world) * scene_scale_len]
+                    world_matrices += [instance.matrix_world.copy() * scene_scale_len]
+    return meshes, instance_meshes, local_matrices, world_matrices
+
 def get_prop_collection(prop_collection, attr, multipler, data_type):
     array = np.empty(len(prop_collection) * multipler, dtype=data_type)  
     prop_collection.foreach_get(attr, array)
@@ -41,4 +122,12 @@ def add_remove_modifier(modifier_name, add):
     
 
 def remove_special_chars(string: str) -> str:
-    return re.sub(r'[^\w\s]+', '_', string)
+    return re.sub(r'[^\w\s]+', '_', string)
+
+def select_set_objects(object_list: list, flag: bool):
+    """! This function will set the select value of all objects in the list.
+    @param object_list is the list of objects.
+    @flag value that will be set
+    """
+    for obj in object_list:
+        obj.select_set(flag)