3
$\begingroup$

Question :

How would one go about making an Add Mesh operator re-entrant ? There are three use cases for this that I can see (1) You want to rebuild the underlying mesh for an object where you have a stack of modifiers you'd like to preserve, (2)You'd like to adjust a bad design choice from earlier and (3) You'd like to build a new object from an existing object given it's dimensions, think russian dolls or possibly puzzle pieces where the next outer doll/next puzzle piece depends upon the previous one, having access to the original piece makes for easy specification for the next.

Example :

An Add On providing a cube generator with variable length, height and width and registers 'Add Custom Cube' under the 'Add Mesh' menu. When selected the mesh is produced along with a panel displaying the above properties. When one changes a property Blender deletes the mesh and produces a new one with the updated dimensions. If one then creates some other mesh e.g. a plane, one looses the focus/history on the original mesh. Re-selecting the original cube does not show the panel with it's properties and it is no longer editable. The only way one might modify the cube is to delete it and create a new cube. Is it perhaps possible to delete/modify the mesh and not the object which would protect say a stack of modifiers applied to the object ?

Factors :

The following are what I consider relevant in answering this, as it appears a bit involved.

1) Re-entrancy

How does one make a mesh's properties editable across reselections ? How does one re-enter the same operator ? Is it possible to have two operators. The first performing object creation the second performing it's updates.

2) Identification

Is there a way to uniquely identify the mesh/object for later editing e.g. Could I name the object 'Custom Mesh' or some thing to that affect ? Should one rather identify it be it's properties i.e. other meshes may not have length, width and height attributes.

3) Properties

How does one attach the dimensions to the cube so that they may be edited later ? Presumably the operator will be re-instantiated when called resorting to it's defaults and not using the cubes current settings.

4) Registration

It would appear necessary to make the class that generates the mesh a subclass of both bpy.types.Operator and bpy.types.Panel allowing it to be visible both when the mesh is created and when it is edited. Would this require class registration at two places or does once suffice.

Homework :

From what I have read the following seems relevant :

1) Re-entrancy

If I have understood the documentation and API references then execute is called to create the original mesh and invoke is called whenever a pertinent event occurs and both are executed only if poll returns true. Logically Add Mesh would run execute to make the object. Subsequently the mesh may be modified from invoke. This hints towards re-entrancy, I suspect I'm aiming for method 2, some sort of scene updater.

2) Identification

Identification seems to be readily handled by merely naming the mesh. When execute is first called there is no object the poll function must account for this somehow. There after the object name reflects the name it was assigned as creation. I'm not sure it the user changes the object name or the mesh name when they fiddle about in the outliner area, presumably they affect both. Is it possible to assign two meshes to an object ?

3) Properties

There seems to be multiple means of specifying a property. It seems standard practice to assign it globally to an bpy.types.Object as in bpy.type.Object.prop = bpy.props.*Property i.e. one makes the property available to both the torus and the monkey when it's really only meant for the custom cube. An alternative is to assign it globally to the window manager. The catch is that assigning to an object persists when one closes the *.blend assigining to the window manager does not. (Further alternatives include scenes etc.)

Being a local is lekker kinda guy I'd assign dimensions locally to the mesh instance using me['prop'] = value. This benefits from persisting once one closes the *.blend, but it does not appear possible to attach subclasses of bpy.types.PropertyGroup or any bpy.prop.*Property. Such properties must be sadly be assigned globally (This is due to the way the data is stored in the *.blend file or in as it is moved from Python to C). One may store state locally within an operator or panel (I believe this too does not persist when closing *.blends).

3) Registriation

I don't see how one would register a class twice. I'm not sure a second class is necessary either.

Code :

Below I have included what I've achieved so far, which is essentially create an object and append the dimensions locally to the mesh. I have not succeeded in re-entering the dimensions so the cycle "Create Custom Cube > Create Plane > Select Custom Cube > Adjust Dimensions" fails at Select Custom Cube.

# Package Dependencies
import bpy
import bpy_extras
#import bmesh
from math import *
from mathutils import *

# Package Information
bl_info = {
    "name"         : "NAME",
    "location"     : "View3D > Add > Mesh > New GEOMETRY",
    "category"     : "Add Mesh",
    "description"  : "This adds a GEOMETRY",
    #"warning"     : "Alpha Release, much still to do...",
    "author"       : "AUTHOR",
    #"support"     : "TESTING",
    #"wiki_url"    : "",
    #"tracker_url" : "",
    "version"      : (0, 1),
    "blender"      : (2, 69, 0),
    }

