1
$\begingroup$

I want to make UI with input fields and a button which add verts with coords based on values in this fields.

I kinda almost understand what I need to do but it slips away because I am not a programmer. Looks like I need a class which creates a point when executed, UI part and reg ofc. Like this:

class addPoint (bpy.types.Operator):
    bl_idname = 'something.point'
    bl_label = 'Point'
    
    def execute (self, context):
        bpy.ops.mesh.primitive_vert_add()
        
class Panel (bpy.types.Panel):
    bl_idname = 'panel'
    bl_label = 'Panel'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Panel'
    
    def draw (self, context):
        layout = self.layout
        row = layout.row()
        row.label(text = 'Add point')

def register():
    bpy.utils.register_class(addPoint)
    bpy.utils.register_class(Panel)
def unregister():
    bpy.utils.unregister_class(addPoint)
    bpy.utils.unregister_class(Panel)

if __name__ == '__main__':
    register()

As far as I know there is no built in way to setup creation coords? I feel like I can add bpy.ops.transform.translate to execute part but how to show inputs for it in UI?

Anyway I guess it will not work because those fields only active if the object is already selected, and it can't be selected before it's created. So I feel like I need to add properties to addPoint class(?), show them in UI, then input data there and use this data for translation. But how to write all this and connect all together?

Or maybe I am totally wrong?

$\endgroup$
4
  • $\begingroup$ Hi and welcome. Recommended way is use low level functions, have a look into the Operator Mesh Add template that comes with blender. For the UI part, I'd suggest start here: blender.stackexchange.com/questions/57306/… BTW: Please take the tour to learn about how this site works, thanks. $\endgroup$
    – brockmann
    Mar 4, 2021 at 13:22
  • $\begingroup$ This link about UI is huge, with my English and programming knowledge I'll look for an answer for a month and most likely I wont find it. I already spent almost a week. "Operator Mesh Add" template, I saw it before. I will be able to define coordinates before mesh is created. I guess it's 'correct" but does not matter because it's not my problem. My problem with showing inputs where I can set xyz values and click a button to create a point. I can't understand logic behind this, all guides move me to some another direction or most likely I just can't see what I need. $\endgroup$
    – gor
    Mar 4, 2021 at 18:30
  • $\begingroup$ Takes a bit experience, that's for sure. Just to clearify, you want a button on a panel along with XYZ inputs and when the user clicks the button, a new vertex should be created on this XYZ position? $\endgroup$
    – brockmann
    Mar 4, 2021 at 19:06
  • $\begingroup$ Yes exactly this. $\endgroup$
    – gor
    Mar 4, 2021 at 19:39

3 Answers 3

2
$\begingroup$

Have a look into the Operator Mesh Add template that comes with Blender.


First of all, you would have to figure out how to create a mesh which is nicely explained in create mesh then add vertices to it in python, then declare an Operator ideally along with a FloatVectorProperty for the actual location and add that code from before to its execute method.

The following code is based on Operator Mesh Add template, adds a menu entry to the Mesh menu (ShiftA) as this is the prefered way of adding a new object to the scene. If bl_options are set to 'UNDO' the properties of the operator will be displayed in the bottom left of the 3d Viewport (location in this case) which allows the user to set the parameter interactively while adding the vertex:

enter image description here

import bpy
import bmesh

class MESH_OT_addVertex(bpy.types.Operator):
    """Add a simple vertex object"""
    bl_idname = "mesh.primitive_vertex_add"
    bl_label = "Add Vertex"
    bl_options = {'REGISTER', 'UNDO'}

    location: bpy.props.FloatVectorProperty(
        name="Location",
        default = (0,0,1))

    def execute(self, context):
        
        # Create a new mesh based on the given location
        # https://blender.stackexchange.com/a/61893/
        # https://blender.stackexchange.com/a/23088/
        mesh = bpy.data.meshes.new("Vertex")
        bm = bmesh.new()
        bm.verts.new(self.location)
        bm.to_mesh(mesh)
        mesh.update()
        
        # Create new object and link object to the active collection
        # https://blender.stackexchange.com/a/145715/
        obj = bpy.data.objects.new("MyVetexObject", mesh)
        context.collection.objects.link(obj)
        
        # Make the object the active one and select it
        # https://blender.stackexchange.com/a/126581/
        context.view_layer.objects.active = obj
        obj.select_set(True)

        return {'FINISHED'}


def menu_func(self, context):
    self.layout.separator()
    self.layout.operator(MESH_OT_addVertex.bl_idname, icon='DOT')


def register():
    bpy.utils.register_class(MESH_OT_addVertex)
    bpy.types.VIEW3D_MT_mesh_add.append(menu_func)


def unregister():
    bpy.utils.unregister_class(MESH_OT_addVertex)
    bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)


if __name__ == "__main__":
    register()

    # test call
    #bpy.ops.mesh.primitive_vertex_add()

