Skip to content
Open
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
185 changes: 137 additions & 48 deletions examples/basics/01_load_comp_basic.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,139 @@
"""
Improved TouchPy example: Run a .tox component for a fixed number of frames (headless mode)

This script demonstrates best practices for:
- Error handling with TouchEngine
- Resource cleanup (always unload!)
- Logging instead of raw print
- Configurable parameters
- Context manager pattern

Requirements:
- Python 3.9+
- touchpy installed (pip install touchpy)
- TouchDesigner / TouchPlayer 2023+ with paid license
- Windows + Vulkan-capable GPU (NVIDIA for TOPs)

Usage:
python this_script.py
"""

import logging
from pathlib import Path
import touchpy as tp

# create a class that inherits from tp.Comp
# inheriting from tp.Comp is not required but it is the recommended way to interface
# with a component

class MyComp (tp.Comp):
def __init__(self):

# call the parent class constructor to initialize the component
super().__init__()

# create a frame counter
self.frame = 0

# set the on_frame_callback to the on_frame method
self.set_on_frame_callback(self.on_frame, {})

# define the on_frame method that will be called on every frame
# the info argument is user data that can be passed to the callback
# in this case it is an empty dictionary
def on_frame(self, info):

# stop running the component after 1200 frames (20 seconds at 60 fps)
if self.frame == 1200:
self.stop()
return

# start_next_frame() is called to advance the component to the next frame
self.start_next_frame()
self.frame += 1

# create an instance of the MyCompBasic class
comp = MyComp()

# load the tox file into the component
comp.load('TopChopDatIO.tox')

# start the component, this will block until self.stop() is called when not running
# the component in async mode
comp.start()

# unload the component to cleanly free up resources
comp.unload()






# Setup logging (bisa diubah ke file: filename='touchpy_run.log')
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


class ControlledComp(tp.Comp):
"""
Custom component wrapper with frame limiting and safe cleanup.
"""

def __init__(self, tox_path: str | Path, max_frames: int = 1200, fps: float = 60.0):
super().__init__()

self.tox_path = Path(tox_path).resolve() # Normalize path
self.max_frames = max_frames
self.frame_counter = 0
self.target_fps = fps

# Optional: set cook time / FPS target if needed
# self.cook_time = 1.0 / self.target_fps # in seconds, but TouchPy may ignore

# Set callback
self.set_on_frame_callback(self._on_frame, user_data={})

logger.info(f"Initialized ControlledComp for {self.tox_path}")

def load_tox(self):
"""Load the .tox file with error checking"""
if not self.tox_path.exists():
raise FileNotFoundError(f"TOX file not found: {self.tox_path}")

try:
self.load(str(self.tox_path)) # TouchPy expects str path
logger.info(f"Successfully loaded TOX: {self.tox_path.name}")
except Exception as e:
logger.error(f"Failed to load TOX: {e}")
raise

def _on_frame(self, info):
"""Called every frame by TouchPy engine"""
self.frame_counter += 1

# Example: Access operators inside the .tox and do something useful
# top = self.op('my_top') # if there's a TOP named 'my_top'
# if top:
# top.par.preview = 1 # example: toggle preview

logger.debug(f"Frame {self.frame_counter}/{self.max_frames}")

if self.frame_counter >= self.max_frames:
logger.info(f"Reached max frames ({self.max_frames}). Stopping.")
self.stop()
return

# Advance to next frame
self.start_next_frame()

def run(self):
"""Start the component and block until stopped"""
try:
self.load_tox()
logger.info(f"Starting component for up to {self.max_frames} frames "
f"({self.max_frames / self.target_fps:.1f} seconds)")
self.start() # Blocks until stop() called
except Exception as e:
logger.error(f"Error during execution: {e}")
raise
finally:
self.unload()
logger.info("Component unloaded (cleanup complete)")


# Context manager version (recommended for safety)
class SafeCompRunner:
"""Context manager to ensure unload even on exceptions"""

def __init__(self, tox_path: str | Path, max_frames: int = 1200):
self.comp = ControlledComp(tox_path, max_frames=max_frames)

def __enter__(self):
return self.comp

def __exit__(self, exc_type, exc_val, exc_tb):
if self.comp:
self.comp.unload()
logger.info("Context exit: component unloaded")


# --------------------
# Main execution
# --------------------
if __name__ == "__main__":
TOX_FILE = "TopChopDatIO.tox" # Ganti sesuai path kamu

try:
with SafeCompRunner(TOX_FILE, max_frames=1200) as runner:
runner.run()

# Alternatif non-blocking (async mode) - uncomment jika perlu
# comp = ControlledComp(TOX_FILE)
# comp.load_tox()
# comp.start(block=False) # Non-blocking
# # Lakukan hal lain...
# while comp.is_running:
# time.sleep(0.1)
# comp.stop()
# comp.unload()

except FileNotFoundError as e:
logger.error(f"TOX file missing: {e}")
except Exception as e:
logger.exception("Unexpected error during TouchPy run")