5

I want to be able to take a function f(x) and first make a plot of the function on given interval and then build a solid of revolution of that mesh by rotating it around X-axis of the plot.

I've made a script that generates the graph. My question is how to spin the mesh (360° angle).

I tried to use function bpy.ops.mesh.spin:

bpy.ops.mesh.spin(steps=36, angle=2*pi, axis=(1, 0, 0))

but I still get RuntimeError

Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
  File "C:\Program Files\Blender Foundation\Blender\2.72\scripts\modules\bpy\ops.py", line 188, in __call__
    ret = op_call(self.idname_py(), None, kw)
RuntimeError: Operator bpy.ops.mesh.spin.poll() failed, context is incorrect

My code for generating graph mesh of given function:

# User function (return None if undefined)
def user_function(x):
    "Math function for given X, returns Y"
    if x == 0:
        return None
    return 1/x

# Resolution - number of samples
res = 100

# MIN and MAX values for the graph to plot
minX = 1
maxX = 10
minY = 0
maxY = 1

# Real size of a rectangle of the graph
sizeX = 2
sizeY = 2

# Recalculate Y-value for undefined points
recalculate_undefined = True

# Whether to delete old object
delete_old = True

# ===== Program =====
import bpy

step = (maxX - minX) / (res - 1)

coefX = sizeX / (maxX - minX)
coefY = sizeY / (maxY - minY)

RminX = - sizeX / 2
RminY = - coefY * minY

Z = 0

vertices = []
undefined = []
# Calculate values
for ix in range(res):
    x = minX + step * ix

    # Calculate and store Y-value
    y = user_function(x)
    if y == None:
        undefined.append(ix)
        y = 0
    y = min(maxY, y)
    y = max(minY, y)

    cX = step * ix * coefX + RminX
    cY = y * coefY + RminY
    cZ = Z
    vertices.append((cX, cY, cZ))

# Calculate undefined points
if recalculate_undefined == True:
    for ix in undefined:
        avg = []
        if ix > 0 and (ix - 1) not in undefined:
            avg.append(ix - 1)
        if ix < (res - 1) and (ix + 1) not in undefined:
            avg.append(ix + 1)

        if len(avg) >= 1:
            y = 0
            for a in avg:
                y += vertices[a][1]
            y /= len(avg)
            vertices[ix] = (vertices[ix][0], y, Z)

# Delete old object
if delete_old == True:
    if "Graph" in bpy.data.objects:
        bpy.ops.object.select_all(action = 'DESELECT')
        bpy.data.objects["Graph"].select = True
        bpy.ops.object.delete()
    for item in bpy.data.meshes:
        if item.users == 0:
            bpy.data.meshes.remove(item)

# Create new mesh
mesh = bpy.data.meshes.new("graph")

# Build edges from vertices
edges = [(ix, ix + 1) for ix in range(res - 1)]
faces = []

# Complete mesh and build object
mesh.from_pydata(vertices, edges, faces)
object = bpy.data.objects.new("Graph", mesh)
bpy.context.scene.objects.link(object)
object.location = bpy.context.scene.cursor_location

Edit1:

This post is not a duplicate of poll() failed, context incorrect? - Example: bpy.ops.view3d.background_image_add().

I'm looking for a solution without only avoiding the error by changing contexts.

jjurm
  • 153
  • 1
  • 5

2 Answers2

4

You could use the spin provided by bmesh operators. This doesn't require any particular context. Here's an example:

From sverchok source code:

import bpy
import bmesh
from bmesh.ops import spin
import math


def lathe_geometry(bm, cent, axis, dvec, angle, steps, remove_doubles=True, dist=0.0001):
    geom = bm.verts[:] + bm.edges[:]

    # super verbose explanation.
    spin(
        bm, 
        geom=geom,         # geometry to use for the spin
        cent=cent,         # center point of the spin world
        axis=axis,         # axis, a (x, y, z) spin axis
        dvec=dvec,         # offset for the center point
        angle=angle,       # how much of the unit circle to rotate around
        steps=steps,       # spin subdivision level 
        use_duplicate=0)   # include existing geometry in returned content

    if remove_doubles:
        bmesh.ops.remove_doubles(bm, verts=bm.verts[:], dist=dist)

obj = bpy.data.objects['Graph']
bm = bmesh.new()
bm.from_mesh(obj.data)

axis = (1,0,0)
dvec = (0,0,0)
angle = 2*math.pi
steps = 20
cent = obj.location

lathe_geometry(bm, cent, axis, dvec, angle, steps, remove_doubles=True, dist=0.0001)

bm.to_mesh(obj.data)
# obj.data.update()   # if you want update to show immediately
bm.free()

Things to be aware of:

  • A small update in 3d view is required to see the result. The mesh will get updated whenever Blender call updates, so you don't strictly need to force the update yourself, but you can if you want to by called obj.data.update()
  • Possible downside of the bmesh.spin is that the order/topology of faces is not guaranteed or predictable.

enter image description here

You might prefer to use a further abstraction, which assumes the center of the rotation is obj.location: https://gist.github.com/zeffii/d4efc7c346d6aa90ae18

zeffii
  • 39,634
  • 9
  • 103
  • 186
4

You could also use the 'screw' modifier, like this :)

mesh in wireframe showing the modifier settings mesh in solid view showing the modifier settings

David
  • 49,291
  • 38
  • 159
  • 317
Faceb Faceb
  • 3,885
  • 3
  • 29
  • 51