# Mesh Properties
class GEOMETRYProperties(bpy.types.PropertyGroup) :
    """GEOMETRY properties"""
    height = bpy.props.FloatProperty(
               name        = "Height",
               description = "The height",
               default     =      5.0,
               unit        = 'LENGTH')
    width  = bpy.props.FloatProperty(
               name        = "Width",
               description = "The width",
               default     =      5.0,
               unit        = 'LENGTH')
    length = bpy.props.FloatProperty(
               name        = "Length",
               description = "The length",
               default     =      5.0,
               unit        = 'LENGTH')

class GEOMETRYPanel(bpy.types.Panel) :
    bl_idname      = "mesh.primitive_geometry_modify"
    bl_label       = "Modify GEOMETRY"
    bl_description = "This panel modifies a GEOMETRY"
    bl_space_type  = "VIEW_3D"
    bl_region_type = "TOOLS"
    
    def draw(self, context) :
        layout = self.layout
        layout.operator("mesh.primitive_geometry_add")
    
class MESH_OT_primitive_GEOMETRY_add(bpy.types.Operator, bpy_extras.object_utils.AddObjectHelper) :
    """Creates GEOMETRY"""
    bl_idname      = "mesh.primitive_geometry_add"
    bl_label       = "Add GEOMETRY"
    bl_description = "This operator creates a GEOMETRY"
    bl_options     = {'REGISTER', 'UNDO'}# , 'PRESET'}

    dims = bpy.props.PointerProperty(name ="Dimensions", type=GEOMETRYProperties)

    def add_GEOMETRY(self, width, length, height) :
        tvrts = [(width*cos(radians(theta)), length*sin(radians(theta)),   height/2) for theta in range(0,360,90)]
        bvrts = [(width*cos(radians(theta)), length*sin(radians(theta)),  -height/2) for theta in range(0,360,90)]
        verts = [item for list in zip(tvrts,bvrts) for item in list]
        edges = []
        faces = list(zip(range(0,len(verts),2),range(1,len(verts),2),range(3,len(verts),2),range(2,len(verts),2))) + [(len(verts)-1,len(verts)-2,0,1)]
        return verts, edges, faces

    def invoke(self, context, event) :
        if context.object :
            data = context.object.data
            self.dims.length = data['dims']['length']
            self.dims.width  = data['dims']['width']
            self.dims.height = data['dims']['height']
        return self.execute(context)
     
    def execute(self, context) :
        me = bpy.data.meshes.new(name="GEOMETRY")
        ob = bpy_extras.object_utils.object_data_add(context, me, operator=self)
        v,e,f = self.add_GEOMETRY(self.dims.width, self.dims.length, self.dims.height)
        me.from_pydata(v, e, f)
        me.update()
        me['dims'] = {'length' : self.dims.length,
                      'width'  : self.dims.width,
                      'height' : self.dims.height}
        return {"FINISHED"}

    def draw(self, context) :
        layout = self.layout
        # Dimensions
        col = layout.column()
        col.prop(self.dims, "height")
        col.prop(self.dims, "width")
        col.prop(self.dims, "length")
        # Alignment
        #col = layout.column(align=True)
        #col.prop(self, 'view_align')
        # Location
        #col = layout.column(align=True)
        #col.label(text="Location")
        #col.prop(self, 'location', text="")
        # Rotation
        #col = layout.column(align=True)
        #col.label(text="Rotation")
        #col.prop(self, 'rotation', text="")

def addGEOMETRYMenu(self, context) :
    self.layout.operator(MESH_OT_primitive_GEOMETRY_add.bl_idname, text="Add GEOMETRY")

def register():
    bpy.utils.register_class(GEOMETRYProperties)
    bpy.utils.register_class(MESH_OT_primitive_GEOMETRY_add)
    bpy.types.INFO_MT_mesh_add.append(addGEOMETRYMenu)

def unregister():
    bpy.utils.unregister_class(MESH_OT_primitive_GEOMETRY_add)
    bpy.utils.unregister_class(GEOMETRYProperties)
    bpy.types.INFO_MT_mesh_add.remove(addGEOMETRYMenu)

