|
@@ -21,6 +21,7 @@ else:
|
|
|
from dataclasses import dataclass
|
|
|
from mathutils import Euler
|
|
|
import math
|
|
|
+ import os
|
|
|
|
|
|
# from . import zs_renderscene as zsrs # noqa
|
|
|
|
|
@@ -456,6 +457,14 @@ def get_subcollection_names(collection):
|
|
|
return subcollection_names
|
|
|
|
|
|
|
|
|
+def select_objects_in_collection(collection):
|
|
|
+ """Recursively select all objects in the given collection and its subcollections."""
|
|
|
+ for obj in collection.objects:
|
|
|
+ obj.select_set(True)
|
|
|
+ for subcollection in collection.children:
|
|
|
+ select_objects_in_collection(subcollection)
|
|
|
+
|
|
|
+
|
|
|
def link_collection_to_collection(parentCollectionName, childCollection):
|
|
|
|
|
|
if bpy.context.scene.collection.children:
|
|
@@ -508,6 +517,246 @@ def hide_collection(collection):
|
|
|
collection.hide_viewport = True
|
|
|
|
|
|
|
|
|
+def check_if_selected_objects_have_parent(self):
|
|
|
+ for obj in bpy.context.selected_objects:
|
|
|
+ if obj.parent is None:
|
|
|
+ message = f"Object {obj.name} has no parent"
|
|
|
+ self.report({"ERROR"}, message)
|
|
|
+ return False
|
|
|
+ return True
|
|
|
+
|
|
|
+
|
|
|
+def add_rig_controller_to_selection(self):
|
|
|
+ # add "Rig_Controller_Main" to the selection
|
|
|
+ has_controller_object = False
|
|
|
+ for obj in bpy.data.objects:
|
|
|
+ if obj.name == "Rig_Controller_Main":
|
|
|
+ if obj.hide_viewport:
|
|
|
+ self.report({"ERROR"}, "Rig_Controller_Main is hidden")
|
|
|
+ return has_controller_object
|
|
|
+ obj.select_set(True)
|
|
|
+ # if object is not visible, make it visible
|
|
|
+
|
|
|
+ has_controller_object = True
|
|
|
+ return has_controller_object
|
|
|
+
|
|
|
+ if not has_controller_object:
|
|
|
+ message = f"Rig_Controller_Main not found"
|
|
|
+ self.report({"ERROR"}, message)
|
|
|
+ print("Rig_Controller_Main not found")
|
|
|
+
|
|
|
+ return has_controller_object
|
|
|
+
|
|
|
+
|
|
|
+def select_objects_in_collection(collection):
|
|
|
+ """Recursively select all objects in the given collection and its subcollections."""
|
|
|
+ for obj in collection.objects:
|
|
|
+ obj.select_set(True)
|
|
|
+ for subcollection in collection.children:
|
|
|
+ select_objects_in_collection(subcollection)
|
|
|
+
|
|
|
+
|
|
|
+def check_if_object_has_principled_material(obj):
|
|
|
+ for slot in obj.material_slots:
|
|
|
+
|
|
|
+ # if more than one slot is principled, return
|
|
|
+ for node in slot.material.node_tree.nodes:
|
|
|
+ # print(node.type)
|
|
|
+ if node.type == "BSDF_PRINCIPLED":
|
|
|
+
|
|
|
+ return True
|
|
|
+ else:
|
|
|
+ print("Object has no principled material", obj.name)
|
|
|
+ return False
|
|
|
+
|
|
|
+ return True
|
|
|
+
|
|
|
+
|
|
|
+def unhide_all_objects():
|
|
|
+ # show all objects using operator
|
|
|
+
|
|
|
+ bpy.ops.object.hide_view_clear()
|
|
|
+
|
|
|
+
|
|
|
+# -------------------------------------------------------------------
|
|
|
+# Scene optimization
|
|
|
+# -------------------------------------------------------------------
|
|
|
+def export_non_configurable_to_fbx():
|
|
|
+ # Get the current .blend file path
|
|
|
+
|
|
|
+ blend_filepath = bpy.data.filepath
|
|
|
+ if not blend_filepath:
|
|
|
+ print("Save the .blend file first.")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Get the parent directory of the .blend file
|
|
|
+ blend_dir = os.path.dirname(blend_filepath)
|
|
|
+ parent_dir = os.path.dirname(blend_dir)
|
|
|
+ blend_filename = os.path.splitext(os.path.basename(blend_filepath))[0]
|
|
|
+
|
|
|
+ # Create the FBX export path
|
|
|
+ fbx_export_path = os.path.join(parent_dir, "FBX", f"{blend_filename}.fbx")
|
|
|
+
|
|
|
+ # Ensure the FBX directory exists
|
|
|
+ os.makedirs(os.path.dirname(fbx_export_path), exist_ok=True)
|
|
|
+
|
|
|
+ # Deselect all objects
|
|
|
+ bpy.ops.object.select_all(action="DESELECT")
|
|
|
+
|
|
|
+ # Select all objects in the NonConfigurable collection
|
|
|
+ collection_name = "NonConfigurable"
|
|
|
+ if collection_name in bpy.data.collections:
|
|
|
+ collection = bpy.data.collections[collection_name]
|
|
|
+ for obj in collection.objects:
|
|
|
+ obj.select_set(True)
|
|
|
+ else:
|
|
|
+ print(f"Collection '{collection_name}' not found.")
|
|
|
+ return
|
|
|
+
|
|
|
+
|
|
|
+def export_scene_to_fbx(self):
|
|
|
+ # Ensure the .blend file is saved
|
|
|
+ if not bpy.data.is_saved:
|
|
|
+ print("Save the .blend file first.")
|
|
|
+ self.report({"ERROR"}, "Save the .blend file first.")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Get the current .blend file path
|
|
|
+ blend_filepath = bpy.data.filepath
|
|
|
+ if not blend_filepath:
|
|
|
+ print("Unable to get the .blend file path.")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Get the parent directory of the .blend file
|
|
|
+ blend_dir = os.path.dirname(blend_filepath)
|
|
|
+ parent_dir = os.path.dirname(blend_dir)
|
|
|
+ blend_filename = os.path.splitext(os.path.basename(blend_filepath))[0]
|
|
|
+
|
|
|
+ # Create the FBX export path
|
|
|
+ fbx_export_path = os.path.join(parent_dir, "FBX", f"{blend_filename}.fbx")
|
|
|
+
|
|
|
+ # Ensure the FBX directory exists
|
|
|
+ os.makedirs(os.path.dirname(fbx_export_path), exist_ok=True)
|
|
|
+
|
|
|
+ # unhide all objects
|
|
|
+ unhide_all_objects()
|
|
|
+ # Deselect all objects
|
|
|
+ bpy.ops.object.select_all(action="DESELECT")
|
|
|
+
|
|
|
+ # Select all objects in the NonConfigurable collection and its subcollections
|
|
|
+ collection_name = "NonConfigurable"
|
|
|
+ if collection_name in bpy.data.collections:
|
|
|
+ collection = bpy.data.collections[collection_name]
|
|
|
+ select_objects_in_collection(collection)
|
|
|
+ else:
|
|
|
+ print(f"Collection '{collection_name}' not found.")
|
|
|
+ return
|
|
|
+
|
|
|
+ # check if all objects selected have a parent, if not return
|
|
|
+
|
|
|
+ if not check_if_selected_objects_have_parent(self):
|
|
|
+ return
|
|
|
+
|
|
|
+ if not add_rig_controller_to_selection(self):
|
|
|
+ return
|
|
|
+
|
|
|
+ # Export selected objects to FBX
|
|
|
+ bpy.ops.export_scene.fbx(
|
|
|
+ filepath=fbx_export_path,
|
|
|
+ use_selection=True,
|
|
|
+ global_scale=1.0,
|
|
|
+ apply_unit_scale=True,
|
|
|
+ bake_space_transform=False,
|
|
|
+ object_types={"MESH", "ARMATURE", "EMPTY"},
|
|
|
+ use_mesh_modifiers=True,
|
|
|
+ mesh_smooth_type="FACE",
|
|
|
+ use_custom_props=True,
|
|
|
+ # bake_anim=False,
|
|
|
+ )
|
|
|
+
|
|
|
+ print(f"Exported to {fbx_export_path}")
|
|
|
+
|
|
|
+
|
|
|
+def export_scene_to_glb(self):
|
|
|
+ # Ensure the .blend file is saved
|
|
|
+ if not bpy.data.is_saved:
|
|
|
+ print("Save the .blend file first.")
|
|
|
+ self.report({"ERROR"}, "Save the .blend file first.")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Get the current .blend file path
|
|
|
+ blend_filepath = bpy.data.filepath
|
|
|
+ if not blend_filepath:
|
|
|
+ print("Unable to get the .blend file path.")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Get the parent directory of the .blend file
|
|
|
+ blend_dir = os.path.dirname(blend_filepath)
|
|
|
+ parent_dir = os.path.dirname(blend_dir)
|
|
|
+ blend_filename = os.path.splitext(os.path.basename(blend_filepath))[0]
|
|
|
+
|
|
|
+ # Create the GLB export path
|
|
|
+ glb_export_path = os.path.join(parent_dir, "WEB", f"{blend_filename}.glb")
|
|
|
+
|
|
|
+ # Ensure the GLB directory exists
|
|
|
+ os.makedirs(os.path.dirname(glb_export_path), exist_ok=True)
|
|
|
+
|
|
|
+ # unhide all objects
|
|
|
+ unhide_all_objects()
|
|
|
+ # Deselect all objects
|
|
|
+ bpy.ops.object.select_all(action="DESELECT")
|
|
|
+
|
|
|
+ # Select all objects in the NonConfigurable collection and its subcollections
|
|
|
+ collection_name = "WebGL"
|
|
|
+ if collection_name in bpy.data.collections:
|
|
|
+ collection = bpy.data.collections[collection_name]
|
|
|
+ select_objects_in_collection(collection)
|
|
|
+ else:
|
|
|
+ print(f"Collection '{collection_name}' not found.")
|
|
|
+ return
|
|
|
+
|
|
|
+ if not check_if_selected_objects_have_parent(self):
|
|
|
+ return
|
|
|
+ # check if all objects selected have a parent, if not return
|
|
|
+ if not add_rig_controller_to_selection(self):
|
|
|
+ return
|
|
|
+
|
|
|
+ # # for each selected objects, check if the the material is principled, if not return
|
|
|
+ # for obj in bpy.context.selected_objects:
|
|
|
+ # if not check_if_object_has_principled_material(obj):
|
|
|
+ # message = f"Object {obj.name} has no principled material"
|
|
|
+ # self.report({"ERROR"}, message)
|
|
|
+ # return
|
|
|
+
|
|
|
+ # Export selected objects to GLB
|
|
|
+ bpy.ops.export_scene.gltf(
|
|
|
+ filepath=glb_export_path,
|
|
|
+ export_format="GLB",
|
|
|
+ use_selection=True,
|
|
|
+ export_apply=True,
|
|
|
+ export_animations=False,
|
|
|
+ export_yup=True,
|
|
|
+ export_cameras=False,
|
|
|
+ export_lights=False,
|
|
|
+ export_materials="EXPORT",
|
|
|
+ export_normals=True,
|
|
|
+ export_tangents=True,
|
|
|
+ export_morph=False,
|
|
|
+ export_skins=False,
|
|
|
+ export_draco_mesh_compression_enable=False,
|
|
|
+ export_draco_mesh_compression_level=6,
|
|
|
+ export_draco_position_quantization=14,
|
|
|
+ export_draco_normal_quantization=10,
|
|
|
+ export_draco_texcoord_quantization=12,
|
|
|
+ export_draco_color_quantization=10,
|
|
|
+ export_draco_generic_quantization=12,
|
|
|
+ export_keep_originals=False,
|
|
|
+ export_texture_dir="",
|
|
|
+ )
|
|
|
+
|
|
|
+ print(f"Exported to {glb_export_path}")
|
|
|
+
|
|
|
+
|
|
|
# -------------------------------------------------------------------
|
|
|
# Operators
|
|
|
# -------------------------------------------------------------------
|
|
@@ -522,6 +771,18 @@ class ZSSD_OT_LoadScene(bpy.types.Operator):
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
+# canvas exporter operator
|
|
|
+class ZSSD_OT_ExportAssets(bpy.types.Operator):
|
|
|
+ bl_idname = "zs_canvas.export_assets"
|
|
|
+ bl_label = "Export Assets"
|
|
|
+ bl_description = "Export Scene Assets to FBX and GLB"
|
|
|
+
|
|
|
+ def execute(self, context):
|
|
|
+ export_scene_to_fbx(self)
|
|
|
+ export_scene_to_glb(self)
|
|
|
+ return {"FINISHED"}
|
|
|
+
|
|
|
+
|
|
|
# parent class for panels
|
|
|
class ZSSDPanel:
|
|
|
bl_space_type = "VIEW_3D"
|
|
@@ -553,11 +814,15 @@ class ZSSD_PT_Main(ZSSDPanel, bpy.types.Panel):
|
|
|
# load scene button
|
|
|
col.operator("zs_sd_loader.load_scene", text="Load Scene")
|
|
|
|
|
|
+ # export assets button
|
|
|
+ col.operator("zs_canvas.export_assets", text="Export Assets")
|
|
|
+
|
|
|
|
|
|
# modify after making products
|
|
|
blender_classes = [
|
|
|
ZSSD_PT_Main,
|
|
|
ZSSD_OT_LoadScene,
|
|
|
+ ZSSD_OT_ExportAssets,
|
|
|
]
|
|
|
|
|
|
|