import bpy from mathutils import Vector, Euler, Color import json import pathlib import os from math import pi VIEW_DATA = { "Front": [0, 1, 2, "Front"], "Angled": [0.25, 1, 2, "Angled"], "Corner": [0.5, 1, 2, "Corner"], "Side": [1, 1, 2, "Side"], "Back Angled": [1.5, 1, 2, "Back Angled"], "Back": [2, 1, 2, "Back"], "Top": [0, 0, 1, "Top"], "Bottom": [0, 2, 1, "Bottom"], "Bottom Flipped": [2, 2, 1, "Bottom Flipped"], } def get_bounds(objects): xl = [] yl = [] zl = [] for obj in objects: if not obj.hide_render: for bounds in obj.bound_box: v = obj.matrix_world @ Vector(bounds) xl += [v[0] for c in obj.bound_box] yl += [v[1] for c in obj.bound_box] zl += [v[2] for c in obj.bound_box] return ( Vector([min(xl), min(yl), min(zl)]), Vector([max(xl), max(yl), max(zl)]) ) class MVConfigCollection(bpy.types.Operator): bl_idname = "mv.config_collection" bl_label = "Configure root collection" @classmethod def poll(cls, context: bpy.types.Context): return True def execute(self, context: bpy.types.Context): coll = context.scene.collection.children[0] coll.name = "Macrovision" coll["MVName"] = "Name" coll["MVViews"] = "Front" coll["MVKind"] = "objects" coll["MVViewLabels"] = "Front: Front" mats = [ ("light", 0, 0, 1), ("medium", 0, 1, 0), ("dark", 1, 0, 0) ] for name, red, green, blue in mats: if name not in bpy.data.materials: bpy.data.materials.new(name) mat = bpy.data.materials[name] mat.use_nodes = True mat.use_fake_user = True mat.node_tree.nodes.clear() rgb = mat.node_tree.nodes.new("ShaderNodeRGB") out = mat.node_tree.nodes.new("ShaderNodeOutputMaterial") input = out.inputs['Surface'] output = rgb.outputs['Color'] mat.node_tree.links.new(input=input, output=output) output.default_value = (red, green, blue, 1.0) if "cam" not in bpy.data.objects: new_cam = bpy.data.cameras.new("cam") new_obj = bpy.data.objects.new("cam", new_cam) context.scene.collection.objects.link(new_obj) if "Lines" not in bpy.data.materials: mat = bpy.data.materials.new("Lines") bpy.data.materials.create_gpencil_data(mat) if "lineart" not in bpy.data.objects: new_lineart = bpy.data.grease_pencils.new("lineart") new_obj = bpy.data.objects.new("lineart", new_lineart) new_obj.show_in_front = True context.scene.collection.objects.link(new_obj) modifier = new_obj.grease_pencil_modifiers.new(name='Lineart', type='GP_LINEART') material = bpy.data.materials["Lines"] new_lineart.materials.append(material) modifier.target_layer = "Lines" modifier.target_material = material new_lineart.layers.new("Lines") # we have to clear the bake, for some reason bpy.ops.object.lineart_clear_all() return {"FINISHED"} class MVExport(bpy.types.Operator): bl_idname = "mv.export" bl_label = "Export objects" @classmethod def poll(cls, context: bpy.types.Context): return True def execute(self, context: bpy.types.Context): path_info = pathlib.Path(bpy.data.filepath).parent.joinpath("macrovision-directory.txt") config_path = pathlib.Path(open(path_info).read().strip()) json_path = config_path.joinpath("config.json") config = json.load(open(json_path.resolve(), encoding="utf-8")) parent_workdir = config["work-directory"] c = bpy.data.objects["cam"] context.scene.camera = c lineart = bpy.data.objects["lineart"] lineart.grease_pencil_modifiers['Lineart'].source_type = 'COLLECTION' c.data.type = "ORTHO" bpy.data.scenes["Scene"].render.resolution_x = 2000 bpy.data.scenes["Scene"].render.resolution_y = 2000 bpy.data.scenes["Scene"].render.film_transparent = True bpy.data.scenes["Scene"].view_settings.view_transform = "Raw" bpy.data.worlds["World"].node_tree.nodes["Background"].inputs[1].default_value = 0 mv = bpy.data.collections["Macrovision"] collections = mv.children all_data = {} all_data["name"] = mv["MVName"] all_data["kind"] = mv["MVKind"] all_data["forms"] = [] default_views = [] for view in mv["MVViews"].split(","): default_views.append(VIEW_DATA[view.strip()]) if "MVViewLabels" in mv: for pair in mv["MVViewLabels"].split(","): key, val = pair.split(":") VIEW_DATA[key.strip()][3] = val.strip() workdir = pathlib.Path(parent_workdir).joinpath(all_data["name"]) os.makedirs(workdir, exist_ok=True) for coll in collections: coll.hide_render = True for coll in collections: coll.hide_render = False bpy.ops.object.select_all(action='DESELECT') for obj in coll.objects: obj.select_set(True) data = {} data["name"] = coll.name data["views"] = [] bound_min, bound_max = get_bounds(coll.objects) dimensions = bound_max - bound_min size = max(dimensions) global_bbox_center = 0.5 * (bound_min + bound_max) view_list = [] lineart.grease_pencil_modifiers['Lineart'].source_collection = coll if "Views" in coll: for view in coll["Views"].split(","): view_list.append(VIEW_DATA[view]) else: view_list = default_views for angles in view_list: c.location = global_bbox_center c.rotation_euler = Euler([angles[1] * pi / 2, 0, angles[0] * pi / 2]) print(list(bound_min) + list(bound_max)) _, c.data.ortho_scale = c.camera_fit_coords(bpy.context.evaluated_depsgraph_get(), list(bound_min) + list(bound_max)) c.location = Vector([c.location[0], c.location[1], c.location[2]]) c.data.ortho_scale *= 1.2 rot = c.rotation_euler.to_matrix() rot.invert() c.location += Vector([0, 0, size * 2]) @ rot c.data.clip_start = size / 4 c.data.clip_end = size * 8 data["views"].append({ "name": angles[3], "height": dimensions[angles[2]] }) if "Volume" in coll: data["views"][-1]["volume"] = coll["Volume"] if "Mass" in coll: data["views"][-1]["mass"] = coll["Mass"] lineart.hide_render = False filename = f"{coll.name}-{angles[3]}.png" bpy.context.scene.render.filepath = workdir.joinpath(filename).resolve().__str__() bpy.ops.render.render(write_still = True) lineart.hide_render = True filename = f"{coll.name}-{angles[3]}-noline.png" bpy.context.scene.render.filepath = workdir.joinpath(filename).resolve().__str__() bpy.ops.render.render(write_still = True) all_data["forms"].append(data) coll.hide_render = True with open(workdir.joinpath("data.json"), "w") as file: json.dump(all_data, file) return {"FINISHED"} clses = [ MVExport, MVConfigCollection ]