if __name__ == "__main__" :
    register(
$\endgroup$
1
  • $\begingroup$ You may want to look at the plancher addon for an example. I would recommend using zeffi's example of subclassing propertygroup to hold your properties instead of adding multiple object properties like this addon. $\endgroup$
    – sambler
    Jan 20, 2016 at 9:10

2 Answers 2

3
$\begingroup$

First clear up a potential misconception: When we register properties to bpy.types.Object.* or bpy.types.Mesh.* etc .. (either as PropertyGroup or individual Int/Float/BoolProperty etc) the data is created once. Only those properties that are modified from the default somehow will create new data (new memory allocation). The same goes for the many other existing properties of an Object.

So i agree, local is lekker but imposing the limitation to ID props only seems a little arbitrary, especially when the combo of an ID property and bpy.types.Object.* allows for a very non-convoluted solution. A single ID property could be added to indicate what kind of parametric object it is, and use that value to do a property group lookup.


There are a couple of approaches, and they share quite a bit of code. You might reuse the bpy_extras.object_utils.AddObjectHelper, but I like more control :)

Approach 1

One approach is to use a combination of Object properties (a propertygroup) and ID properties

  1. register new Object properties (all objects in the .blend will then have these properties)
  2. give the object one or more ID properties at creation time.

You can give the object a propertygroup, and populate your panel's draw function with those properties when an active object has an identifier you're looking for. Each property would have an update function which will cause slider adjustments to affect only the active object's geometry

Here is a simple Operator / Panel combo. Parametric circle.

  • If nothing is selected it shows a Make A new Circle button in the panel,
  • if a non parametric object is selected it will say so and offer the Make A new Circle button
  • if you've just created a parametric object (ie, this Circle) or you set an existing parametric object to active, then the Panel will display update-able properties.

code:

bl_info = {
    "name": "parametric circle",
    "author": "Dealga McArdle",
    "version": (0, 1),
    "blender": (2, 7, 6),
    "category": "3D View"
}

import bpy 
import bmesh

import math
from math import sin, cos, pi

def make_geom(props):
    r = props.radius
    num = props.verts
    theta = 2 * pi / num
    vertices = [(sin(i*theta)*r, cos(i*theta)*r, 0) for i in range(num)]
    edges = [(i, i+1) for i in range(num-1)] + [(num-1, 0)]
    return vertices, edges

def add_obj_to_scene(obj, active=True):
    scene = bpy.context.scene
    scene.objects.link(obj)
    if active:
        scene.objects.active = obj

def update_mesh(obj, wipe=False):
    mesh = obj.data

    if wipe:
        bm = bmesh.new()
        bm.to_mesh(mesh)
        mesh.update()

    verts, edges = make_geom(obj.circle_props)
    mesh.from_pydata(verts, edges, [])
    mesh.update()


def update_object(self, context):
    obj = context.active_object 
    update_mesh(obj, wipe=True)


class CircleProps(bpy.types.PropertyGroup):
    radius = bpy.props.FloatProperty(default=1.0, min=0.01, max=6.0, update=update_object)
    verts = bpy.props.IntProperty(default=8, min=3, update=update_object)


class CircleGen(bpy.types.Operator):
    bl_idname = "circlegen.make_circle"
    bl_label = "Make A new Circle"

    def make_me(self, context):
        ID = str(hash(self))
        mesh = bpy.data.meshes.new("mesh_name_" + ID)
        obj = bpy.data.objects.new("obj_name", mesh)
        obj['ID'] = ID
        update_mesh(obj)
        add_obj_to_scene(obj)

    def execute(self, context):
        self.make_me(context)

        return {'FINISHED'}

class CirclePanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'

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

        obj = context.active_object
        if not obj:
            r = l.row()
            r.operator('circlegen.make_circle')

        else:
            ID = obj.get('ID')
            if ID:
                l.label(ID)
                r = l.row()
                r.prop(obj.circle_props, 'radius')
                r.prop(obj.circle_props, 'verts')
            else:
                l.label('not a dynamic object')
                r = l.row()
                r.operator('circlegen.make_circle')


def register():
    bpy.utils.register_module(__name__)
    bpy.types.Object.circle_props = bpy.props.PointerProperty(
        name="circle_props", type=CircleProps
    )


def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Object.circle_props

Approach 2

Different but the same end result. (todo)

Things to note

