2011-10-09 10 views
5

Qual è lo strumento giusto per il lavoro se voglio scrivere uno script Python che produce grafica vettoriale in formato PDF? In particolare, ho bisogno di disegnare poligoni pieni con angoli arrotondati (cioè figure piane composte da linee rette e archi circolari).Produce file PDF, disegna poligoni con angoli arrotondati

Sembra che matplotlib rende abbastanza facile disegnare rettangoli con angoli arrotondati e poligoni generici con angoli acuti. Tuttavia, per disegnare poligoni con angoli arrotondati, sembra che debba prima calcolare una curva di Bézier che approssimi la forma.

C'è qualcosa di più semplice disponibile? O c'è un'altra libreria che posso usare per calcolare la curva di Bézier che approssima la forma che voglio produrre? Idealmente, vorrei semplicemente specificare la coppia (posizione, raggio dell'angolo) per ogni vertice.

Ecco un esempio: Vorrei specificare il poligono rosso (+ il raggio di ciascun angolo) e la libreria sarebbe uscita la figura grigio:

example

(Per poligono convesso ho potuto ingannare e utilizzare una penna spessa per disegnare il contorno del poligono.Tuttavia, questo non funziona nel caso non convesso.)

risposta

4

Per quanto riguarda la produzione di file PDF, vorrei suggerire di dare un'occhiata alla libreria cairo, una grafica vettoriale libaray che supporta il "disegno" nelle superfici PDF. Ha anche collegamenti Python.

Come per il disegno di poligoni con angoli arrotondati, non sono a conoscenza di alcuna libreria grafica che supporti questo fuori dalla scatola.

Ma non dovrebbe essere troppo complicato calcolare le coordinate dell'arco in angoli di poligono dato il raggio dell'angolo. Fondamentalmente devi trovare il punto sulla bisettrice di due bordi adiacenti che ha la distanza r (cioè il raggio desiderato) da entrambi i lati. Questo è il centro dell'arco, per trovare il punto iniziale e finale, dovrai proiettare da questo punto ai due bordi.

Ci potrebbero essere casi non banali, ad es. cosa fare, se i bordi dei poligoni sono troppo corti per adattarsi a due archi (suppongo che in questo caso dovrai selezionare un raggio più piccolo), e forse altri, al momento non sono a conoscenza ...

HTH

+0

Grazie per la risposta! Certo, calcolare le coordinate dell'arco e le coordinate del segmento non è così difficile; cioè, potrei facilmente disegnare il * contorno * della forma usando archi separati e segmenti di linea. Tuttavia, come posso ottenere una figura piana * piena *? Posso usare la libreria Cairo per comporre un percorso chiuso da archi e segmenti di linea e quindi dire alla biblioteca di riempire l'interno del percorso? –

+1

Sì, è possibile :) – MartinStettner

13

Ecco una soluzione matplotlib un po 'hacky. Le principali complicazioni sono legate all'uso degli oggetti matplotlib Path per creare un composito Path.

#!/usr/bin/env python 

import numpy as np 
from matplotlib.path import Path 
from matplotlib.patches import PathPatch, Polygon 
from matplotlib.transforms import Bbox, BboxTransformTo 

def side(a, b, c): 
    "On which side of line a-b is point c? Returns -1, 0, or 1." 
    return np.sign(np.linalg.det(np.c_[[a,b,c],[1,1,1]])) 

def center((prev, curr, next), radius): 
    "Find center of arc approximating corner at curr." 
    p0, p1 = prev 
    c0, c1 = curr 
    n0, n1 = next 
    dp = radius * np.hypot(c1 - p1, c0 - p0) 
    dn = radius * np.hypot(c1 - n1, c0 - n0) 
    p = p1 * c0 - p0 * c1 
    n = n1 * c0 - n0 * c1 
    results = \ 
     np.linalg.solve([[p1 - c1, c0 - p0], 
         [n1 - c1, c0 - n0]], 
         [[p - dp, p - dp, p + dp, p + dp], 
         [n - dn, n + dn, n - dn, n + dn]]) 
    side_n = side(prev, curr, next) 
    side_p = side(next, curr, prev) 
    for r in results.T: 
     if (side(prev, curr, r), side(next, curr, r)) == (side_n, side_p): 
      return r 
    raise ValueError, "Cannot find solution" 

