4
$\begingroup$

How do I instantiate objects in Blender 3?

I am trying to programmatically draw on a mesh's texture. According to the docs, the function to call is bpy.ops.paint.image_paint(), which takes a bpy_prop_collection of OperatorStrokeElement. I tried various ways to instantiate the object, the errors were 'is not a type of PropertyGroup', 'an id was expected'.

Also, do I need to select area or context before drawing? Which coordinate system is used for the strokes' position, do I specify offset in pixels from top-left corder of the window?

EDIT After the first answer, here is the code that I am running in the console (after making sure that the texture (or, rather, material) can be painted on (manually):

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 {'area': area, 'region': region} 

strokes = [
{"name":"stroke", "mouse": (0,0), "mouse_event": (0,0),  "x_tilt": 0,   "y_tilt": 0,  "pen_flip" : False,  "is_start": True, "location": (0,0,0), "size": 10, "pressure": 1,"time": float(1)}
,{"name":"stroke", "mouse": (0,0), "mouse_event": (0,0),  "x_tilt": 0,   "y_tilt": 0,  "pen_flip" : False,  "is_start": False, "location": (0,0,1), "size": 10, "pressure": 1,"time": float(2)}
,{"name":"stroke", "mouse": (0,0), "mouse_event": (0,0),  "x_tilt": 0,   "y_tilt": 0,  "pen_flip" : False,  "is_start": False, "location": (0,1,0), "size": 10, "pressure": 1,"time": float(3)}
,{"name":"stroke", "mouse": (0,0), "mouse_event": (0,0),  "x_tilt": 0,   "y_tilt": 0,  "pen_flip" : False,  "is_start": False, "location": (0,1,1), "size": 10, "pressure": 1,"time": float(4)}
]

bpy.context.scene.tool_settings.image_paint.use_clone_layer = True
bpy.context.scene.tool_settings.image_paint.mode = "MATERIAL"

bpy.ops.paint.image_paint(context_override(), stroke=strokes)

It returns "{'Finished'}", and no effect takes place. Screenshot (the stroke you see is by hand):

enter image description here

EDIT Found it. After a number of random experiments, it is the 'mouse' -- the offset in pixels from lower-left corner of the window (region). 'Location' value seems to have no effect, same for 'mouse_event', 'is_start', 'time'. I could not get a continuous stroke (not that I spent much time on it). Also, the documentation says there are default values, but you still have to specify all of them when instantiating stroke objects.

Code:

def stroke_def(mouse, start, time):
    return {"name":"stroke","mouse":mouse,"mouse_event": (0,0),"x_tilt":0,"y_tilt":0,"pen_flip":False,"is_start":start,"location":(0,0,0),"size":100,"pressure":1,"time":time}


bpy.ops.paint.image_paint(context_override("VIEW_3D"), stroke=[stroke_def((400, 400), True, 1.0), stroke_def((500, 500), False, 2.0)])
$\endgroup$
2
  • $\begingroup$ Could you paste your code into your question? It would make it much easier to debug. $\endgroup$ Commented Apr 21, 2022 at 17:04
  • $\begingroup$ There is no code. In the console, I type in 'bpy.ops.paint.image_paint()' and getting various errors. I am asking for example code on how to call that function, or at least how to instantiate OperatorStrokeElement (or any other object) from bpy.types). $\endgroup$ Commented Apr 21, 2022 at 18:55

1 Answer 1

2
$\begingroup$

Since you mention in a comment that you're looking for examples, here is a partial example:

import bpy

# Setup the overrrides necessary to run the rest of the script.

def get_override(area_type, region_type):
    for area in bpy.context.screen.areas: 
        if area.type == area_type:             
            for region in area.regions:                 
                if region.type == region_type:                    
                    override = {'area': area, 'region': region} 
                    return override
    raise RuntimeError("Wasn't able to find", region_type," in area ", area_type,
                        "\n Make sure it's open while executing script.")


override = get_override( 'VIEW_3D', 'WINDOW' ) # In the image editor use 'IMAGE_EDITOR'

# This is missing the code to set up of an image to work with.
# You need to create a texture image and set it in the brush texture slot
# before you run the next bit.  You can do it by hand.

# Create an OperatorStrokeElement collection
coordinates = [(0,0,0), (0, 0, 1), (0, 1, 1)]

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

# Enable the scene
bpy.context.scene.tool_settings.image_paint.use_clone_layer = True
bpy.context.scene.tool_settings.image_paint.mode = 'IMAGE'

# Enter texture paint mode
bpy.ops.paint.texture_paint_toggle()

# Apply the selection
bpy.ops.paint.brush_select(override, image_tool='DRAW')
bpy.ops.paint.image_paint(override, mode='NORMAL', stroke=strokes)

# Exit texture paint mode
bpy.ops.paint.texture_paint_toggle()

To answer some of your specific questions:

  • One way to make a valid OperatorStrokeElement collection is shown here, along with a reference to an answer the code was taken from. This is done by creating a dictionary with the necessary fields. Blender will handle the conversion.

  • Yes, you need an area and region. You can use VIEW_3D or IMAGE_EDITOR depending on what you have set up.

  • The coordinate system depends on how you've set up the tool settings.

  • You can find more sample code in the Material Brush Add-On. It's a working add-on that supports texture painting and has all of the code, except the actual paint operations, that you would use.

  • If by 'instantiate objects' you mean add one of the primitive objects you find in the add menu, there are bpy.ops functions for adding them. For instance, you add Suzanne with bpy.ops.mesh.primitive_monkey_add(). If you need to know more, please ask a separate question.

3.2 Update

Context overrides are deprecated in Blender 3.2 and are scheduled to be removed in Blender 3.3 The replacement is temp_override. The manual has examples of how to use the new function.

$\endgroup$
2
  • $\begingroup$ So to summarize: 1. There is another, undocumented argument to the function (all functions?) -- context override, a dictionary that contains region and area and 2. Blender converts dictionaries into objects on the fly, no need to instantiate them as you would in other languages. I am no longer getting errors, it returns 'FINISHED', but nothing happens... $\endgroup$ Commented Apr 22, 2022 at 17:44
  • $\begingroup$ 1. It's documented here which is not easy to find. 2. not all dictionaries, no. This is the only instance of it I'm aware of. You will probably have to take a look at the references to sort out your specific issues; but if you do paste your current code into your question I'll look at it. $\endgroup$ Commented Apr 22, 2022 at 17:51

You must log in to answer this question.

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