If you would like to place the operator onto a panel, register your panel, add the Layout.operator() call to the draw() method of the panel class and pass its bl_idname:

enter image description here

def draw (self, context):
    layout = self.layout

    row = layout.row()
    row.operator(MESH_OT_addVertex.bl_idname)
    ...

In case there is a serious reason to display the coords on your panel as well, I'd suggest register a new FloatVectorProperty property per scene (bpy.types.Scene) and pass the values to the Operator within the draw method of the panel class:

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

    bpy.types.Scene.mytool_location = bpy.props.FloatVectorProperty(
        name="Location",
        default = (0,0,0)
        )

def unregister():
    bpy.utils.unregister_class(OBJECT_PT_CustomPanel)
    ...
    
    del bpy.types.Scene.mytool_location

enter image description here

def draw(self, context):
    layout = self.layout
    scene = context.scene
    
    layout.prop(scene, "mytool_location")
    op = layout.operator(MESH_OT_addVertex.bl_idname)
    op.location = scene.mytool_location
    ...

Further reading

$\endgroup$
4
  • $\begingroup$ Took a while but I was able to reproduce it. But still there are no input fields on the panel. I assume I need to show 'location' property from addVertex class on a panel somehow? Something like 'row.prop('mesh.primitive_vertex_add')'. But from examples I saw it's wrong, because this method need some object and it's attribute. But before I hit a button to create an object I do not have object to have input fields. Really confusing. $\endgroup$
    – gor
    Mar 5, 2021 at 13:57
  • $\begingroup$ The input fields are part of the 3d viewport because "this is the prefered way of adding a new object to the scene", I disagree, this isn't confusing because this is what the Blender user is used to. But yeah, we can add that to the panel as well if there is a serios reason having them on the panel and in the viewport...? @gor. $\endgroup$
    – brockmann
    Mar 5, 2021 at 14:12
  • $\begingroup$ I do not really need that part in left bottom corner, just on panel. Serious reasons? I used to have that UI in other software as a result for me it's more comfortable also I want to understand how to do it. $\endgroup$
    – gor
    Mar 5, 2021 at 14:26
  • 1
    $\begingroup$ Serious reasons, because it always will be there anyway, that's how blender is designed so I'd use it, no matter what you like. Anyway, I'm going to add how to do it... @gor In the meantime, your can also read that up here: blender.stackexchange.com/questions/57306/… $\endgroup$
    – brockmann
    Mar 5, 2021 at 14:32
1
$\begingroup$

Using the add vert operator from add mesh extras.

In addition to @brockmann's excellent answer, can use the existing add vert operator that comes with Add Mesh Extras.

enter image description here

The new vert is added at the scene cursor location.

