File: sculpt_brush_render_tests.py

package info (click to toggle)
blender 5.0.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 329,128 kB
  • sloc: cpp: 2,489,823; python: 349,859; ansic: 261,364; xml: 2,103; sh: 999; javascript: 317; makefile: 193
file content (158 lines) | stat: -rw-r--r-- 4,218 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2025 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later

import argparse
import os
import sys


def set_view3d_context_override(context_override):
    """
    Set context override to become the first viewport in the active workspace

    The ``context_override`` is expected to be a copy of an actual current context
    obtained by `context.copy()`
    """

    for area in context_override["screen"].areas:
        if area.type != 'VIEW_3D':
            continue
        for space in area.spaces:
            if space.type != 'VIEW_3D':
                continue
            for region in area.regions:
                if region.type != 'WINDOW':
                    continue
                context_override["area"] = area
                context_override["region"] = region


def generate_stroke(context):
    """
    Generate stroke for the bpy.ops.sculpt.brush_stroke operator

    The generated stroke covers the full plane diagonal.
    """
    from mathutils import Vector

    template = {
        "name": "stroke",
        "mouse": (0.0, 0.0),
        "mouse_event": (0, 0),
        "location": (0.0, 0.0, 0.0),
        "is_start": True,
        "pressure": 1.0,
        "time": 1.0,
        "size": 1.0,
        "x_tilt": 0,
        "y_tilt": 0
    }

    num_steps = 250
    start = Vector((context['area'].width, context['area'].height))
    end = Vector((0, 0))
    delta = (end - start) / (num_steps - 1)

    stroke = []
    for i in range(num_steps):
        step = template.copy()
        step["mouse"] = start + delta * i
        step["mouse_event"] = start + delta * i
        stroke.append(step)

    return stroke


def setup():
    """
    Prepare the scene for rendering - generates objects then performs a stroke
    """

    import bpy
    context = bpy.context

    # Create an undo stack explicitly. This isn't created by default in background mode.
    bpy.ops.ed.undo_push()

    # Forcibly flip the object out of and back into sculpt mode to avoid poll errors due to non-initialized
    # tool runtime data.
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.mode_set(mode='SCULPT')

    context_override = context.copy()
    set_view3d_context_override(context_override)

    with context.temp_override(**context_override):
        bpy.ops.sculpt.brush_stroke(stroke=generate_stroke(context_override), override_location=True)

    # Multires workaround - we need to leave sculpt mode currently to flush MDISP data so that the
    # render actually works.
    bpy.ops.object.mode_set(mode='OBJECT')


try:
    import bpy
    inside_blender = True
except ImportError:
    inside_blender = False


if inside_blender:
    try:
        setup()
    except Exception as e:
        print(e)
        sys.exit(1)


def get_arguments(filepath, output_filepath):
    dirname = os.path.dirname(filepath)

    args = [
        "--background",
        "--factory-startup",
        "--enable-autoexec",
        "--debug-memory",
        "--debug-exit-on-error",
        filepath,
        "-E", "BLENDER_WORKBENCH",
        "-P", os.path.realpath(__file__),
        "-o", output_filepath,
        "-f", "1",
        "-F", "PNG"]

    return args


def create_argparse():
    parser = argparse.ArgumentParser(
        description="Run test script for each blend file in TESTDIR, comparing the render result with known output."
    )
    parser.add_argument("--blender", required=True)
    parser.add_argument("--testdir", required=True)
    parser.add_argument("--outdir", required=True)
    parser.add_argument("--oiiotool", required=True)
    parser.add_argument("--batch", default=False, action="store_true")
    return parser


def main():
    parser = create_argparse()
    args = parser.parse_args()

    from modules import render_report
    report = render_report.Report("Sculpt", args.outdir, args.oiiotool)
    report.set_pixelated(True)
    report.set_fail_threshold(2.0 / 255.0)
    report.set_fail_percent(1.5)
    report.set_reference_dir("reference")

    ok = report.run(args.testdir, args.blender, get_arguments, batch=args.batch)

    sys.exit(not ok)


if not inside_blender and __name__ == "__main__":
    main()