1

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?

brockmann
  • 12,613
  • 4
  • 50
  • 93
gor
  • 11
  • 4
  • 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: https://blender.stackexchange.com/questions/57306/how-to-create-a-custom-ui BTW: Please take the [tour] to learn about how this site works, thanks. – brockmann Mar 04 '21 at 13:22
  • 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. – gor Mar 04 '21 at 18:30
  • 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? – brockmann Mar 04 '21 at 19:06
  • Yes exactly this. – gor Mar 04 '21 at 19:39

3 Answers3

2

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

brockmann
  • 12,613
  • 4
  • 50
  • 93
  • 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. – gor Mar 05 '21 at 13:57
  • 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. – brockmann Mar 05 '21 at 14:12
  • 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. – gor Mar 05 '21 at 14:26
  • 1
    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: https://blender.stackexchange.com/questions/57306/how-to-create-a-custom-ui/57332 – brockmann Mar 05 '21 at 14:32
1

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.

batFINGER
  • 84,216
  • 10
  • 108
  • 233
  • 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. – gor Mar 05 '21 at 14:02
  • 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. – batFINGER Mar 05 '21 at 14:05
  • 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. – gor Mar 05 '21 at 14:10
  • Indeed useful, thanks for posting @batFINGER – brockmann Mar 05 '21 at 14:15
  • Yes, that is exactly what above does. Thanks @brockers. – batFINGER Mar 05 '21 at 14:16
0

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()

gor
  • 11
  • 4