2011-10-09 57 views
5

Công cụ thích hợp cho công việc là gì nếu tôi muốn viết một tập lệnh Python tạo ra các đồ họa vector ở định dạng PDF? Đặc biệt, tôi cần vẽ các hình đa giác có các góc được làm tròn với các góc tròn (tức là các hình dạng phẳng bao gồm các đường thẳng và cung tròn).Tạo các tệp PDF, vẽ đa giác với các góc tròn

Dường như matplotlib làm cho nó khá dễ dàng để vẽ hình chữ nhật với các góc tròn và đa giác chung với các góc sắc nét. Tuy nhiên, để vẽ các đa giác với các góc tròn, có vẻ như tôi phải tính toán đường cong Bézier đầu tiên gần đúng với hình dạng.

Có điều gì đơn giản hơn không? Hoặc là có một thư viện khác mà tôi có thể sử dụng để tính toán đường cong Bézier mà xấp xỉ hình dạng mà tôi muốn sản xuất? Lý tưởng nhất, tôi chỉ cần xác định cặp (vị trí, bán kính góc) cho mỗi đỉnh.

Dưới đây là một ví dụ: Tôi muốn để xác định đa giác màu đỏ (+ bán kính của mỗi góc) và thư viện sẽ ra con số màu xám:

example

(Đối với đa giác lồi tôi có thể lừa gạt và sử dụng bút dày để vẽ đường viền của đa giác. Tuy nhiên, điều này không hoạt động trong trường hợp không lồi.)

Trả lời

4

Để sản xuất tệp PDF, tôi khuyên bạn nên xem thư viện cairo, đồ họa vector libaray hỗ trợ "vẽ" vào các bề mặt PDF. Nó cũng có các ràng buộc Python.

Đối với việc vẽ đa giác với các góc tròn, tôi không biết bất kỳ thư viện đồ họa nào hỗ trợ tính năng này ra khỏi hộp.

Nhưng không quá phức tạp để tính toán tọa độ vòng cung tại các góc đa giác cho bán kính góc. Về cơ bản bạn phải tìm điểm trên bisector góc của hai cạnh liền kề có khoảng cách r (nghĩa là bán kính mong muốn) từ cả hai cạnh. Đây là trung tâm của vòng cung, để tìm điểm bắt đầu và điểm kết thúc, bạn sẽ chiếu từ điểm này đến hai cạnh.

Có thể có các trường hợp không nhỏ, ví dụ: phải làm gì, nếu các cạnh đa giác quá ngắn để vừa với hai vòng cung (tôi đoán bạn sẽ phải chọn một bán kính nhỏ hơn trong trường hợp này), và có lẽ những người khác, tôi hiện không biết ...

HTH

+0

Cảm ơn câu trả lời! Chắc chắn, tính toán các tọa độ vòng cung và các tọa độ phân đoạn đường không phải là khó khăn; có nghĩa là, tôi có thể dễ dàng vẽ đường viền * của hình dạng bằng cách sử dụng các phân đoạn và đường kẻ riêng biệt. Tuy nhiên, làm thế nào để tôi có được một chiếc máy bay * đầy *? Tôi có thể sử dụng thư viện Cairo để soạn một đường dẫn khép kín từ các cung và đoạn đường và sau đó yêu cầu thư viện điền vào bên trong đường dẫn không? –

+1

Có, bạn có thể :) – MartinStettner

13

Đây là giải pháp matplotlib hơi khó hiểu. Các biến chứng chính có liên quan đến việc sử dụng các đối tượng matplotlib Path để tạo một hỗn hợp 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

Cảm ơn, điều này thật tuyệt! Tôi chấp nhận câu trả lời khác, vì có vẻ như Cairo là công cụ "đúng" cho loại nhiệm vụ này, nhưng thật sự rất hay khi thấy rằng người ta cũng có thể làm điều đó trong matplotlib.Đặc biệt, đoạn mã xây dựng một đường dẫn phức hợp từ các đoạn đường dẫn là rất hữu ích. –

Các vấn đề liên quan