Search code examples
3dblender

How to concave a flat surface with blender?


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.


Solution

  • 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