Problems creating UI for my script

I’m trying to learn scripting in blender by writing a simple plugin. The basic script works, it generates a helix mesh. I can even start it from the tools menu. The problem is creating a UI to change the values of the variables in the script. I have set them as properties (variable = bpy.prop.IntProperty), but when I try calling them in the UI, (self.layout.prop(“mesh.helix”, variable)) the script crashes.


# Helix Script

import bpy
from bpy.props import *
from math import radians, cos, sin


class Helix(bpy.types.Operator):
    bl_label = "Helix"
    bl_idname = "mesh.helix"
    bl_description = "Create a helix primative"
    bl_options = {"UNDO"}
    
    numberOfCoils = IntProperty(
        name = "numberOfCoils",
        description = "How many coils in the spring",
        default = 10)
        
    length = FloatProperty(
        name = "length",
        default = 1.0)
    
    coilRadius = FloatProperty(
        name = "coilRadius",
        description = "Radius of the spring",
        default = 0.5)
        
    coilResolution = IntProperty(
        name = "coilResolution",
        description = "number of verticies per revolution",
        default = 16)
    
    def execute(self, context):
        cursor = bpy.context.scene.cursor_location
        anglesInRadians = [radians(degree) for degree in range(0, 360, int(360 / self.coilResolution))]


        currentHeight = 0
        verticies = []
        for currentCoil in range(self.numberOfCoils):
            for theta in anglesInRadians:
                x = cursor.x + self.coilRadius * cos(theta)
                y = cursor.y + self.coilRadius * sin(theta)
                z = cursor.z + currentHeight
                verticies.append((x, y, z))
                currentHeight += self.length / self.numberOfCoils / self.coilResolution
            
        edges = []
        for vertexIndex in range(len(verticies)-1):
            edges.append((vertexIndex, vertexIndex + 1))
            
         # write data to the scene
        mesh = bpy.data.meshes.new("HelixMesh")
        ob = bpy.data.objects.new("Helix", mesh)
        bpy.context.scene.objects.link(ob);


        mesh.from_pydata(verticies, edges, [])
        mesh.update()       
        return{"FINISHED"}

class HelixPanel(bpy.types.Panel):
    bl_label = "Helix Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    
    def draw(self, context):
        layout = self.layout

# This next line is the problem
        layout.prop("mesh.helix", 'numberOfCoils')
        layout.operator("mesh.helix")
        
bpy.utils.register_class(Helix)
bpy.utils.register_class(HelixPanel)


Any help would be apreciated

Hi,

the issue here is your properties belong to the operator

Here is a quick hack to put a draw method in your op, give it the ‘REGISTER’ option.


# Helix Script


import bpy
from bpy.props import *
from math import radians, cos, sin




class Helix(bpy.types.Operator):
    bl_label = "Helix"
    bl_idname = "mesh.helix"
    bl_description = "Create a helix primative"
    bl_options = {'REGISTER', 'UNDO'}
    
    numberOfCoils = IntProperty(
        name = "numberOfCoils",
        description = "How many coils in the spring",
        default = 10)
        
    length = FloatProperty(
        name = "length",
        default = 1.0)
    
    coilRadius = FloatProperty(
        name = "coilRadius",
        description = "Radius of the spring",
        default = 0.5)
        
    coilResolution = IntProperty(
        name = "coilResolution",
        description = "number of verticies per revolution",
        default = 16)
    
    def draw(self, context):
        layout = self.layout
        layout.prop(self, "numberOfCoils")
        
    def execute(self, context):
        scene = context.scene
        cursor = scene.cursor_location
        anglesInRadians = [radians(degree) for degree in range(0, 360, int(360 / self.coilResolution))]




        currentHeight = 0
        verticies = []
        for currentCoil in range(self.numberOfCoils):
            for theta in anglesInRadians:
                x = cursor.x + self.coilRadius * cos(theta)
                y = cursor.y + self.coilRadius * sin(theta)
                z = cursor.z + currentHeight
                verticies.append((x, y, z))
                currentHeight += self.length / self.numberOfCoils / self.coilResolution
            
        edges = []
        for vertexIndex in range(len(verticies)-1):
            edges.append((vertexIndex, vertexIndex + 1))
            
         # write data to the scene
        mesh = bpy.data.meshes.new("HelixMesh")






        mesh.from_pydata(verticies, edges, [])
        mesh.update()
        
<b>        ob = bpy.data.objects.new("Helix", mesh)
        scene.objects.link(ob)
        scene.objects.active = ob      </b> 
        return{"FINISHED"}


class HelixPanel(bpy.types.Panel):
    bl_label = "Helix Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    
    def draw(self, context):
        layout = self.layout


# This next line is the problem
        #layout.prop("mesh.helix", 'numberOfCoils')
        layout.operator("mesh.helix")
        
bpy.utils.register_class(Helix)
bpy.utils.register_class(HelixPanel)

Have a look in scripts/ startup / bl_operators add Torus mesh script and how it uses the object_utils.object_add_grid_scale_apply_operator(self, context) and foreach_set to make the mesh, quicker, and in particular the last line. Replace the bits marked bold with this.


object_utils.object_data_add(context, mesh, operator=self)

Which stops the 'orrible lag. It adds an object to the scene named after the created mesh, and makes it active.

If you want the user input first, you may use invoke_props_dialog():

Or register the same properties on a bpy.types type (e.g. WindowManager or Scene) and add them to the layout. You should set options={‘SKIP_SAVE’} for the properties.

Thanks for the feedback.
I didn’t want to add the draw method directly to the class, so that I could use more than one panel (one in the tool section, another in the properties panel)

As for the second method, would registering the property to the scene make it a global? (Coming from C++, I’ve always been taught to avoid globals at all cost) Is there any way to do this without it begin global? My worry with this is namespace collision, such as if another module uses a property called radius.

Thanks

They are per-type, you reigster them on bpy.types.* globally, but use an instance (e.g. bpy.context.scene). So it’s local to the scene, but all scenes will have this property (set to the default value until you change it on an instance).

You can avoid collisions by using a PointerProperty with a custom PropertyGroup:

http://www.blender.org/documentation/blender_python_api_2_69_10/bpy.props.html#propertygroup-example