OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / modals / adjust_curve.py

370 lines · python ref:
import bpy, math
from mathutils import Vector
from bpy.props import IntProperty, FloatProperty
from . import infobar
from ... utility import addon
from ... ui_framework.master import Master
from ... ui_framework.utils.mods_list import get_mods_list
from ... utility.base_modal_controls import Base_Modal_Controls

# Cursor Warp imports
from ... utils.toggle_view3d_panels import collapse_3D_view_panels
from ... utils.modal_frame_drawing import draw_modal_frame
from ... utils.cursor_warp import mouse_warp
from ... utility import method_handler


class HOPS_OT_AdjustCurveOperator(bpy.types.Operator):
    bl_idname = "hops.adjust_curve"
    bl_label = "Adjust Curve"
    bl_description = "Interactive Curve adjustment. 1/2/3 provides presets for curves"
    bl_options = {"REGISTER", "UNDO", "BLOCKING"}

    first_mouse_x: IntProperty()
    first_value: FloatProperty()
    second_value: IntProperty()

    @classmethod
    def poll(cls, context):
        return getattr(context.active_object, "type", "") == "CURVE"


    def invoke(self, context, event):

        self.back_dict = {}
        self.active_curve = context.active_object

        if self.active_curve:
            self.active_curve.select_set(True)

        self.slected_curves = [c for c in context.selected_objects if c.type == 'CURVE' and c.data.splines]
        self.back_objects()
        self.master = None

        self.fill_type_3d = ["FULL", "BACK", "FRONT", "HALF"]
        self.fill_type_2d = ["NONE", "BACK", "FRONT", "BOTH"]
        self.spline_type = ["POLY", "NURBS", "BEZIER"]

        if not self.active_curve or not self.active_curve.data.splines:
            if self.slected_curves:
                self.active_curve = self.slected_curves[0]

            else:

                self.active_curve = None

        if self.active_curve:

            if not self.active_curve.data.splines.active:
                self.active_curve.data.splines.active =  self.active_curve.data.splines[0]

            self.start_fill_mode = self.active_curve.data.fill_mode
            self.start_spline_type = self.active_curve.data.splines.active.type
            self.spline_type_index = self.spline_type.index(self.start_spline_type)
            self.fill_index = self.fill_type_3d.index(self.start_fill_mode) if self.active_curve.data.dimensions == '3D' else  self.fill_type_2d.index(self.start_fill_mode)
            self.start_show_wire = self.active_curve.show_wire
            # Base Systems
            self.master = Master(context=context)
            self.master.only_use_fast_ui = True
            self.base_controls = Base_Modal_Controls(context, event)
            self.original_tool_shelf, self.original_n_panel = collapse_3D_view_panels()
            self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.safe_draw_shader, (context,), 'WINDOW', 'POST_PIXEL')


            context.window_manager.modal_handler_add(self)
            infobar.initiate(self)
            return {"RUNNING_MODAL"}

        else:
            self.report({'WARNING'}, "No valid curve objects in selection, could not finish")
            return {'CANCELLED'}


    def modal(self, context, event):

        # Base Systems
        self.master.receive_event(event=event)
        self.base_controls.update(context, event)
        mouse_warp(context, event)

        if self.base_controls.pass_through:
            return {'PASS_THROUGH'}

        if self.base_controls.mouse:
            for curve in self.slected_curves:
                curve.data.bevel_depth += self.base_controls.mouse

        if self.base_controls.scroll:
            #bevel res
            if event.ctrl :
                for curve in self.slected_curves:
                    curve.data.resolution_u += self.base_controls.scroll
                    curve.data.render_resolution_u = curve.data.resolution_u
                self.report({'INFO'}, F'Curve Resolution : {self.active_curve.data.resolution_u}')
            #curve order
            elif event.shift :
                for curve in self.slected_curves:
                    self.splines_add(curve.data.splines, "order_u", self.base_controls.scroll)# spline.order_u += self.base_controls.scroll
                self.report({'INFO'}, F'Spline order:{self.active_curve.data.splines.active.order_u }')
            #bevel res
            else:
                for curve in self.slected_curves:
                    curve.data.bevel_resolution += self.base_controls.scroll
                    self.report({'INFO'}, F'Curve Bevel Resolution : {self.active_curve.data.bevel_resolution}')

        if event.type == 'S' and event.value == 'PRESS':

            if not event.shift:
                for curve in self.slected_curves:
                    for spline in curve.data.splines:
                        spline.use_smooth = not spline.use_smooth
                smooth = self.active_curve.data.splines.active.use_smooth
                shade ={True:"Smooth", False:"Flat"}
                self.report({'INFO'}, F'Shade {shade[smooth]}')
            else:
                use_smooth = self.active_curve.data.splines.active.use_smooth
                for curve in self.slected_curves:
                    self.splines_set(curve.data.splines, "use_smooth", use_smooth)
                self.report({'INFO'}, F'Shading Synced')

        edgeSplit = [mod.name for mod in self.active_curve.modifiers if mod.type == 'EDGE_SPLIT']

        if event.type == 'C' and event.value == 'PRESS':
            for curve in self.slected_curves:
                for spline in curve.data.splines:
                    length_limit = False
                    if spline.type == 'BEZIER'and len(spline.bezier_points)>1 :
                        length_limit = True
                    elif len(spline.points) >2:
                        length_limit = True
                    spline.use_cyclic_u = not spline.use_cyclic_u if length_limit else False
            self.report({'INFO'}, F'Toggled Cyclic')

        if event.type == 'W' and event.value == 'PRESS':
            for curve in self.slected_curves:
                curve.show_wire = not curve.show_wire
            wire ={True:"ON", False:"OFF"}
            self.report({'INFO'}, F'Wireframe:{wire[self.active_curve.show_wire]}')

        if event.type == 'F' and event.value == 'PRESS':
            self.fill_index= self.fill_index+1 if self.fill_index<3 else 0
            for curve in self.slected_curves:
                curve.data.fill_mode = self.fill_type_3d[self.fill_index] if curve.data.dimensions == '3D' else self.fill_type_2d[self.fill_index]
            self.report({'INFO'}, F'Fill Mode:{self.active_curve.data.fill_mode}')

        if event.type == 'V' and event.value == 'PRESS':
            self.spline_type_index = self.spline_type_index+1 if self.spline_type_index<2 else 0
            self.active_curve.data.splines.active.type = self.spline_type[self.spline_type_index]
            if self.active_curve.data.splines.active.type != 'BEZIER' and self.spline_type_index == 2:
                self.spline_type_index =0
            for curve in self.slected_curves:
                for spline in curve.data.splines:
                    spline.type = self.spline_type[self.spline_type_index]
                    self.spline_type[self.spline_type_index]
                curve.data.splines.update()
            if addon.preference().ui.Hops_extra_info:
                bpy.ops.hops.display_notification(info=F'Spline Type : {self.active_curve.data.splines.active.type}' )
            self.report({'INFO'}, F'Spline type:{self.active_curve.data.splines.active.type}')

        if event.type == 'ONE' and event.value == 'PRESS':
            for curve in self.slected_curves:
                curve.data.resolution_u = 6
                curve.data.render_resolution_u = 12
                curve.data.bevel_resolution = 6
                curve.data.fill_mode = 'FULL'
            self.report({'INFO'}, F'Resolution : 6')

            for name in edgeSplit:
                bpy.ops.object.modifier_remove(modifier=name)

        if event.type == 'TWO' and event.value == 'PRESS':
            for curve in self.slected_curves:
                curve.data.resolution_u = 64
                curve.data.render_resolution_u = 64
                curve.data.bevel_resolution = 16
                curve.data.fill_mode = 'FULL'
            self.report({'INFO'}, F'Resolution : 64')

            for name in edgeSplit:
                bpy.ops.object.modifier_remove(modifier=name)

        if event.type == 'THREE' and event.value == 'PRESS':
            for curve in self.slected_curves:
                curve.data.resolution_u = 64
                curve.data.render_resolution_u = 64
                curve.data.bevel_resolution = 0
                curve.data.fill_mode = 'FULL'
            if not len(edgeSplit):
                edge_split = self.active_curve.modifiers.new('Edge Split', type='EDGE_SPLIT')
                edge_split.split_angle = math.radians(60)
            self.report({'INFO'}, F'Resolution : 64 / Edge Split Added')

        if self.base_controls.tilde and event.shift == True:
            bpy.context.space_data.overlay.show_overlays = not bpy.context.space_data.overlay.show_overlays

        if self.base_controls.confirm:
            if not self.start_show_wire:
                for curve in self.slected_curves:
                    curve.show_wire = False
            self.remove_shader()
            collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
            self.master.run_fade()
            infobar.remove(self)
            return {'FINISHED'}

        if event.type == 'X' and event.value == 'PRESS':
            for curve in self.slected_curves:
                curve.data.bevel_depth = 0.0
            self.report({'INFO'}, F'Depth Set To 0 - exit')
            self.remove_shader()
            collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
            self.master.run_fade()
            infobar.remove(self)
            return {'FINISHED'}

        if self.base_controls.cancel:
            #self.reset_object()
            self.restore_objects()
            self.remove_shader()
            collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
            self.master.run_fade()
            infobar.remove(self)
            return {'CANCELLED'}

        self.draw_master(context=context)
        context.area.tag_redraw()
        return {"RUNNING_MODAL"}


    # def reset_object(self):
    #     self.active_curve.show_wire = self.start_show_wire
    #     self.active_curve.data.fill_mode = self.start_fill_mode
    #     self.active_curve.data.bevel_depth = self.start_bevel_depth
    #     for spline in self.active_curve.data.splines:
    #         spline.type = self.start_spline_type
    #         spline.order_u =self.start_order_u

    def back_objects(self):

        for curve in self.slected_curves:
            back = {}
            back["show_wire"]= curve.show_wire
            back["fill_mode"]= curve.data.fill_mode
            back["bevel_depth"]= curve.data.bevel_depth
            back["bevel_resolution"]= curve.data.bevel_resolution
            back["resolution_u"] = curve.data.resolution_u
            back["render_resolution_u"] = curve.data.render_resolution_u
            back["spline_type"] = [spline.type for spline in curve.data.splines]
            back["spline_order_u"] = [spline.order_u for spline in curve.data.splines]
            back["use_cyclic_u"] = [spline.use_cyclic_u for spline in curve.data.splines]
            self.back_dict.update({curve:back})

    def restore_objects(self):

        for curve, back in self.back_dict.items():
            curve.show_wire = back["show_wire"]
            curve.data.fill_mode = back["fill_mode"]
            curve.data.bevel_depth = back["bevel_depth"]
            curve.data.bevel_resolution = back["bevel_resolution"]
            curve.data.resolution_u = back["resolution_u"]
            curve.data.render_resolution_u = back["render_resolution_u"]
            for spline , spline_type, spline_order, use_cyclic_u in zip(
                curve.data.splines, back["spline_type"], back["spline_order_u"], back["use_cyclic_u"] ):
                spline.type = spline_type
                spline.order_u = spline_order
                spline.use_cyclic_u = use_cyclic_u


    def draw_master(self, context):

        # Start
        self.master.setup()


        ########################
        #   Fast UI
        ########################


        if self.master.should_build_fast_ui():

            # Main
            win_list = []
            if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1: #Fast Floating
                win_list.append("{:.2f}".format(self.active_curve.data.bevel_depth))
                win_list.append("{:.0f}".format(self.active_curve.data.render_resolution_u))
                win_list.append("{:.0f}".format(self.active_curve.data.bevel_resolution))
            else:
                win_list.append("Curve Adjust")
                win_list.append(self.active_curve.data.splines.active.type)
                win_list.append(F"Fill type: {self.active_curve.data.fill_mode}")
                win_list.append("Width - {:.3f}".format(self.active_curve.data.bevel_depth))
                win_list.append("Segments (ctrl) - {:.0f}".format(self.active_curve.data.render_resolution_u))
                win_list.append("Profile:{:.0f}".format(self.active_curve.data.bevel_resolution))
                win_list.append("Order:{:.0f}".format(self.active_curve.data.splines.active.order_u))

            # Help
            help_items = {"GLOBAL" : [], "STANDARD" : []}

            help_items["GLOBAL"] = [
                ("M", "Toggle mods list"),
                ("H", "Toggle help"),
                ("~", "Toggle UI Display Type"),
                ("O", "Toggle viewport rendering")]

            help_items["STANDARD"] = [
                ("X",             "Set Depth to 0 and end"),
                ("C",             "Toggle cyclic"),
                ("V",             "Cycle spline type"),
                ("SHIFT+S",       "Sync spline shading"),
                ("S",             "Toggle smooth shading"),
                ("W",             "Toggle Wireframe"),
                ("F",             "Cycle Fill Mode"),
                ("3",             "Set profile 64 x 4 (Box)"),
                ("2",             "Set profile 64 x 16"),
                ("1",             "Set profile 12 x 6"),
                ("Shift + Scroll", "Set  order"),
                ("Ctrl + Scroll", "Set segments"),
                ("Scroll",        "Set resolution"),
                ("Mouse",          "Adjust Bevel Depth")]

            # Mods
            mods_list = get_mods_list(mods=bpy.context.active_object.modifiers)

            self.master.receive_fast_ui(win_list=win_list, help_list=help_items, image="Curve", mods_list=mods_list)

        # Finished
        self.master.finished()

    ####################################################
    #   CURSOR WARP
    ####################################################

    def safe_draw_shader(self, context):
        method_handler(self.draw_shader,
            arguments = (context,),
            identifier = 'UI Framework',
            exit_method = self.remove_shader)


    def remove_shader(self):
        '''Remove shader handle.'''

        if self.draw_handle:
            self.draw_handle = bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW")


    def draw_shader(self, context):
        '''Draw shader handle.'''

        draw_modal_frame(context)


    def splines_set (self, splines=[], attr = "", val = None ):
        for spline in splines:
            setattr(spline, attr, val )

    def splines_add (self, splines=[], attr = "", val = None ):
        for spline in splines:
            current = getattr(spline, attr)
            setattr(spline, attr, current+val )