__init__.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. # ----------------------------------------------------------
  2. # File __init__.py
  3. # ----------------------------------------------------------
  4. # Imports
  5. # Check if this add-on is being reloaded
  6. if "bpy" in locals():
  7. # reloading .py files
  8. import bpy
  9. import importlib
  10. # from . import zs_renderscene as zsrs
  11. # importlib.reload(zsrs)
  12. # or if this is the first load of this add-on
  13. else:
  14. import bpy
  15. import json
  16. from pathlib import Path
  17. from dataclasses import dataclass
  18. from mathutils import Euler
  19. import math
  20. import os
  21. # from . import zs_renderscene as zsrs # noqa
  22. # Addon info
  23. bl_info = {
  24. "name": "ZS Stable Diffusion Connection V0.0.1",
  25. "author": "Sergiu <sergiu@zixelise.com>",
  26. "Version": (0, 0, 1),
  27. "blender": (4, 00, 0),
  28. "category": "Scene",
  29. "description": "Stable Diffusion Connection",
  30. }
  31. # -------------------------------------------------------------------
  32. # Functions
  33. # -------------------------------------------------------------------
  34. @dataclass
  35. class AssetData:
  36. path: str
  37. collection_name: str
  38. type: str
  39. product_asset = AssetData(
  40. path="sample_scene/01_Products", collection_name="01_Products", type="product"
  41. )
  42. element_asset = AssetData(
  43. path="sample_scene/02_Elements", collection_name="02_Elements", type="asset"
  44. )
  45. basic_shapes_asset = AssetData(
  46. path="sample_scene/03_BasicShapes",
  47. collection_name="03_BasicShapes",
  48. type="shape",
  49. )
  50. def load_scene():
  51. print("Loading Scene")
  52. # load scene data
  53. scene_data = load_scene_data()
  54. # create parent collections
  55. create_parent_collections(product_asset.collection_name)
  56. create_parent_collections(element_asset.collection_name)
  57. create_parent_collections(basic_shapes_asset.collection_name)
  58. create_parent_collections("05_Cameras")
  59. # append products
  60. products_data = load_objects_data(scene_data, product_asset.type)
  61. for index, product in enumerate(products_data):
  62. append_objects(product, index, product_asset)
  63. # append elements
  64. elements_data = load_objects_data(scene_data, element_asset.type)
  65. for index, product in enumerate(elements_data):
  66. append_objects(product, index, element_asset)
  67. # append shapes
  68. basic_shapes_data = load_objects_data(scene_data, basic_shapes_asset.type)
  69. for index, product in enumerate(basic_shapes_data):
  70. append_objects(product, index, basic_shapes_asset)
  71. # set lighting
  72. set_environment(scene_data)
  73. # set camera
  74. create_cameras(scene_data)
  75. # rename outputs
  76. set_output_paths(
  77. "D://Git//ap-canvas-creation-module//03_blender//sd_blender//sample_scene//Renders",
  78. scene_data["project_id"],
  79. )
  80. # setup compositing
  81. set_cryptomatte_objects("01_Products", "mask_product")
  82. def load_scene_data():
  83. print("Loading Scene Data")
  84. # load scene data
  85. # to be replaced with actual data
  86. # open scene_info.json
  87. script_path = Path(__file__).resolve()
  88. scene_data_path = script_path.parent / "sample_scene" / "scene_info.json"
  89. with scene_data_path.open() as file:
  90. scene_data = json.load(file)
  91. print(scene_data)
  92. return scene_data
  93. def load_objects_data(scene_data, object_type: str):
  94. print("Loading Assets Data")
  95. # load assets data
  96. objects_data = []
  97. for object in scene_data["scene"]["objects"]:
  98. if object["group_type"] == object_type:
  99. # get additional object data by id and combine with object data
  100. object_data = get_object_data_by_id(object["id"])
  101. object.update(object_data)
  102. objects_data.append(object)
  103. return objects_data
  104. # to be replaced with actual data
  105. def get_object_data_by_id(object_id: str):
  106. print("Getting Object Data")
  107. # open assets_database.json
  108. script_path = Path(__file__).resolve()
  109. assets_data_path = script_path.parent / "sample_scene" / "assets_database.json"
  110. with assets_data_path.open() as file:
  111. assets_data = json.load(file)
  112. # get object data by id
  113. for object in assets_data:
  114. if object["id"] == object_id:
  115. return object
  116. def get_hdri_data_by_id(object_id: str):
  117. print("Getting HDRI Data")
  118. # open assets_database.json
  119. script_path = Path(__file__).resolve()
  120. assets_data_path = script_path.parent / "sample_scene" / "lighting_database.json"
  121. with assets_data_path.open() as file:
  122. assets_data = json.load(file)
  123. # get object data by id
  124. for object in assets_data:
  125. if object["id"] == object_id:
  126. return object
  127. def append_objects(asset_info, index, asset_data: AssetData):
  128. print("Appending Objects")
  129. blendFileName = asset_info["name"]
  130. # visibleLayersJSONName = productInfo["Version"]
  131. # activeSwitchMaterials = json.dumps(productInfo["ActiveMaterials"])
  132. collectionName = blendFileName + "_" + str(index)
  133. append_active_layers(collectionName, asset_info, asset_data)
  134. # replace_switch_materials(shotInfo, productInfo["ActiveMaterials"])
  135. link_collection_to_collection(
  136. asset_data.collection_name, bpy.data.collections[collectionName]
  137. )
  138. def append_active_layers(newCollectionName, product_info, asset_data: AssetData):
  139. utility_collections = [
  140. "MaterialLibrary",
  141. "Animation_Controller",
  142. "Animation_Rig",
  143. "Animation_Target",
  144. ]
  145. # visible_layers = utility_collections + product_info["VisibleLayers"]
  146. visible_layers = utility_collections
  147. script_path = Path(__file__).resolve().parent
  148. filePath = str(
  149. script_path
  150. / asset_data.path
  151. / product_info["name"]
  152. / "BLEND"
  153. / (product_info["name"] + ".blend")
  154. )
  155. print(filePath)
  156. # delete all objects and collections from product collection
  157. create_parent_collections(newCollectionName)
  158. # append active collections
  159. with bpy.data.libraries.load(filePath) as (data_from, data_to):
  160. data_to.collections = []
  161. for name in data_from.collections:
  162. if name in visible_layers:
  163. data_to.collections.append(name)
  164. if "NonConfigurable" in name:
  165. data_to.collections.append(name)
  166. # link appended colections to newCollection
  167. for collection in data_to.collections:
  168. # try:
  169. # bpy.context.scene.collection.children.link(collection)
  170. # except:
  171. # print(collection)
  172. link_collection_to_collection(newCollectionName, collection)
  173. # hide utility collections
  174. for utilityCollectionName in utility_collections:
  175. if utilityCollectionName in collection.name:
  176. # rename utility collections
  177. collection.name = newCollectionName + "_" + utilityCollectionName
  178. hide_collection(collection)
  179. # need to redo this in the future
  180. if "Animation_Target" in collection.name:
  181. # print object name from collection
  182. collection.objects[0].location = product_info["properties"][
  183. "transform"
  184. ]["position"]
  185. # collection.objects[0].rotation_euler = product_info["properties"][
  186. # "transform"
  187. # ]["rotation"]
  188. rotation_in_degrees = product_info["properties"]["transform"][
  189. "rotation"
  190. ]
  191. rotation_in_radians = [math.radians(deg) for deg in rotation_in_degrees]
  192. collection.objects[0].rotation_euler = rotation_in_radians
  193. collection.objects[0].scale = product_info["properties"]["transform"][
  194. "scale"
  195. ]
  196. # make all objects in collection local
  197. for obj in bpy.data.collections[newCollectionName].all_objects:
  198. if obj.type == "MESH":
  199. obj.make_local()
  200. obj.data.make_local()
  201. # remove duplicated material slots
  202. mats = bpy.data.materials
  203. for obj in bpy.data.collections[newCollectionName].all_objects:
  204. for slot in obj.material_slots:
  205. part = slot.name.rpartition(".")
  206. if part[2].isnumeric() and part[0] in mats:
  207. slot.material = mats.get(part[0])
  208. def create_parent_collections(group_name: str):
  209. if collection_exists(group_name):
  210. remove_collection_and_objects(group_name)
  211. else:
  212. create_collection(group_name)
  213. def set_environment(scene_data):
  214. # Find the group named NG_Canvas_Background in the Blender world
  215. world = bpy.context.scene.world
  216. if world is not None:
  217. # Get the node group named NG_Canvas_Background
  218. node_group = world.node_tree.nodes.get("NG_Canvas_Background")
  219. if node_group is not None:
  220. # Set the group's properties from scene_data
  221. node_group.inputs[0].default_value = float(
  222. scene_data["scene"]["environment"]["lighting"]["rotation"]
  223. )
  224. node_group.inputs[1].default_value = float(
  225. scene_data["scene"]["environment"]["lighting"]["intensity"]
  226. )
  227. if scene_data["scene"]["environment"]["lighting"]["visible"] == True:
  228. node_group.inputs[2].default_value = 1.0
  229. else:
  230. node_group.inputs[2].default_value = 0.0
  231. node_group.inputs[3].default_value = (
  232. scene_data["scene"]["environment"]["background"]["color"]["r"],
  233. scene_data["scene"]["environment"]["background"]["color"]["g"],
  234. scene_data["scene"]["environment"]["background"]["color"]["b"],
  235. 1.0,
  236. )
  237. hdri_data = get_hdri_data_by_id(
  238. scene_data["scene"]["environment"]["lighting"]["id"]
  239. )
  240. # go inside the node group, and find the image texture node, and set the image to the one from the scene data
  241. for node in node_group.node_tree.nodes:
  242. if node.name == "environment_map":
  243. node.image = bpy.data.images.load(
  244. str(
  245. Path(__file__).resolve().parent
  246. / "sample_scene"
  247. / "05_Lighting"
  248. / "HDRI"
  249. / (hdri_data["name"] + ".exr")
  250. )
  251. )
  252. else:
  253. print("Group 'NG_Canvas_Background' not found")
  254. def create_cameras(scene_data):
  255. # # Get the 05_Cameras collection, or create it if it doesn't exist
  256. collection_name = "05_Cameras"
  257. if collection_name in bpy.data.collections:
  258. collection = bpy.data.collections[collection_name]
  259. else:
  260. return
  261. for camera_data in scene_data["scene"]["cameras"]:
  262. # Create a new camera object
  263. bpy.ops.object.camera_add()
  264. # Get the newly created camera
  265. camera = bpy.context.object
  266. # Set the camera's name
  267. camera.name = camera_data["name"]
  268. # Set the camera's position
  269. position = camera_data["properties"]["transform"]["position"]
  270. camera.location.x = position[0]
  271. camera.location.y = position[1]
  272. camera.location.z = position[2]
  273. # Set the camera's rotation
  274. rotation = camera_data["properties"]["transform"]["rotation"]
  275. # Convert the rotation from degrees to radians
  276. rotation = [math.radians(r) for r in rotation]
  277. camera.rotation_euler = Euler(rotation, "XYZ")
  278. # Set the camera's lens properties
  279. lens = camera_data["properties"]["lens"]
  280. type_mapping = {
  281. "PERSPECTIVE": "PERSP",
  282. "ORTHOGRAPHIC": "ORTHO",
  283. "PANORAMIC": "PANO",
  284. }
  285. camera.data.type = type_mapping.get(lens["type"].upper(), "PERSP")
  286. camera.data.angle = math.radians(lens["fov"])
  287. camera.data.clip_start = lens["near"]
  288. camera.data.clip_end = lens["far"]
  289. # Add the camera to the 05_Cameras collection
  290. collection.objects.link(camera)
  291. bpy.context.scene.collection.objects.unlink(camera)
  292. # Set the camera as the active camera if "active" is true
  293. if camera_data["properties"]["active"]:
  294. bpy.context.scene.camera = camera
  295. def set_output_paths(base_path, project_name):
  296. # Get the current scene
  297. scene = bpy.context.scene
  298. # Check if the scene has a compositor node tree
  299. if scene.node_tree:
  300. # Iterate over all nodes in the node tree
  301. for node in scene.node_tree.nodes:
  302. # Check if the node is an output node
  303. if node.type == "OUTPUT_FILE":
  304. # Set the base path of the output node
  305. node.base_path = base_path + "//" + project_name
  306. # Iterate over all file slots of the output node
  307. # for file_slot in node.file_slots:
  308. # # Set the path of the file slot
  309. # file_slot.path = project_name
  310. def set_cryptomatte_objects(collection_name, node_name):
  311. # Get all objects in the specified collection
  312. objects = bpy.data.collections[collection_name].all_objects
  313. # Convert the objects to a list
  314. object_names = [obj.name for obj in objects]
  315. print(object_names)
  316. # Get the current scene
  317. scene = bpy.context.scene
  318. # Check if the scene has a compositor node tree
  319. if scene.node_tree:
  320. # Iterate over all nodes in the node tree
  321. for node in scene.node_tree.nodes:
  322. # Check if the node is a Cryptomatte node with the specified name
  323. if node.type == "CRYPTOMATTE_V2" and node.name == node_name:
  324. # Set the Matte ID objects of the node
  325. node.matte_id = ",".join(object_names)
  326. # -------------------------------------------------------------------
  327. # Utilities
  328. # -------------------------------------------------------------------
  329. def remove_collection_and_objects(collection_name):
  330. oldObjects = list(bpy.data.collections[collection_name].all_objects)
  331. for obj in oldObjects:
  332. bpy.data.objects.remove(obj, do_unlink=True)
  333. old_collection = bpy.data.collections[collection_name]
  334. if old_collection is not None:
  335. old_collection_names = get_subcollection_names(old_collection)
  336. else:
  337. print("Collection not found.")
  338. # print line break
  339. print("-----------------------------------------------------------------")
  340. print(old_collection_names)
  341. print("-----------------------------------------------------------------")
  342. for old_collection_name in old_collection_names:
  343. for collection in bpy.data.collections:
  344. if collection.name == old_collection_name:
  345. bpy.data.collections.remove(collection)
  346. bpy.ops.outliner.orphans_purge(
  347. do_local_ids=True, do_linked_ids=True, do_recursive=True
  348. )
  349. def get_subcollection_names(collection):
  350. subcollection_names = []
  351. for child in collection.children:
  352. subcollection_names.append(child.name)
  353. subcollection_names.extend(get_subcollection_names(child))
  354. return subcollection_names
  355. def select_objects_in_collection(collection):
  356. """Recursively select all objects in the given collection and its subcollections."""
  357. for obj in collection.objects:
  358. obj.select_set(True)
  359. for subcollection in collection.children:
  360. select_objects_in_collection(subcollection)
  361. def link_collection_to_collection(parentCollectionName, childCollection):
  362. if bpy.context.scene.collection.children:
  363. parentCollection = bpy.context.scene.collection.children.get(
  364. parentCollectionName
  365. )
  366. # Add it to the main collection
  367. # childCollection = bpy.context.scene.collection.children.get(childCollection)
  368. parentCollection.children.link(childCollection)
  369. # if child collection is in scene collection unlink it
  370. if bpy.context.scene.collection.children.get(childCollection.name):
  371. bpy.context.scene.collection.children.unlink(childCollection)
  372. # bpy.context.scene.collection.children.unlink(childCollection)
  373. # link collection to collection
  374. def link_collection_to_collection_old(parentCollectionName, childCollection):
  375. if bpy.context.scene.collection.children:
  376. parentCollection = bpy.context.scene.collection.children.get(
  377. parentCollectionName
  378. )
  379. # Add it to the main collection
  380. try:
  381. childCollection = bpy.data.collections[childCollection]
  382. except:
  383. print("Collection not found.")
  384. return
  385. parentCollection.children.link(childCollection)
  386. bpy.context.scene.collection.children.unlink(childCollection)
  387. # function that checks if a collection exists
  388. def collection_exists(collection_name):
  389. return collection_name in bpy.data.collections
  390. # function that creates a new collection and adds it to the scene
  391. def create_collection(collection_name):
  392. new_collection = bpy.data.collections.new(collection_name)
  393. bpy.context.scene.collection.children.link(new_collection)
  394. def hide_collection(collection):
  395. collection.hide_render = True
  396. collection.hide_viewport = True
  397. def check_if_selected_objects_have_parent(self):
  398. for obj in bpy.context.selected_objects:
  399. if obj.parent is None:
  400. message = f"Object {obj.name} has no parent"
  401. self.report({"ERROR"}, message)
  402. return False
  403. return True
  404. def add_rig_controller_to_selection(self):
  405. # add "Rig_Controller_Main" to the selection
  406. has_controller_object = False
  407. for obj in bpy.data.objects:
  408. if obj.name == "Rig_Controller_Main":
  409. if obj.hide_viewport:
  410. self.report({"ERROR"}, "Rig_Controller_Main is hidden")
  411. return has_controller_object
  412. obj.select_set(True)
  413. # if object is not visible, make it visible
  414. has_controller_object = True
  415. return has_controller_object
  416. if not has_controller_object:
  417. message = f"Rig_Controller_Main not found"
  418. self.report({"ERROR"}, message)
  419. print("Rig_Controller_Main not found")
  420. return has_controller_object
  421. def select_objects_in_collection(collection):
  422. """Recursively select all objects in the given collection and its subcollections."""
  423. for obj in collection.objects:
  424. obj.select_set(True)
  425. for subcollection in collection.children:
  426. select_objects_in_collection(subcollection)
  427. def check_if_object_has_principled_material(obj):
  428. for slot in obj.material_slots:
  429. # if more than one slot is principled, return
  430. for node in slot.material.node_tree.nodes:
  431. # print(node.type)
  432. if node.type == "BSDF_PRINCIPLED":
  433. return True
  434. else:
  435. print("Object has no principled material", obj.name)
  436. return False
  437. return True
  438. def unhide_all_objects():
  439. # show all objects using operator
  440. bpy.ops.object.hide_view_clear()
  441. # -------------------------------------------------------------------
  442. # Scene optimization
  443. # -------------------------------------------------------------------
  444. def export_non_configurable_to_fbx():
  445. # Get the current .blend file path
  446. blend_filepath = bpy.data.filepath
  447. if not blend_filepath:
  448. print("Save the .blend file first.")
  449. return
  450. # Get the parent directory of the .blend file
  451. blend_dir = os.path.dirname(blend_filepath)
  452. parent_dir = os.path.dirname(blend_dir)
  453. blend_filename = os.path.splitext(os.path.basename(blend_filepath))[0]
  454. # Create the FBX export path
  455. fbx_export_path = os.path.join(parent_dir, "FBX", f"{blend_filename}.fbx")
  456. # Ensure the FBX directory exists
  457. os.makedirs(os.path.dirname(fbx_export_path), exist_ok=True)
  458. # Deselect all objects
  459. bpy.ops.object.select_all(action="DESELECT")
  460. # Select all objects in the NonConfigurable collection
  461. collection_name = "NonConfigurable"
  462. if collection_name in bpy.data.collections:
  463. collection = bpy.data.collections[collection_name]
  464. for obj in collection.objects:
  465. obj.select_set(True)
  466. else:
  467. print(f"Collection '{collection_name}' not found.")
  468. return
  469. def export_scene_to_fbx(self):
  470. # Ensure the .blend file is saved
  471. if not bpy.data.is_saved:
  472. print("Save the .blend file first.")
  473. self.report({"ERROR"}, "Save the .blend file first.")
  474. return
  475. # Get the current .blend file path
  476. blend_filepath = bpy.data.filepath
  477. if not blend_filepath:
  478. print("Unable to get the .blend file path.")
  479. return
  480. # Get the parent directory of the .blend file
  481. blend_dir = os.path.dirname(blend_filepath)
  482. parent_dir = os.path.dirname(blend_dir)
  483. blend_filename = os.path.splitext(os.path.basename(blend_filepath))[0]
  484. # Create the FBX export path
  485. fbx_export_path = os.path.join(parent_dir, "FBX", f"{blend_filename}.fbx")
  486. # Ensure the FBX directory exists
  487. os.makedirs(os.path.dirname(fbx_export_path), exist_ok=True)
  488. # unhide all objects
  489. unhide_all_objects()
  490. # Deselect all objects
  491. bpy.ops.object.select_all(action="DESELECT")
  492. # Select all objects in the NonConfigurable collection and its subcollections
  493. collection_name = "NonConfigurable"
  494. if collection_name in bpy.data.collections:
  495. collection = bpy.data.collections[collection_name]
  496. select_objects_in_collection(collection)
  497. else:
  498. print(f"Collection '{collection_name}' not found.")
  499. return
  500. # check if all objects selected have a parent, if not return
  501. if not check_if_selected_objects_have_parent(self):
  502. return
  503. if not add_rig_controller_to_selection(self):
  504. return
  505. # Export selected objects to FBX
  506. bpy.ops.export_scene.fbx(
  507. filepath=fbx_export_path,
  508. use_selection=True,
  509. global_scale=1.0,
  510. apply_unit_scale=True,
  511. bake_space_transform=False,
  512. object_types={"MESH", "ARMATURE", "EMPTY"},
  513. use_mesh_modifiers=True,
  514. mesh_smooth_type="FACE",
  515. use_custom_props=True,
  516. # bake_anim=False,
  517. )
  518. print(f"Exported to {fbx_export_path}")
  519. def export_scene_to_glb(self):
  520. # Ensure the .blend file is saved
  521. if not bpy.data.is_saved:
  522. print("Save the .blend file first.")
  523. self.report({"ERROR"}, "Save the .blend file first.")
  524. return
  525. # Get the current .blend file path
  526. blend_filepath = bpy.data.filepath
  527. if not blend_filepath:
  528. print("Unable to get the .blend file path.")
  529. return
  530. # Get the parent directory of the .blend file
  531. blend_dir = os.path.dirname(blend_filepath)
  532. parent_dir = os.path.dirname(blend_dir)
  533. blend_filename = os.path.splitext(os.path.basename(blend_filepath))[0]
  534. # Create the GLB export path
  535. glb_export_path = os.path.join(parent_dir, "WEB", f"{blend_filename}.glb")
  536. # Ensure the GLB directory exists
  537. os.makedirs(os.path.dirname(glb_export_path), exist_ok=True)
  538. # unhide all objects
  539. unhide_all_objects()
  540. # Deselect all objects
  541. bpy.ops.object.select_all(action="DESELECT")
  542. # Select all objects in the NonConfigurable collection and its subcollections
  543. collection_name = "WebGL"
  544. if collection_name in bpy.data.collections:
  545. collection = bpy.data.collections[collection_name]
  546. select_objects_in_collection(collection)
  547. else:
  548. print(f"Collection '{collection_name}' not found.")
  549. return
  550. if not check_if_selected_objects_have_parent(self):
  551. return
  552. # check if all objects selected have a parent, if not return
  553. if not add_rig_controller_to_selection(self):
  554. return
  555. # # for each selected objects, check if the the material is principled, if not return
  556. # for obj in bpy.context.selected_objects:
  557. # if not check_if_object_has_principled_material(obj):
  558. # message = f"Object {obj.name} has no principled material"
  559. # self.report({"ERROR"}, message)
  560. # return
  561. # Export selected objects to GLB
  562. bpy.ops.export_scene.gltf(
  563. filepath=glb_export_path,
  564. export_format="GLB",
  565. use_selection=True,
  566. export_apply=True,
  567. export_animations=False,
  568. export_yup=True,
  569. export_cameras=False,
  570. export_lights=False,
  571. export_materials="EXPORT",
  572. export_normals=True,
  573. export_tangents=True,
  574. export_morph=False,
  575. export_skins=False,
  576. export_draco_mesh_compression_enable=False,
  577. export_draco_mesh_compression_level=6,
  578. export_draco_position_quantization=14,
  579. export_draco_normal_quantization=10,
  580. export_draco_texcoord_quantization=12,
  581. export_draco_color_quantization=10,
  582. export_draco_generic_quantization=12,
  583. export_keep_originals=False,
  584. export_texture_dir="",
  585. )
  586. print(f"Exported to {glb_export_path}")
  587. # -------------------------------------------------------------------
  588. # Operators
  589. # -------------------------------------------------------------------
  590. # load scene operator
  591. class ZSSD_OT_LoadScene(bpy.types.Operator):
  592. bl_idname = "zs_sd_loader.load_scene"
  593. bl_label = "Load Scene"
  594. bl_description = "Load Scene"
  595. def execute(self, context):
  596. load_scene()
  597. return {"FINISHED"}
  598. # canvas exporter operator
  599. class ZSSD_OT_ExportAssets(bpy.types.Operator):
  600. bl_idname = "zs_canvas.export_assets"
  601. bl_label = "Export Assets"
  602. bl_description = "Export Scene Assets to FBX and GLB"
  603. def execute(self, context):
  604. export_scene_to_fbx(self)
  605. export_scene_to_glb(self)
  606. return {"FINISHED"}
  607. # parent class for panels
  608. class ZSSDPanel:
  609. bl_space_type = "VIEW_3D"
  610. bl_region_type = "UI"
  611. bl_category = "ZS SD Loader"
  612. # -------------------------------------------------------------------
  613. # Draw
  614. # -------------------------------------------------------------------
  615. # Panels
  616. class ZSSD_PT_Main(ZSSDPanel, bpy.types.Panel):
  617. bl_label = "SD Loader"
  618. def draw(self, context):
  619. layout = self.layout
  620. scene = context.scene
  621. col = layout.column()
  622. self.is_connected = False
  623. col.label(text="Stable Diffusion Connection")
  624. # load scene button
  625. col.operator("zs_sd_loader.load_scene", text="Load Scene")
  626. # export assets button
  627. col.operator("zs_canvas.export_assets", text="Export Assets")
  628. # modify after making products
  629. blender_classes = [
  630. ZSSD_PT_Main,
  631. ZSSD_OT_LoadScene,
  632. ZSSD_OT_ExportAssets,
  633. ]
  634. def register():
  635. # register classes
  636. for blender_class in blender_classes:
  637. bpy.utils.register_class(blender_class)
  638. # Has to be afqter class registering to correctly register property
  639. # register global properties
  640. # register list
  641. # list data
  642. # bpy.types.Scene.zs_product_list = bpy.props.CollectionProperty(
  643. # type=ZS_Product_ListItem)
  644. # current item in list
  645. def unregister():
  646. # unregister classes
  647. for blender_class in blender_classes:
  648. bpy.utils.unregister_class(blender_class)
  649. # unregister global properties
  650. # unregister list items
  651. # del bpy.types.Scene.my_list
  652. # del bpy.types.Scene.product_product_list_index