pythonblender3d-modelling

Cant get this bianchi-Pinkall script to work in blender


This is not my work but i am trying to get it to work. Its a script i found online to generate a bianchi Pinkall surface, apparently it worked at one point but now does not. If something can make it work that would be incredible as i am trying to generate the object in 3d

import bpy
import math
from mathutils import Vector
import numpy

subDivIterations = 5

valminu=0
valmaxu=math.pi*2
valminv=0
valmaxv=math.pi*2
resolutionu=150
resolutionv=150
pasu = (valmaxu - valminu) / resolutionu
pasv = (valmaxv - valminv) / resolutionv

a=0.93 #rayon de la corale à tester sous : analyticphysics.com/Higher Dimensions/Interactive Bianchi-Pinkall Flat Tori.htm
n=3 #Nbre de sous rotation ou de pétaile
b=0.36 #rayon internet de la fleur
c=0
d=0
k=15
varu= valminu
varv= valminv

goldenRatio = (1 + math.sqrt(5)) / 2

def subdivide(triangles):
result = []
for color, A, B, C in triangles:
if color == 0:
# Subdivide red triangle
P = A + (B - A) / goldenRatio
result += [(0, C, P, B), (1, P, C, A)]
else:
# Subdivide blue triangle
Q = B + (A - B) / goldenRatio
R = B + (C - B) / goldenRatio
result += [(1, R, C, A), (1, Q, R, B), (0, R, Q, A)]
return result

# Create des tirangles du coquillage
# Create wheel of red triangles around the origin
#x = f(u,v) = u + uv2 – (1/3)u3, y = g(u,v) = v + u2v – (1/3)v3, z = h(u,v) = u2 – v2.
def createSurfaceBianchiPinkall():
triangles = []

for varu in numpy.arange(valminu, valmaxu, pasu):
for varv in numpy.arange(valminv, valmaxv, pasv):

gamma = a+b*math.sin(2*n*varv)
x = math.cos(varu+varv)*math.cos(gamma)
y = math.sin(varu+varv)*math.cos(gamma)
z = math.cos(varu-varv)*math.sin(gamma)
w = math.sin(varu-varv)*math.sin(gamma)
r = math.acos(w)/math.pi/math.sqrt(1-w*w);
A = Vector((x*r,y*r,z*r))

varva = varv + pasv
gamma = a+b*math.sin(2*n*varva)
x = math.cos(varu+varva)*math.cos(gamma)
y = math.sin(varu+varva)*math.cos(gamma)
z = math.cos(varu-varva)*math.sin(gamma)
w = math.sin(varu-varva)*math.sin(gamma)
r = math.acos(w)/math.pi/math.sqrt(1-w*w);
B = Vector((x*r,y*r,z*r))

varua = varu + pasu
gamma = a+b*math.sin(2*n*varva)
x = math.cos(varua+varva)*math.cos(gamma)
y = math.sin(varua+varva)*math.cos(gamma)
z = math.cos(varua-varva)*math.sin(gamma)
w = math.sin(varua-varva)*math.sin(gamma)
r = math.acos(w)/math.pi/math.sqrt(1-w*w);
C = Vector((x*r,y*r,z*r))
triangles.append((0, A, B, C))

gamma = a+b*math.sin(2*n*varv)
x = math.cos(varua+varv)*math.cos(gamma)
y = math.sin(varua+varv)*math.cos(gamma)
z = math.cos(varua-varv)*math.sin(gamma)
w = math.sin(varua-varv)*math.sin(gamma)
r = math.acos(w)/math.pi/math.sqrt(1-w*w);
D = Vector((x*r,y*r,z*r))
triangles.append((0, A, C, D))
return triangles

# Generate map of tiling
listTriangles = createSurfaceBianchiPinkall()
#for x in range(subDivIterations):
# listTriangles = subdivide(listTriangles)

