__init__.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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. # from . import zs_renderscene as zsrs # noqa
  21. # Addon info
  22. bl_info = {
  23. "name": "ZS Stable Diffusion Connection V0.0.1",
  24. "author": "Sergiu <sergiu@zixelise.com>",
  25. "Version": (0, 0, 1),
  26. "blender": (4, 00, 0),
  27. "category": "Scene",
  28. "description": "Stable Diffusion Connection",
  29. }
  30. # -------------------------------------------------------------------
  31. # Functions
  32. # -------------------------------------------------------------------
  33. @dataclass
  34. class AssetData:
  35. path: str
  36. collection_name: str
  37. type: str
  38. product_asset = AssetData(
  39. path="sample_scene/01_Products", collection_name="01_Products", type="product"
  40. )
  41. element_asset = AssetData(
  42. path="sample_scene/02_Elements", collection_name="02_Elements", type="asset"
  43. )
  44. basic_shapes_asset = AssetData(
  45. path="sample_scene/03_BasicShapes",
  46. collection_name="03_BasicShapes",
  47. type="shape",
  48. )
  49. def load_scene():
  50. print("Loading Scene")
  51. # load scene data
  52. scene_data = load_scene_data()
  53. # create parent collections
  54. create_parent_collections(product_asset.collection_name)
  55. create_parent_collections(element_asset.collection_name)
  56. create_parent_collections(basic_shapes_asset.collection_name)
  57. create_parent_collections("05_Cameras")
  58. # append products
  59. products_data = load_objects_data(scene_data, product_asset.type)
  60. for index, product in enumerate(products_data):
  61. append_objects(product, index, product_asset)
  62. # append elements
  63. elements_data = load_objects_data(scene_data, element_asset.type)
  64. for index, product in enumerate(elements_data):
  65. append_objects(product, index, element_asset)
  66. # append shapes
  67. basic_shapes_data = load_objects_data(scene_data, basic_shapes_asset.type)
  68. for index, product in enumerate(basic_shapes_data):
  69. append_objects(product, index, basic_shapes_asset)
  70. # set lighting
  71. set_environment(scene_data)
  72. # set camera
  73. create_cameras(scene_data)
  74. # rename outputs
  75. set_output_paths(
  76. "D://Git//ap-canvas-creation-module//03_blender//sd_blender//sample_scene//Renders",
  77. scene_data["project_id"],
  78. )
  79. # setup compositing
  80. set_cryptomatte_objects("01_Products", "mask_product")
  81. def load_scene_data():
  82. print("Loading Scene Data")
  83. # load scene data
  84. # to be replaced with actual data
  85. # open scene_info.json
  86. script_path = Path(__file__).resolve()
  87. scene_data_path = script_path.parent / "sample_scene" / "scene_info.json"
  88. with scene_data_path.open() as file:
  89. scene_data = json.load(file)
  90. print(scene_data)
  91. return scene_data
  92. def load_objects_data(scene_data, object_type: str):
  93. print("Loading Assets Data")
  94. # load assets data
  95. objects_data = []
  96. for object in scene_data["scene"]["objects"]:
  97. if object["group_type"] == object_type:
  98. # get additional object data by id and combine with object data
  99. object_data = get_object_data_by_id(object["id"])
  100. object.update(object_data)
  101. objects_data.append(object)
  102. return objects_data
  103. # to be replaced with actual data
  104. def get_object_data_by_id(object_id: str):
  105. print("Getting Object Data")
  106. # open assets_database.json
  107. script_path = Path(__file__).resolve()
  108. assets_data_path = script_path.parent / "sample_scene" / "assets_database.json"
  109. with assets_data_path.open() as file:
  110. assets_data = json.load(file)
  111. # get object data by id
  112. for object in assets_data:
  113. if object["id"] == object_id:
  114. return object
  115. def get_hdri_data_by_id(object_id: str):
  116. print("Getting HDRI Data")
  117. # open assets_database.json
  118. script_path = Path(__file__).resolve()
  119. assets_data_path = script_path.parent / "sample_scene" / "lighting_database.json"
  120. with assets_data_path.open() as file:
  121. assets_data = json.load(file)
  122. # get object data by id
  123. for object in assets_data:
  124. if object["id"] == object_id:
  125. return object
  126. def append_objects(asset_info, index, asset_data: AssetData):
  127. print("Appending Objects")
  128. blendFileName = asset_info["name"]
  129. # visibleLayersJSONName = productInfo["Version"]
  130. # activeSwitchMaterials = json.dumps(productInfo["ActiveMaterials"])
  131. collectionName = blendFileName + "_" + str(index)
  132. append_active_layers(collectionName, asset_info, asset_data)
  133. # replace_switch_materials(shotInfo, productInfo["ActiveMaterials"])
  134. link_collection_to_collection(
  135. asset_data.collection_name, bpy.data.collections[collectionName]
  136. )
  137. def append_active_layers(newCollectionName, product_info, asset_data: AssetData):
  138. utility_collections = [
  139. "MaterialLibrary",
  140. "Animation_Controller",
  141. "Animation_Rig",
  142. "Animation_Target",
  143. ]
  144. # visible_layers = utility_collections + product_info["VisibleLayers"]
  145. visible_layers = utility_collections
  146. script_path = Path(__file__).resolve().parent
  147. filePath = str(
  148. script_path
  149. / asset_data.path
  150. / product_info["name"]
  151. / "BLEND"
  152. / (product_info["name"] + ".blend")
  153. )
  154. print(filePath)
  155. # delete all objects and collections from product collection
  156. create_parent_collections(newCollectionName)
  157. # append active collections
  158. with bpy.data.libraries.load(filePath) as (data_from, data_to):
  159. data_to.collections = []
  160. for name in data_from.collections:
  161. if name in visible_layers:
  162. data_to.collections.append(name)
  163. if "NonConfigurable" in name:
  164. data_to.collections.append(name)
  165. # link appended colections to newCollection
  166. for collection in data_to.collections:
  167. # try:
  168. # bpy.context.scene.collection.children.link(collection)
  169. # except:
  170. # print(collection)
  171. link_collection_to_collection(newCollectionName, collection)
  172. # hide utility collections
  173. for utilityCollectionName in utility_collections:
  174. if utilityCollectionName in collection.name:
  175. # rename utility collections
  176. collection.name = newCollectionName + "_" + utilityCollectionName
  177. hide_collection(collection)
  178. # need to redo this in the future
  179. if "Animation_Target" in collection.name:
  180. # print object name from collection
  181. collection.objects[0].location = product_info["properties"][
  182. "transform"
  183. ]["position"]
  184. # collection.objects[0].rotation_euler = product_info["properties"][
  185. # "transform"
  186. # ]["rotation"]
  187. rotation_in_degrees = product_info["properties"]["transform"][
  188. "rotation"
  189. ]
  190. rotation_in_radians = [math.radians(deg) for deg in rotation_in_degrees]
  191. collection.objects[0].rotation_euler = rotation_in_radians
  192. collection.objects[0].scale = product_info["properties"]["transform"][
  193. "scale"
  194. ]
  195. # make all objects in collection local
  196. for obj in bpy.data.collections[newCollectionName].all_objects:
  197. if obj.type == "MESH":
  198. obj.make_local()
  199. obj.data.make_local()
  200. # remove duplicated material slots
  201. mats = bpy.data.materials
  202. for obj in bpy.data.collections[newCollectionName].all_objects:
  203. for slot in obj.material_slots:
  204. part = slot.name.rpartition(".")
  205. if part[2].isnumeric() and part[0] in mats:
  206. slot.material = mats.get(part[0])
  207. def create_parent_collections(group_name: str):
  208. if collection_exists(group_name):
  209. remove_collection_and_objects(group_name)
  210. else:
  211. create_collection(group_name)
  212. def set_environment(scene_data):
  213. # Find the group named NG_Canvas_Background in the Blender world
  214. world = bpy.context.scene.world
  215. if world is not None:
  216. # Get the node group named NG_Canvas_Background
  217. node_group = world.node_tree.nodes.get("NG_Canvas_Background")
  218. if node_group is not None:
  219. # Set the group's properties from scene_data
  220. node_group.inputs[0].default_value = float(
  221. scene_data["scene"]["environment"]["lighting"]["rotation"]
  222. )
  223. node_group.inputs[1].default_value = float(
  224. scene_data["scene"]["environment"]["lighting"]["intensity"]
  225. )
  226. if scene_data["scene"]["environment"]["lighting"]["visible"] == True:
  227. node_group.inputs[2].default_value = 1.0
  228. else:
  229. node_group.inputs[2].default_value = 0.0
  230. node_group.inputs[3].default_value = (
  231. scene_data["scene"]["environment"]["background"]["color"]["r"],
  232. scene_data["scene"]["environment"]["background"]["color"]["g"],
  233. scene_data["scene"]["environment"]["background"]["color"]["b"],
  234. 1.0,
  235. )
  236. hdri_data = get_hdri_data_by_id(
  237. scene_data["scene"]["environment"]["lighting"]["id"]
  238. )
  239. # go inside the node group, and find the image texture node, and set the image to the one from the scene data
  240. for node in node_group.node_tree.nodes:
  241. if node.name == "environment_map":
  242. node.image = bpy.data.images.load(
  243. str(
  244. Path(__file__).resolve().parent
  245. / "sample_scene"
  246. / "05_Lighting"
  247. / "HDRI"
  248. / (hdri_data["name"] + ".exr")
  249. )
  250. )
  251. else:
  252. print("Group 'NG_Canvas_Background' not found")
  253. def create_cameras(scene_data):
  254. # # Get the 05_Cameras collection, or create it if it doesn't exist
  255. collection_name = "05_Cameras"
  256. if collection_name in bpy.data.collections:
  257. collection = bpy.data.collections[collection_name]
  258. else:
  259. return
  260. for camera_data in scene_data["scene"]["cameras"]:
  261. # Create a new camera object
  262. bpy.ops.object.camera_add()
  263. # Get the newly created camera
  264. camera = bpy.context.object
  265. # Set the camera's name
  266. camera.name = camera_data["name"]
  267. # Set the camera's position
  268. position = camera_data["properties"]["transform"]["position"]
  269. camera.location.x = position[0]
  270. camera.location.y = position[1]
  271. camera.location.z = position[2]
  272. # Set the camera's rotation
  273. rotation = camera_data["properties"]["transform"]["rotation"]
  274. # Convert the rotation from degrees to radians
  275. rotation = [math.radians(r) for r in rotation]
  276. camera.rotation_euler = Euler(rotation, "XYZ")
  277. # Set the camera's lens properties
  278. lens = camera_data["properties"]["lens"]
  279. type_mapping = {
  280. "PERSPECTIVE": "PERSP",
  281. "ORTHOGRAPHIC": "ORTHO",
  282. "PANORAMIC": "PANO",
  283. }
  284. camera.data.type = type_mapping.get(lens["type"].upper(), "PERSP")
  285. camera.data.angle = math.radians(lens["fov"])
  286. camera.data.clip_start = lens["near"]
  287. camera.data.clip_end = lens["far"]
  288. # Add the camera to the 05_Cameras collection
  289. collection.objects.link(camera)
  290. bpy.context.scene.collection.objects.unlink(camera)
  291. # Set the camera as the active camera if "active" is true
  292. if camera_data["properties"]["active"]:
  293. bpy.context.scene.camera = camera
  294. def set_output_paths(base_path, project_name):
  295. # Get the current scene
  296. scene = bpy.context.scene
  297. # Check if the scene has a compositor node tree
  298. if scene.node_tree:
  299. # Iterate over all nodes in the node tree
  300. for node in scene.node_tree.nodes:
  301. # Check if the node is an output node
  302. if node.type == "OUTPUT_FILE":
  303. # Set the base path of the output node
  304. node.base_path = base_path + "//" + project_name
  305. # Iterate over all file slots of the output node
  306. # for file_slot in node.file_slots:
  307. # # Set the path of the file slot
  308. # file_slot.path = project_name
  309. def set_cryptomatte_objects(collection_name, node_name):
  310. # Get all objects in the specified collection
  311. objects = bpy.data.collections[collection_name].all_objects
  312. # Convert the objects to a list
  313. object_names = [obj.name for obj in objects]
  314. print(object_names)
  315. # Get the current scene
  316. scene = bpy.context.scene
  317. # Check if the scene has a compositor node tree
  318. if scene.node_tree:
  319. # Iterate over all nodes in the node tree
  320. for node in scene.node_tree.nodes:
  321. # Check if the node is a Cryptomatte node with the specified name
  322. if node.type == "CRYPTOMATTE_V2" and node.name == node_name:
  323. # Set the Matte ID objects of the node
  324. node.matte_id = ",".join(object_names)
  325. # -------------------------------------------------------------------
  326. # Utilities
  327. # -------------------------------------------------------------------
  328. def remove_collection_and_objects(collection_name):
  329. oldObjects = list(bpy.data.collections[collection_name].all_objects)
  330. for obj in oldObjects:
  331. bpy.data.objects.remove(obj, do_unlink=True)
  332. old_collection = bpy.data.collections[collection_name]
  333. if old_collection is not None:
  334. old_collection_names = get_subcollection_names(old_collection)
  335. else:
  336. print("Collection not found.")
  337. # print line break
  338. print("-----------------------------------------------------------------")
  339. print(old_collection_names)
  340. print("-----------------------------------------------------------------")
  341. for old_collection_name in old_collection_names:
  342. for collection in bpy.data.collections:
  343. if collection.name == old_collection_name:
  344. bpy.data.collections.remove(collection)
  345. bpy.ops.outliner.orphans_purge(
  346. do_local_ids=True, do_linked_ids=True, do_recursive=True
  347. )
  348. def get_subcollection_names(collection):
  349. subcollection_names = []
  350. for child in collection.children:
  351. subcollection_names.append(child.name)
  352. subcollection_names.extend(get_subcollection_names(child))
  353. return subcollection_names
  354. def link_collection_to_collection(parentCollectionName, childCollection):
  355. if bpy.context.scene.collection.children:
  356. parentCollection = bpy.context.scene.collection.children.get(
  357. parentCollectionName
  358. )
  359. # Add it to the main collection
  360. # childCollection = bpy.context.scene.collection.children.get(childCollection)
  361. parentCollection.children.link(childCollection)
  362. # if child collection is in scene collection unlink it
  363. if bpy.context.scene.collection.children.get(childCollection.name):
  364. bpy.context.scene.collection.children.unlink(childCollection)
  365. # bpy.context.scene.collection.children.unlink(childCollection)
  366. # link collection to collection
  367. def link_collection_to_collection_old(parentCollectionName, childCollection):
  368. if bpy.context.scene.collection.children:
  369. parentCollection = bpy.context.scene.collection.children.get(
  370. parentCollectionName
  371. )
  372. # Add it to the main collection
  373. try:
  374. childCollection = bpy.data.collections[childCollection]
  375. except:
  376. print("Collection not found.")
  377. return
  378. parentCollection.children.link(childCollection)
  379. bpy.context.scene.collection.children.unlink(childCollection)
  380. # function that checks if a collection exists
  381. def collection_exists(collection_name):
  382. return collection_name in bpy.data.collections
  383. # function that creates a new collection and adds it to the scene
  384. def create_collection(collection_name):
  385. new_collection = bpy.data.collections.new(collection_name)
  386. bpy.context.scene.collection.children.link(new_collection)
  387. def hide_collection(collection):
  388. collection.hide_render = True
  389. collection.hide_viewport = True
  390. # -------------------------------------------------------------------
  391. # Operators
  392. # -------------------------------------------------------------------
  393. # load scene operator
  394. class ZSSD_OT_LoadScene(bpy.types.Operator):
  395. bl_idname = "zs_sd_loader.load_scene"
  396. bl_label = "Load Scene"
  397. bl_description = "Load Scene"
  398. def execute(self, context):
  399. load_scene()
  400. return {"FINISHED"}
  401. # parent class for panels
  402. class ZSSDPanel:
  403. bl_space_type = "VIEW_3D"
  404. bl_region_type = "UI"
  405. bl_category = "ZS SD Loader"
  406. # -------------------------------------------------------------------
  407. # Draw
  408. # -------------------------------------------------------------------
  409. # Panels
  410. class ZSSD_PT_Main(ZSSDPanel, bpy.types.Panel):
  411. bl_label = "SD Loader"
  412. def draw(self, context):
  413. layout = self.layout
  414. scene = context.scene
  415. col = layout.column()
  416. self.is_connected = False
  417. col.label(text="Stable Diffusion Connection")
  418. # load scene button
  419. col.operator("zs_sd_loader.load_scene", text="Load Scene")
  420. # modify after making products
  421. blender_classes = [
  422. ZSSD_PT_Main,
  423. ZSSD_OT_LoadScene,
  424. ]
  425. def register():
  426. # register classes
  427. for blender_class in blender_classes:
  428. bpy.utils.register_class(blender_class)
  429. # Has to be afqter class registering to correctly register property
  430. # register global properties
  431. # register list
  432. # list data
  433. # bpy.types.Scene.zs_product_list = bpy.props.CollectionProperty(
  434. # type=ZS_Product_ListItem)
  435. # current item in list
  436. def unregister():
  437. # unregister classes
  438. for blender_class in blender_classes:
  439. bpy.utils.unregister_class(blender_class)
  440. # unregister global properties
  441. # unregister list items
  442. # del bpy.types.Scene.my_list
  443. # del bpy.types.Scene.product_product_list_index