3
$\begingroup$

I'm trying to deform a mesh using python. My application is to randomly deform thousands of shapes, so I need to use a python script. With the GUI, I really like the ability to hold left-click while moving the mouse across a shape, which results in a smooth and continuous deformation. I want to emulate this with python.

The best code example of how to use the sculpt function with python comes from this code example.

There, the bpy.ops.sculpt.brush_stroke() command is given a list of strokes to apply, with the following form:

strokes = []
for i, coordinate in enumerate(coordinates):
    stroke = {
        "name": "stroke",
        "mouse": (0,0),
        "pen_flip" : pen_flip,
        "is_start": True if i==0 else False,
        "location": coordinate,
        "size": 50,
        "pressure": 1,
        "time": float(i)
    }
    strokes.append(stroke)

However, despite the 'is_start' key being set to False for all but the first stroke, it appears to me that blender applies these strokes as if they were all independent left-clicks; it doesn't emulate the smoothness of holding down the left-mouse-button.

Here's my full script:

import bpy
import random
import numpy as np
import math

# Clear all objects
try:
    bpy.ops.object.mode_set(mode='OBJECT')
except:
    pass
for obj in bpy.context.scene.objects:
    obj.select_set(True)
bpy.ops.object.delete()

# Create basic ico_sphere with 7 subdivisions
bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=7)
bpy.ops.object.mode_set(mode='SCULPT')
bpy.data.objects[0].select_set(True)
bpy.context.scene.tool_settings.sculpt.use_symmetry_x = False

# Must override context while stroke is applied
# https://github.com/christian-vorhemus/procedural-3d-image-generation/blob/master/blenderBackgroundTask.py#L54
def context_override():
    for window in bpy.context.window_manager.windows:
        screen = window.screen
        for area in screen.areas:
            if area.type == 'VIEW_3D':
                for region in area.regions:
                    if region.type == 'WINDOW':
                        return {'window': window, 'screen': screen, 'area': area, 'region': region, 'scene': bpy.context.scene} 

# Define brush stroke parameters
num_steps = 5
steps = np.linspace(0, np.pi/2, num_steps, endpoint=False)
points = []
for i, s in enumerate(steps):
    px = np.sin(s)
    py = np.cos(s)
    pz = 0 # np.sin(s)*np.cos(s)
    points.append([px, py, pz])
    
strokes = []
for i, p in enumerate(points):
    stroke = {    
    "name": "stroke",
    "mouse": (0, 0),
    "pen_flip": False,
    "is_start": True, # if i==0 else False,
    "location": p,
    "size": 1.0,
    "pressure": 1.0, # 
    "time": float(i)}
    strokes.append(stroke)

# Set brush settings
bpy.data.scenes["Scene"].tool_settings.unified_paint_settings.use_locked_size = "SCENE"
bpy.data.scenes["Scene"].tool_settings.unified_paint_settings.unprojected_radius = 0.50
bpy.ops.paint.brush_select(sculpt_tool='BLOB', toggle=False)
bpy.data.brushes["Blob"].strength = 1
bpy.data.brushes["Blob"].curve_preset = "SMOOTH"
bpy.data.brushes["Blob"].auto_smooth_factor = 1.0

# Apply stroke
bpy.ops.sculpt.brush_stroke(context_override(), stroke=strokes)

Running that script with num_steps=5, I get this result, which is choppy. enter image description here

If I run the script with num_steps=50, the result appears smoother, but there is some unevenness; if you look closely there is a bump on the right-most portion of the deformation. enter image description here

Finally, if I run the script with num_steps=500, the result is also smooth, but it's clear that the effect is accumulating (the entire deformation is larger in intensity). This would not happen if I used the GUI and just held down the left-mouse-button. For this reason, just increasing the number of steps between two points in order to achieve smoothness is not ideal because the spacing of the steps causes the deformation to have a varying intensity. enter image description here

In short, how can I smoothly sculpt along the surface between two points on a mesh using a python script? bpy.ops.sculpt.brush_stroke() appears to apply strokes independently.

$\endgroup$
1
$\begingroup$

Might the result be different if your last stroke sample has pressure 0 ?
(or something low not 1)

Cause using the mouse or pen would act like that i think.
pen goes up pressure is zero drawing ends.

A second option would be to remesh to a lower resolution, do the sculpting in high detail then finish by, remesh to lower, (against corner artifacts). As for sculpting i think the brush takes averages of mouse locations, while in code one created fixed steps.

Those are my thoughts on it, i've not been into sculpting with python code, i just imagine they created the best experience for mouse users and have added several tricks to optimize the result which one might when coding directly to xyz positions.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.