import rhinoscriptsyntax as rs
import math
import Rhino
import scriptcontext as sc
import random
from System.Drawing import Bitmap
def grayscale(color):
    return (color.R + color.G + color.B) / 3
def get_image_pixels(image_path):
    img = Bitmap(image_path)
    width, height = img.Width, img.Height
    pixels = []
    for y in range(height):
        row = []
        for x in range(width):
            color = img.GetPixel(x, height - 1 - y)
            gray = grayscale(color)
            row.append(gray)
        pixels.append(row)
    return pixels, width, height
def mask_circle(pixels, width, height):
    cx, cy = width / 2, height / 2
    r = min(width, height) / 2
    masked = []
    for y in range(height):
        row = []
        for x in range(width):
            d = math.hypot(x - cx, y - cy)
            row.append(pixels[y][x] if d <= r else 255)
        masked.append(row)
    return masked
pin_points = []
def create_pin_points(center, radius, count):
    global pin_points
    pin_points = []
    for i in range(count):
        angle = 2 * math.pi * i / count
        x = center[0] + radius * math.cos(angle)
        y = center[1] + radius * math.sin(angle)
        z = center[2]
        pt = Rhino.Geometry.Point3d(x, y, z)
        pin_points.append((pt, i+1))
        if count <= 200 or i % 10 == 0:
            rs.AddTextDot(str(i+1), pt)
def average_grayscale_on_line(p1, p2, pixels, width, height):
    dx, dy = p2.X - p1.X, p2.Y - p1.Y
    length = math.hypot(dx, dy)
    samples = int(length)
    if samples == 0:
        return 255
    inv = 1.0 / samples
    total, count = 0, 0
    for i in range(samples):
        t = i * inv
        x = (1 - t) * p1.X + t * p2.X
        y = (1 - t) * p1.Y + t * p2.Y
        px, py = int(x + 0.5), int(y + 0.5)
        if 0 <= px < width and 0 <= py < height:
            total += pixels[py][px]
            count += 1
    return total / count if count > 0 else 255
def lighten_pixel(pixels, x, y, strength=0.02):
    if 0 <= x < len(pixels[0]) and 0 <= y < len(pixels):
        g = pixels[y][x]
        pixels[y][x] = min(255, g + (255 - g) * strength)
def draw_string_art(pixels, pin_points, width, height, total_lines=500, min_dist_ratio=None):
    sequence = []
    radius = min(width, height) / 2
    if min_dist_ratio is None:
        min_dist_ratio = max(0.1, 10.0 / len(pin_points))
    min_dist = radius * min_dist_ratio
    used_lines = set()
    current_pt, _ = random.choice(pin_points)
    poly_pts = [current_pt]
    lines_drawn = 0
    while lines_drawn < total_lines:
        min_gray = float('inf')
        next_pt = None
        for pt, _ in pin_points:
            if pt == current_pt:
                continue
            dist = current_pt.DistanceTo(pt)
            if dist < min_dist:
                continue
            key = tuple(sorted([(current_pt.X, current_pt.Y), (pt.X, pt.Y)]))
            if key in used_lines:
                continue
            avg = average_grayscale_on_line(current_pt, pt, pixels, width, height)
            if avg < min_gray:
                min_gray = avg
                next_pt = pt
        if next_pt:
            key = tuple(sorted([(current_pt.X, current_pt.Y), (next_pt.X, next_pt.Y)]))
            used_lines.add(key)
            l = current_pt.DistanceTo(next_pt)
            samples = int(l)
            for i in range(samples):
                t = i / samples
                x = int((1 - t) * current_pt.X + t * next_pt.X + 0.5)
                y = int((1 - t) * current_pt.Y + t * next_pt.Y + 0.5)
                lighten_pixel(pixels, x, y, strength=contrast)
            poly_pts.append(next_pt)
            current_pt = next_pt
            for pt, idx in pin_points:
                if pt == next_pt:
                    sequence.append(idx)
                    break
            lines_drawn += 1
        else:
            current_pt, _ = random.choice(pin_points)
            poly_pts.append(current_pt)
    polyline = Rhino.Geometry.Polyline(poly_pts)
    sc.doc.Objects.AddPolyline(polyline)
    sc.doc.Views.Redraw()
    print("Done. Here is the ordered list of pin IDs:")
    print(sequence)
image_path = rs.OpenFileName("Choose an image file")
contrast = rs.GetReal("Contrast level (0.00-1.00)", 0.25)
line_count = rs.GetInteger("Number of lines", 700)
pin_count = rs.GetInteger("Number of pins", 200)
max_lines = min(line_count, pin_count * 6)
if image_path:
    print("Calculating...")
    pixels, img_w, img_h = get_image_pixels(image_path)
    masked = mask_circle(pixels, img_w, img_h)
    create_pin_points(center=(img_w/2, img_h/2, 0), radius=img_w/2, count=pin_count)
    draw_string_art(masked, pin_points, img_w, img_h, total_lines=max_lines)