Need assistance to combine 2 scripts in one single addon

Hi guy’s, i tried 2 addons today but there is no ui access to them and you have to use search to use them and i found this to be counter productive so i need assistance to combine both and place them in blender UI.

I already know how to make a button or a menu entry for a custom script but i a missing the knowledge how to put both scripts in one single addon that we can reach in blender UI?

here both scripts i want to merge in a single addon

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  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 2
#  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
#  MERCHANTABILITY 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, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####


bl_info = {
    "name": "Edges To Curve",
    "category": "Object",
    "description": "Converts selected edges into curve with extrusion",
    "author": "Andreas Strømberg, Chris Kohl",
    "wiki_url": "https://github.com/Stromberg90/Scripts/tree/master/Blender",
    "tracker_url": "https://github.com/Stromberg90/Scripts/issues",
    "blender": (2, 80, 0),
    "version": (1, 0, 2)
}

import bpy


class MeshMode:
    VERTEX = (True, False, False)
    EDGE = (False, True, False)
    FACE = (False, False, True)


class ObjectMode:
    OBJECT = 'OBJECT'
    EDIT = 'EDIT'
    POSE = 'POSE'
    SCULPT = 'SCULPT'
    VERTEX_PAINT = 'VERTEX_PAINT'
    WEIGHT_PAINT = 'WEIGHT_PAINT'
    TEXTURE_PAINT = 'TEXTURE_PAINT'
    PARTICLE_EDIT = 'PARTICLE_EDIT'
    GPENCIL_EDIT = 'GPENCIL_EDIT'


class EventType:
    MOUSEMOVE = 'MOUSEMOVE'
    WHEELUPMOUSE = 'WHEELUPMOUSE'
    WHEELDOWNMOUSE = 'WHEELDOWNMOUSE'
    LEFTMOUSE = 'LEFTMOUSE'
    RIGHTMOUSE = 'RIGHTMOUSE'
    ESC = 'ESC'

class ModalEdgeToCurve(bpy.types.Operator):
    bl_idname = "object.edge_to_curve"
    bl_label = "Edges To Curve"
    bl_description = "Takes selected mesh edges and converts them into a curve."
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return context.active_object.mode == ObjectMode.EDIT and context.active_object.type == 'MESH' or context.active_object.type == 'CURVE'

    def execute(self, context):
        context.object.data.bevel_depth = self.value / 100.0
        context.object.data.bevel_resolution = self.resolution
        return {'FINISHED'}

    def modal(self, context, event):
        if event.type == EventType.MOUSEMOVE:  # Apply
            self.value = max(0, (event.mouse_x - self.start_value))
        elif event.type == EventType.WHEELUPMOUSE:
            self.resolution += 1
        elif event.type == EventType.WHEELDOWNMOUSE and self.resolution > 1:
            self.resolution -= 1
        elif event.type == EventType.LEFTMOUSE:  # Confirm
            if context.active_object.type != 'CURVE':
                context.view_layer.objects.active = self.original_object
                bpy.ops.object.select_all(action='DESELECT')
                self.original_object.select_set(True)
                bpy.ops.object.mode_set(mode=ObjectMode.EDIT)
                context.tool_settings.mesh_select_mode = MeshMode.EDGE
            return {'FINISHED'}
        elif event.type in {EventType.RIGHTMOUSE, EventType.ESC}:  # Cancel
            if context.active_object.type == 'CURVE':
                context.object.data.bevel_depth = 0
            else:
                bpy.ops.object.delete()
                context.view_layer.objects.active = self.original_object
                bpy.ops.object.select_all(action='DESELECT')
                self.original_object.select_set(True)
                bpy.ops.object.mode_set(mode=ObjectMode.EDIT)
                context.tool_settings.mesh_select_mode = MeshMode.EDGE
            return {'CANCELLED'}

        self.execute(context)
        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.value = 0.0
        self.start_value = event.mouse_x
        self.resolution = 2
        self.original_object = bpy.context.active_object
        if context.active_object.type == 'CURVE':
            context.object.data.fill_mode = 'FULL'
            context.object.data.bevel_resolution = self.resolution
            self.execute(context)
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}

        else:
            if context.tool_settings.mesh_select_mode[0]:
                context.tool_settings.mesh_select_mode = MeshMode.EDGE
                if not context.object.data.total_vert_sel > 1:
                    return {'CANCELLED'}
                context.tool_settings.mesh_select_mode = MeshMode.VERTEX

            bpy.ops.mesh.duplicate()
            bpy.ops.mesh.separate()
            bpy.ops.object.mode_set(mode=ObjectMode.OBJECT)
            curve_object = context.selected_objects[-1]
            context.view_layer.objects.active = curve_object
            bpy.ops.object.select_all(action='DESELECT')
            curve_object.select_set(True)
            bpy.ops.object.convert(target='CURVE')
            context.object.data.fill_mode = 'FULL'
            context.object.data.bevel_resolution = self.resolution
            self.execute(context)
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}

