PsychoBlend: use stdin/out to transfer scene data by default.

This eliminates writing temp files to disk for any part of the
Blender/Psychopath integration.

The option to export to a file still exists, however, by
specifying an export output path.
This commit is contained in:
Nathan Vegdahl 2017-06-04 23:24:45 -07:00
parent a82c674308
commit 59555f67f9
3 changed files with 91 additions and 45 deletions

View File

@ -86,14 +86,16 @@ class IndentedWriter:
def write(self, text, do_indent=True): def write(self, text, do_indent=True):
if do_indent: if do_indent:
self.f.write(' '*self.indent_level + text) self.f.write(bytes(' '*self.indent_level + text, "utf-8"))
else: else:
self.f.write(text) self.f.write(bytes(text, "utf-8"))
class PsychoExporter: class PsychoExporter:
def __init__(self, render_engine, scene): def __init__(self, f, render_engine, scene):
self.w = IndentedWriter(f)
self.render_engine = render_engine self.render_engine = render_engine
self.scene = scene self.scene = scene
@ -119,24 +121,19 @@ class PsychoExporter:
else: else:
self.scene.frame_set(frame-1, 1.0+fraction) self.scene.frame_set(frame-1, 1.0+fraction)
def export_psy(self, export_path): def export_psy(self):
try: try:
f = open(export_path, 'w') self._export_psy()
self._export_psy(f, export_path)
except ExportCancelled: except ExportCancelled:
# Cleanup # Cleanup
f.close()
self.scene.frame_set(self.fr) self.scene.frame_set(self.fr)
return False return False
else: else:
# Cleanup # Cleanup
f.close()
self.scene.frame_set(self.fr) self.scene.frame_set(self.fr)
return True return True
def _export_psy(self, f, export_path): def _export_psy(self):
self.w = IndentedWriter(f)
# Info # Info
self.w.write("# Exported from Blender 2.7x\n") self.w.write("# Exported from Blender 2.7x\n")

View File

