blenderings/playground/playground.py

195 lines
5.7 KiB
Python

import bpy
from bpy.types import (
Curve,
LayerCollection,
MaterialSlot,
ViewLayer,
Object,
Mesh,
Collection,
)
from pathlib import Path
from typing import Generic, TypeVar, Union
from mathutils import Color
# Tiny helper to make autocompletion more precise for objects
T = TypeVar("T")
class ObjectData(Generic[T], Object):
data: T
# Default names
PARTS = ("controller", "curve", "fin", "maker", "screw", "spool")
class Slide:
collection: LayerCollection
controller: ObjectData[None]
curve: ObjectData[Curve]
fin: ObjectData[Curve]
maker: ObjectData[Mesh]
screw: ObjectData[Mesh]
spool: ObjectData[Mesh]
@staticmethod
def from_curve(curve: ObjectData[Curve], blend_file_path: str) -> "Slide":
slide = Slide(blend_file_path)
bpy.data.objects.remove(slide.curve, do_unlink=True)
slide.change_curve(curve)
return slide
@classmethod
def rehydrate(Cls, c: Collection) -> Union["Slide", None]:
slide = Cls()
slide.__dict__.update(get_parts(c))
return slide
def __init__(self, blend_file_path: str = None):
if blend_file_path:
parts = load_slide(blend_file_path)
else:
parts = {"collection": None}
parts.update({part: None for part in PARTS})
self.__dict__.update(parts)
if blend_file_path:
self.__localize()
def __localize(self):
"Make sure node inputs are linked to themselves"
self.change_curve(self.curve)
self.maker.modifiers["AddSpool"]["Input_3"] = self.spool
slide_body_material = self.maker.material_slots[0].material
self.maker.modifiers["AddSpool"]["Input_7"] = slide_body_material
self.fin.modifiers["AddFin"]["Input_3"] = self.spool.material_slots[0].material
self.screw.hide_viewport = True
self.screw.hide_render = True
self.spool.hide_viewport = True
self.spool.hide_render = True
for part in PARTS[1:]:
getattr(self, part).parent = self.controller
@property
def radius(self) -> float:
return self.maker.modifiers["MakeTunnel"]["Input_2"]
@radius.setter
def radius(self, v: float):
self.maker.modifiers["MakeTunnel"]["Input_2"] = v
self.maker.modifiers["AddSpool"]["Input_8"] = v
self.fin.modifiers["AddFin"]["Input_4"] = v
@property
def thickness(self) -> float:
return self.maker.modifiers["Solidify"].thickness
@thickness.setter
def thickness(self, v: float):
self.maker.modifiers["Solidify"].thickness = v
self.maker.modifiers["AddSpool"]["Input_9"] = v
self.fin.modifiers["AddFin"]["Input_5"] = v
@property
def spool_spacing(self) -> float:
return self.maker.modifiers["AddSpool"]["Input_5"]
@spool_spacing.setter
def spool_spacing(self, v: float):
self.maker.modifiers["AddSpool"]["Input_5"] = v
@property
def body_color(self) -> Color:
c = tuple(
self.maker.material_slots[0]
.material.node_tree.nodes["Principled BSDF.001"]
.inputs[0]
.default_value
)
return Color(c[:3])
@body_color.setter
def body_color(self, c: Color):
c = (*c, 1.0)
self.maker.material_slots[0].material.node_tree.nodes[
"Principled BSDF.001"
].inputs[0].default_value = c
@property
def secondary_color(self) -> Color:
c = tuple(
self.spool.material_slots[0]
.material.node_tree.nodes["Principled BSDF.001"]
.inputs[0]
.default_value
)
return Color(c[:3])
@secondary_color.setter
def secondary_color(self, c: Color):
c = (*c, 1.0)
self.spool.material_slots[0].material.node_tree.nodes[
"Principled BSDF.001"
].inputs[0].default_value = c
def change_curve(self, c: ObjectData[Curve]):
self.maker.modifiers["MakeTunnel"]["Input_5"] = c
self.maker.modifiers["AddSpool"]["Input_6"] = c
self.fin.modifiers["AddFin"]["Input_2"] = c
c.parent = self.controller
if not any(c == o for o in self.collection.collection.all_objects):
self.collection.collection.objects.link(c)
def get_parts(c: Collection):
return {
part: next(obj for obj in c.all_objects if part in obj.name.lower())
for part in PARTS
}
def toplevel_collection_to_layer_collection(
c: Collection, view_layer: ViewLayer
) -> LayerCollection:
return next(cv for cv in view_layer.layer_collection.children if cv.collection == c)
def duplicate_collection_objects(c: Collection, name: str):
c2 = bpy.data.collections.new(name)
for obj in c.all_objects:
c2.objects.link(obj.copy())
return c2
def dup_obj_materials_inplace(obj: Object):
for slot in obj.material_slots:
slot: MaterialSlot
slot.material = slot.material.copy()
def load_slide(file_path: str):
bpy.ops.wm.append(
filepath=f"{file_path}/Scene/SlideTemplate",
directory=f"{file_path}/Scene/",
filename="SlideTemplate",
)
scene = bpy.data.scenes["SlideTemplate"]
template = scene.collection.children[0]
c = duplicate_collection_objects(template, "Slide")
bpy.context.scene.collection.children.link(c)
bpy.data.scenes.remove(scene)
c = toplevel_collection_to_layer_collection(c, bpy.context.scene.view_layers[0])
parts = get_parts(c.collection)
for part_obj in parts.values():
dup_obj_materials_inplace(part_obj)
return {"collection": c, **parts}
DEFAULT_SLIDE_BLEND_PATH = str((Path(__file__) / ".." / "playground.blend").resolve())
if __name__ == "__main__":
slide = Slide(DEFAULT_SLIDE_BLEND_PATH)