2013-01-18 9 views
8

Sto provando a implementare i calcoli del percorso SVG in Python, ma sto riscontrando problemi con le curve Arc.Implementazione di curve di arco SVG in Python

Penso che il problema sia nella conversione dalla parametrizzazione da punto finale a centro, ma non riesco a trovare il problema. È possibile trovare note su come implementarlo nella sezione F6.5 di SVG specifications. Ho anche esaminato le implementazioni in altre lingue e non riesco a vedere cosa fanno di diverso.

implementazione oggetto il mio arco è qui:

class Arc(object): 

    def __init__(self, start, radius, rotation, arc, sweep, end): 
     """radius is complex, rotation is in degrees, 
      large and sweep are 1 or 0 (True/False also work)""" 

     self.start = start 
     self.radius = radius 
     self.rotation = rotation 
     self.arc = bool(arc) 
     self.sweep = bool(sweep) 
     self.end = end 

     self._parameterize() 

    def _parameterize(self): 
     # Conversion from endpoint to center parameterization 
     # http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes 

     cosr = cos(radians(self.rotation)) 
     sinr = sin(radians(self.rotation)) 
     dx = (self.start.real - self.end.real)/2 
     dy = (self.start.imag - self.end.imag)/2 
     x1prim = cosr * dx + sinr * dy 
     x1prim_sq = x1prim * x1prim 
     y1prim = -sinr * dx + cosr * dy 
     y1prim_sq = y1prim * y1prim 

     rx = self.radius.real 
     rx_sq = rx * rx 
     ry = self.radius.imag   
     ry_sq = ry * ry 

     # Correct out of range radii 
     radius_check = (x1prim_sq/rx_sq) + (y1prim_sq/ry_sq) 
     if radius_check > 1: 
      rx *= sqrt(radius_check) 
      ry *= sqrt(radius_check) 
      rx_sq = rx * rx 
      ry_sq = ry * ry 

     t1 = rx_sq * y1prim_sq 
     t2 = ry_sq * x1prim_sq 
     c = sqrt((rx_sq * ry_sq - t1 - t2)/(t1 + t2)) 
     if self.arc == self.sweep: 
      c = -c 
     cxprim = c * rx * y1prim/ry 
     cyprim = -c * ry * x1prim/rx 

     self.center = complex((cosr * cxprim - sinr * cyprim) + 
           ((self.start.real + self.end.real)/2), 
           (sinr * cxprim + cosr * cyprim) + 
           ((self.start.imag + self.end.imag)/2)) 

     ux = (x1prim - cxprim)/rx 
     uy = (y1prim - cyprim)/ry 
     vx = (-x1prim - cxprim)/rx 
     vy = (-y1prim - cyprim)/ry 
     n = sqrt(ux * ux + uy * uy) 
     p = ux 
     theta = degrees(acos(p/n)) 
     if uy > 0: 
      theta = -theta 
     self.theta = theta % 360 

     n = sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)) 
     p = ux * vx + uy * vy 
     if p == 0: 
      delta = degrees(acos(0)) 
     else: 
      delta = degrees(acos(p/n)) 
     if (ux * vy - uy * vx) < 0: 
      delta = -delta 
     self.delta = delta % 360 
     if not self.sweep: 
      self.delta -= 360 

    def point(self, pos): 
     if self.arc == self.sweep: 
      angle = radians(self.theta - (self.delta * pos)) 
     else: 
      angle = radians(self.delta + (self.delta * pos)) 

     x = sin(angle) * self.radius.real + self.center.real 
     y = cos(angle) * self.radius.imag + self.center.imag 
     return complex(x, y) 

È possibile verificare questo con il seguente codice che trarrà le curve con il modulo Turtle. (Il raw_input() alla fine è solo per quello lo schermo non scompare quando il programma si chiude).

arc1 = Arc(0j, 100+50j, 0, 0, 0, 100+50j) 
arc2 = Arc(0j, 100+50j, 0, 1, 0, 100+50j) 
arc3 = Arc(0j, 100+50j, 0, 0, 1, 100+50j) 
arc4 = Arc(0j, 100+50j, 0, 1, 1, 100+50j) 