You mention the modifier stack, most modifiers don't care if you flatten/rebuild the Mesh data - except the Skin Modifier, for this you need to remove it from the stack and add it back again.

$\endgroup$
5
  • $\begingroup$ I've added where I got up to for some other readers benefit (probably my own in a number of months), more as a bad example then anything else. Thanks once again for the tiger like reflexes ! Mumble... AddObjectHelper... mumble mumble.. Sverchok looks like a cool library I read the bit you pointed towards and will go through it a bit more later on. $\endgroup$
    – Carel
    Jan 21, 2016 at 9:09
  • $\begingroup$ I mention the modifier stack more for an example then anything else I have no idea what might stack on an object I just want to make the lower most/Original mesh modifiable. Approach 1 seems the standard means of doing this. Since I posted this question I seem to have seen at least 5-10 variations of approach 1 as the most common solution. My example below really goes against the current but I thought it might be useful to others to see how not to go about it. Eventually I want to generate one object based upon the geometry of another like puzzle pieces/russian dolls, but that's another post. $\endgroup$
    – Carel
    Jan 21, 2016 at 11:13
  • $\begingroup$ Would you assign those the the bpy.types.Object itself as you did, approach 1, or the window manager as I did, below, or some where else entirely. $\endgroup$
    – Carel
    Jan 28, 2016 at 9:26
  • $\begingroup$ You are under no obligation to accept my answer as valid if you think it doesn't answer the question to satisfaction. $\endgroup$
    – zeffii
    Jan 28, 2016 at 11:23
  • $\begingroup$ Oh no it certainly seems the standard means of doing so. I didn't accept initially as I was trying to scan any related questions. After a day of that it seemed they all took the approach you took. My best attempt at deviating from that was what I did below. Invoke hooks correctly but does so aggressively for the few cases where it ought not to. Really sorry to hear about the drive dying nothing sucks worse then that. $\endgroup$
    – Carel
    Jan 28, 2016 at 15:32
1
$\begingroup$

This is an example showing what I believe to be an alternate to @Zeffii's answer. I really dislike attaching an Addons' properties to all bpy.types.Object instances, especially when the intent is to distinguish the Addons' meshes from the other meshes in a scene. It seems however that this is the best way to do it. My hackery is listed below in all it's gory.

# Package Dependencies
import bpy
import bpy_extras
import bmesh
from math import *
from mathutils import *

# Package Information
bl_info = {
    "name"         : "NAME",
    "location"     : "View3D > Add > Mesh > New GEOMETRY",
    "category"     : "Add Mesh",
    "description"  : "This adds a GEOMETRY",
    #"warning"     : "Alpha Release, much still to do...",
    "author"       : "AUTHOR",
    #"support"     : "TESTING",
    #"wiki_url"    : "",
    #"tracker_url" : "",
    "version"      : (0, 1),
    "blender"      : (2, 69, 0),
    }

# Mesh Properties
class GEOMETRYProperties(bpy.types.PropertyGroup) :
    """GEOMETRY properties"""
    height = bpy.props.FloatProperty(
               name        = "Height",
               description = "The height",
               default     =      5.0,
               min         =      0.0,
               max         =    100.0,
               unit        = 'LENGTH')
    width  = bpy.props.FloatProperty(
               name        = "Width",
               description = "The width",
               default     =      5.0,
               min         =      0.0,
               max         =    100.0,
               unit        = 'LENGTH')
    length = bpy.props.FloatProperty(
               name        = "Length",
               description = "The length",
               default     =      5.0,
               min         =      0.0,
               max         =    100.0)

