less copy protection, more size visualization
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

245 строки
7.8 KiB

  1. import bpy
  2. from mathutils import Vector, Euler, Color
  3. import json
  4. import pathlib
  5. import os
  6. from math import pi
  7. VIEW_DATA = {
  8. "Front": [0, 1, 2, "Front"],
  9. "Angled": [0.25, 1, 2, "Angled"],
  10. "Corner": [0.5, 1, 2, "Corner"],
  11. "Side": [1, 1, 2, "Side"],
  12. "Back Angled": [1.5, 1, 2, "Back Angled"],
  13. "Back": [2, 1, 2, "Back"],
  14. "Top": [0, 0, 1, "Top"],
  15. "Bottom": [0, 2, 1, "Bottom"],
  16. "Bottom Flipped": [2, 2, 1, "Bottom Flipped"],
  17. }
  18. def get_bounds(objects):
  19. xl = []
  20. yl = []
  21. zl = []
  22. for obj in objects:
  23. if not obj.hide_render:
  24. for bounds in obj.bound_box:
  25. v = obj.matrix_world @ Vector(bounds)
  26. xl += [v[0] for c in obj.bound_box]
  27. yl += [v[1] for c in obj.bound_box]
  28. zl += [v[2] for c in obj.bound_box]
  29. return (
  30. Vector([min(xl), min(yl), min(zl)]),
  31. Vector([max(xl), max(yl), max(zl)])
  32. )
  33. class MVConfigCollection(bpy.types.Operator):
  34. bl_idname = "mv.config_collection"
  35. bl_label = "Configure root collection"
  36. @classmethod
  37. def poll(cls, context: bpy.types.Context):
  38. return True
  39. def execute(self, context: bpy.types.Context):
  40. coll = context.scene.collection.children[0]
  41. coll.name = "Macrovision"
  42. coll["MVName"] = "Name"
  43. coll["MVViews"] = "Front"
  44. coll["MVKind"] = "objects"
  45. coll["MVViewLabels"] = "Front: Front"
  46. mats = [
  47. ("light", 0, 0, 1),
  48. ("medium", 0, 1, 0),
  49. ("dark", 1, 0, 0)
  50. ]
  51. for name, red, green, blue in mats:
  52. if name not in bpy.data.materials:
  53. bpy.data.materials.new(name)
  54. mat = bpy.data.materials[name]
  55. mat.use_nodes = True
  56. mat.use_fake_user = True
  57. mat.node_tree.nodes.clear()
  58. rgb = mat.node_tree.nodes.new("ShaderNodeRGB")
  59. out = mat.node_tree.nodes.new("ShaderNodeOutputMaterial")
  60. input = out.inputs['Surface']
  61. output = rgb.outputs['Color']
  62. mat.node_tree.links.new(input=input, output=output)
  63. output.default_value = (red, green, blue, 1.0)
  64. if "cam" not in bpy.data.objects:
  65. new_cam = bpy.data.cameras.new("cam")
  66. new_obj = bpy.data.objects.new("cam", new_cam)
  67. context.scene.collection.objects.link(new_obj)
  68. if "Lines" not in bpy.data.materials:
  69. mat = bpy.data.materials.new("Lines")
  70. bpy.data.materials.create_gpencil_data(mat)
  71. if "lineart" not in bpy.data.objects:
  72. new_lineart = bpy.data.grease_pencils.new("lineart")
  73. new_obj = bpy.data.objects.new("lineart", new_lineart)
  74. new_obj.show_in_front = True
  75. context.scene.collection.objects.link(new_obj)
  76. modifier = new_obj.grease_pencil_modifiers.new(name='Lineart', type='GP_LINEART')
  77. material = bpy.data.materials["Lines"]
  78. new_lineart.materials.append(material)
  79. modifier.target_layer = "Lines"
  80. modifier.target_material = material
  81. new_lineart.layers.new("Lines")
  82. # we have to clear the bake, for some reason
  83. bpy.ops.object.lineart_clear_all()
  84. return {"FINISHED"}
  85. class MVExport(bpy.types.Operator):
  86. bl_idname = "mv.export"
  87. bl_label = "Export objects"
  88. @classmethod
  89. def poll(cls, context: bpy.types.Context):
  90. return True
  91. def execute(self, context: bpy.types.Context):
  92. path_info = pathlib.Path(bpy.data.filepath).parent.joinpath("macrovision-directory.txt")
  93. config_path = pathlib.Path(open(path_info).read().strip())
  94. json_path = config_path.joinpath("config.json")
  95. config = json.load(open(json_path.resolve(), encoding="utf-8"))
  96. parent_workdir = config["work-directory"]
  97. c = bpy.data.objects["cam"]
  98. context.scene.camera = c
  99. lineart = bpy.data.objects["lineart"]
  100. lineart.grease_pencil_modifiers['Lineart'].source_type = 'COLLECTION'
  101. c.data.type = "ORTHO"
  102. bpy.data.scenes["Scene"].render.resolution_x = 2000
  103. bpy.data.scenes["Scene"].render.resolution_y = 2000
  104. bpy.data.scenes["Scene"].render.film_transparent = True
  105. bpy.data.scenes["Scene"].view_settings.view_transform = "Raw"
  106. bpy.data.worlds["World"].node_tree.nodes["Background"].inputs[1].default_value = 0
  107. mv = bpy.data.collections["Macrovision"]
  108. collections = mv.children
  109. all_data = {}
  110. all_data["name"] = mv["MVName"]
  111. all_data["kind"] = mv["MVKind"]
  112. all_data["forms"] = []
  113. default_views = []
  114. for view in mv["MVViews"].split(","):
  115. default_views.append(VIEW_DATA[view.strip()])
  116. if "MVViewLabels" in mv:
  117. for pair in mv["MVViewLabels"].split(","):
  118. key, val = pair.split(":")
  119. VIEW_DATA[key.strip()][3] = val.strip()
  120. workdir = pathlib.Path(parent_workdir).joinpath(all_data["name"])
  121. os.makedirs(workdir, exist_ok=True)
  122. for coll in collections:
  123. coll.hide_render = True
  124. for coll in collections:
  125. coll.hide_render = False
  126. bpy.ops.object.select_all(action='DESELECT')
  127. for obj in coll.objects:
  128. obj.select_set(True)
  129. data = {}
  130. data["name"] = coll.name
  131. data["views"] = []
  132. bound_min, bound_max = get_bounds(coll.objects)
  133. dimensions = bound_max - bound_min
  134. size = max(dimensions)
  135. global_bbox_center = 0.5 * (bound_min + bound_max)
  136. view_list = []
  137. lineart.grease_pencil_modifiers['Lineart'].source_collection = coll
  138. if "Views" in coll:
  139. for view in coll["Views"].split(","):
  140. view_list.append(VIEW_DATA[view])
  141. else:
  142. view_list = default_views
  143. for angles in view_list:
  144. c.location = global_bbox_center
  145. c.rotation_euler = Euler([angles[1] * pi / 2, 0, angles[0] * pi / 2])
  146. print(list(bound_min) + list(bound_max))
  147. _, c.data.ortho_scale = c.camera_fit_coords(bpy.context.evaluated_depsgraph_get(), list(bound_min) + list(bound_max))
  148. c.location = Vector([c.location[0], c.location[1], c.location[2]])
  149. c.data.ortho_scale *= 1.2
  150. rot = c.rotation_euler.to_matrix()
  151. rot.invert()
  152. c.location += Vector([0, 0, size * 2]) @ rot
  153. c.data.clip_start = size / 4
  154. c.data.clip_end = size * 8
  155. data["views"].append({
  156. "name": angles[3],
  157. "height": dimensions[angles[2]]
  158. })
  159. if "Volume" in coll:
  160. data["views"][-1]["volume"] = coll["Volume"]
  161. if "Mass" in coll:
  162. data["views"][-1]["mass"] = coll["Mass"]
  163. lineart.hide_render = False
  164. filename = f"{coll.name}-{angles[3]}.png"
  165. bpy.context.scene.render.filepath = workdir.joinpath(filename).resolve().__str__()
  166. bpy.ops.render.render(write_still = True)
  167. lineart.hide_render = True
  168. filename = f"{coll.name}-{angles[3]}-noline.png"
  169. bpy.context.scene.render.filepath = workdir.joinpath(filename).resolve().__str__()
  170. bpy.ops.render.render(write_still = True)
  171. all_data["forms"].append(data)
  172. coll.hide_render = True
  173. with open(workdir.joinpath("data.json"), "w") as file:
  174. json.dump(all_data, file)
  175. return {"FINISHED"}
  176. clses = [
  177. MVExport,
  178. MVConfigCollection
  179. ]