import turtle 
t = turtle.Turtle() 
t.penup() 
t.goto(0, 0) 
t.dot(5, 'red') 
t.write('Start') 
t.goto(100, 50) 
t.dot(5, 'red') 
t.write('End')   
t.pencolor = t.color('blue') 

for arc in (arc1, arc2, arc3, arc4): 
    t.penup() 
    p = arc.point(0) 
    t.goto(p.real, p.imag) 
    t.pendown() 
    for x in range(1,101): 
     p = arc.point(x*0.01) 
     t.goto(p.real, p.imag) 

raw_input() 

Il problema:

Ognuno di questi quattro archi tratte dovrebbe trarre dal punto di partenza al punto finale. Tuttavia, sono tratti dai punti sbagliati. Due curve vanno dalla fine all'inizio e due vanno da 100, da -50 a 0,0 anziché da 0,0 a 100, 50.

Parte del problema è che le note di implementazione forniscono la formula da come eseguire il modulo di conversione endpoint al centro, ma non spiega cosa fa geometricamente, quindi non sono del tutto chiaro su cosa fa ogni passaggio. Una spiegazione di ciò sarebbe anche utile.

risposta

3

credo di aver trovato alcuni errori nel codice:

theta = degrees(acos(p/n)) 
if uy > 0: 
    theta = -theta 
self.theta = theta % 360 

La condizione uy > 0 è mal corretta è uy < 0 (l'angolo diretto da (1, 0) a (ux, uy) è negativo se uy < 0):

theta = degrees(acos(p/n)) 
if uy < 0: 
    theta = -theta 
self.theta = theta % 360 

Poi

if self.arc == self.sweep: 
    angle = radians(self.theta - (self.delta * pos)) 
else: 
    angle = radians(self.delta + (self.delta * pos)) 

La distinzione non è necessario qui, il sweep e i parametri sono già contabilizzati in theta e delta.Questo può essere semplificata per:

angle = radians(self.theta + (self.delta * pos)) 

E infine

x = sin(angle) * self.radius.real + self.center.real 
y = cos(angle) * self.radius.imag + self.center.imag 

Qui sin e cos si confondono, corretta è

x = cos(angle) * self.radius.real + self.center.real 
y = sin(angle) * self.radius.imag + self.center.imag 

Dopo queste modifiche, il programma viene eseguito come previsto.


EDIT: C'è un problema più. Il metodo point non tiene conto di un possibile parametro rotation. La seguente versione dovrebbe essere corretto:

def point(self, pos): 
    angle = radians(self.theta + (self.delta * pos)) 
    cosr = cos(radians(self.rotation)) 
    sinr = sin(radians(self.rotation)) 

    x = cosr * cos(angle) * self.radius.real - sinr * sin(angle) * self.radius.imag + self.center.real 
    y = sinr * cos(angle) * self.radius.real + cosr * sin(angle) * self.radius.imag + self.center.imag 
    return complex(x, y) 

(. Vedi formula F.6.3.1 nel SVG specification)

+0

Grazie per il mixaggio di cos/sin. Anche con queste modifiche, però, non funziona correttamente, ma ora le cose diventano più sensate. –

+0

@LennartRegebro: Ho inserito il codice modificato qui: http://pastebin.com/dp8bYVSq. Sembra produrre l'output atteso. –

+0

Lo fa, devo aver aggiustato qualcos'altro anche sulla strada. Troppo stanco con questo per capire cosa. Grazie! (Il primo era solo un avanzo da me che testava le cose per cercare di capire il codice, ma gli altri errori erano reali. Avevo appena capito il mixaggio di cos/sin, ma che self.arc == self.sweep era il torto mi avrebbe richiesto ore in più per capire –

3
+0

Buona idea, così uno, ma mi piacerebbe già attraversato con un altro esempio. Ho controllato anche uno dei precedenti, ma non ho trovato nulla di nuovo. –