OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / editmode / to_rope.py

167 lines · python ref:
import bpy, bmesh
from ... ui_framework.operator_ui import Master
from ... utility import addon


class HOPS_OT_ToRope(bpy.types.Operator):
    bl_idname = 'hops.to_rope'
    bl_label = 'To Rope'
    bl_options = {'REGISTER', 'UNDO'}
    bl_description = '''Converts selection to Smart Ropes
LMB - Regular Smart Rope
SHIFT - Subdivided Smart Rope
CTRL - Alternative Smart Rope'''

    called_ui = False


    @classmethod
    def poll(cls, context):

        if context.active_object != None:
            active = context.active_object
            return active and active.type in {'MESH', 'CURVE'} and active.mode in {'OBJECT', 'EDIT'}
        return False
        

    def invoke(self, context, event):
        HOPS_OT_ToRope.called_ui = False

        self.selection = context.selected_objects
        self.generated_objects = []
        self.generated_curves = []
        self.result_dict = {}

        if context.mode == 'EDIT_MESH':
            context.active_object.select_set(True)

        return self.execute(context)


    def execute(self, context):
        for obj in self.selection:
            if obj.mode not in {'OBJECT', 'EDIT'}:
                continue

            if obj.type == 'MESH':
                if obj.mode == 'OBJECT':
                    bm = bmesh.new()
                    bm.from_mesh(obj.data)

                    if bm.verts:
                        self.result_dict[obj.name] = 'Success'
                    else:
                        self.result_dict[obj.name] = 'Failed, invalid selection'
                        continue

                elif obj.mode == 'EDIT':
                    bm = bmesh.from_edit_mesh(obj.data).copy()
                    deselect_geom = [elem for elem in bm.edges if not elem.select]

                    if deselect_geom:
                        bmesh.ops.delete(bm, geom=deselect_geom, context='EDGES')

                    if bm.verts:
                        self.result_dict[obj.name] = 'Success'
                    else:
                        self.result_dict[obj.name] = 'Failed, invalid selection'
                        continue

                new_mesh = bpy.data.meshes.new(f'{obj.data.name}_Rope')
                bm.to_mesh(new_mesh)
                bm.free()

                gen_obj = obj.copy()
                gen_obj.name = f'{obj.name}_Rope'
                gen_obj.data = new_mesh

                for col in obj.users_collection:
                    if col not in gen_obj.users_collection:
                        col.objects.link(gen_obj)

                self.generated_objects.append(gen_obj)

            elif obj.type == 'CURVE':
                for index, spline in enumerate(obj.data.splines):
                    valid_spline = len(spline.points) > 1 or len(spline.bezier_points) > 1

                    if obj.mode == 'EDIT':
                        points_select = any(p.select for p in spline.points)
                        bezier_select = any(p.select_control_point for p in spline.bezier_points)
                        valid_spline = valid_spline and (points_select or bezier_select)

                    if valid_spline:
                        new_curve = obj.data.copy()
                        index_str = f'0{index+1}' if index < 10 else f'{index+1}'
                        new_curve.name = f'{obj.name}_Spline{index_str}'

                        other_splines = [s for i, s in enumerate(new_curve.splines) if i != index]
                        for new_spline in other_splines:
                            new_curve.splines.remove(new_spline)

                        new_obj = obj.copy()
                        new_obj.name = new_curve.name
                        new_obj.data = new_curve

                        for col in obj.users_collection:
                            if col not in new_obj.users_collection:
                                col.objects.link(new_obj)

                        self.generated_curves.append(new_obj)

        if self.generated_objects:
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')

            for gen_obj in self.generated_objects:
                gen_obj.select_set(True)

                context.view_layer.objects.active = gen_obj
                bpy.ops.hops.reset_status()

                bpy.ops.object.convert(target='CURVE')
                if gen_obj.type == 'CURVE':
                    self.generated_curves.append(gen_obj)

                else:
                    bpy.ops.object.convert(target='MESH')
                    bm = bmesh.new()
                    bm.from_mesh(gen_obj.data)
                    bmesh.ops.delete(bm, geom=bm.faces, context='FACES_ONLY')
                    bm.to_mesh(gen_obj.data)
                    bm.free()

                    bpy.ops.object.convert(target='CURVE')
                    if gen_obj.type == 'CURVE':
                        self.generated_curves.append(gen_obj)

                    else:
                        self.result_dict[self.gen_obj.name] = 'Failed, too complex'
                        bpy.data.objects.remove(gen_obj)
                        del gen_obj
                        continue

                gen_obj.select_set(False)

        if self.generated_curves:
            for obj in self.generated_curves:
                context.view_layer.objects.active = obj
                bpy.ops.hops.add_rope('INVOKE_DEFAULT')

        if not HOPS_OT_ToRope.called_ui:
            HOPS_OT_ToRope.called_ui = True
            ui = Master()
            draw_data = [['To_Rope']]

            result = [[f'{obj} {result}'] for obj, result in self.result_dict.items()]
            draw_data.extend(result)
            draw_data.append([f'Conversion to Rope'])
            ui.receive_draw_data(draw_data=draw_data)

            prefs = addon.preference()
            draw_bg = prefs.ui.Hops_operator_draw_bg
            draw_border = prefs.ui.Hops_operator_draw_border
            ui.draw(draw_bg=draw_bg, draw_border=draw_border)

        return {'FINISHED'}