# Operator(s)
class MESH_OT_primitive_GEOMETRY_add(bpy.types.Operator, bpy_extras.object_utils.AddObjectHelper) :
    """Creates GEOMETRY"""
    bl_idname      = "mesh.primitive_geometry_add"
    bl_label       = "Add GEOMETRY"
    bl_description = "This operator creates a GEOMETRY"
    bl_options     = {'REGISTER', 'UNDO'}# , 'PRESET'}

    #dims = bpy.props.PointerProperty(name ="Dimensions", type=GEOMETRYProperties)

    def gen_GEOMETRY(self, width, length, height) :
        tvrts = [(width*cos(radians(theta)), length*sin(radians(theta)),   height/2) for theta in range(0,360,90)]
        bvrts = [(width*cos(radians(theta)), length*sin(radians(theta)),  -height/2) for theta in range(0,360,90)]
        verts = [item for list in zip(tvrts,bvrts) for item in list]
        edges = []
        faces = list(zip(range(0,len(verts),2),range(1,len(verts),2),range(3,len(verts),2),range(2,len(verts),2))) + [(len(verts)-1,len(verts)-2,0,1)]
        return verts, edges, faces

    def mod_GEOMETRY(self, context) :
        ob = context.active_object
        me = ob.data
        bm = bmesh.new()
        bm.to_mesh(me)
        me.update()
        return ob,me

    def add_GEOMETRY(self, context) :
        me = bpy.data.meshes.new(name="GEOMETRY")
        ob = bpy_extras.object_utils.object_data_add(context, me, operator=self)
        return ob,me

    def execute(self, context) :
        wm = bpy.context.window_manager
        if context.active_object and all([context.active_object == object for object in context.selected_objects]) and 'dims' in context.active_object.data.keys() :
            ob,me = self.mod_GEOMETRY(context)
        else :
            ob,me = self.add_GEOMETRY(context)
        v,e,f = self.gen_GEOMETRY(wm.GEOMETRY.width, wm.GEOMETRY.length, wm.GEOMETRY.height)
        me.from_pydata(v, e, f)
        me.update()
        me['dims'] = {'length' : wm.GEOMETRY.length,
                      'width'  : wm.GEOMETRY.width,
                      'height' : wm.GEOMETRY.height}
        return {"FINISHED"}

    def invoke(self, context, event) :
        print("Invoke")
        if context.active_object :
            ob = context.active_object
            wm = bpy.context.window_manager
            if 'dims' in ob.data.keys() :
                data = ob.data
                wm.GEOMETRY.length = data['dims']['length']
                wm.GEOMETRY.width  = data['dims']['width']
                wm.GEOMETRY.height = data['dims']['height']
        return self.execute(context)

    def draw(self, context) :
        layout = self.layout
        object = context.active_object
        window = context.window_manager
        # Dimensions
        col = layout.column(align=True)
        col.prop(window.GEOMETRY, "height")
        col.prop(window.GEOMETRY, "width")
        col.prop(window.GEOMETRY, "length")
        # Alignment
        col = layout.column(align=True)
        col.prop(self, 'view_align')
        # Location
        col = layout.column(align=True)
        col.label(text="Location")
        col.prop(self, 'location', text="")
        # Rotation
        col = layout.column(align=True)
        col.label(text="Rotation")
        col.prop(self, 'rotation', text="")

# Panel(s)
class GEOMETRYPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label       = "GEOMETRY Panel"
    bl_idname      = "OBJECT_PT_geometry"
    bl_space_type  = 'VIEW_3D'     # Displays in 3D ... 
    bl_region_type = 'TOOL_PROPS'  # ... On the left  (undo/redo space on bottom half of the panel)
    # bl_region_type = 'UI'        # ... On the right
    #bl_space_type  = 'PROPERTIES' # Displays in Properties view, select the cube
    #bl_region_type = 'WINDOW'
    bl_context = "object"

    # dims = bpy.props.PointerProperty(name ="Dimensions", type=GEOMETRYProperties)

    @classmethod
    def poll(self, context) :
        if context.active_object and all([context.active_object == object for object in context.selected_objects]) and 'dims' in context.active_object.data.keys() :
            return True
        return False
    #     return context.active_object and all(['dims' in object.data.keys() for object in context.selected_objects])

    def draw(self,context) :
        layout = self.layout                # lo   - layout
        object = context.active_object      # ob   - object
        window = context.window_manager     # wm/p - window manager/properties
        dimens = context.active_object.data # op   - object parameters

        layout.label(text = "Testing")
        op = layout.operator("mesh.primitive_geometry_add")
        # The following lines were a bust 
        window.GEOMETRY.length = dimens['dims']['length']
        window.GEOMETRY.width  = dimens['dims']['width']
        window.GEOMETRY.height = dimens['dims']['height']
        col = layout.column(align = True)
        col.prop(window.GEOMETRY, 'length')
        col.prop(window.GEOMETRY, 'length')
        col.prop(window.GEOMETRY, 'length')

def addGEOMETRYMenu(self, context) :
    self.layout.operator(MESH_OT_primitive_GEOMETRY_add.bl_idname, text="Add GEOMETRY")