def proj((prev, curr, next), center): 
    "Project center onto lines prev-curr and next-curr." 
    p0, p1 = prev = np.asarray(prev) 
    c0, c1 = curr = np.asarray(curr) 
    n0, n1 = next = np.asarray(next) 
    pc = curr - prev 
    nc = curr - next 
    pc2 = np.dot(pc, pc) 
    nc2 = np.dot(nc, nc) 
    return (prev + np.dot(center - prev, pc)/pc2 * pc, 
      next + np.dot(center - next, nc)/nc2 * nc) 

def rad2deg(angle): 
    return angle * 180.0/np.pi 

def angle(center, point): 
    x, y = np.asarray(point) - np.asarray(center) 
    return np.arctan2(y, x) 

def arc_path(center, start, end): 
    "Return a Path for an arc from start to end around center." 
    # matplotlib arcs always go ccw so we may need to mirror 
    mirror = side(center, start, end) < 0 
    if mirror: 
     start *= [1, -1] 
     center *= [1, -1] 
     end *= [1, -1] 
    return Path.arc(rad2deg(angle(center, start)), 
        rad2deg(angle(center, end))), \ 
      mirror 

def path(vertices, radii): 
    "Return a Path for a closed rounded polygon." 
    if np.isscalar(radii): 
     radii = np.repeat(radii, len(vertices)) 
    else: 
     radii = np.asarray(radii) 
    pv = [] 
    pc = [] 
    first = True 
    for i in range(len(vertices)): 
     if i == 0: 
      seg = (vertices[-1], vertices[0], vertices[1]) 
     elif i == len(vertices) - 1: 
      seg = (vertices[-2], vertices[-1], vertices[0]) 
     else: 
      seg = vertices[i-1:i+2] 
     r = radii[i] 
     c = center(seg, r) 
     a, b = proj(seg, c) 
     arc, mirror = arc_path(c, a, b) 
     m = [1,1] if not mirror else [1,-1] 
     bb = Bbox([c, c + (r, r)]) 
     iter = arc.iter_segments(BboxTransformTo(bb)) 
     for v, c in iter: 
      if c == Path.CURVE4: 
       pv.extend([m * v[0:2], m * v[2:4], m * v[4:6]]) 
       pc.extend([c, c, c]) 
      elif c == Path.MOVETO: 
       pv.append(m * v) 
       if first: 
        pc.append(Path.MOVETO) 
        first = False 
       else: 
        pc.append(Path.LINETO) 
    pv.append([0,0]) 
    pc.append(Path.CLOSEPOLY) 

    return Path(pv, pc) 

if __name__ == '__main__': 
    from matplotlib import pyplot 
    fig = pyplot.figure() 
    ax = fig.add_subplot(111) 
    vertices = [[3,0], [5,2], [10,0], [6,9], [6,5], [3, 5], [0,2]] 

    patch = Polygon(vertices, edgecolor='red', facecolor='None', 
        linewidth=1) 
    ax.add_patch(patch) 

    patch = PathPatch(path(vertices, 0.5), 
         edgecolor='black', facecolor='blue', alpha=0.4, 
         linewidth=2) 
    ax.add_patch(patch) 

    ax.set_xlim(-1, 11) 
    ax.set_ylim(-1, 9) 
    fig.savefig('foo.pdf') 

output of script above

+0

Grazie, è fantastico! Ho accettato l'altra risposta, poiché sembra che Cairo sia lo strumento "giusto" per questo tipo di attività, ma è davvero bello vedere che lo si può fare anche in matplotlib.In particolare, il frammento di codice che costruisce un percorso composito da frammenti di percorso è molto utile. –