Try this code
#!/bin/bash
# Laser Engraving Converter - Final Vertical Fix
echo "========================================"
echo " LASER ENGRAVING CONVERTER "
echo "========================================"
echo ""
# Get filename
read -p "Enter image name (without .png): " base_name
# Find the file
if [ -f "${base_name}.png" ]; then
png_file="${base_name}.png"
elif [ -f "${base_name}.PNG" ]; then
png_file="${base_name}.PNG"
elif [ -f "$base_name" ]; then
png_file="$base_name"
else
echo "ERROR: File '${base_name}' not found!"
read -p "Press Enter to exit..."
exit 1
fi
echo "Found: $png_file"
gcode_file="${base_name}laser.ngc"
echo "Output: $gcode_file"
echo ""
# Get settings
read -p "Enter target width in mm: " target_width_mm
read -p "Enter DPI [75]: " dpi_input
dpi=${dpi_input:-75}
read -p "Enter feed rate [500]: " feed_rate_input
feed_rate=${feed_rate_input:-500}
read -p "Enter safe Z height [10]: " safe_z_input
safe_z=${safe_z_input:-10}
read -p "Enter engrave Z height [0]: " engrave_z_input
engrave_z=${engrave_z_input:-0}
read -p "Enter minimum power [1]: " min_power_input
min_power=${min_power_input:-1}
read -p "Enter maximum power [500]: " max_power_input
max_power=${max_power_input:-500}
read -p "Cross-hatching? (y/n) [n]: " cross_hatch_input
cross_hatch=${cross_hatch_input:-n}
echo ""
echo "=== SETTINGS ==="
echo "Image: $png_file"
echo "Width: ${target_width_mm}mm"
echo "DPI: $dpi"
echo "Feed: ${feed_rate} mm/min"
echo "Safe Z: ${safe_z}mm"
echo "Engrave Z: ${engrave_z}mm"
echo "Power: ${min_power}-${max_power}"
echo "Cross-hatch: $cross_hatch"
echo ""
read -p "Continue? (y/n): " confirm
if
! "$confirm" =~ ^[Yy]$; then
echo "Cancelled."
read -p "Press Enter to exit..."
exit 0
fi
echo "Converting..."
echo ""
cat > /tmp/laser_final_fix.py << 'PYTHON_EOF'
import sys
import os
from PIL import Image
def main():
if len(sys.argv) != 11:
print("ERROR: Wrong number of arguments")
return 1
# Parse arguments
input_file = sys.argv[1]
target_width_mm = float(sys.argv[2])
dpi = int(sys.argv[3])
feed_rate = int(sys.argv[4])
safe_z = float(sys.argv[5])
engrave_z = float(sys.argv[6])
min_power = int(sys.argv[7])
max_power = int(sys.argv[8])
cross_hatch = sys.argv[9]
output_file = sys.argv[10]
print(f"Converting: {os.path.basename(input_file)}")
print(f"Power range: {min_power}-{max_power}")
try:
# Load image
img = Image.open(input_file)
if img.mode != 'L':
img = img.convert('L')
orig_w, orig_h = img.size
# Calculate new size
pixels_per_mm = dpi / 25.4
target_px_w = int(target_width_mm * pixels_per_mm)
aspect_ratio = orig_h / orig_w
target_px_h = int(target_px_w * aspect_ratio)
actual_mm_w = target_px_w / pixels_per_mm
actual_mm_h = target_px_h / pixels_per_mm
mm_per_pixel = 25.4 / dpi
print(f"Image size: {target_px_w}x{target_px_h} pixels")
print(f"Physical size: {actual_mm_w:.1f}x{actual_mm_h:.1f} mm")
print(f"mm per pixel: {mm_per_pixel:.3f}")
# Resize
img = img.resize((target_px_w, target_px_h))
pixels = list(img.getdata())
# Generate G-code
with open(output_file, 'w') as f:
# Header
f.write('; Laser engraving - FINAL FIX\n')
f.write(f'; Image: {os.path.basename(input_file)}\n')
f.write(f'; Size: {actual_mm_w:.1f}x{actual_mm_h:.1f} mm\n')
f.write(f'; DPI: {dpi}\n')
f.write(f'; Feed: {feed_rate}\n')
f.write(f'; Power: {min_power}-{max_power}\n\n')
# Setup
f.write('G21\nG90\nG64\nG17\nG54\n\n')
# Initialize
f.write(f'F{feed_rate}\n')
f.write(f'G0 Z{safe_z:.1f}\n')
f.write('G0 X0 Y0\n')
f.write(f'G0 Z{engrave_z:.1f}\n')
f.write('M3\n\n')
# HORIZONTAL PASSES
f.write('; --- HORIZONTAL PASSES ---\n')
print("\nGenerating horizontal passes...")
for y in range(target_px_h):
# Flip Y: image row y (0=top) -> machine Y (target_px_h-1-y)*mm_per_pixel
y_pos = (target_px_h - 1 - y) * mm_per_pixel
if y % 2 == 0:
# Left to right
f.write(f'G0 X0 Y{y_pos:.3f}\n')
for x in range(target_px_w):
pixel = pixels[y * target_px_w + x]
power = min_power + int((max_power - min_power) * (255 - pixel) / 255)
f.write(f'G1 X{x*mm_per_pixel:.3f} S{power}\n')
else:
# Right to left
f.write(f'G0 X{actual_mm_w:.3f} Y{y_pos:.3f}\n')
for x in range(target_px_w-1, -1, -1):
pixel = pixels[y * target_px_w + x]
power = min_power + int((max_power - min_power) * (255 - pixel) / 255)
f.write(f'G1 X{x*mm_per_pixel:.3f} S{power}\n')
if y % 50 == 0:
print(f" Row {y+1}/{target_px_h}")
# VERTICAL PASSES
if cross_hatch.lower() == 'y':
f.write('\n; --- VERTICAL PASSES (CROSS-HATCH) ---\n')
print("\nGenerating vertical passes...")
print("DEBUG: This should engrave on the way UP, not on the way DOWN!")
for x in range(target_px_w):
x_pos = x * mm_per_pixel
if x % 2 == 0:
# Even columns: bottom to top (engrave on the way UP)
f.write(f'G0 X{x_pos:.3f} Y0\n')
# We want to go from Y=0 to Y=max
# Read pixels from BOTTOM (image row target_px_h-1) to TOP (image row 0)
for step in range(target_px_h):
# step: 0 to target_px_h-1 (bottom to top in machine coords)
# image_y: target_px_h-1 to 0 (bottom to top in image)
image_y = target_px_h - 1 - step
pixel = pixels[image_y * target_px_w + x]
power = min_power + int((max_power - min_power) * (255 - pixel) / 255)
current_y = step * mm_per_pixel # 0 to max
f.write(f'G1 Y{current_y:.3f} S{power}\n')
else:
# Odd columns: top to bottom (engrave on the way DOWN)
f.write(f'G0 X{x_pos:.3f} Y{actual_mm_h:.3f}\n')
# We want to go from Y=max to Y=0
# Read pixels from TOP (image row 0) to BOTTOM (image row target_px_h-1)
for step in range(target_px_h):
# step: 0 to target_px_h-1 (represents position from top)
image_y = step # 0 to target_px_h-1 (top to bottom in image)
pixel = pixels[image_y * target_px_w + x]
power = min_power + int((max_power - min_power) * (255 - pixel) / 255)
current_y = actual_mm_h - (step * mm_per_pixel) # max to 0
f.write(f'G1 Y{current_y:.3f} S{power}\n')
if x % 50 == 0:
print(f" Column {x+1}/{target_px_w}")
# End program
f.write('\n; --- END ---\n')
f.write('M5\n')
f.write(f'G0 Z{safe_z:.1f}\n')
f.write('G0 X0 Y0\n')
f.write('M2\n')
print(f"\n✅ G-code written to: {output_file}")
# Show sample of vertical section
if cross_hatch.lower() == 'y' and os.path.exists(output_file):
with open(output_file, 'r') as f:
lines = f.readlines()
# Find vertical section
for i, line in enumerate(lines):
if 'VERTICAL PASSES' in line:
print("\n=== SAMPLE OF VERTICAL SECTION ===")
# Show first 20 lines of vertical section
for j in range(i, min(i+20, len(lines))):
print(f" {lines[j].rstrip()}")
print("...")
break
return 0
except Exception as e:
print(f"\n❌ ERROR: {e}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())
PYTHON_EOF
# Run the Python script
python3 /tmp/laser_final_fix.py \
"$png_file" \
"$target_width_mm" \
"$dpi" \
"$feed_rate" \
"$safe_z" \
"$engrave_z" \
"$min_power" \
"$max_power" \
"$cross_hatch" \
"$gcode_file"
result=$?
echo ""
echo "========================================"
if [ $result -eq 0 ]; then
echo "CONVERSION COMPLETE!"
echo "File: $gcode_file"
echo ""
else
echo "CONVERSION FAILED!"
fi
echo ""
read -p "Press Enter to exit..."