Putting three things together,

  1. Enable Add Mesh Extras addon if it is not already. Disable if enabled by addon (possibly requires better logic, eg flag if enabled by this addon to disable on unreg.)
  2. Add a button to add vert to panel
  3. Add the cursor location (it's in global coordinates) to the panel.

Test script, using question code.

import bpy

class AddVertPanel (bpy.types.Panel):
    bl_idname = 'VIEW3D_PT_add_vert'
    bl_label = 'Panel'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Panel'
    
    def draw (self, context):
        layout = self.layout
        scene = context.scene
        col = layout.column()
        col.label(text = 'Add point')        
        col.prop(scene.cursor, "location")
        col.operator("mesh.primitive_vert_add")        

def register():
    from addon_utils import check, enable
    loaded_default, loaded_state = check("add_mesh_extra_objects")
    if not loaded_state:
        enable("add_mesh_extra_objects")         
    bpy.utils.register_class(AddVertPanel)

def unregister():
    from addon_utils import check, disable
    loaded_default, loaded_state = check("add_mesh_extra_objects")
    if not loaded_default:
        disable("add_mesh_extra_objects")    
    bpy.utils.unregister_class(AddVertPanel)

if __name__ == '__main__':
    register()

Please also take note of the naming of classes and bl_idname of the panel using the naming convention. "Panel" is possibly the worst name for a panel.

$\endgroup$
5
  • $\begingroup$ Thank you but it's not what I need. P.S. I know about naming convention but I just trying to not hurt my brain too much in this spaghetti. $\endgroup$
    – gor
    Mar 5, 2021 at 14:02
  • $\begingroup$ That's fine. Other's may find it useful. (IMO totally fulfills remit of question) Btw please pay heed to the naming convention of panels, as noted "Panel" is possibly the worst name possible. $\endgroup$
    – batFINGER
    Mar 5, 2021 at 14:05
  • $\begingroup$ Thread name was changed by mod I guess because it was just tags in fact :D. Actual question is in first sentence - ui with input fields and a button to create a vertex in location from input fields. $\endgroup$
    – gor
    Mar 5, 2021 at 14:10
  • $\begingroup$ Indeed useful, thanks for posting @batFINGER $\endgroup$
    – brockmann
    Mar 5, 2021 at 14:15
  • $\begingroup$ Yes, that is exactly what above does. Thanks @brockers. $\endgroup$
    – batFINGER
    Mar 5, 2021 at 14:16
0
$\begingroup$

I was able to do what I want with PropertyGroup and PointerProperty. Here is my rough "project" for reference, there is a bit more than my question but I guess it's ok. Script creates Panel in Sidebar, bottom part of this panel has input-fields for location and button to create a vertex. Also if one vertex is already exist, next vertex will be connected to previous one with edge.

import bpy
import bmesh

class Variables(bpy.types.PropertyGroup):
    loc: bpy.props.FloatVectorProperty(name='Add element')
    nam: bpy.props.StringProperty(name='Name', default = 'test')
    lpx: bpy.props.FloatProperty(name='X', default = 396.0)
    lpy: bpy.props.FloatProperty(name='Y', default = 296.0)
    lpz: bpy.props.FloatProperty(name='Z', default = 19.0)

class MESH_OT_addPieceBoundary(bpy.types.Operator):
    bl_idname = 'mesh.primitive_piece_boundary_add'
    bl_label = 'Add piece boundary'
    
    def structure(self, context):
        props = context.scene.var
        self.lpx = props.lpx/1000
        self.lpy = props.lpy/1000
        self.lpz = props.lpz/1000
        
    
    def execute(self, context):
        self.structure(context)    
        bm = bmesh.new()
        if 'Boundary' in bpy.data.meshes:
            pass
        else:
            boundary_verts = [[0,0, self.lpz], [0, self.lpy, self.lpz], [self.lpx, self.lpy, self.lpz], [self.lpx, 0, self.lpz]]
            mesh = bpy.data.meshes.new('Boundary')
            for vert in boundary_verts:
                bm.verts.new(vert)
            bm.verts.ensure_lookup_table()    
            v1, v2, v3, v4 = bm.verts[0], bm.verts[1], bm.verts[2], bm.verts[3]
            bm.edges.new((v1, v2))
            bm.edges.new((v2, v3))
            bm.edges.new((v3, v4))
            bm.edges.new((v4, v1))
                        
            bm.to_mesh(mesh)
            obj = bpy.data.objects.new('PieceBoundary', mesh)
            context.collection.objects.link(obj)
            
        return {'FINISHED'}
    
class MESH_OT_addVertex(bpy.types.Operator):
    """Add a simple vertex object"""
    bl_idname = "mesh.primitive_vertex_add"
    bl_label = "Add vert, edge between verts"
        
    def structure(self, context):
        props = context.scene.var
        self.location = [x / 1000 for x in props.loc]
        self.nam = props.nam

    def execute(self, context):

        self.structure(context)
                
        bm = bmesh.new()
        
        if self.nam in bpy.data.meshes:
            mesh = bpy.data.meshes[self.nam]
            bm.from_mesh(mesh)
        else:
            mesh = bpy.data.meshes.new(self.nam)
        
        bm.verts.new(self.location)
        if len(bm.verts) > 1:
            bm.verts.ensure_lookup_table()
            v1 = bm.verts[-1]
            v2 = bm.verts[-2]
            bm.edges.new((v1, v2))
        bm.to_mesh(mesh)
        mesh.update()
        
        if self.nam in bpy.data.objects:
            pass
        else:
            obj = bpy.data.objects.new(self.nam, mesh)
            context.collection.objects.link(obj)

            context.view_layer.objects.active = bpy.data.objects.get(self.nam)
            obj.select_set(True)

        return {'FINISHED'}

class PROPERTIES_PT_panel(bpy.types.Panel):
    bl_idname = 'PROPERTIES_PT_panel'
    bl_label = 'Panel'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Panel'
            
    def draw (self, context):
        layout = self.layout
        props = context.scene.var

        row = layout.row()
        row.label(text='Piece size:')

        col = layout.column(align=True)
        col.prop(props, 'lpx')
        col.prop(props, 'lpy')
        col.prop(props, 'lpz')
        row = layout.row()
        row.operator('mesh.primitive_piece_boundary_add')
        row = layout.row()
        row = layout.row()
        row = layout.row()
        row.prop(props, 'nam')
        col = layout.column()
        col.prop(props, 'loc')
        
        row = layout.row()
        row.operator('mesh.primitive_vertex_add')

#        return {'FINISHED'}

def register():
    bpy.utils.register_class(Variables)
    bpy.utils.register_class(MESH_OT_addPieceBoundary)
    bpy.utils.register_class(MESH_OT_addVertex)
    bpy.utils.register_class(PROPERTIES_PT_panel)
    bpy.types.Scene.var = bpy.props.PointerProperty(type = Variables)
    
 

def unregister():
    bpy.utils.unregister_class(Variables)
    bpy.utils.unregister_class(MESH_OT_addPieceBoundary)
    bpy.utils.unregister_class(MESH_OT_addVertex)
    bpy.utils.unregister_class(PROPERTIES_PT_panel)

if __name__ == "__main__":
    register()
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .