Skip to content
Draft
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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,10 @@ cython_debug/
marimo/_static/
marimo/_lsp/
__marimo__/

# Thread Art Generator specific
test_output/
demo_output/
test_images/
test_files/
screenshots/
117 changes: 115 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,115 @@
# thread-art
Software to generate paths for thread art.
# Thread Art Generator

A PySide6 GUI application for generating thread art paths from anchor points and target images.

## Features

- **DXF Import**: Load anchor point positions from DXF files
- **Image Processing**: Process target images for optimal thread art generation
- **Thread Path Generation**: Generate optimal threading paths using advanced algorithms
- **CSV Export**: Export thread paths as CSV files for manufacturing
- **Simulation**: Generate realistic previews of the final thread art
- **User-friendly GUI**: Intuitive interface built with PySide6

## Installation

1. Clone the repository:
```bash
git clone https://github.com/juice-tea/thread-art.git
cd thread-art
```

2. Install required dependencies:
```bash
pip install -r requirements.txt
```

3. Run the application:
```bash
python main.py
```

## Usage

### 1. Load Anchor Points
- Click "Load DXF (Anchor Points)" to load anchor point positions from a DXF file
- Or use the default circular anchor pattern (256 points)
- Supported DXF entities: POINT, CIRCLE, LINE, POLYLINE

### 2. Load Target Image
- Click "Load Image" to select your target image
- Supported formats: PNG, JPG, JPEG, BMP, TIFF
- Image will be automatically preprocessed for optimal thread art generation

### 3. Configure Parameters
- **Max Lines**: Maximum number of thread lines (100-10000)
- **Line Weight**: Darkness factor for each thread line (1.0-100.0)

### 4. Generate Thread Art
- Click "Generate Thread Art" to start the generation process
- The algorithm will find the optimal path between anchor points
- Progress will be shown in the progress bar

### 5. Export Results
- **Export Path to CSV**: Save the threading sequence as a CSV file
- **Export Simulation Image**: Save the thread art preview as an image

## File Formats

### DXF Files
The application reads anchor points from DXF files. Supported entities:
- `POINT`: Direct anchor points
- `CIRCLE`: Center points used as anchors
- `LINE`: Both endpoints used as anchors
- `POLYLINE`/`LWPOLYLINE`: All vertices used as anchors

### CSV Output
The exported CSV contains:
- Step number
- Anchor point index
- X and Y coordinates (normalized 0-1)
- Metadata about generation parameters

### Images
- Input: PNG, JPG, JPEG, BMP, TIFF
- Output: PNG simulation images

## Algorithm

The thread art generation uses a greedy algorithm that:
1. Starts from an initial anchor point
2. Evaluates all possible next connections
3. Selects the line that best matches the target image darkness
4. Updates the working image to simulate thread placement
5. Repeats until the maximum line count is reached

## Testing

Run the test suite to verify installation:
```bash
python test_complete.py
```

Create test files for experimentation:
```bash
python create_test_files.py
```

## System Requirements

- Python 3.8+
- PySide6 (Qt6)
- NumPy
- OpenCV
- ezdxf
- Pillow
- SciPy
- Matplotlib

## License

MIT License - see LICENSE file for details.

## Contributing

Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.
89 changes: 89 additions & 0 deletions create_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""
Demo of thread art generation functionality.
"""

import os
import matplotlib.pyplot as plt
import numpy as np
from src.core.dxf_processor import DXFProcessor
from src.core.image_processor import ImageProcessor
from src.core.thread_art import ThreadArtGenerator

def create_demo():
"""Create a visual demo of the thread art generation process."""
print("🎨 Creating Thread Art Generation Demo")
print("=" * 40)

# Load test data
print("Loading test data...")
anchors = DXFProcessor.create_circular_anchors(128) # More points for better result
if os.path.exists('test_images/test_image.png'):
image = ImageProcessor.load_and_preprocess('test_images/test_image.png', (400, 400))
else:
# Create a simple test pattern
image = np.ones((400, 400), dtype=np.uint8) * 255
image[150:250, 150:250] = 100 # Dark square

print(f"Using {len(anchors)} anchor points")
print(f"Image size: {image.shape}")

# Generate thread art
print("Generating thread art...")
generator = ThreadArtGenerator(anchors, image)
path = generator.generate_path(max_lines=500, line_weight=30)
simulation = generator.generate_simulation(path)

print(f"Generated path with {len(path)} lines")

# Create visualization
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
fig.suptitle('Thread Art Generator - Demo Results', fontsize=16, fontweight='bold')

# Plot 1: Anchor points
anchor_array = np.array(anchors)
axes[0, 0].scatter(anchor_array[:, 0], anchor_array[:, 1], s=10, c='red', alpha=0.7)
axes[0, 0].set_title(f'Anchor Points ({len(anchors)} points)')
axes[0, 0].set_aspect('equal')
axes[0, 0].grid(True, alpha=0.3)

# Plot 2: Original image
axes[0, 1].imshow(image, cmap='gray')
axes[0, 1].set_title('Target Image (Processed)')
axes[0, 1].axis('off')

# Plot 3: Path visualization (first 50 lines)
axes[1, 0].scatter(anchor_array[:, 0], anchor_array[:, 1], s=5, c='lightgray', alpha=0.5)
for i in range(min(50, len(path) - 1)):
p1 = anchors[path[i]]
p2 = anchors[path[i + 1]]
axes[1, 0].plot([p1[0], p2[0]], [p1[1], p2[1]], 'b-', alpha=0.6, linewidth=0.5)
axes[1, 0].set_title(f'Thread Path (first 50 of {len(path)} lines)')
axes[1, 0].set_aspect('equal')

# Plot 4: Final simulation
axes[1, 1].imshow(simulation, cmap='gray')
axes[1, 1].set_title('Thread Art Simulation')
axes[1, 1].axis('off')

plt.tight_layout()

# Save the demo
os.makedirs('demo_output', exist_ok=True)
plt.savefig('demo_output/thread_art_demo.png', dpi=150, bbox_inches='tight')
print("Demo visualization saved: demo_output/thread_art_demo.png")

# Save individual images
plt.figure(figsize=(8, 8))
plt.imshow(simulation, cmap='gray')
plt.title('Thread Art Simulation Result', fontsize=14, fontweight='bold')
plt.axis('off')
plt.savefig('demo_output/simulation_result.png', dpi=150, bbox_inches='tight')
print("Simulation result saved: demo_output/simulation_result.png")

return True

if __name__ == "__main__":
create_demo()
print("\n🎉 Demo created successfully!")
print("Check the demo_output/ directory for visualization files.")
123 changes: 123 additions & 0 deletions create_test_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
Create test files for the thread art application.
"""

import numpy as np
import cv2
import ezdxf
from PIL import Image, ImageDraw
import os

def create_test_image():
"""Create a test image for thread art generation."""
# Create a simple test image with geometric patterns
width, height = 800, 600

# Create image using PIL
img = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(img)

# Draw a circle
center_x, center_y = width // 2, height // 2
radius = min(width, height) // 4
draw.ellipse([center_x - radius, center_y - radius,
center_x + radius, center_y + radius],
outline='black', width=3)

# Draw some lines to create interesting patterns
for i in range(8):
angle = i * np.pi / 4
x1 = center_x + int(radius * 0.7 * np.cos(angle))
y1 = center_y + int(radius * 0.7 * np.sin(angle))
x2 = center_x + int(radius * 1.3 * np.cos(angle))
y2 = center_y + int(radius * 1.3 * np.sin(angle))
draw.line([x1, y1, x2, y2], fill='black', width=2)

# Add some text
try:
# Try to draw some text
draw.text((center_x - 50, center_y + radius + 20),
"Thread Art", fill='black')
except:
# If font is not available, skip text
pass

# Save the image
img.save('test_images/test_image.png')
print("✓ Created test image: test_images/test_image.png")

def create_test_dxf():
"""Create a test DXF file with anchor points."""
# Create new DXF document
doc = ezdxf.new('R2010')
msp = doc.modelspace()

# Create anchor points in a circle
num_points = 64
radius = 50
center_x, center_y = 0, 0

for i in range(num_points):
angle = 2 * np.pi * i / num_points
x = center_x + radius * np.cos(angle)
y = center_y + radius * np.sin(angle)

# Add a point
msp.add_point((x, y))

# Add some additional points for variety
# Inner circle
inner_radius = 25
for i in range(0, num_points, 4): # Every 4th point
angle = 2 * np.pi * i / num_points
x = center_x + inner_radius * np.cos(angle)
y = center_y + inner_radius * np.sin(angle)
msp.add_circle((x, y), 0.5) # Small circles as anchor points

# Save the DXF file
doc.saveas('test_files/test_anchors.dxf')
print("✓ Created test DXF: test_files/test_anchors.dxf")

def create_complex_test_image():
"""Create a more complex test image."""
width, height = 800, 600

# Create image with OpenCV for more complex patterns
img = np.ones((height, width), dtype=np.uint8) * 255

# Draw a portrait-like pattern
center_x, center_y = width // 2, height // 2

# Face outline
cv2.ellipse(img, (center_x, center_y), (120, 160), 0, 0, 360, 100, -1)
cv2.ellipse(img, (center_x, center_y), (110, 150), 0, 0, 360, 255, -1)

# Eyes
cv2.circle(img, (center_x - 40, center_y - 30), 15, 50, -1)
cv2.circle(img, (center_x + 40, center_y - 30), 15, 50, -1)

# Nose
cv2.line(img, (center_x, center_y - 10), (center_x - 5, center_y + 20), 100, 2)

# Mouth
cv2.ellipse(img, (center_x, center_y + 40), (30, 15), 0, 0, 180, 80, 2)

# Save the image
cv2.imwrite('test_images/portrait_test.png', img)
print("✓ Created complex test image: test_images/portrait_test.png")

if __name__ == "__main__":
# Create directories
os.makedirs('test_images', exist_ok=True)
os.makedirs('test_files', exist_ok=True)

# Create test files
create_test_image()
create_test_dxf()
create_complex_test_image()

print("\n🎉 All test files created successfully!")
print("You can now use these files to test the Thread Art Generator:")
print("- DXF file: test_files/test_anchors.dxf")
print("- Test images: test_images/test_image.png, test_images/portrait_test.png")
Loading