# Construction du coquilage
# Construct the lists necessary for generation of geometry
listVertices = []
listFaces = []
for triangle in listTriangles:
# write the vertex coords to the list, and remember the vertex indices
# In Blender, the mesh data stores each vertex as a tuple of 3 floats.
newVertex1 = (triangle[1][0],triangle[1][1],triangle[1][2])
newVertex2 = (triangle[2][0],triangle[2][1],triangle[2][2])
newVertex3 = (triangle[3][0],triangle[3][1],triangle[3][2])

listVertices.append(newVertex1)
newVertex1_i = len(listVertices) - 1
listVertices.append(newVertex2)
newVertex2_i = len(listVertices) - 1
listVertices.append(newVertex3)
newVertex3_i = len(listVertices) - 1
# write to the list of edges
# Define the faces by index numbers. Each faces is defined by 4 consecutive integers.
# For triangles you need to repeat the first vertex also in the fourth position.
newFace = (newVertex1_i,newVertex2_i,newVertex3_i)
listFaces.append(newFace)


mesh = bpy.data.meshes.new("SurfaceBianchiPinkallMesh") # create a new mesh

ob = bpy.data.objects.new("SurfaceBianchiPinkall", mesh) # create an object with that mesh
ob.location = Vector((0,0,0)) #by.context.scene.cursor_location # position object at 3d-cursor
bpy.context.scene.objects.link(ob) # Link object to scene

# Fill the mesh with verts, edges, faces
mesh.from_pydata(listVertices,[],listFaces) # edges or faces should be [], or you ask for problems
mesh.validate(True)
#mesh.update(calc_edges=True) # Update mesh with new data

I tried to use VScode to look at the python script but i dont know python so… this is all for a present for someone special.


