5
$\begingroup$

I am trying to create a bowl in Blender by using the Python scipt. My method is as follows:

  1. Clear cube in new file
  2. Create UV sphere via Add --> Mesh --> UV Sphere
  3. Tab (enter edit mode)
  4. a' (unselect all points)
  5. Highlight all vertices below the XY plane (Z < 0), and delete them.

    Is there an efficient way to carry out step 5 via python scripting? Doing so in Blender and watching the Python script window doesn't help, since the selection is manual.

$\endgroup$
2
  • $\begingroup$ you may loop through vertices and select them based on thier Z component; or use the view3d.select_border() but you'll need to have the correct view centred $\endgroup$
    – Chebhou
    Sep 2, 2015 at 23:51
  • 1
    $\begingroup$ Hmm ... if you're deleting points below z=0 isn't that a cap, not a bowl? :) $\endgroup$ May 4, 2020 at 18:18

4 Answers 4

9
$\begingroup$

This small script loops through the vertices in the active object and deletes all the vertices that are below 0 on the local Z axis. The local axis starts at the object origin.

Just paste this in to the text editor in blender and with your UV sphere selected press the Run button.

import bpy

threshold = 0

# deselect all vertices
bpy.ops.object.mode_set(mode='OBJECT')  # can change selection only in Object mode
me = bpy.context.active_object.data
for face in me.polygons:  # you also have to deselect faces and edges
    face.select = False
for edge in me.edges:
    edge.select = False
for vert in me.vertices:
    vert.select = vert.co.z < threshold  # select vertices that are below the threshold
    
# enter edit mode and delete vertices
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.delete(type='VERT')
bpy.ops.object.mode_set(mode='OBJECT')

EDIT (2021-09-07): I have found that the script won't work if you apply it to "untouched" objects like an imported mesh, where all vertices are preselected. Consequently, the script would delete all vertices. Therefore, you need to deselect all vertices before selecting the filtered set.

$\endgroup$
0
5
$\begingroup$

Bmesh module solution for both, object and edit mode. Turn global_z_plane on for global Z < 0:

import bpy
import bmesh

ob = bpy.context.object
assert ob.type == "MESH"
mat = ob.matrix_world
me = ob.data

global_z_plane = False

if me.is_editmode:
    bm = bmesh.from_edit_mesh(me)
else:
    bm = bmesh.new()
    bm.from_mesh(me)

for v in bm.verts:
    co = mat * v.co if global_z_plane else v.co
    if co.z < 0:
        bm.verts.remove(v)

if bm.is_wrapped:
    bmesh.update_edit_mesh(me)
else:
    bm.to_mesh(me)
    me.update()
$\endgroup$
3
$\begingroup$

[Using Blender 2.82a]

Here is an alternative view on David's excellent answer which can be done entirely on the command line. I found that when using his answer, my object was selected by default, so all vertices were selected (and thus the for loop was meaningless). I was unable to replicate his result by inverting the if condition to set vertices to be False. The reason? De-selecting an already selected vertex does not work.

Suppose I have the following script in the command line, called test.py:

import bpy

def clear_scene():
    """Clear existing objects in scene."""
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete()

def _debug(vert):
    n_selected = len([v for v in vert if v.select])
    n_deselect = len([v for v in vert if not v.select])
    print('\nlen vertices = {}, {} selected, {} not selected'.format(
            len(vert), n_selected, n_deselect))

def make_cap(radius=1.0, x=0.0, y=0.0, z=0.0, z_thresh=0.0):
    bpy.ops.mesh.primitive_uv_sphere_add(radius=radius, location=(x, y, z))
    vert = bpy.context.object.data.vertices
    _debug(vert)
    for v in vert:
        if v.co[2] >= z_thresh:
            v.select = False
    _debug(vert)
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.delete(type='VERT')
    bpy.ops.object.mode_set(mode='OBJECT')

if __name__ == '__main__':
    clear_scene()
    make_cap()

Running blender -P test.py on the command line will produce this output for the prints:

len vertices = 482, 482 selected, 0 not selected
len vertices = 482, 225 selected, 257 not selected

Notice how the vertices of the sphere are already selected by default. However, even though de-selecting (via v.select=False) will work in the sense that the number of selected vertices is as expected (225 out of 482), the resulting Blender scene will not show anything. The sphere is still there, it's just not visible.

The only way I have been able to successfully get this working is by following this answer to go to edit mode and then de-select everything. Here's the Minimal Working Example script:

import bpy

def clear_scene():
    """Clear existing objects in scene."""
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete()

def _debug(vert):
    n_selected = len([v for v in vert if v.select])
    n_deselect = len([v for v in vert if not v.select])
    print('\nlen vertices = {}, {} selected, {} not selected'.format(
            len(vert), n_selected, n_deselect))

def make_cap(radius=1.0, x=0.0, y=0.0, z=0.0, z_thresh=0.0):
    bpy.ops.mesh.primitive_uv_sphere_add(radius=radius, location=(x, y, z))
    bpy.ops.object.mode_set(mode="EDIT") #Activating Edit mode
    bpy.ops.mesh.select_all(action='DESELECT') #Deselecting all
    bpy.ops.object.mode_set(mode="OBJECT") #Going back to Object mode
    vert = bpy.context.object.data.vertices
    _debug(vert)
    for v in vert:
        if v.co[2] < z_thresh:
            v.select = True
    _debug(vert)
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.delete(type='VERT')
    bpy.ops.object.mode_set(mode='OBJECT')

if __name__ == '__main__':
    clear_scene()
    make_cap()

Running blender -P test.py on the command line will produce this output for the prints:

len vertices = 482, 0 selected, 482 not selected
len vertices = 482, 225 selected, 257 not selected

But more importantly, here's what the scene looks like;

enter image description here

As desired!

$\endgroup$
3
$\begingroup$

Bisect Operator.

By deleting verts below z=0 For a mesh where any edges cross the z=0 plane one will be removed one will not. Slight tolerance inaccuracies may lead to unwanted removal. The default cube would be reduced to top plane.

enter image description here

Another option here may be to bisect the object using a global plane and delete the inner. (The other side pointing away from normal). This will cut a new edge across a face that bridges plane.

Have used method outlined in this answer in reverse to get a local normal given a global normal.

import bpy
import bmesh
from mathutils import Vector

context = bpy.context
ob = context.object
mw = ob.matrix_world
N = mw.inverted_safe().transposed().to_3x3()

me = ob.data


# global Z=0 or XY plane
plane_no = Vector((0, 0, 1))
plane_co = Vector((0, 0, 0))

bm = bmesh.new()
bm.from_mesh(me)

bmesh.ops.bisect_plane(
        bm,
        geom=bm.verts[:] + bm.edges[:] + bm.faces[:],
        plane_no = N.inverted() @ plane_no,
        plane_co = mw.inverted() @ plane_co, 
        clear_inner=True,
        )
# clean up if required
bmesh.ops.remove_doubles(
        bm,
        verts=bm.verts,
        dist=1e-4,
        )
bm.to_mesh(me)
me.update()
$\endgroup$

You must log in to answer this question.

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