@ -2,16 +2,10 @@ import bpy
import time import time
import os import os
import subprocess import subprocess
import tempfile
import base64 import base64
import struct import struct
from . import psy_export from . import psy_export
def get_temp_filename(suffix=""):
tmpf = tempfile.mkstemp(suffix=suffix, prefix='tmp')
os.close(tmpf[0])
return(tmpf[1])
class PsychopathRender(bpy.types.RenderEngine): class PsychopathRender(bpy.types.RenderEngine):
bl_idname = 'PSYCHOPATH_RENDER' bl_idname = 'PSYCHOPATH_RENDER'
bl_label = "Psychopath" bl_label = "Psychopath"
@ -40,23 +34,21 @@ class PsychopathRender(bpy.types.RenderEngine):
return psy_binary return psy_binary
return "" return ""
def _export(self, scene, export_path): def _render(self, scene, psy_filepath, use_stdin):
exporter = psy_export.PsychoExporter(self, scene)
return exporter.export_psy(export_path)
def _render(self, scene, psy_filepath):
psy_binary = PsychopathRender._locate_binary() psy_binary = PsychopathRender._locate_binary()
if not psy_binary: if not psy_binary:
print("Psychopath: could not execute psychopath, possibly Psychopath isn't installed") print("Psychopath: could not execute psychopath, possibly Psychopath isn't installed")
return False return False
# TODO: figure out command line options # TODO: figure out command line options
if use_stdin:
args = ["--spb", str(scene.psychopath.max_samples_per_bucket), "--blender_output", "--use_stdin"]
else:
args = ["--spb", str(scene.psychopath.max_samples_per_bucket), "--blender_output", "-i", psy_filepath] args = ["--spb", str(scene.psychopath.max_samples_per_bucket), "--blender_output", "-i", psy_filepath]
# Start Rendering! # Start Rendering!
try: try:
self._process = subprocess.Popen([psy_binary] + args, bufsize=1, self._process = subprocess.Popen([psy_binary] + args, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except OSError: except OSError:
# TODO, report api # TODO, report api
print("Psychopath: could not execute '%s'" % psy_binary) print("Psychopath: could not execute '%s'" % psy_binary)
@ -90,23 +82,42 @@ class PsychopathRender(bpy.types.RenderEngine):
# has to be called to update the frame on exporting animations # has to be called to update the frame on exporting animations
scene.frame_set(scene.frame_current) scene.frame_set(scene.frame_current)
export_path = scene.psychopath.export_path export_path = scene.psychopath.export_path.strip()
use_stdin = False
if export_path != "": if export_path != "":
export_path += "_%d.psy" % scene.frame_current export_path += "_%d.psy" % scene.frame_current
else: else:
# Create a temporary file for exporting # We'll write directly to Psychopath's stdin
export_path = get_temp_filename('.psy') use_stdin = True
if use_stdin:
# Start rendering
if not self._render(scene, export_path, use_stdin):
self.update_stats("", "Psychopath: Not found")
return
self.update_stats("", "Psychopath: Exporting data from Blender")
# Export to Psychopath's stdin
if not psy_export.PsychoExporter(self._process.stdin, self, scene).export_psy():
# Render cancelled in the middle of exporting,
# so just return.
return
self._process.stdin.write(bytes("__PSY_EOF__", "utf-8"))
self._process.stdin.flush()
self.update_stats("", "Psychopath: Rendering")
else:
# start export # start export
self.update_stats("", "Psychopath: Exporting data from Blender") self.update_stats("", "Psychopath: Exporting data from Blender")
if not self._export(scene, export_path): with open(export_path, 'w+b') as f:
if not psy_export.PsychoExporter(f, self, scene).export_psy():
# Render cancelled in the middle of exporting, # Render cancelled in the middle of exporting,
# so just return. # so just return.
return return
# Start rendering # Start rendering
self.update_stats("", "Psychopath: Rendering from exported file") self.update_stats("", "Psychopath: Rendering from %s" % export_path)
if not self._render(scene, export_path): if not self._render(scene, export_path, use_stdin):
self.update_stats("", "Psychopath: Not found") self.update_stats("", "Psychopath: Not found")
return return

View File

@ -82,7 +82,7 @@ fn main() {
.value_name("FILE") .value_name("FILE")
.help("Input .psy file") .help("Input .psy file")
.takes_value(true) .takes_value(true)
.required_unless("dev") .required_unless_one(&["dev", "use_stdin"])
) )
.arg( .arg(
Arg::with_name("spp") Arg::with_name("spp")
@ -148,6 +148,12 @@ fn main() {
.help("Used by PsychoBlend to pass render output through standard in/out.") .help("Used by PsychoBlend to pass render output through standard in/out.")
.hidden(true) .hidden(true)
) )
.arg(
Arg::with_name("use_stdin")
.long("use_stdin")
.help("Take scene file in from stdin instead of a file path.")
.hidden(true)
)
.get_matches(); .get_matches();
// Print some misc useful dev info. // Print some misc useful dev info.
@ -169,14 +175,46 @@ fn main() {
"Parsing scene file...", "Parsing scene file...",
); );
t.tick(); t.tick();
let mut psy_contents = String::new(); let psy_contents = if args.is_present("use_stdin") {
let dt = { // Read from stdin
let mut input = Vec::new();
let tmp = std::io::stdin();
let mut stdin = tmp.lock();
let mut buf = vec![0u8; 4096];
loop {
let count = stdin
.read(&mut buf)
.expect("Unexpected end of scene input.");
let start = if input.len() < 11 {
0
} else {
input.len() - 11
};
let end = input.len() + count;
input.extend(&buf[..count]);
let mut done = false;
let mut trunc_len = 0;
if let nom::IResult::Done(remaining, _) = take_until!(&input[start..end], "__PSY_EOF__") {
done = true;
trunc_len = input.len() - remaining.len();
}
if done {
input.truncate(trunc_len);
break;
}
}
String::from_utf8(input).unwrap()
} else {
// Read from file
let mut input = String::new();
let fp = args.value_of("input").unwrap(); let fp = args.value_of("input").unwrap();
let mut f = io::BufReader::new(File::open(fp).unwrap()); let mut f = io::BufReader::new(File::open(fp).unwrap());
let _ = f.read_to_string(&mut psy_contents); let _ = f.read_to_string(&mut input);
input
DataTree::from_str(&psy_contents).unwrap()
}; };
let dt = DataTree::from_str(&psy_contents).unwrap();
println!("\tParsed scene file in {:.3}s", t.tick()); println!("\tParsed scene file in {:.3}s", t.tick());
// Iterate through scenes and render them // Iterate through scenes and render them