Solution

  • There could be a number of things causing issues, for one the code you attached doesn't follow proper python indenting. If we fix that we may proceed with the real modifications which are simply the result of Blender 2.8+ refactoring.

    From the Blender 2.8 change log for collections we learn

    Layers and groups have been replaced by collections.

    For us this means your line 121, bpy.context.scene.objects.link(ob), now throws AttributeError: bpy_prop_collection: attribute "link" not found as the link property has been placed under bpy.context.scene.objects as per the documentation. Concreatly this means you need to replace line 121 with

    bpy.context.collection.objects.link(ob)
    

    The next error we get TypeError: Mesh.validate(): required parameter "verbose" to be a keyword argument!, this originates from line 124, mesh.validate(True), the issue is quite simple, to avoid ambiguity with the clean_customdata parameter we simply need to specify we are providing the verbosity hence we replace line 124 with

    mesh.validate(verbose=True)
    

    I was able to valid this working on Blender 3.4 and 4.0.

    I've attached the full modified code bellow (with my recreation of the indentation) as well as a screenshot of the successful output. Bianchi Pinkall Surface in Blender 4.0

    import bpy
    import math
    from mathutils import Vector
    import numpy
    
    subDivIterations = 5
    
    valminu=0
    valmaxu=math.pi*2
    valminv=0
    valmaxv=math.pi*2
    resolutionu=150
    resolutionv=150
    pasu = (valmaxu - valminu) / resolutionu
    pasv = (valmaxv - valminv) / resolutionv
    
    a=0.93 #rayon de la corale à tester sous : analyticphysics.com/Higher Dimensions/Interactive Bianchi-Pinkall Flat Tori.htm
    n=3 #Nbre de sous rotation ou de pétaile
    b=0.36 #rayon internet de la fleur
    c=0
    d=0
    k=15
    varu= valminu
    varv= valminv
    
    goldenRatio = (1 + math.sqrt(5)) / 2
    
    def subdivide(triangles):
        result = []
        for color, A, B, C in triangles:
            if color == 0:
                # Subdivide red triangle
                P = A + (B - A) / goldenRatio
                result += [(0, C, P, B), (1, P, C, A)]
            else:
                # Subdivide blue triangle
                Q = B + (A - B) / goldenRatio
                R = B + (C - B) / goldenRatio
                result += [(1, R, C, A), (1, Q, R, B), (0, R, Q, A)]
        return result
    
    # Create des tirangles du coquillage
    # Create wheel of red triangles around the origin
    #x = f(u,v) = u + uv2 – (1/3)u3, y = g(u,v) = v + u2v – (1/3)v3, z = h(u,v) = u2 – v2.
    def createSurfaceBianchiPinkall():
        triangles = []
    
        for varu in numpy.arange(valminu, valmaxu, pasu):
            for varv in numpy.arange(valminv, valmaxv, pasv):
    
                gamma = a+b*math.sin(2*n*varv)
                x = math.cos(varu+varv)*math.cos(gamma)
                y = math.sin(varu+varv)*math.cos(gamma)
                z = math.cos(varu-varv)*math.sin(gamma)
                w = math.sin(varu-varv)*math.sin(gamma)
                r = math.acos(w)/math.pi/math.sqrt(1-w*w);
                A = Vector((x*r,y*r,z*r))
    
                varva = varv + pasv
                gamma = a+b*math.sin(2*n*varva)
                x = math.cos(varu+varva)*math.cos(gamma)
                y = math.sin(varu+varva)*math.cos(gamma)
                z = math.cos(varu-varva)*math.sin(gamma)
                w = math.sin(varu-varva)*math.sin(gamma)
                r = math.acos(w)/math.pi/math.sqrt(1-w*w);
                B = Vector((x*r,y*r,z*r))
    
                varua = varu + pasu
                gamma = a+b*math.sin(2*n*varva)
                x = math.cos(varua+varva)*math.cos(gamma)
                y = math.sin(varua+varva)*math.cos(gamma)
                z = math.cos(varua-varva)*math.sin(gamma)
                w = math.sin(varua-varva)*math.sin(gamma)
                r = math.acos(w)/math.pi/math.sqrt(1-w*w);
                C = Vector((x*r,y*r,z*r))
                triangles.append((0, A, B, C))
    
                gamma = a+b*math.sin(2*n*varv)
                x = math.cos(varua+varv)*math.cos(gamma)
                y = math.sin(varua+varv)*math.cos(gamma)
                z = math.cos(varua-varv)*math.sin(gamma)
                w = math.sin(varua-varv)*math.sin(gamma)
                r = math.acos(w)/math.pi/math.sqrt(1-w*w);
                D = Vector((x*r,y*r,z*r))
                triangles.append((0, A, C, D))
        return triangles
    
    # Generate map of tiling
    listTriangles = createSurfaceBianchiPinkall()
    #for x in range(subDivIterations):
    # listTriangles = subdivide(listTriangles)
    
    # Construction du coquilage
    # Construct the lists necessary for generation of geometry
    listVertices = []
    listFaces = []
    for triangle in listTriangles:
        # write the vertex coords to the list, and remember the vertex indices
        # In Blender, the mesh data stores each vertex as a tuple of 3 floats.
        newVertex1 = (triangle[1][0],triangle[1][1],triangle[1][2])
        newVertex2 = (triangle[2][0],triangle[2][1],triangle[2][2])
        newVertex3 = (triangle[3][0],triangle[3][1],triangle[3][2])
    
        listVertices.append(newVertex1)
        newVertex1_i = len(listVertices) - 1
        listVertices.append(newVertex2)
        newVertex2_i = len(listVertices) - 1
        listVertices.append(newVertex3)
        newVertex3_i = len(listVertices) - 1
        # write to the list of edges
        # Define the faces by index numbers. Each faces is defined by 4 consecutive integers.
        # For triangles you need to repeat the first vertex also in the fourth position.
        newFace = (newVertex1_i,newVertex2_i,newVertex3_i)
        listFaces.append(newFace)
    
    
    mesh = bpy.data.meshes.new("SurfaceBianchiPinkallMesh") # create a new mesh
    
    ob = bpy.data.objects.new("SurfaceBianchiPinkall", mesh) # create an object with that mesh
    ob.location = Vector((0,0,0)) #by.context.scene.cursor_location # position object at 3d-cursor
    bpy.context.collection.objects.link(ob) # Link object to scene
    # Fill the mesh with verts, edges, faces
    mesh.from_pydata(listVertices,[],listFaces) # edges or faces should be [], or you ask for problems
    mesh.validate(verbose=True)
    #mesh.update(calc_edges=True) # Update mesh with new data