blenderings/playground/addon/tool.py

184 lines
6.4 KiB
Python

# SlideTools: Tools for generating slides
# Copyright (C) 2022 Spencer Killen
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from functools import partial
from typing import Collection
import bpy
from bpy.types import ID, Curve, Operator, Context, Event, VIEW3D_MT_object, Object
from bpy.props import StringProperty, FloatProperty, FloatVectorProperty
from .playground import DEFAULT_SLIDE_BLEND_PATH, ObjectData, Slide, PARTS, ObjectData
class CreateSlideHelper:
bl_idname = "object.createslide"
bl_label = "Create Slide"
bl_options = {"UNDO", "REGISTER"}
blend_file: StringProperty(
name="Blend File",
description=".blend file to use as a slide template",
default=DEFAULT_SLIDE_BLEND_PATH,
)
radius: FloatProperty(name="Radius", description="Big the slide bigger")
thickness: FloatProperty(name="Thickness", description="Make the slide thicker")
body_color: FloatVectorProperty(
name="Body Color", description="Change the main color of the slide"
)
secondary_color: FloatVectorProperty(
name="Secondary Color", description="Change the secondary color of the slide"
)
@classmethod
def poll(cls, context: Context) -> bool:
return True
def _invoke_helper(self, context: Context, event: Event, slide=None):
if slide is None:
slide = Slide(self.blend_file)
self.radius = slide.radius
self.thickness = slide.thickness
self.body_color = slide.body_color
self.secondary_color = slide.secondary_color
return slide
def _execute_helper(self, context: Context, slide=None):
if slide is None:
slide = Slide(self.blend_file)
slide.radius = self.radius
slide.thickness = self.thickness
slide.body_color = self.body_color
slide.secondary_color = self.secondary_color
return slide
class CreateSlideOperator(Operator, CreateSlideHelper):
def invoke(self, context: Context, event: Event):
self._invoke_helper(context, event, slide=None)
return {"FINISHED"}
def execute(self, context: Context):
self._execute_helper(context, slide=None)
return {"FINISHED"}
def slide_collection_from_part(selected: ID):
if type(selected) is Collection:
return selected
if any(part in selected.name.lower() for part in PARTS):
for collection in bpy.data.collections:
if any(o == selected for o in collection.objects):
return collection
return None
class EditSlideOperator(Operator, CreateSlideHelper):
bl_idname = "object.editslide"
bl_label = "Edit Slide"
bl_options = {"UNDO", "REGISTER"}
POLL_CANT_MESSAGE = "Operator requires a slide collection or part to be selected"
blend_file: None = None
@staticmethod
def get_selection(context: Context):
if not hasattr(context, "selected_ids"):
return context.selected_ids[0] if len(context.selected_ids) == 1 else None
elif hasattr(context, "active_object"):
return context.active_object
else:
return None
@classmethod
def poll(cls, context: Context) -> bool:
return cls.get_selection(context) is not None
def invoke(self, context: Context, event: Event):
if not EditSlideOperator.poll(context):
self.report(EditSlideOperator.POLL_CANT_MESSAGE)
sel = EditSlideOperator.get_selection(context)
collection = slide_collection_from_part(sel)
slide = Slide.rehydrate(collection)
super()._invoke_helper(context, event, slide)
return {"FINISHED"}
def execute(self, context: Context):
if not EditSlideOperator.poll(context):
self.report(EditSlideOperator.POLL_CANT_MESSAGE)
sel = EditSlideOperator.get_selection(context)
collection = slide_collection_from_part(sel)
slide = Slide.rehydrate(collection)
super()._execute_helper(context, slide)
return {"FINISHED"}
class CreateSlideFromCurveOperator(Operator, CreateSlideHelper):
bl_idname = "object.createslidefromcurve"
bl_label = "Create Slide From Curve"
bl_options = {"UNDO", "REGISTER"}
POLL_CANT_MESSAGE = "Operator requires a single curve to be selected"
@staticmethod
def get_selection(context: Context) -> ObjectData[Curve]:
if hasattr(context, "selected_objects"):
if len(context.selected_objects) == 1:
sel: Object = context.selected_objects[0]
if type(sel.data) is Curve:
sel: ObjectData[Curve]
return sel
return None
@classmethod
def poll(cls, context: Context) -> bool:
return cls.get_selection(context) is not None
def invoke(self, context: Context, event: Event):
if not CreateSlideFromCurveOperator.poll(context):
self.report(CreateSlideFromCurveOperator.POLL_CANT_MESSAGE)
curve = CreateSlideFromCurveOperator.get_selection(context)
slide = Slide.from_curve(curve, self.blend_file)
super()._invoke_helper(context, event, slide)
return {"FINISHED"}
def execute(self, context: Context):
if not CreateSlideFromCurveOperator.poll(context):
self.report(CreateSlideFromCurveOperator.POLL_CANT_MESSAGE)
curve = CreateSlideFromCurveOperator.get_selection(context)
slide = Slide.from_curve(curve, self.blend_file)
super()._execute_helper(context, slide)
return {"FINISHED"}
def menu_func(bl_idname: str, self, context: Context):
self.layout.operator(bl_idname)
ops = (CreateSlideOperator, EditSlideOperator, CreateSlideFromCurveOperator)
menu_funcs = tuple(partial(menu_func, op.bl_idname) for op in ops)
def register():
for f in menu_funcs:
VIEW3D_MT_object.append(f)
def unregister():
for f in menu_funcs:
VIEW3D_MT_object.remove(f)