Cet article a été écrit selon le calendrier de l'Avent, qui est écrit par un candidat du groupe DMM diplômé en 2020.
Calendrier de l'Avent 2019 pour diplômés DMM Group '20
Je regrette que j'aurais dû écrire un peu plus d'auto-introduction ou quelque chose du genre, mais après avoir écrit le contenu ci-dessous, le développement de planétarium était une activité assez centrale dans ma vie étudiante, donc cet article Je vais me présenter à la place.
Comme d'habitude, un certain cercle qui appartient au bord pourri exposera le planétarium au festival du collège technique, et je ferai le maître stellaire dont j'ai besoin pour fabriquer l'appareil source de lumière. (Toute la conversion des coordonnées de la sortie des étoiles vers le maître des étoiles a été effectuée par notre super super super excellent junior. Je & cet article ne fait que l'esquisser.) J'avais l'habitude de produire un master similaire en Java et Gnuplot, mais le résultat était quelque peu bimyo. Puisque Gnuplot est un outil d'affichage de graphes, il semble qu'il ne soit pas bon pour produire des points fins comme les étoiles de planétarium. Donc, cette fois, j'ai décidé d'utiliser le wrapper Python Pycairo du Caire, qui est une bibliothèque de traitement d'image de niveau inférieur, pour produire le maître stellaire. Celui qui est dessiné (la première page) est illustré ci-dessous.
En imprimant ce format pdf (format JIS A4 x 12 feuilles) sur un papier d'impression transparent appelé feuille OHP, puis en le développant sur un film à haute performance de contraste appelé film d'écureuil, un dispositif de source de lumière de planétarium de type sténopé Vous pouvez créer un filtre. L'objectif de cette fois est de faire ce film.
cairo est une bibliothèque de bas niveau pour dessiner des images vectorielles 2D.
Quand je l'ai recherché, il semble qu'il est utilisé dans divers ossements majeurs.
Comme mentionné précédemment, Pycairo est le wrapper Python du Caire. C'est Pycairo, mais je pense qu'il y a relativement peu de documents japonais. Eh bien, les documents en anglais sont partout sur le net, donc je n'ai pas de problème avec la quantité d'informations.
Si vous avez des questions sur Internet, vous pouvez lire ces documents et les essayer. Surtout, si vous lisez le tutoriel du Caire, vous pouvez comprendre le principe de fonctionnement de la bibliothèque du Caire elle-même, c'est donc une lecture incontournable.
Veuillez installer python3 et numpy. (Procédure omise) Il est également recommandé d'installer virtualenv et Jupyter Notebook (extension Colaboratory)
Complet avec la commande suivante sans aucun spécial
pip install pycairo
Seules les fonctions utilisées pour le développement du maître stellaire sont répertoriées.
Le format JIS A4 est spécifié comme "210 mm x 297 mm" et est expliqué dans le système métrique, tandis que Pycairo est spécifié comme "1pt = 1 / 72e de 1in" pour la plus petite unité de 1pt, qui est la livre de yard. Je me sens meurtrier devant la loi. Par conséquent, nous n'avons pas d'autre choix que de définir une fonction de pontage entre la méthode métrique et la méthode yard-livre.
def inch(meter):
return 0.0393701 * meter * 72.0
Dans Pycairo, les informations de couleur RVB sont transmises sous forme de valeur numérique (0,0 à 1,0), mais comme c'est gênant, nous définissons également une fonction pour convertir la chaîne de caractères rgb en vecteur.
def color(color_string):
return np.array([int(color_string[1:3], 16), int(color_string[3:5], 16), int(color_string[5:7], 16)])
def draw_face(context, color_as_vector, width=100, height=100, margine=0):
context.save()
context.set_source_rgb(
color_as_vector[0],
color_as_vector[1],
color_as_vector[2])
context.translate(margine, margine) #Déplacer vers la coordonnée de référence (point de référence) pour le dessin
context.rectangle(0,0,width, height) #Dessinez en passant chaque paramètre à partir du point de référence
context.fill()
context.restore()
with cairo.PDFSurface("example.pdf", canvas_width, canvas_height) as surface:
for face_num in range(12):
context = cairo.Context(surface)
context.scale(1, 1)
draw_face(context,
color_as_vector=color('#000000'),
width=canvas_width-inch(30),
height=canvas_height-inch(30),
margine=inch(15))
surface.show_page() #Créer une nouvelle page&commutation
Créez un objet PDF avec cairo.PDFSurface
. D'autres incluent «cairo.PSSurface», «cairo.SVGSurface» et «cairo.ImageSurface».
De plus, surface.show_page
est une fonction pour créer une nouvelle page de la même taille en pdf.
Notez ensuite que le processus de création d'une figure est écrit entre «context.save» et «context.restore».
Cette fois, j'ai reçu un fichier csv de mon junior, donc je vais l'importer comme les données du catalogue stellaire.
def gen_star_catalogue(file_name=None, face_num=0):
face_vector = [[] for _ in range(face_num)]
with open(file_name, mode='r') as csv_file:
for line in csv.reader(csv_file, delimiter=' '):
face_vector[int(line[0])].append([ast.literal_eval(line[1]),
ast.literal_eval(line[2]),
ast.literal_eval(line[3])])
for face in range(face_num):
face_vector[face] = np.array(face_vector[face])
return face_vector
Cette fois, je produis une figure d'un pentagone régulier, mais afin d'augmenter la flexibilité du code, je vais implémenter un code qui génère un ensemble de polygones réguliers de triangles ou plus.
Supposons un polygone régulier avec un cercle circonscrit de rayon de rayon centré sur center_pos. Calculez de manière appropriée les coordonnées de chaque sommet de la forme à n côtés, puis sortez le groupe de sommets multiplié par la matrice de rotation qui tourne par n'importe quel raduis à n'importe quel point des deux dimensions.
Génération d'un vecteur de vertex positif à N côtés
def gen_regular_polygon(center_pos=None, n=3, radius=1, rotate=0):
theta = 2 * math.pi / n
rot_rad = 2 * math.pi * rotate / 360.0
verts = np.array([[[math.cos(i*theta) * radius + center_pos[0]],
[math.sin(i*theta) * radius + center_pos[1]],
[1]]
for i in range(n)])
rcos = math.cos(rot_rad)
rsin = math.sin(rot_rad)
r31 = -center_pos[0]*rcos + center_pos[1]*rsin + center_pos[0]
r32 = -center_pos[0]*rsin - center_pos[1]*rcos + center_pos[1]
rot_vec = np.array([[rcos, -rsin, r31],
[rsin, rcos, r32],
[0, 0, 1]])
verts = np.concatenate([[np.dot(rot_vec, vert)] for vert in verts])
verts = np.apply_along_axis(lambda x: np.array([x[0], x[1]]), arr=verts, axis=1).reshape([n, 2])
return verts
De plus, si vous en faites un polygone à 50 côtés, un polygone proche d'un cercle sera dessiné comme indiqué dans la figure ci-dessous.
Vous pouvez le visser avec la puissance des mathématiques. Cependant, je n'utilise que des formules simples.
\vec{n}=\frac{\vec{oc}}{|\vec{oc}|}
\vec{m_a}=len\cdot\vec{n}+\vec{a}
\vec{m_b}=len\cdot\vec{n}+\vec{b}
Après cela, vous pouvez créer une marge en les reliant dans l'ordre et en traçant une ligne.
def draw_margines(context=None, verts=None, center_pos=None, edge_size=1, line_color_as_vec=None, frame_width=1, margin_face_num_list=None):
vert_multi = verts.repeat(2, axis=0)
vert_pairs = np.roll(vert_multi, -2).reshape([verts.shape[0], 2, verts.shape[1]])
midpoints = np.apply_along_axis(func1d=lambda x: np.sum(a=x, axis=0)/2, axis=1, arr=vert_pairs)
orth_vecs = midpoints - center_pos
euclid_dists = np.mean(np.apply_along_axis(func1d=lambda x: np.linalg.norm(x), axis=1, arr=orth_vecs))
normals = orth_vecs / euclid_dists
normals_pairs = normals.repeat(2, axis=0).reshape([normals.shape[0], 2, 2])
edges_arr = edge_size * normals_pairs + vert_pairs
# [Une extrémité A,Coin latéral Edge A,Coin latéral bord B,Une extrémité B]Organisez dans l'ordre et passez-le au contexte
edges_arr = np.array([[vert_pairs[x,0], edges_arr[x,0], edges_arr[x,1], vert_pairs[x,1]] for x in range(edges_arr.shape[0])])
context.save()
for edges in edges_arr:
first_edge = edges[0]
context.move_to(first_edge[0], first_edge[1])
for edge in edges[1:]:
context.line_to(edge[0], edge[1])
context.set_source_rgb(line_color_as_vec[0],line_color_as_vec[1],line_color_as_vec[2])
context.set_line_width(frame_width)
context.stroke()
context.restore()
Est-il plus précis de dire la sortie «cercle» que la sortie ponctuelle?
def draw_stars(context=None, stars=None, radius=1.0, center_pos=None, brightness=1.0, color_as_vector=None):
context.save()
for star in stars:
context.set_source_rgb(color_as_vector[0],
color_as_vector[1],
color_as_vector[2])
context.arc(star[0]*radius+center_pos[0],
star[1]*radius+center_pos[1],
np.math.sqrt(star[2]) *brightness,
0, 2*math.pi)
context.fill()
context.restore()
Il est possible d'utiliser la police installée dans l'OS. Définissons à l'avance le nom de la police dans context.select_font_face. Peut-être dessiner sans.
def draw_text(context=None, text=None, font_size=1, pos_as_vec=None, rotate=0, color_as_vec=None):
context.save()
context.set_font_size(font_size)
context.move_to(pos_as_vec[0], pos_as_vec[1]) #Déplacer vers le point de dessin
context.rotate(2*math.pi*rotate/360) #rotation
context.set_source_rgb(color_as_vec[0], color_as_vec[1], color_as_vec[2])
context.show_text(text) #Dessiner des personnages
context.restore()
with cairo.PDFSurface("example.pdf", canvas_width, canvas_height) as surface:
for face_num in range(12):
context = cairo.Context(surface)
context.scale(1, 1)
context.select_font_face("Futura")
draw_text(context=context,
text='Face/{:0=2}'.format(face_num+1),
font_size=100,
pos_as_vec=np.array([inch(250),inch(170)]),
rotate=-90,
color_as_vec=color("#ffffff"))
draw_text(context=context,
text="Copyright(C) 2019 Space Science Research Club, National Institute of Technology, Kitakyushu College All rights reserved.",
font_size=10,
pos_as_vec=np.array([inch(40),inch(193)]),
rotate=0,
color_as_vec=color("#ffffff"))
surface.show_page()
Enfin, comparez l'image dessinée cette fois avec l'image dessinée avec Gnuplot la dernière fois.
L'inversion en noir et blanc est dérivée en raison de la différence dans le but d'utilisation du maître, mais même si elle est soustraite, non seulement les étoiles sont tracées sous forme de points ronds, mais aussi la différence de grade de chaque étoile est soigneusement utilisée comme taille du cercle. Vous pouvez voir qu'il peut être affiché dans. Comme vous pouvez le voir, Pycairo est une bibliothèque de niveau relativement bas, c'est pourquoi c'est une bibliothèque qui permet un affichage graphique flexible. Cependant, puisque la bibliothèque Pycairo elle-même est conçue pour dessiner par description procédurale, il est quelque peu difficile de séparer la partie de dessin d'effet secondaire de la description pure au sens linguistique fonctionnel lors de l'écriture de code. Je l'ai senti dans le cou. Cela ressemble à un défi technique. Cependant, je pense que la possibilité de créer des documents graphiques flexibles en utilisant Pycairo, qui peut produire des fichiers PDF, peut être appliquée au travail général.
Enfin, je vais coller le croquis du document PDF dessiné cette fois et l'intégralité du code source. Cette fois, j'ai utilisé Goole Colaboratory avec Jupyter Notebook, donc la source sera adaptée en conséquence.
import cairo, math, csv, ast
from IPython.display import SVG, display
import numpy as np
#Dessiner une chaîne
def draw_text(context=None, text=None, font_size=1, pos_as_vec=None, rotate=0, color_as_vec=None):
context.save()
context.set_font_size(font_size)
context.move_to(pos_as_vec[0], pos_as_vec[1])
context.rotate(2*math.pi*rotate/360)
context.set_source_rgb(color_as_vec[0], color_as_vec[1], color_as_vec[2])
context.show_text(text)
context.restore()
#Couleur d'arrière-plan du dessin
def draw_face(context, color_as_vector, width=100, height=100, margine=0):
context.save()
context.set_source_rgb(
color_as_vector[0],
color_as_vector[1],
color_as_vector[2])
context.translate(margine, margine)
context.rectangle(0,0,width, height)
context.fill()
context.restore()
#Dessin du vecteur de sommet
def draw_frame(
context,
verts_array,
fill_color_as_rgb_vec,
frame_color_as_rgb_vec,
frame_width):
if verts_array.shape[0] < 3:
print("ERROR")
exit()
else:
context.save()
first_vert = verts_array[0]
tail_vert_array = verts_array[1:]
context.move_to(first_vert[0], first_vert[1])
for vert in tail_vert_array:
context.line_to(vert[0], vert[1])
context.close_path()
context.set_source_rgb(
fill_color_as_rgb_vec[0],
fill_color_as_rgb_vec[1],
fill_color_as_rgb_vec[2])
context.fill_preserve()
context.set_source_rgb(
frame_color_as_rgb_vec[0],
frame_color_as_rgb_vec[1],
frame_color_as_rgb_vec[2]
)
context.set_line_width(frame_width)
context.stroke()
context.restore()
#Dessine une étoile
def draw_stars(context=None, stars=None, radius=1.0, center_pos=None, brightness=1.0, color_as_vector=None):
context.save()
for star in stars:
context.set_source_rgb(color_as_vector[0],
color_as_vector[1],
color_as_vector[2])
context.arc(star[0]*radius+center_pos[0],
star[1]*radius+center_pos[1],
np.math.sqrt(star[2]) *brightness,
0, 2*math.pi)
context.fill()
context.restore()
#Génération d'un vecteur de vertex positif à N côtés
def gen_regular_polygon(center_pos=None, n=3, radius=1, rotate=0):
theta = 2 * math.pi / n
rot_rad = 2 * math.pi * rotate / 360.0
verts = np.array([[[math.cos(i*theta) * radius + center_pos[0]],
[math.sin(i*theta) * radius + center_pos[1]],
[1]]
for i in range(n)])
rcos = math.cos(rot_rad)
rsin = math.sin(rot_rad)
r31 = -center_pos[0]*rcos + center_pos[1]*rsin + center_pos[0]
r32 = -center_pos[0]*rsin - center_pos[1]*rcos + center_pos[1]
rot_vec = np.array([[rcos, -rsin, r31],
[rsin, rcos, r32],
[0, 0, 1]])
verts = np.concatenate([[np.dot(rot_vec, vert)] for vert in verts])
verts = np.apply_along_axis(lambda x: np.array([x[0], x[1]]), arr=verts, axis=1).reshape([n, 2])
return verts
#Conversion mètre-pouce
def inch(meter):
return 0.0393701 * meter * 72.0
#Vectorisation des chaînes agb
def color(color_string):
return np.array([int(color_string[1:3], 16), int(color_string[3:5], 16), int(color_string[5:7], 16)])
#Dessin de colle
def draw_margines(context=None, verts=None, center_pos=None, edge_size=1, line_color_as_vec=None, frame_width=1, margin_face_num_list=None):
vert_multi = verts.repeat(2, axis=0)
vert_pairs = np.roll(vert_multi, -2).reshape([verts.shape[0], 2, verts.shape[1]])
midpoints = np.apply_along_axis(func1d=lambda x: np.sum(a=x, axis=0)/2, axis=1, arr=vert_pairs)
orth_vecs = midpoints - center_pos
euclid_dists = np.mean(np.apply_along_axis(func1d=lambda x: np.linalg.norm(x), axis=1, arr=orth_vecs))
normals = orth_vecs / euclid_dists
normals_pairs = normals.repeat(2, axis=0).reshape([normals.shape[0], 2, 2])
edges_arr = edge_size * normals_pairs + vert_pairs
edges_arr = np.array([[vert_pairs[x,0], edges_arr[x,0], edges_arr[x,1], vert_pairs[x,1]] for x in range(edges_arr.shape[0])])
context.save()
for edges in edges_arr:
first_edge = edges[0]
context.move_to(first_edge[0], first_edge[1])
for edge in edges[1:]:
context.line_to(edge[0], edge[1])
context.set_source_rgb(line_color_as_vec[0],line_color_as_vec[1],line_color_as_vec[2])
context.set_line_width(frame_width)
context.stroke()
context.restore()
inner_product = np.apply_along_axis(lambda x: np.dot(x, np.array([0,1])), axis=1, arr=normals)
thetas = np.apply_along_axis(lambda x: np.arccos(x)/(2*np.pi)*360, axis=0, arr=inner_product)
sign = np.apply_along_axis(lambda x: -1 if x[0]>0 else 1, axis=1, arr=normals)
signed_thetas = sign * thetas
print(signed_thetas)
context.save()
for index, theta in enumerate(signed_thetas):
draw_text(context=context,
text='Face/{:0=2}'.format(margin_face_num_list[index]),
font_size=15,
pos_as_vec=orth_vecs[index] + center_pos + normals[index] * edge_size*0.7,
rotate=theta,
color_as_vec=color("#ffffff"))
context.restore()
#Découper une carte des étoiles
def gen_star_catalogue(file_name=None, face_num=0):
face_vector = [[] for _ in range(face_num)]
with open(file_name, mode='r') as csv_file:
for line in csv.reader(csv_file, delimiter=' '):
face_vector[int(line[0])].append([ast.literal_eval(line[1]),
ast.literal_eval(line[2]),
ast.literal_eval(line[3])])
for face in range(face_num):
face_vector[face] = np.array(face_vector[face])
return face_vector
#Allocation de l'indice d'allocation de colle
margin_index = [[6, 5, 4, 3, 2], [7, 6, 1, 3, 8], [8, 2, 1, 4, 9],
[9, 3, 1, 5, 10], [10, 4, 1, 6, 11], [11, 5, 1, 2, 7],
[2, 8, 12, 11, 6], [3, 9, 12, 7, 2], [4, 10, 12, 8, 3],
[5, 11, 12, 9, 4], [6, 7, 12, 10, 5], [10, 11, 7, 8, 9]]
#Hyper paramètres
normal_scale = 100
canvas_height = inch(210)
canvas_width = inch(297)
face_center_position = np.array([inch(125), inch(105)])
face_radius = inch(74.85727113)
face_mid_dist = inch(77.15727113)
face_rotate_list = [0]+[180]*10+[0]
#Tableau de dessin de données de table
star_catalogue = gen_star_catalogue(file_name="./starout.txt", face_num=12)
#Sortie de fichier PDF
with cairo.PDFSurface("example.pdf", canvas_width, canvas_height) as surface:
for face_num in range(12):
context = cairo.Context(surface)
context.scale(1, 1)
context.select_font_face("Futura")
draw_face(context,
color_as_vector=color('#000000'),
width=canvas_width-inch(30),
height=canvas_height-inch(30),
margine=inch(15))
verts = gen_regular_polygon(
center_pos=face_center_position,
n=5,
radius=face_radius,
rotate=face_rotate_list[face_num])
draw_frame(context=context,
verts_array=verts,
fill_color_as_rgb_vec=color('#ffffff'),
frame_color_as_rgb_vec=color('#000000'),
frame_width=0.02)
draw_margines(context=context,
verts=verts,
center_pos=face_center_position,
edge_size=inch(10),
line_color_as_vec=color("#ff0000"),
frame_width=1,
margin_face_num_list=margin_index[face_num])
stars = star_catalogue[face_num]
draw_stars(context=context,
stars=stars,
radius=face_mid_dist,
center_pos=face_center_position,
brightness=0.5,
color_as_vector=color('#000000'))
draw_text(context=context,
text='Face/{:0=2}'.format(face_num+1),
font_size=100,
pos_as_vec=np.array([inch(250),inch(170)]),
rotate=-90,
color_as_vec=color("#ffffff"))
draw_text(context=context,
text="Copyright(C) 2019 Space Science Research Club, National Institute of Technology, Kitakyushu College All rights reserved.",
font_size=10,
pos_as_vec=np.array([inch(40),inch(193)]),
rotate=0,
color_as_vec=color("#ffffff"))
surface.show_page()
Lors de l'impression d'un master de planétarium, j'utilise une feuille OHP qui équivaut presque à fossile, mais dans les imprimantes à la maison et à l'école, la feuille est bouchée et la densité d'impression est très fine (même si c'est la plus sombre du décor). Il y a beaucoup de choses comme ça. Si vous tombez dans cette situation, il est rapide de vous rendre dans une imprimerie qui peut imprimer des feuilles OHP telles que Kinko, et ici (au moins dans les magasins locaux) vous pouvez imprimer en noir sur des feuilles OHP. Est fait. Cependant, lors de l'impression de feuilles OHP, l'atelier d'impression ne prend en charge que l'impression en noir et blanc en premier lieu, et même si cela peut être fait, la couleur est super claire, de sorte que les informations de couleur spéciales ne sont pas utilisées, alors faites attention à ce point. Devrait être. En outre, il est possible que la feuille et les mains se salissent avec de l'encre au moment de l'impression, sans se limiter aux feuilles OHP, c'est donc une bonne idée de prévoir une zone qui ne s'imprime pas comme indiqué sur la figure ci-dessus.
Recommended Posts