reorganize

This commit is contained in:
Cristian Predut
2025-12-03 18:04:54 +01:00
parent 18ed7934d6
commit e101f4e304
15 changed files with 81 additions and 668 deletions
-208
View File
@@ -1,208 +0,0 @@
# Camera Conversion Pipeline - Fixes and Improvements
## Summary
This document outlines the issues found and fixes applied to the Nuke → ComfyUI camera conversion pipeline.
## Critical Issue Found: Missing Coordinate System Conversion
### Problem
The converter was **not converting from Nuke's Y-up coordinate system to OpenCV's Y-down coordinate system**. This causes camera motion to appear incorrect in ComfyUI, as the coordinate axes don't match what the Wan model expects.
### Root Cause
- **Nuke uses**: +X right, +Y up, +Z forward
- **OpenCV/RealEstate10k uses**: +X right, +Y down, +Z forward
- The converter was building rotation matrices in Nuke's coordinate system but not transforming them to OpenCV's system
### Fix Applied
Added `convert_yup_to_ydown()` function that properly transforms rotation matrices:
```python
def convert_yup_to_ydown(rotation: List[List[float]]) -> List[List[float]]:
"""
Convert rotation matrix from Nuke's Y-up coordinate system to OpenCV's Y-down.
Transformation: R_opencv = T * R_nuke * T, where T = diag(1, -1, 1)
This negates the Y row and Y column of the rotation matrix.
"""
r = rotation
return [
[r[0][0], -r[0][1], r[0][2]], # X row: keep X, negate Y, keep Z
[-r[1][0], r[1][1], -r[1][2]], # Y row: negate X, keep Y, negate Z
[r[2][0], -r[2][1], r[2][2]], # Z row: keep X, negate Y, keep Z
]
```
This function is now called in `main()` before computing translations and writing pose data.
### Impact
- **Before**: Camera motion would be flipped/mirrored on the Y axis
- **After**: Camera motion matches the original Nuke camera path
## Code Improvements
### 1. Added Documentation
- Added docstrings to key functions explaining coordinate system transformations
- Clarified the purpose of `camera_center_to_translation()` function
### 2. Improved Code Structure
- Separated coordinate system conversion into its own function
- Made the transformation explicit and testable
## Testing Tools Created
### 1. `test_nuke_scene.py`
Generates a Nuke script (.nk) with:
- A simple camera animation (forward dolly + pan)
- Known lens parameters (35mm focal, 36x24mm filmback)
- Reference geometry (grid) for visualization
- Easy to validate motion
**Usage:**
```bash
python test_nuke_scene.py --output test_scene.nk --frames 50
```
### 2. `validate_conversion.py`
Validates the conversion by:
- Reading both ASCI input and pose output
- Checking rotation matrix validity (orthogonality, determinant = 1)
- Verifying coordinate system conversion (Y-up → Y-down)
- Validating translation conversion (camera center → world-to-camera)
- Reporting any issues found
**Usage:**
```bash
python validate_conversion.py inputs/camera.asci outputs/camera_converted.txt --unit-scale 0.1
```
### 3. `test_pipeline.sh`
Automated test script that runs the full pipeline:
1. Generates test scene
2. (Manual) Export from Nuke
3. Converts ASCI to pose
4. Validates conversion
### 4. `README_TESTING.md`
Comprehensive testing guide with:
- Step-by-step workflow
- Common issues and fixes
- Integration instructions for ComfyUI
## How to Test the Fix
### Quick Test
1. **Generate test scene:**
```bash
python test_nuke_scene.py --output test_scene.nk
```
2. **Export camera from Nuke:**
- Open `test_scene.nk` in Nuke
- Select Camera2 node
- Export Ascii with columns: `[translate.x, rotate.x, translate.y, rotate.y, translate.z, rotate.z]`
- Save as `inputs/test_camera.asci`
3. **Convert:**
```bash
python nuke_ASCI-2-Pose_converter.py \
inputs/test_camera.asci \
outputs/test_camera_converted.txt \
--fps 25.0 \
--focal-length-mm 35.0 \
--sensor-width-mm 36.0 \
--sensor-height-mm 24.0 \
--width 1920 \
--height 1080 \
--unit-scale 1.0
```
4. **Validate:**
```bash
python validate_conversion.py \
inputs/test_camera.asci \
outputs/test_camera_converted.txt \
--unit-scale 1.0
```
### Test with Existing Data
To test with your existing `camTrack_1.7m_v02.asci`:
```bash
# Convert with fix
python nuke_ASCI-2-Pose_converter.py \
inputs/camTrack_1.7m_v02.asci \
outputs/camTrack_1.7m_v02_converted_FIXED.txt \
--config configs/camera_config_v01.json
# Validate
python validate_conversion.py \
inputs/camTrack_1.7m_v02.asci \
outputs/camTrack_1.7m_v02_converted_FIXED.txt \
--unit-scale 0.1
# Compare with old output
python validate_conversion.py \
inputs/camTrack_1.7m_v02.asci \
outputs/camTrack_1.7m_v02_converted.txt \
--unit-scale 0.1
```
## Expected Results
### Before Fix
- Validation would show coordinate system conversion warnings
- Camera motion in ComfyUI would be flipped/mirrored on Y axis
- Rotation matrices would be in Nuke's coordinate system
### After Fix
- Validation should pass coordinate system checks
- Camera motion should match original Nuke camera path
- Rotation matrices properly converted to OpenCV coordinate system
- Translation values correctly computed using converted rotation matrix
## Additional Considerations
### Unit Scale
The `unit_scale` parameter is critical for correct motion:
- Measure real-world distance traveled by camera
- Calculate: `unit_scale = real_distance_meters / asci_delta`
- Example: Camera moves 1.7m, ASCI Tz delta is 17.0 → `unit_scale = 0.1`
### Lens Parameters
Ensure these match your Nuke camera:
- `--focal-length-mm`: Focal length in millimeters
- `--sensor-width-mm`: Horizontal filmback/aperture
- `--sensor-height-mm`: Vertical filmback/aperture
### Frame Range
Match the frame range between:
- Nuke ASCI export
- ComfyUI render
- Use `--start-frame` and `--end-frame` if needed
## Next Steps
1. **Test the fix** with your existing data
2. **Validate** using the validation script
3. **Compare** old vs new output in ComfyUI
4. **Adjust** `unit_scale` if motion scale is still off
5. **Verify** lens parameters match production data
## Files Modified
- `nuke_ASCI-2-Pose_converter.py`: Added coordinate system conversion
- `test_nuke_scene.py`: New test scene generator
- `validate_conversion.py`: New validation tool
- `test_pipeline.sh`: New automated test script
- `README_TESTING.md`: New testing documentation
## Questions?
If you encounter issues:
1. Run `validate_conversion.py` to identify problems
2. Check `README_TESTING.md` for common issues
3. Verify `unit_scale` calculation
4. Ensure lens parameters match Nuke camera settings
-155
View File
@@ -1,155 +0,0 @@
# Testing the Camera Conversion Pipeline
This document explains how to test and validate the Nuke → ComfyUI camera conversion pipeline.
## Quick Test Workflow
### 1. Generate Test Scene
```bash
python test_nuke_scene.py --output test_scene.nk --frames 81
```
This creates a Nuke script with:
- A camera that moves forward (dollies in) from Z=-5 to Z=-3
- A slight pan (Y rotation from -10° to -15°)
- Known lens parameters (35mm focal, 36x24mm filmback)
- A reference grid for visualization
### 2. Export Camera from Nuke
1. Open `test_scene.nk` in Nuke
2. Select the `Camera2` node
3. Right-click → **Export Ascii**
4. Configure export:
- **Columns**: `translate.x`, `rotate.x`, `translate.y`, `rotate.y`, `translate.z`, `rotate.z`
- **Rotation order**: ZXY (Nuke's default)
- **Rotation units**: Degrees
5. Save as `inputs/test_camera.asci`
### 3. Convert to Pose Format
```bash
python nuke_ASCI-2-Pose_converter.py \
inputs/test_camera.asci \
outputs/test_camera_converted.txt \
--fps 25.0 \
--focal-length-mm 35.0 \
--sensor-width-mm 36.0 \
--sensor-height-mm 24.0 \
--width 1920 \
--height 1080 \
--unit-scale 1.0
```
Or use a config file:
```bash
python nuke_ASCI-2-Pose_converter.py --config configs/test_camera.json
```
### 4. Validate Conversion
```bash
python validate_conversion.py \
inputs/test_camera.asci \
outputs/test_camera_converted.txt \
--unit-scale 1.0
```
This script checks:
- Rotation matrix validity (orthogonality, determinant = 1)
- Coordinate system conversion (Y-up → Y-down)
- Translation conversion (camera center → world-to-camera translation)
- Frame count consistency
## Common Issues and Fixes
### Issue: Camera motion doesn't match original plate
**Possible causes:**
1. **Missing coordinate system conversion**
- ✅ Fixed: The converter now includes `convert_yup_to_ydown()` function
- Check validation output for coordinate system warnings
2. **Camera solver rotated 90° (or any large rotation)**
- **This is normal!** Nuke's camera solver may orient the camera with large rotations (e.g., 90° Y-axis)
- The converter preserves all rotations correctly, including 90° rotations
- Use `python debug_rotation.py inputs/your_camera.asci` to analyze rotations
- If motion still doesn't match, check `unit_scale` and lens parameters (see below)
3. **Incorrect unit_scale**
- Measure real-world distance traveled by camera
- Calculate: `unit_scale = real_distance_meters / asci_delta`
- Example: Camera moves 1.7m, ASCI Tz delta is 17.0 → `unit_scale = 0.1`
4. **Wrong lens parameters**
- Verify focal length matches Nuke camera settings
- Check sensor size (filmback) matches camera node
- Use `--focal-length-mm` and `--sensor-width-mm` / `--sensor-height-mm` flags
5. **Frame range mismatch**
- Ensure ASCI export includes all frames you want
- Use `--start-frame` and `--end-frame` to slice if needed
### Issue: Validation shows coordinate system errors
If `validate_conversion.py` reports coordinate system issues:
1. Check that `convert_yup_to_ydown()` is being called in the converter
2. Verify the Y-row and Y-column negation pattern:
- `R_opencv[1,0] ≈ -R_nuke[1,0]`
- `R_opencv[1,2] ≈ -R_nuke[1,2]`
- `R_opencv[0,1] ≈ -R_nuke[0,1]`
- `R_opencv[2,1] ≈ -R_nuke[2,1]`
### Issue: Translation values look wrong
The converter uses `t = -R * C` where:
- `R` is the rotation matrix (after Y-up→Y-down conversion)
- `C` is the camera center scaled by `unit_scale`
If translations are off:
1. Verify `unit_scale` is correct
2. Check that camera center values from ASCI are being read correctly
3. Ensure rotation matrix is valid (orthogonal, det=1)
## Testing with Real Production Data
For production shots:
1. **Measure reference distance**
- Use CameraTracker's "Set Reference Distance" feature
- Or measure a known object size in the scene
- Calculate `unit_scale` from this measurement
2. **Verify lens metadata**
- Get focal length from camera report or EXIF data
- Get sensor size from camera specs (e.g., Full Frame = 36x24mm)
3. **Export with correct frame range**
- Match the frame range you'll use in ComfyUI
- Use `--start-frame` and `--end-frame` to match exactly
4. **Validate before using in ComfyUI**
- Run `validate_conversion.py` to catch issues early
- Check that rotation matrices are valid
- Verify coordinate system conversion
## Integration with ComfyUI
After conversion:
1. Load the pose file in ComfyUI using `Koolook Load Camera Poses`
2. Connect to `WanVideo FunCamera Embeds`
3. Set render resolution to match your pose file's width/height
4. Test with a simple prompt first to verify motion
If motion still doesn't match:
- Double-check `unit_scale` calculation
- Verify lens parameters match production data
- Check that ComfyUI render resolution matches pose file resolution
- Consider using camera projection in Nuke to validate the converted poses
-89
View File
@@ -1,89 +0,0 @@
# Understanding Camera Rotations and Coordinate Systems
## The 90° Y-Axis Rotation Question
If you notice that your solved camera has a **90° rotation on the Y-axis** (or any large rotation), this is **normal and expected**. Here's why:
### Why Nuke Camera Solver May Rotate the Camera
1. **Ambiguity in camera solving**: Camera tracking has inherent ambiguities - the solver can't always determine the absolute world orientation
2. **Coordinate system choice**: The solver picks a convenient coordinate system that may not align with your scene's "natural" orientation
3. **Plate orientation**: If your plate is rotated (e.g., portrait vs landscape), the solver may rotate the camera to match
### Does This Affect the Conversion?
**No!** The converter handles **any rotation correctly**, including:
- 90° rotations
- 180° rotations
- Arbitrary rotations
The coordinate system conversion (`convert_yup_to_ydown`) is a **linear transformation** that preserves all rotations correctly. It only changes how the coordinate system is expressed (Y-up → Y-down), not the actual camera orientation.
### How to Verify Rotations Are Correct
Use the debug tool to analyze your camera rotations:
```bash
python debug_rotation.py inputs/your_camera.asci --frames 10
```
This will show:
- Rotation values for each frame
- Whether Y rotation is near ±90° (warns if so)
- Forward direction vectors (should be preserved after conversion)
- Rotation matrix determinants (should be ~1.0)
### Test 90° Rotation Handling
To verify the converter handles 90° rotations correctly:
```bash
python debug_rotation.py inputs/your_camera.asci --test-90deg
```
This runs a specific test case with a 90° Y rotation to ensure the conversion preserves it correctly.
### What If Motion Still Doesn't Match?
If your camera has a 90° rotation but motion still doesn't match in ComfyUI, check:
1. **Unit scale** - This is the most common issue
```bash
# Measure real distance, calculate scale
unit_scale = real_distance_meters / asci_translation_delta
```
2. **Lens parameters** - Must match Nuke camera exactly
- Focal length (mm)
- Sensor width/height (mm)
3. **Coordinate system** - The converter handles this, but verify:
```bash
python validate_conversion.py inputs/camera.asci outputs/camera_converted.txt --unit-scale 0.1
```
4. **Frame range** - Ensure you're comparing the same frames
### Example: Analyzing Your Camera
If your camera has rotations like:
```
Frame 1: Y rotation = -102.4°
Frame 2: Y rotation = -102.6°
```
This is fine! The converter will:
1. Convert Euler angles → rotation matrix (preserving the -102° rotation)
2. Convert coordinate system (Y-up → Y-down)
3. Compute world-to-camera translation
The **-102° rotation is preserved** - it's just expressed in OpenCV's coordinate system instead of Nuke's.
### Key Takeaway
**A 90° (or any) rotation in the solved camera is NOT a problem.** The converter preserves all rotations correctly. If motion doesn't match, look at:
- `unit_scale` (most common)
- Lens parameters
- Frame range
- Coordinate system validation (use `validate_conversion.py`)
@@ -1,5 +1,7 @@
{
"input": "../inputs/camTrack_v01.asci",
"input_translation": null,
"input_rotation": null,
"output": "../outputs/camTrack_v01_converted.txt",
"fps": 25.0,
"unit_scale": 1.0,
@@ -1,5 +1,7 @@
{
"input": "../inputs/nukeCam_unit-1_wRotation.asci",
"input_translation": "../inputs/nukeCam_unit-1-split_tr.asci",
"input_rotation": "../inputs/nukeCam_unit-1-split_rot.asci",
"output": "../outputs/nukeCam_unit-1_wRotation_converted.txt",
"fps": 25.0,
"unit_scale": 1,
+5
View File
@@ -6,6 +6,7 @@ This document captures the end-to-end workflow we established for converting Nuk
- **Nuke camera**: standard `Camera` node with Translation / Rotation channels keyed over time.
- **Export step**: use Nuke's *Export Ascii* tool with the column mapping shown in `ASCI_source_data/AnimationChannels_and_values_firstFrame_plus-ASCI-window.png`.
- Column order: `[translate.x, rotate.x, translate.y, rotate.y, translate.z, rotate.z]`.
- Prefer to stick to the combined export unless you want to split the channels into two files. When splitting, export translations as `Tx Ty Tz` and rotations as `Rx Ry Rz` (degrees) and reference them via the config keys described below.
- Rotations are exported in **degrees** using Nuke's **ZXY rotation order** (see `DataValidation-nuke.png`).
- **Raw file**: `nuke_CAM_exporter/inputs/camTrack_v01.asci` (162 lines covering frames 1001–1161 in this example).
@@ -55,6 +56,8 @@ Use the template in `configs/camera_config_template.json` (copy + rename per sho
```json
{
"input": "../inputs/camTrack_v01.asci",
"input_translation": null,
"input_rotation": null,
"output": "../outputs/camTrack_v06_converted.txt",
"fps": 25.0,
"unit_scale": 1.0,
@@ -77,6 +80,8 @@ Use the template in `configs/camera_config_template.json` (copy + rename per sho
}
```
Set `"input_translation"` / `"input_rotation"` when you export translations and rotations as two separate ASCI files. Leave them `null` (or omit entirely) when you rely on the combined `input` file.
Run:
```bash
@@ -26,7 +26,7 @@ def parse_args() -> argparse.Namespace:
type=Path,
nargs="?",
default=Path("inputs/camTrack_v01.asci"),
help="Path to Nuke ASCI export",
help="Path to Nuke ASCI export (combined order: Tx Rx Ty Ry Tz Rz)",
)
parser.add_argument(
"output",
@@ -35,6 +35,16 @@ def parse_args() -> argparse.Namespace:
default=Path("outputs/camTrack_v01_converted.txt"),
help="Destination RealEstate-style TXT file",
)
parser.add_argument(
"--input-translation",
type=Path,
help="Optional ASCI file containing translation columns only (order: Tx Ty Tz)",
)
parser.add_argument(
"--input-rotation",
type=Path,
help="Optional ASCI file containing rotation columns only (order: Rx Ry Rz in degrees)",
)
parser.add_argument("--config", type=Path, help="Optional JSON pipeline config")
parser.add_argument("--fps", type=float, default=DEFAULT_FPS, help="Frame rate for timestamps")
parser.add_argument("--fx", type=float, default=DEFAULT_FX, help="Normalized focal length x")
@@ -65,7 +75,9 @@ def parse_args() -> argparse.Namespace:
@dataclass
class ConverterSettings:
input_path: Path
input_path: Path | None
translation_input_path: Path | None
rotation_input_path: Path | None
output_path: Path
fps: float
fx: float
@@ -138,6 +150,18 @@ def resolve_settings(args: argparse.Namespace) -> ConverterSettings:
config = load_config(args.config)
input_path = resolve_path(config, "input", args.input)
def resolve_optional_path(config_key: str, arg_value: Path | None) -> Path | None:
if arg_value is not None:
return arg_value
cfg_val = config.get(config_key)
if cfg_val is None:
return None
return resolve_path(config, config_key, Path(cfg_val))
translation_input_path = resolve_optional_path("input_translation", args.input_translation)
rotation_input_path = resolve_optional_path("input_rotation", args.input_rotation)
output_path = resolve_path(config, "output", args.output)
fps = config.get("fps", args.fps)
@@ -157,6 +181,9 @@ def resolve_settings(args: argparse.Namespace) -> ConverterSettings:
metadata = {
"config": str(args.config) if args.config else None,
"input": str(input_path) if input_path else None,
"input_translation": str(translation_input_path) if translation_input_path else None,
"input_rotation": str(rotation_input_path) if rotation_input_path else None,
"lens": config.get("lens"),
"resolution": resolution_cfg or {"width_px": width, "height_px": height},
"unit_scale": unit_scale,
@@ -169,6 +196,8 @@ def resolve_settings(args: argparse.Namespace) -> ConverterSettings:
return ConverterSettings(
input_path=input_path,
translation_input_path=translation_input_path,
rotation_input_path=rotation_input_path,
output_path=output_path,
fps=fps,
fx=fx,
@@ -255,6 +284,29 @@ def iter_asci_poses(path: Path) -> Iterator[Tuple[List[List[float]], Tuple[float
yield euler_zxy_to_matrix(rx, ry, rz), (tx, ty, tz)
def iter_split_asci_poses(
translation_path: Path, rotation_path: Path
) -> Iterator[Tuple[List[List[float]], Tuple[float, float, float]]]:
translation_lines = [line.strip() for line in translation_path.read_text().splitlines() if line.strip()]
rotation_lines = [line.strip() for line in rotation_path.read_text().splitlines() if line.strip()]
if len(translation_lines) != len(rotation_lines):
raise ValueError(
f"Translation ({len(translation_lines)}) and rotation ({len(rotation_lines)}) ASCI files "
"must contain the same number of rows."
)
for idx, (t_line, r_line) in enumerate(zip(translation_lines, rotation_lines)):
t_values = t_line.split()
r_values = r_line.split()
if len(t_values) != 3:
raise ValueError(f"Expected 3 translation columns per line in {translation_path}, got {len(t_values)} (line {idx})")
if len(r_values) != 3:
raise ValueError(f"Expected 3 rotation columns per line in {rotation_path}, got {len(r_values)} (line {idx})")
tx, ty, tz = map(float, t_values)
rx, ry, rz = (math.radians(float(val)) for val in r_values)
yield euler_zxy_to_matrix(rx, ry, rz), (tx, ty, tz)
def camera_center_to_translation(
rotation: Sequence[Sequence[float]], camera_center: Tuple[float, float, float]
) -> Tuple[float, float, float]:
@@ -288,7 +340,23 @@ def format_pose_rows(
def main() -> None:
args = parse_args()
settings = resolve_settings(args)
poses = list(iter_asci_poses(settings.input_path))
# Determine data source: single combined ASCI or split translation/rotation files.
if settings.translation_input_path and settings.rotation_input_path:
poses = list(
iter_split_asci_poses(settings.translation_input_path, settings.rotation_input_path)
)
source_label = (
f"translation:{settings.translation_input_path.as_posix()} "
f"rotation:{settings.rotation_input_path.as_posix()}"
)
elif settings.translation_input_path or settings.rotation_input_path:
raise ValueError(
"Both input_translation and input_rotation must be provided together. "
"Either supply both split files or rely on the combined 'input' ASCI path."
)
else:
poses = list(iter_asci_poses(settings.input_path))
source_label = settings.input_path.as_posix()
total_available = len(poses)
start_idx = max(0, settings.frame_start)
end_idx = settings.frame_end if settings.frame_end is not None else total_available
@@ -302,7 +370,7 @@ def main() -> None:
frame_time_us = int(round(1_000_000 / settings.fps))
header = (
f"source:{settings.input_path.as_posix()} fps:{settings.fps} fx:{settings.fx} fy:{settings.fy} "
f"source:{source_label} fps:{settings.fps} fx:{settings.fx} fy:{settings.fy} "
f"cx:{settings.cx} cy:{settings.cy} width:{settings.width} height:{settings.height} unit_scale:{settings.unit_scale}"
)
-107
View File
@@ -1,107 +0,0 @@
#!/usr/bin/env python3
"""
Nuke script generator for testing camera export pipeline.
This script generates a Nuke script (.nk) that creates a simple test scene with:
- A camera with known, predictable motion
- Simple geometry (a grid/card) for reference
- Camera animation that can be easily validated
Usage:
python test_nuke_scene.py --output test_scene.nk
"""
import argparse
from pathlib import Path
def generate_nuke_script(output_path: Path, num_frames: int = 50) -> None:
"""Generate a Nuke script with a test camera animation."""
# Create a simple camera animation:
# - Start at (0, 0, -5) looking at origin
# - Move forward along Z axis (dolly in)
# - Slight pan left
# - Known focal length and filmback for easy validation
script_lines = [
"set cut_paste_input [stack 0]",
"version 15.0 v1",
"push $cut_paste_input",
"",
"# Test Camera - Simple forward dolly with pan",
"Camera2 {",
" inputs 0",
f" translate {{curve x {0.0} {num_frames-1} {0.0}; curve y {0.0} {num_frames-1} {0.0}; curve z {-5.0} {num_frames-1} {-3.0}}}",
f" rotate {{curve x {0.0} {num_frames-1} {0.0}; curve y {-10.0} {num_frames-1} {-15.0}; curve z {0.0} {num_frames-1} {0.0}}}",
" focal {35.0}",
" haperture {36.0}",
" vaperture {24.0}",
" near {0.1}",
" far {1000.0}",
" win_translate {{0.5 0.5}}",
" win_scale {{1 1}}",
" winroll {0}",
" fov {54.432}",
" f_stop {2.8}",
" focus_distance {5.0}",
" lens {35.0}",
" name Camera2",
" selected true",
" xpos 0",
" ypos 0",
"}",
"",
"# Reference Grid - helps visualize camera motion",
"Grid2 {",
" inputs 0",
" display {luminance}",
" name Grid2",
" selected false",
" xpos 0",
" ypos -100",
"}",
"",
"# Render node for validation",
"Write1 {",
" file {test_render_%04d.exr}",
" file_type exr",
" datatype {32 bit float}",
" compression {Zip (1 scanline)}",
" name Write1",
" selected false",
" xpos 0",
" ypos -200",
"}",
]
output_path.write_text("\n".join(script_lines))
print(f"Generated Nuke script: {output_path}")
print(f" - {num_frames} frames")
print(f" - Camera starts at (0, 0, -5), ends at (0, 0, -3)")
print(f" - Camera rotates Y from -10° to -15°")
print(f" - Focal: 35mm, Filmback: 36x24mm")
def main():
parser = argparse.ArgumentParser(description="Generate Nuke test scene")
parser.add_argument(
"--output",
type=Path,
default=Path("test_scene.nk"),
help="Output Nuke script path",
)
parser.add_argument(
"--frames",
type=int,
default=50,
help="Number of frames in animation",
)
args = parser.parse_args()
generate_nuke_script(args.output, args.frames)
if __name__ == "__main__":
main()
-57
View File
@@ -1,57 +0,0 @@
#!/bin/bash
# Test pipeline script for validating the camera conversion workflow
#
# This script:
# 1. Generates a test Nuke scene
# 2. (Manual step) Export camera from Nuke as ASCI
# 3. Converts ASCI to pose format
# 4. Validates the conversion
#
# Usage: ./test_pipeline.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
echo "=== Step 1: Generate Nuke test scene ==="
python3 test_nuke_scene.py --output test_scene.nk --frames 50
echo "✓ Generated test_scene.nk"
echo ""
echo "=== Step 2: Manual step - Export camera from Nuke ==="
echo "1. Open test_scene.nk in Nuke"
echo "2. Select Camera2 node"
echo "3. Export Ascii with columns: [translate.x, rotate.x, translate.y, rotate.y, translate.z, rotate.z]"
echo "4. Save as: inputs/test_camera.asci"
echo ""
read -p "Press Enter when you've exported the ASCI file..."
if [ ! -f "inputs/test_camera.asci" ]; then
echo "ERROR: inputs/test_camera.asci not found!"
exit 1
fi
echo ""
echo "=== Step 3: Convert ASCI to pose format ==="
python3 nuke_ASCI-2-Pose_converter.py \
inputs/test_camera.asci \
outputs/test_camera_converted.txt \
--fps 25.0 \
--focal-length-mm 35.0 \
--sensor-width-mm 36.0 \
--sensor-height-mm 24.0 \
--width 1920 \
--height 1080 \
--unit-scale 1.0
echo ""
echo "=== Step 4: Validate conversion ==="
python3 validate_conversion.py \
inputs/test_camera.asci \
outputs/test_camera_converted.txt \
--unit-scale 1.0
echo ""
echo "=== Pipeline test complete! ==="
-48
View File
@@ -1,48 +0,0 @@
set cut_paste_input [stack 0]
version 15.0 v1
push $cut_paste_input
# Test Camera - Simple forward dolly with pan
Camera2 {
inputs 0
translate {curve x 0.0 80 0.0; curve y 0.0 80 0.0; curve z -5.0 80 -3.0}
rotate {curve x 0.0 80 0.0; curve y -10.0 80 -15.0; curve z 0.0 80 0.0}
focal {35.0}
haperture {36.0}
vaperture {24.0}
near {0.1}
far {1000.0}
win_translate {{0.5 0.5}}
win_scale {{1 1}}
winroll {0}
fov {54.432}
f_stop {2.8}
focus_distance {5.0}
lens {35.0}
name Camera2
selected true
xpos 0
ypos 0
}
# Reference Grid - helps visualize camera motion
Grid2 {
inputs 0
display {luminance}
name Grid2
selected false
xpos 0
ypos -100
}
# Render node for validation
Write1 {
file {test_render_%04d.exr}
file_type exr
datatype {32 bit float}
compression {Zip (1 scanline)}
name Write1
selected false
xpos 0
ypos -200
}