def register():
    bpy.utils.register_class(GEOMETRYProperties)
    bpy.types.WindowManager.GEOMETRY = bpy.props.PointerProperty(name = "GEOMETRY Properties", type = GEOMETRYProperties)
    # bpy.types.Object.dims = bpy.props.PointerProperty(name = "Dimensions", type = GEOMETRY)
    bpy.utils.register_class(GEOMETRYPanel)
    bpy.utils.register_class(MESH_OT_primitive_GEOMETRY_add)
    bpy.types.INFO_MT_mesh_add.append(addGEOMETRYMenu)

def unregister():
    bpy.utils.unregister_class(MESH_OT_primitive_GEOMETRY_add)
    bpy.utils.unregister_class(GEOMETRYPanel)
    del bpy.types.WindowManager.GEOMETRY
    # del bpy.types.Object.dims
    bpy.utils.unregister_class(GEOMETRYProperties)
    bpy.types.INFO_MT_mesh_add.remove(addGEOMETRYMenu)

if __name__ == "__main__" :
    register()

I don't show it, but @Zeffii does. A nice way of identifying ones objects uniquely is to assign a property to the mesh as in mesh['ID'] = ...

My strategy is to let the operator modify the properties and to store the final values in the mesh. When the mesh is edited again the values are read from the mesh and the operator properties are repopulated from the active mesh. The operator properties are stored in the window manager rather then the object and the operator is re-entrant with the caveat that one can not add a new mesh if a previously generated generated mesh is selected as the operator defaults to modifying the existing mesh over creating a new one. The other caveat is that one may only edit the mesh through the operators' panel. That is modifying the settings for the object within the separate panel doesn't actually have any effect. One has to click the button, which invokes the operator, to perform the modifications. (This is similar to how adding a mesh invokes the operator). I also tried displaying the Object properties in the TOOL_PROPS region in the VIEW_3D area but this is displayed somewhat haphazardly and needs the mouse to pass over the area before displaying or some other (undo/redo ?) event to fire before displaying.

Two things that still seem odd is how can one call another classes draw method. Calling an operators' draw method from a panel seems a no no beacuse the operator's properties are only active when the operator is. I haven't seen any questions regarding calling a panels draw function from an operator. The second thing is how a PropertyGroup is correctly drawn when accessed from the custom properties panel but not when coded into a draw command. It seems possibly to re-invoke an operator I'm just not sure how.

@Zeffii's answer differs from mine in that it creates the mesh and modifies it through the property update methods. I think the ideal answer I was looking for was to access the Operator when one reselects the object, that is selecting the first object upon creating two objects. The first caveat above points to why this might be tricky to implement. The F6 menu can call up the panel for the last executed operator (I believe by re-executing the operator) is there some way to do this from code ?

$\endgroup$
4
  • $\begingroup$ I shall do so in future. I was still finding my feet somewhat hence the lengthy answer. I've often found the slightly more chatty answers more helpful on SO though and didn't think I was pushing the rules too much. I had asked the one question previously, and have edited the answer to point there. I'll ask the other two questions or provide links later. $\endgroup$
    – Carel
    Jan 28, 2016 at 9:21
  • $\begingroup$ i'm just one guy spouting an opinion, it's only a suggestion to keep it concise. Usually If I have to scroll in a question it's a sign that the question isn't narrow enough or it's too verbose. The problem of parametric objects is real and interesting, that's why I contribute to the Sverchok development -- it might just be what you are looking for github.com/nortikin/sverchok. no need to write the mesh creation code, concentrate on the algorithm to make the geometry. $\endgroup$
    – zeffii
    Jan 28, 2016 at 10:06
  • $\begingroup$ I took no offence, I knew I was pushing the rules a bit when I answered :D I haven't had much chance to go through Sverchok properly yet. I'll make more of an effort to do so. Out of interest does it support that Opne Objeject Modelling Language (OOML) stuff that SCAD/OpenSCAD does ? $\endgroup$
    – Carel
    Jan 28, 2016 at 15:36
  • $\begingroup$ OOML, not currently -- but I just looked around and if there was enough interest it would be interesting to implement. the C++ for it exists github.com/avalero/OOML $\endgroup$
    – zeffii
    Jan 28, 2016 at 16:06

You must log in to answer this question.

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