def EdgeToCurveMenuItem(self, context):
    layout = self.layout
    layout.separator()
    layout.operator("object.edge_to_curve", text="Edges to Curve")

def register():
    bpy.utils.register_class(ModalEdgeToCurve)
    bpy.types.VIEW3D_MT_edit_mesh_edges.append(EdgeToCurveMenuItem)


def unregister():
    bpy.utils.unregister_class(ModalEdgeToCurve)
    bpy.types.VIEW3D_MT_edit_mesh_edges.remove(EdgeToCurveMenuItem)


if __name__ == "__main__":
    register()

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  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 2
#  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
#  MERCHANTABILITY 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, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import bpy
import bgl
import gpu
import bmesh
import math
from gpu_extras.batch import batch_for_shader

bl_info = {
   "name": "Merge Tool",
   "category": "User",
   "author": "Andreas Strømberg",
   "wiki_url": "https://github.com/Stromberg90/Scripts/tree/master/Blender",
   "tracker_url": "https://github.com/Stromberg90/Scripts/issues",
   "blender": (2, 80, 0),
   "version": (1, 1)
}


def draw_callback_px(self, context):
   if self.started and self.start_vertex is not None and self.end_vertex is not None:
       bgl.glEnable(bgl.GL_BLEND)
       coords = [self.start_vertex_transformed, self.end_vertex_transformed]
       shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
       batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": coords})
       shader.bind()
       shader.uniform_float("color", (1, 0, 0, 1))
       batch.draw(shader)

       shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
       batch = batch_for_shader(shader, 'POINTS', {"pos": coords})
       shader.bind()
       shader.uniform_float("color", (1, 0, 0, 1))
       batch.draw(shader)

       bgl.glLineWidth(1)
       bgl.glDisable(bgl.GL_BLEND)


def main(context, event, started):
   """Run this function on left mouse, execute the ray cast"""
   coord = event.mouse_region_x, event.mouse_region_y

   if started:
       result = bpy.ops.view3d.select(extend=True, location=coord)
   else:
       result = bpy.ops.view3d.select(extend=False, location=coord)

   if result == {'PASS_THROUGH'}:
       bpy.ops.mesh.select_all(action='DESELECT')


class MergeTool(bpy.types.Operator):
   """Modal object selection with a ray cast"""
   bl_idname = "object.merge_tool"
   bl_label = "Merge Tool Operator"
   bl_options = {'REGISTER', 'UNDO'}

   def __init__(self):
       self.start_vertex = None
       self.end_vertex = None
       self.started = False
       self._handle = None

   def modal(self, context, event):
       context.area.tag_redraw()

       if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
           # allow navigation
           return {'PASS_THROUGH'}
       elif event.type == 'MOUSEMOVE':
           if self.started:
               coord = event.mouse_region_x, event.mouse_region_y
               bpy.ops.view3d.select(extend=False, location=coord)

               selected_vertex = None
               for v in self.bm.verts:
                   if v.select:
                       selected_vertex = v
                       break

               if selected_vertex:
                   self.end_vertex = selected_vertex
                   self.end_vertex_transformed = self.world_matrix @ self.end_vertex.co
       elif event.type == 'LEFTMOUSE':
           main(context, event, self.started)
           if not self.started:
               if context.object.data.total_vert_sel == 1:
                   selected_vertex = None
                   for v in self.bm.verts:
                       if v.select:
                           selected_vertex = v
                           break

                   if selected_vertex:
                       self.start_vertex = selected_vertex
                       self.start_vertex_transformed = self.world_matrix @ self.start_vertex.co
                   else:
                       bpy.types.SpaceView3D.draw_handler_remove(
                           self._handle, 'WINDOW')
                       return {'CANCELLED'}
                   self.started = True
           elif self.start_vertex is self.end_vertex:
               bpy.types.SpaceView3D.draw_handler_remove(
                   self._handle, 'WINDOW')
               context.workspace.status_text_set(None)
               return {'CANCELLED'}
           elif self.start_vertex is not None and self.end_vertex is not None:
               self.start_vertex.select = True
               self.end_vertex.select = True
               try:
                   bpy.ops.mesh.merge(type='LAST')
                   bpy.ops.ed.undo_push(
                       message="Add an undo step *function may be moved*")
               except TypeError:
                   pass
               finally:
                   self.start_vertex = None
                   self.end_vertex = None
                   self.started = False
           else:
               bpy.types.SpaceView3D.draw_handler_remove(
                   self._handle, 'WINDOW')
               context.workspace.status_text_set(None)
               return {'CANCELLED'}
           return {'RUNNING_MODAL'}
       elif event.type in {'RIGHTMOUSE', 'ESC'}:
           bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
           context.workspace.status_text_set(None)
           return {'CANCELLED'}

       return {'RUNNING_MODAL'}

   def invoke(self, context, event):
       if context.space_data.type == 'VIEW_3D':
           context.workspace.status_text_set(
               "Left click drag to merge vertices, Esc or right click to cancel")

           self.start_vertex = None
           self.end_vertex = None
           self.started = False
           self.me = bpy.context.object.data
           self.world_matrix = bpy.context.object.matrix_world
           self.bm = bmesh.from_edit_mesh(self.me)

           args = (self, context)
           self._handle = bpy.types.SpaceView3D.draw_handler_add(
               draw_callback_px, args, 'WINDOW', 'POST_VIEW')

           context.window_manager.modal_handler_add(self)

           return {'RUNNING_MODAL'}
       else:
           self.report({'WARNING'}, "Active space must be a View3d")
           return {'CANCELLED'}


