Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
2c24bbf
1
Parent(s):
643e04c
limiting gpu usage by moving autoforge into its own function
Browse files
app.py
CHANGED
|
@@ -310,6 +310,28 @@ if os.path.exists(DEFAULT_MATERIALS_CSV):
|
|
| 310 |
else:
|
| 311 |
initial_df.to_csv(DEFAULT_MATERIALS_CSV, index=False)
|
| 312 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
|
| 314 |
# Helper for creating an empty 10-tuple for error returns
|
| 315 |
def create_empty_error_outputs(log_message=""):
|
|
@@ -625,14 +647,14 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 625 |
)
|
| 626 |
|
| 627 |
with gr.Row():
|
| 628 |
-
|
| 629 |
-
label="Download
|
|
|
|
| 630 |
interactive=True,
|
| 631 |
visible=False,
|
| 632 |
)
|
| 633 |
|
| 634 |
# --- Backend Function for Running the Script ---
|
| 635 |
-
@spaces.GPU(duration=120)
|
| 636 |
def execute_autoforge_script(
|
| 637 |
current_filaments_df_state_val, input_image, *accordion_param_values
|
| 638 |
):
|
|
@@ -737,40 +759,6 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 737 |
"extra": {"command": cmd_str}, # still searchable
|
| 738 |
}
|
| 739 |
)
|
| 740 |
-
process = subprocess.Popen(
|
| 741 |
-
command,
|
| 742 |
-
stdout=subprocess.PIPE,
|
| 743 |
-
stderr=subprocess.PIPE,
|
| 744 |
-
text=True,
|
| 745 |
-
bufsize=1,
|
| 746 |
-
universal_newlines=True,
|
| 747 |
-
)
|
| 748 |
-
|
| 749 |
-
# ---- helper: read stdout in a background thread -------------------
|
| 750 |
-
from threading import Thread
|
| 751 |
-
from queue import Queue, Empty
|
| 752 |
-
|
| 753 |
-
def _enqueue(pipe, q):
|
| 754 |
-
"""Forward stdout/stderr to a queue, emitting on both '\n' and '\r'."""
|
| 755 |
-
buf = ""
|
| 756 |
-
while True:
|
| 757 |
-
ch = pipe.read(1) # read a single character
|
| 758 |
-
if ch == "": # EOF
|
| 759 |
-
if buf:
|
| 760 |
-
q.put(buf) # flush whatever is left
|
| 761 |
-
break
|
| 762 |
-
buf += ch
|
| 763 |
-
if ch in ("\n", "\r"): # tqdm uses '\r'
|
| 764 |
-
q.put(buf)
|
| 765 |
-
buf = ""
|
| 766 |
-
pipe.close()
|
| 767 |
-
|
| 768 |
-
q_out = Queue()
|
| 769 |
-
Thread(target=_enqueue, args=(process.stdout, q_out), daemon=True).start()
|
| 770 |
-
Thread(target=_enqueue, args=(process.stderr, q_out), daemon=True).start()
|
| 771 |
-
|
| 772 |
-
preview_mtime = 0
|
| 773 |
-
last_push = 0
|
| 774 |
|
| 775 |
def _maybe_new_preview():
|
| 776 |
"""
|
|
@@ -792,28 +780,45 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 792 |
|
| 793 |
return src # → refresh image
|
| 794 |
|
| 795 |
-
# ----
|
| 796 |
-
|
| 797 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 798 |
try:
|
| 799 |
-
while True:
|
| 800 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 801 |
except Empty:
|
| 802 |
pass
|
| 803 |
|
| 804 |
now = time.time()
|
| 805 |
-
if now - last_push >= 1.0: #
|
| 806 |
current_preview = _maybe_new_preview()
|
| 807 |
yield (
|
| 808 |
log_output,
|
| 809 |
current_preview,
|
| 810 |
-
gr.update(), #
|
| 811 |
)
|
| 812 |
last_push = now
|
| 813 |
|
| 814 |
time.sleep(0.05) # keep CPU load low
|
| 815 |
|
| 816 |
-
return_code = process.wait()
|
| 817 |
if return_code != 0:
|
| 818 |
err = RuntimeError(f"Autoforge exited with code {return_code} \n {log_output}")
|
| 819 |
capture_exception(err) # send to Sentry
|
|
@@ -833,22 +838,17 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 833 |
}
|
| 834 |
)
|
| 835 |
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
# 4. Prepare output file paths
|
| 847 |
png_path = os.path.join(run_output_dir_val, "final_model.png")
|
| 848 |
-
stl_path = os.path.join(run_output_dir_val, "final_model.stl")
|
| 849 |
-
txt_path = os.path.join(run_output_dir_val, "swap_instructions.txt")
|
| 850 |
-
hfp_path = os.path.join(run_output_dir_val, "project_file.hfp")
|
| 851 |
-
|
| 852 |
out_png = png_path if os.path.exists(png_path) else None
|
| 853 |
|
| 854 |
if out_png is None:
|
|
@@ -856,10 +856,12 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 856 |
|
| 857 |
yield (
|
| 858 |
log_output, # progress_output
|
| 859 |
-
out_png, # final_image_preview
|
| 860 |
-
gr.update(
|
| 861 |
-
value=
|
| 862 |
-
|
|
|
|
|
|
|
| 863 |
)
|
| 864 |
|
| 865 |
run_inputs = [filament_df_state, input_image_component] + [
|
|
@@ -868,7 +870,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 868 |
run_outputs = [
|
| 869 |
progress_output,
|
| 870 |
final_image_preview,
|
| 871 |
-
|
| 872 |
]
|
| 873 |
|
| 874 |
run_button.click(execute_autoforge_script, inputs=run_inputs, outputs=run_outputs)
|
|
|
|
| 310 |
else:
|
| 311 |
initial_df.to_csv(DEFAULT_MATERIALS_CSV, index=False)
|
| 312 |
|
| 313 |
+
@spaces.GPU()
|
| 314 |
+
def run_autoforge_process(cmd, q):
|
| 315 |
+
"""
|
| 316 |
+
Start the Autoforge CLI, stream its stdout to Queue *q*,
|
| 317 |
+
then drop a sentinel ('__RC__', return_code) at the end.
|
| 318 |
+
"""
|
| 319 |
+
import subprocess, os, sys
|
| 320 |
+
|
| 321 |
+
proc = subprocess.Popen(
|
| 322 |
+
cmd,
|
| 323 |
+
stdout=subprocess.PIPE,
|
| 324 |
+
stderr=subprocess.STDOUT,
|
| 325 |
+
text=True,
|
| 326 |
+
bufsize=1,
|
| 327 |
+
universal_newlines=True,
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
for line in proc.stdout: # line-by-line streaming
|
| 331 |
+
q.put(line)
|
| 332 |
+
proc.wait()
|
| 333 |
+
q.put(("__RC__", proc.returncode))
|
| 334 |
+
|
| 335 |
|
| 336 |
# Helper for creating an empty 10-tuple for error returns
|
| 337 |
def create_empty_error_outputs(log_message=""):
|
|
|
|
| 647 |
)
|
| 648 |
|
| 649 |
with gr.Row():
|
| 650 |
+
download_results = gr.File(
|
| 651 |
+
label="Download results",
|
| 652 |
+
file_count="multiple",
|
| 653 |
interactive=True,
|
| 654 |
visible=False,
|
| 655 |
)
|
| 656 |
|
| 657 |
# --- Backend Function for Running the Script ---
|
|
|
|
| 658 |
def execute_autoforge_script(
|
| 659 |
current_filaments_df_state_val, input_image, *accordion_param_values
|
| 660 |
):
|
|
|
|
| 759 |
"extra": {"command": cmd_str}, # still searchable
|
| 760 |
}
|
| 761 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
|
| 763 |
def _maybe_new_preview():
|
| 764 |
"""
|
|
|
|
| 780 |
|
| 781 |
return src # → refresh image
|
| 782 |
|
| 783 |
+
# ---- run Autoforge on the GPU in a helper thread ------------------
|
| 784 |
+
from threading import Thread
|
| 785 |
+
from queue import Queue, Empty
|
| 786 |
+
|
| 787 |
+
q_out = Queue()
|
| 788 |
+
worker = Thread(
|
| 789 |
+
target=run_autoforge_process,
|
| 790 |
+
args=(command, q_out),
|
| 791 |
+
daemon=True,
|
| 792 |
+
)
|
| 793 |
+
worker.start()
|
| 794 |
+
|
| 795 |
+
preview_mtime = 0
|
| 796 |
+
last_push = 0
|
| 797 |
+
return_code = None
|
| 798 |
+
|
| 799 |
+
while worker.is_alive() or not q_out.empty():
|
| 800 |
try:
|
| 801 |
+
while True: # drain whatever is ready
|
| 802 |
+
msg = q_out.get_nowait()
|
| 803 |
+
if isinstance(msg, tuple): # ('__RC__', code) sentinel
|
| 804 |
+
return_code = msg[1]
|
| 805 |
+
else:
|
| 806 |
+
log_output += msg
|
| 807 |
except Empty:
|
| 808 |
pass
|
| 809 |
|
| 810 |
now = time.time()
|
| 811 |
+
if now - last_push >= 1.0: # 1-second UI tick
|
| 812 |
current_preview = _maybe_new_preview()
|
| 813 |
yield (
|
| 814 |
log_output,
|
| 815 |
current_preview,
|
| 816 |
+
gr.update(), # placeholder for download widget
|
| 817 |
)
|
| 818 |
last_push = now
|
| 819 |
|
| 820 |
time.sleep(0.05) # keep CPU load low
|
| 821 |
|
|
|
|
| 822 |
if return_code != 0:
|
| 823 |
err = RuntimeError(f"Autoforge exited with code {return_code} \n {log_output}")
|
| 824 |
capture_exception(err) # send to Sentry
|
|
|
|
| 838 |
}
|
| 839 |
)
|
| 840 |
|
| 841 |
+
files_to_offer = [
|
| 842 |
+
p
|
| 843 |
+
for p in [
|
| 844 |
+
os.path.join(run_output_dir_val, "final_model.png"),
|
| 845 |
+
os.path.join(run_output_dir_val, "final_model.stl"),
|
| 846 |
+
os.path.join(run_output_dir_val, "swap_instructions.txt"),
|
| 847 |
+
os.path.join(run_output_dir_val, "project_file.hfp"),
|
| 848 |
+
]
|
| 849 |
+
if os.path.exists(p)
|
| 850 |
+
]
|
|
|
|
| 851 |
png_path = os.path.join(run_output_dir_val, "final_model.png")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 852 |
out_png = png_path if os.path.exists(png_path) else None
|
| 853 |
|
| 854 |
if out_png is None:
|
|
|
|
| 856 |
|
| 857 |
yield (
|
| 858 |
log_output, # progress_output
|
| 859 |
+
out_png, # final_image_preview (same as before)
|
| 860 |
+
gr.update( # download_results
|
| 861 |
+
value=files_to_offer,
|
| 862 |
+
visible=True,
|
| 863 |
+
interactive=True,
|
| 864 |
+
),
|
| 865 |
)
|
| 866 |
|
| 867 |
run_inputs = [filament_df_state, input_image_component] + [
|
|
|
|
| 870 |
run_outputs = [
|
| 871 |
progress_output,
|
| 872 |
final_image_preview,
|
| 873 |
+
download_results, # ### ZIP PATCH: only three outputs now
|
| 874 |
]
|
| 875 |
|
| 876 |
run_button.click(execute_autoforge_script, inputs=run_inputs, outputs=run_outputs)
|