diff --git a/psychoblend/psy_export.py b/psychoblend/psy_export.py index cdb710c..7457b5a 100644 --- a/psychoblend/psy_export.py +++ b/psychoblend/psy_export.py @@ -86,14 +86,16 @@ class IndentedWriter: def write(self, text, do_indent=True): if do_indent: - self.f.write(' '*self.indent_level + text) + self.f.write(bytes(' '*self.indent_level + text, "utf-8")) else: - self.f.write(text) + self.f.write(bytes(text, "utf-8")) + 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.scene = scene @@ -119,24 +121,19 @@ class PsychoExporter: else: self.scene.frame_set(frame-1, 1.0+fraction) - def export_psy(self, export_path): + def export_psy(self): try: - f = open(export_path, 'w') - self._export_psy(f, export_path) + self._export_psy() except ExportCancelled: # Cleanup - f.close() self.scene.frame_set(self.fr) return False else: # Cleanup - f.close() self.scene.frame_set(self.fr) return True - def _export_psy(self, f, export_path): - self.w = IndentedWriter(f) - + def _export_psy(self): # Info self.w.write("# Exported from Blender 2.7x\n") diff --git a/psychoblend/render.py b/psychoblend/render.py index fd5303a..cf07e2b 100644 --- a/psychoblend/render.py +++ b/psychoblend/render.py @@ -2,16 +2,10 @@ import bpy import time import os import subprocess -import tempfile import base64 import struct 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): bl_idname = 'PSYCHOPATH_RENDER' bl_label = "Psychopath" @@ -40,23 +34,21 @@ class PsychopathRender(bpy.types.RenderEngine): return psy_binary return "" - def _export(self, scene, export_path): - exporter = psy_export.PsychoExporter(self, scene) - return exporter.export_psy(export_path) - - def _render(self, scene, psy_filepath): + def _render(self, scene, psy_filepath, use_stdin): psy_binary = PsychopathRender._locate_binary() if not psy_binary: print("Psychopath: could not execute psychopath, possibly Psychopath isn't installed") return False # TODO: figure out command line options - args = ["--spb", str(scene.psychopath.max_samples_per_bucket), "--blender_output", "-i", psy_filepath] + 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] # Start Rendering! try: - self._process = subprocess.Popen([psy_binary] + args, bufsize=1, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + self._process = subprocess.Popen([psy_binary] + args, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE) except OSError: # TODO, report api print("Psychopath: could not execute '%s'" % psy_binary) @@ -90,25 +82,44 @@ class PsychopathRender(bpy.types.RenderEngine): # has to be called to update the frame on exporting animations 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 != "": export_path += "_%d.psy" % scene.frame_current else: - # Create a temporary file for exporting - export_path = get_temp_filename('.psy') + # We'll write directly to Psychopath's stdin + use_stdin = True - # start export - self.update_stats("", "Psychopath: Exporting data from Blender") - if not self._export(scene, export_path): - # Render cancelled in the middle of exporting, - # so just return. - return + if use_stdin: + # Start rendering + if not self._render(scene, export_path, use_stdin): + self.update_stats("", "Psychopath: Not found") + return - # Start rendering - self.update_stats("", "Psychopath: Rendering from exported file") - if not self._render(scene, export_path): - 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 + self.update_stats("", "Psychopath: Exporting data from Blender") + 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, + # so just return. + return + + # Start rendering + self.update_stats("", "Psychopath: Rendering from %s" % export_path) + if not self._render(scene, export_path, use_stdin): + self.update_stats("", "Psychopath: Not found") + return r = scene.render # compute resolution diff --git a/src/main.rs b/src/main.rs index a8c7aeb..1411b4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,7 +82,7 @@ fn main() { .value_name("FILE") .help("Input .psy file") .takes_value(true) - .required_unless("dev") + .required_unless_one(&["dev", "use_stdin"]) ) .arg( Arg::with_name("spp") @@ -148,6 +148,12 @@ fn main() { .help("Used by PsychoBlend to pass render output through standard in/out.") .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(); // Print some misc useful dev info. @@ -169,14 +175,46 @@ fn main() { "Parsing scene file...", ); t.tick(); - let mut psy_contents = String::new(); - let dt = { + let psy_contents = if args.is_present("use_stdin") { + // 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 mut f = io::BufReader::new(File::open(fp).unwrap()); - let _ = f.read_to_string(&mut psy_contents); - - DataTree::from_str(&psy_contents).unwrap() + let _ = f.read_to_string(&mut input); + input }; + + let dt = DataTree::from_str(&psy_contents).unwrap(); println!("\tParsed scene file in {:.3}s", t.tick()); // Iterate through scenes and render them