def register():
   bpy.utils.register_class(MergeTool)


def unregister():
   bpy.utils.unregister_class(MergeTool)


if __name__ == "__main__":
   register()

I’m trying to understand why you need them combined in the first place- these scripts do completely different things, combining them would have no effect other than to make it one file you have to install rather than two. I don’t mind helping you if there’s a purpose behind it, but I’m not going to waste 20 minutes of my day so you don’t have to click the install button a second time :slight_smile:

You can create custom addon that have a buttons that call classes from other addons.
Other addons need to be installed.
Icons can be removed, just for example here

import bpy
from bpy.types import Panel

bl_info = {
    "name": "Your_Addon_Name",
    "author": "AUTHOR",
    "version": (0, 0, 1),
    "blender": (2, 90, 0),
    "location": "View3D > Properties > Panel_Name",
    "description": "", 
    "doc_url": "",
    "tracker_url": "",      
    "category": "3D View"
}


class COMBINED_PT_main(Panel):          
    bl_label = "Your_Script_Name"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = 'Panel_Name'
    
    def draw (self, context):
        layout = self.layout
        col = layout.column()
        
        col.operator("object.edge_to_curve", icon = 'MOD_BEVEL')
        col.operator("object.merge_tool", icon = 'MESH_DATA')
        
def register():
   bpy.utils.register_class(COMBINED_PT_main)


def unregister():
   bpy.utils.unregister_class(COMBINED_PT_main)

if __name__ == "__main__":
    register()

and here you can add buttons from other addons, just to keep your actions more organized

1 Like

That’s why i ask for help because i was not sure how to do it properly! And the main goal was to put them in blender UI under proper location like in edit mode edges context menu so people can easily access them.

Thank for your help and the goal i want to achieve is to make a single addon that combine everything so people just have to install one addon to get both scripts features in blender UI correct location.

My coding knowledge is very basic so that’s the reason i was asking for help to make this available to the community.

Let’s ask Andreas and see what he thinks.

1 Like

Sure this a good idea i my bad for not mentioning this first thank for bringing this up. I could have easily make it just for my personal use with my addon pie menu editor but i was in the sharing spirit to brought it to the whole community.

Hi, were you able to figure this out? Andreas seems indifferent to the idea. I spent the last month writing a big overhaul of the Merge Tool to make it an actual tool in Blender’s toolbar (1.3.0).


There was also a small update to Edge to Curve (1.0.3).

To combine the two you just need to copy all of the defs and classes from one file into the other; I’d recommend copying the Edge to Curve functions into the Merge Tool file. Note that the Merge Tool has to be installed as a .zip file now. Take a look at the folder structure inside the .zip file; you’ll be copying the Edge to Curve functions into the __init__.py file inside the mesh_merge_tool folder.

The most important part is making sure that the contents of the register and unregister functions down at the bottom are combined.

Edge to Curve:

def register():
    bpy.utils.register_class(ModalEdgeToCurve)
    bpy.types.VIEW3D_MT_edit_mesh_edges.append(EdgeToCurveMenuItem)
    bpy.types.VIEW3D_MT_edit_mesh_context_menu.append(EdgeToCurveMenuItem)


def unregister():
    bpy.utils.unregister_class(ModalEdgeToCurve)
    bpy.types.VIEW3D_MT_edit_mesh_edges.remove(EdgeToCurveMenuItem)
    bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(EdgeToCurveMenuItem)

Merge tool:

def register():
    for every_class in classes:
        bpy.utils.register_class(every_class)
    bpy.utils.register_tool(WorkSpaceMergeTool, after={"builtin.measure"}, separator=True, group=False)


def unregister():
    for every_class in classes:
        bpy.utils.unregister_class(every_class)
    bpy.utils.unregister_tool(WorkSpaceMergeTool)