159 lines
4.1 KiB
Python
159 lines
4.1 KiB
Python
|
#!/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()
|