How to concave a flat surface with blender?

Viewed 19

I want to make this thing in Blender so I can simulate the same thing on the computer later but as I am new to blender, I can't think of a way to make it and can't find a video that helps me. I created a flat surface with squares in it so I can just concave it and have the wanted object but I can't find a setting that helps me...

This is the object that I want to create

And this is me trying to find a way... I tried overusing "Inflate" in sculpting but as you can see, it wont make the wanted result and later, when I lighten it, the squares won't be really.. squares.

1 Answers

Since this is a stackoverflow question, I'm assuming you're interested in using Blender's Python API to recreate one of Henry Sagerman's stereographic projection geometries.

grid of thumbnails of Henry Sagerman's stereographic projection video: a point light is passed through a steorephically projected square grid whic is 3d printed in a white material. the sphere like 3d printed objects projects a square grid to the ground plane when the point light is at the centre of the printed object (as well as the right height)

Doing a quick search reveals https://github.com/macbuse/Stereographic-projection.

For reference, here are relevant scripts: grid.py

#makes a pair of meshes with lots of holes

import bpy, bmesh
import numpy as np 
from bpy_extras import object_utils

def apply_boolean(obj_A, obj_B, bool_type='INTERSECT'):
    
    print('+++',obj_A, obj_B)
    bpy.ops.object.select_all(action='DESELECT')
    obj_A.select= True
    
    bpy.context.scene.objects.active = obj_A
    bpy.ops.object.modifier_add(type='BOOLEAN')

    mod = obj_A.modifiers
    mod[0].name = obj_A.name + bool_type
    mod[0].object = obj_B
    mod[0].operation = bool_type

    bpy.ops.object.modifier_apply(apply_as='DATA', 
                                  modifier=mod[0].name)

def chessboard(npts=24,
              size = 2):
                  
    me = bpy.data.meshes.new("Chess")
    ob = bpy.data.objects.new("Chess", me)
    bpy.context.scene.objects.link(ob)
    ob.location = [0,0,0]
    
    bm = bmesh.new()   # create an empty BMesh
    bm.from_mesh(me)   # fill it in from a Mesh

    xs = np.linspace(-size,size,npts)
    ys = xs[:]

    tt = []
    for x in xs:        
        tt.append([])
        for y in ys:
            tt[-1].append(bm.verts.new((x,y,0)))
        
    for i,row in enumerate(tt[:-1]):
        for j,elt in enumerate(row[:-1]):
            #skip every fourth face
            if i*j % 2 == 1: continue
            bm.faces.new([tt[i][j],tt[i][j+1],tt[i+1][j+1],tt[i+1][j]])
                
    # Finish up, write the bmesh back to the mesh
    bm.to_mesh(me)
    bm.free()  # free and prevent further access
    #be polite: make active and return a reference
    bpy.context.scene.objects.active = ob
    return ob

def cube_slicer(cube_scale=.85,
                z_offset=1):
                    
    '''splits selected mesh into two parts 
    using boolean operations'''
    
    def clean_up(cut_off=.1):
        '''cleans up after boolean operations
        deleting any extraneous vertices out of 
        the xy-plane
        '''
        ob = bpy.context.object
        bpy.ops.object.mode_set(mode = 'EDIT')
        me = ob.data
        bm = bmesh.from_edit_mesh(me)

        verts = [v for v in bm.verts 
                if abs(v.co[2]) > cut_off]

        bmesh.ops.delete(bm, geom=verts, context=1)
        bmesh.update_edit_mesh(me)
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

    xx = bpy.context.active_object 
    xx.select= True
    bpy.ops.object.duplicate()
    yy = bpy.context.scene.objects.active
    yy.location = [0,0,z_offset]

    bpy.ops.object.select_all(action='DESELECT')

    bpy.ops.mesh.primitive_cube_add(location=[0,0,0])
    cc = bpy.context.scene.objects.active
    cc.scale = [cube_scale,cube_scale,1]

    bpy.ops.object.duplicate()
    dd = bpy.context.scene.objects.active
    dd.location = [0,0,z_offset]

    apply_boolean(xx, cc, bool_type='INTERSECT')
    clean_up()
    apply_boolean(yy, dd, bool_type='DIFFERENCE')
    clean_up()

    #get rid of the cubes
    bpy.ops.object.select_all(action='DESELECT')
    for x in [cc,dd]:
        x.select = True
    
    bpy.ops.object.delete() 
        

chessboard()

cube_slicer(cube_scale=.85)

and stereo_proj.py

import bpy, bmesh
from mathutils import Vector

###################got this from mesh_looptools.py#################

# input: bmesh, output: dict with the edge-key as key and face-index as value
def dict_edge_faces(bm):
    edge_faces = dict([[edgekey(edge), []] for edge in bm.edges 
                                        if not edge.hide])
    for face in bm.faces:
        if face.hide:
            continue
        for key in face_edgekeys(face):
            edge_faces[key].append(face.index)

    return(edge_faces)


# return the edgekey ([v1.index, v2.index]) of a bmesh edge
def edgekey(edge):
    return(tuple(sorted([edge.verts[0].index, edge.verts[1].index])))

# returns the edgekeys of a bmesh face
def face_edgekeys(face):
    return([tuple(sorted([edge.verts[0].index, edge.verts[1].index])) for \
        edge in face.edges])


###################################################################


def stereo_proj(scale_factor=.9):

    # Get the active mesh
    me = bpy.context.object.data
    # Get a BMesh representation
    bm = bmesh.new()   
    bm.from_mesh(me)   

    #this gets border edges which should be extruded to make  a solid
    borders = [ x for x,y in dict_edge_faces(bm).items()
                 if len(y) < 2 ]
    
    borders.sort()             
              
    for v in bm.verts:
        x,y,z = v.co[:]
        r2 = x*x + y*y
        nv = 1./(r2 + 1) * Vector((2*x, 2*y, (r2 - 1)))
        v.co = nv
        
    offset = len(bm.verts)
    
    #take a copy as we are adding to bm.verts
    #otherwise hangs with an infinite loop
    copy_verts =  bm.verts[:]
    for pt in copy_verts:   
        bm.verts.new( scale_factor*pt.co )
    
    #hash this to make clearer
    vvs = bm.verts
    vvs.ensure_lookup_table()

    #new layer of faces concentric to original faces
    new_faces  = [ ( vvs[vv.index + offset] for vv in ff.verts[:] )
                                           for ff in bm.faces]
 
    #add faces that go between the two layers
    extruded_edge_faces =  [ (vvs[a], vvs[b], vvs[ b + offset ], vvs[a + offset])
                                                                    for a,b in borders ]
                                                                    
    #add the new faces to the mesh
    new_faces.extend(extruded_edge_faces)
    for ff in new_faces:
        bm.faces.new(ff)
           
    # Finish up, write the bmesh back to the mesh
    bm.to_mesh(me)
    bm.free()  


stereo_proj()

(Bare in mind the source code is 6 years old, so if you're using a newer version of Blender where the API changed, you might need to few tweaks (e.g. swapping bpy.context.scene.objects.link(ob) for bpy.context.collection.objects.link(ob))

You might also find the Blender Stackexhange helpful (for example this post) , as well as this article.

Blender Stackexchange screenshot of the spherical object with simulated light casting the square grid on the ground plane as well as arrounds illustrating ratios between the light source, intersection with the spherical object, with the ground plane and the triangle formed by them

Related