Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist
.DS_Store
.vs/
*.zip
CLAUDE.md
78 changes: 78 additions & 0 deletions examples/send_render_progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
Example: send render progress updates to Octolapse from an external script.

Usage:
python send_render_progress.py --host http://localhost:5000 --api-key YOUR_KEY --percent 42.5

This is useful for before/after render scripts configured in a camera profile that
do their own processing (e.g. re-encoding, watermarking) and want to drive the
Octolapse progress bar in the OctoPrint UI.
"""

import argparse
import sys
import time
import urllib.request
import urllib.error
import json


def send_progress(host, api_key, percent):
"""POST a progress update to Octolapse. Returns True on success."""
url = "{}/plugin/octolapse/renderProgress".format(host.rstrip("/"))
payload = json.dumps({"percent": percent}).encode("utf-8")
req = urllib.request.Request(
url,
data=payload,
headers={
"Content-Type": "application/json",
"X-Api-Key": api_key,
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
body = json.loads(resp.read().decode("utf-8"))
if not body.get("success"):
print("Octolapse returned an error: {}".format(body.get("error")), file=sys.stderr)
return False
return True
except urllib.error.HTTPError as e:
print("HTTP {}: {}".format(e.code, e.reason), file=sys.stderr)
return False
except urllib.error.URLError as e:
print("Connection error: {}".format(e.reason), file=sys.stderr)
return False


def main():
parser = argparse.ArgumentParser(description="Send a render progress update to Octolapse.")
parser.add_argument("--host", default="http://localhost:5000",
help="OctoPrint base URL (default: http://localhost:5000)")
parser.add_argument("--api-key", required=True,
help="OctoPrint API key")
parser.add_argument("--percent", type=float,
help="Progress percentage to send (0-100). "
"Omit to run a demo that counts from 0 to 100.")
args = parser.parse_args()

if args.percent is not None:
# Single update mode
ok = send_progress(args.host, args.api_key, args.percent)
sys.exit(0 if ok else 1)
else:
# Demo mode: simulate progress from 0 to 100 over ~10 seconds
print("Demo mode: sending progress 0→100 over 10 seconds")
for i in range(101):
percent = float(i)
ok = send_progress(args.host, args.api_key, percent)
status = "ok" if ok else "FAILED"
print(" {:.1f}% [{}]".format(percent, status))
if i < 100:
time.sleep(0.1)
print("Done.")


if __name__ == "__main__":
main()
45 changes: 45 additions & 0 deletions examples/send_render_progress.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
# Example: send render progress updates to Octolapse from a before/after render script.
#
# Usage:
# OCTOPRINT_API_KEY=your_key ./send_render_progress.sh 42
#
# Set OCTOPRINT_HOST to override the default (http://localhost:5000).
#
# This is useful for before/after render scripts that do their own processing
# (e.g. re-encoding, watermarking) and want to drive the Octolapse progress
# bar in the OctoPrint UI.

OCTOPRINT_HOST="${OCTOPRINT_HOST:-http://localhost:5000}"
OCTOPRINT_API_KEY="${OCTOPRINT_API_KEY:-}"
PERCENT="${1:-}"

if [ -z "$OCTOPRINT_API_KEY" ]; then
echo "Error: OCTOPRINT_API_KEY is not set" >&2
exit 1
fi

if [ -z "$PERCENT" ]; then
echo "Usage: $0 <percent>" >&2
echo " percent: 0-100" >&2
exit 1
fi

response=$(curl -sf -X POST \
-H "Content-Type: application/json" \
-H "X-Api-Key: $OCTOPRINT_API_KEY" \
-d "{\"percent\": $PERCENT}" \
"$OCTOPRINT_HOST/plugin/octolapse/renderProgress")

if [ $? -ne 0 ]; then
echo "Error: failed to connect to OctoPrint at $OCTOPRINT_HOST" >&2
exit 1
fi

success=$(echo "$response" | grep -o '"success": *true')
if [ -z "$success" ]; then
echo "Error: $response" >&2
exit 1
fi

echo "Progress updated: ${PERCENT}%"
18 changes: 18 additions & 0 deletions octoprint_octolapse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,24 @@ def stop_timelapse_request(self):
self._timelapse.stop_snapshots()
return jsonify({'success': True})

@octoprint.plugin.BlueprintPlugin.route("/renderProgress", methods=["POST"])
@restricted_access
def render_progress_request(self):
with OctolapsePlugin.admin_permission.require(http_exception=403):
request_values = request.get_json()
percent = request_values.get("percent")
if percent is None:
return jsonify({'success': False, 'error': 'Missing required field: percent'})
try:
percent = float(percent)
except (ValueError, TypeError):
return jsonify({'success': False, 'error': 'percent must be a number'})
if not 0.0 <= percent <= 100.0:
return jsonify({'success': False, 'error': 'percent must be between 0 and 100'})
job = self._rendering_processor.get_current_rendering_job()
self.send_render_progress_message(percent, job)
return jsonify({'success': True})

@octoprint.plugin.BlueprintPlugin.route("/previewStabilization", methods=["POST"])
@restricted_access
def preview_stabilization(self):
Expand Down
5 changes: 4 additions & 1 deletion octoprint_octolapse/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ def migrate_settings(current_version, settings_dict, default_settings_directory,
raise Exception("Could not find version information within the settings json, cannot perform migration.")

if version == "0+unknown":
raise Exception("An unknown settings version was detected, cannot perform migration.")
if settings_version is None:
raise Exception("An unknown settings version was detected, cannot perform migration.")
# settings_version is present — all migration steps that use `version` also gate on
# `settings_version is None`, so we can proceed safely without a resolvable version string.

# create a copy of the settings
original_settings_copy = copy.deepcopy(settings_dict)
Expand Down
4 changes: 4 additions & 0 deletions octoprint_octolapse/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,10 @@ def is_processing(self):
with self.r_lock:
return self._has_pending_jobs() or self.rendering_task_queue.qsize() > 0

def get_current_rendering_job(self):
with self.r_lock:
return copy.copy(self._current_rendering_job)

def get_failed(self):
with self.r_lock:
return {
Expand Down
9 changes: 8 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@
import platform

# Versioneer import with fallback
_PINNED_VERSION = "0.4.5+azuer88"

try:
import versioneer
cmdclass = versioneer.get_cmdclass()
_raw_get_version = versioneer.get_version
def get_version():
v = _raw_get_version()
return _PINNED_VERSION if v == "0+unknown" else v
versioneer.get_version = get_version
except (ImportError, AttributeError):
print("Warning: versioneer not available, using fallback version")
cmdclass = {}
def get_version():
return "0.4.51"
return _PINNED_VERSION
def get_cmdclass():
return {}
class _versioneer:
Expand Down