GM3Dps 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gm3dps-0.0.1/GM3Dps/__init__.py +1 -0
- gm3dps-0.0.1/GM3Dps/core.py +919 -0
- gm3dps-0.0.1/GM3Dps.egg-info/PKG-INFO +17 -0
- gm3dps-0.0.1/GM3Dps.egg-info/SOURCES.txt +13 -0
- gm3dps-0.0.1/GM3Dps.egg-info/dependency_links.txt +1 -0
- gm3dps-0.0.1/GM3Dps.egg-info/requires.txt +1 -0
- gm3dps-0.0.1/GM3Dps.egg-info/top_level.txt +1 -0
- gm3dps-0.0.1/LICENSE +21 -0
- gm3dps-0.0.1/PKG-INFO +17 -0
- gm3dps-0.0.1/README.md +3 -0
- gm3dps-0.0.1/pyproject.toml +29 -0
- gm3dps-0.0.1/setup.cfg +4 -0
- gm3dps-0.0.1/tests/test.py +22 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .core import *
|
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Core part of ``GM3Dps``
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
class GM3DpsError(ValueError) :
|
|
10
|
+
"""
|
|
11
|
+
Error raise by GM3Dps when computation is no longer viable
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Figure3D :
|
|
16
|
+
"""
|
|
17
|
+
Base of every figure where everything else will be drawn
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self,
|
|
20
|
+
width : float,
|
|
21
|
+
height : float,
|
|
22
|
+
zvector : np.ndarray = np.array([0., -1., 0.]),
|
|
23
|
+
origin : np.ndarray = np.array([0., 0., 0.]),
|
|
24
|
+
alpha : float = 0.) :
|
|
25
|
+
"""
|
|
26
|
+
Base of every figure where everything else will be drawn
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
width : float
|
|
31
|
+
Screen width of the figure
|
|
32
|
+
height : float
|
|
33
|
+
Screen height of the figure
|
|
34
|
+
zvector : 3D vector, default : [0., -1., 0.]
|
|
35
|
+
Vector facing in the screen (away from the observer)
|
|
36
|
+
origin : 3D vector, default : [0., 0., 0.]
|
|
37
|
+
Point in the center of the screen
|
|
38
|
+
alpha : float
|
|
39
|
+
Rotation (in degrees) of the figure on the screen anticlockwise
|
|
40
|
+
"""
|
|
41
|
+
self._children = []
|
|
42
|
+
self._labels = []
|
|
43
|
+
self.width = width
|
|
44
|
+
"""int: Width of the figure"""
|
|
45
|
+
self.height = height
|
|
46
|
+
"""int: Height of the figure"""
|
|
47
|
+
self._zvector = zvector/np.linalg.norm(zvector)
|
|
48
|
+
self._alpha = alpha
|
|
49
|
+
self._origin = origin
|
|
50
|
+
self._posmatrixinit()
|
|
51
|
+
|
|
52
|
+
def _posmatrixinit(self) : #à tester
|
|
53
|
+
xtemp = np.cross(np.array([0., 0., 1.]), self._zvector)
|
|
54
|
+
x = xtemp/np.linalg.norm(xtemp)
|
|
55
|
+
ytemp = np.cross(self._zvector, x)
|
|
56
|
+
y = ytemp/np.linalg.norm(ytemp)
|
|
57
|
+
rot = np.array([[np.cos(self._alpha), np.sin(self._alpha)],
|
|
58
|
+
[-np.sin(self._alpha), np.cos(self._alpha)]])
|
|
59
|
+
self._positionmatrix = rot @ np.array([x, y])
|
|
60
|
+
return self._positionmatrix
|
|
61
|
+
|
|
62
|
+
def _screen_projection(self, point : np.ndarray) :
|
|
63
|
+
return np.dot(self._positionmatrix, point - self._origin)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def export(self, path : str, frame : bool = True) :
|
|
67
|
+
"""
|
|
68
|
+
Method to export the figure to a PostScript file
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
path : str
|
|
73
|
+
Path to save the figure
|
|
74
|
+
frame : bool, default : True
|
|
75
|
+
Whether or not to have frame around the bounding box
|
|
76
|
+
Note
|
|
77
|
+
----
|
|
78
|
+
Since the extension of the file can be specified in the
|
|
79
|
+
path, the user can chose between a postscript and an
|
|
80
|
+
encapsulated postscript file with it.
|
|
81
|
+
|
|
82
|
+
But it will not change the heading of the file which eps ready.
|
|
83
|
+
"""
|
|
84
|
+
with open(path, 'w') as dest :
|
|
85
|
+
dest.write("%!PS-Adobe-3.0 EPSF-3.0\n%%Creator : GM3Dps\n")
|
|
86
|
+
dest.write("%%BoundingBox : {} {} {} {}\n"
|
|
87
|
+
.format(300-self.width/2,
|
|
88
|
+
421-self.height/2,
|
|
89
|
+
self.width,
|
|
90
|
+
self.height)) #centered bbox
|
|
91
|
+
dest.write("300 421 translate\ngsave\n") #center the figure
|
|
92
|
+
if frame :
|
|
93
|
+
dest.write("newpath {} dup 2 div neg {} dup 2 div neg 2 index exch moveto 2 index 0 rlineto exch 0 exch pop exch rlineto neg 0 rlineto closepath stroke\n".format(self.width, self.height))
|
|
94
|
+
for label in self._labels :
|
|
95
|
+
label._export(dest)
|
|
96
|
+
for child in self._children :
|
|
97
|
+
child._export(dest)
|
|
98
|
+
dest.write("grestore\n")
|
|
99
|
+
dest.write("showpage")
|
|
100
|
+
|
|
101
|
+
def plane2D(self, *args, **kwargs) :
|
|
102
|
+
"""
|
|
103
|
+
Method to create :obj:`Plane2D` with the figure as its master
|
|
104
|
+
"""
|
|
105
|
+
return Plane2D(self, *args, **kwargs)
|
|
106
|
+
|
|
107
|
+
def label(self, *args, **kwargs) :
|
|
108
|
+
"""
|
|
109
|
+
Method to create :obj:`Label` with the figure as its master
|
|
110
|
+
"""
|
|
111
|
+
return Label(self, *args, **kwargs)
|
|
112
|
+
|
|
113
|
+
class Label :
|
|
114
|
+
"""
|
|
115
|
+
Label to have text on the figure
|
|
116
|
+
"""
|
|
117
|
+
def __init__(self,
|
|
118
|
+
master : Figure3D,
|
|
119
|
+
text : str,
|
|
120
|
+
origin : np.ndarray,
|
|
121
|
+
offset2D : np.ndarray = np.array([0.,0.]), **kwargs) :
|
|
122
|
+
"""
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
master : :obj:`Figure3D`
|
|
126
|
+
Where to draw the label
|
|
127
|
+
text : str
|
|
128
|
+
Text to have on the label
|
|
129
|
+
origin :
|
|
130
|
+
3D coordinates of the label (bottom left)
|
|
131
|
+
offset2D :
|
|
132
|
+
Screen offset to apply on the label
|
|
133
|
+
**kwargs :
|
|
134
|
+
Arguments passed down to style
|
|
135
|
+
"""
|
|
136
|
+
self.text = text
|
|
137
|
+
"""str: Text of the Label"""
|
|
138
|
+
self.master = master
|
|
139
|
+
""":py:obj:`Figure3D`: Figure where the label is drawn on"""
|
|
140
|
+
self.master._labels.append(self)
|
|
141
|
+
self.origin = origin
|
|
142
|
+
"""3D Vector: 3D position of the label"""
|
|
143
|
+
self.offset2D = offset2D
|
|
144
|
+
"""2D Vector: Screen offset relative to its origin"""
|
|
145
|
+
self._drawflag = True
|
|
146
|
+
self.style(**kwargs)
|
|
147
|
+
|
|
148
|
+
def style(self, font : str = "Times-New-Roman", size : int = 15) :
|
|
149
|
+
"""
|
|
150
|
+
Edit the style of the label
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
font : str, default : "Times-New-Roman"
|
|
155
|
+
Font used by the label
|
|
156
|
+
size : int, default : 15
|
|
157
|
+
Font size used by the label
|
|
158
|
+
"""
|
|
159
|
+
self.font = font
|
|
160
|
+
self.size = size
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def draw(self) -> bool :
|
|
164
|
+
"""
|
|
165
|
+
bool : Whether or not the label will be drawn. Default, True
|
|
166
|
+
"""
|
|
167
|
+
return self._drawflag
|
|
168
|
+
|
|
169
|
+
@draw.setter
|
|
170
|
+
def draw(self, value : bool) :
|
|
171
|
+
self._drawflag = value
|
|
172
|
+
|
|
173
|
+
def _drawstyle(self, file) :
|
|
174
|
+
file.write("/{} findfont {} scalefont setfont\n".format(self.font, self.size))
|
|
175
|
+
|
|
176
|
+
def _export(self, file) :
|
|
177
|
+
if self._drawflag :
|
|
178
|
+
position = self.master._screen_projection(self.origin) + self.offset2D
|
|
179
|
+
file.write("{} {} moveto \n".format(position[0], position[1]))
|
|
180
|
+
self._drawstyle(file)
|
|
181
|
+
file.write("({}) show\n".format(self.text))
|
|
182
|
+
|
|
183
|
+
class Plane2D :
|
|
184
|
+
"""
|
|
185
|
+
2D plane on the Figure to draw 2D objects
|
|
186
|
+
"""
|
|
187
|
+
def __init__(self,
|
|
188
|
+
master : Figure3D,
|
|
189
|
+
origin : np.ndarray,
|
|
190
|
+
normal : np.ndarray,
|
|
191
|
+
alpha : float = 0.) :
|
|
192
|
+
"""
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
master : :obj:`Figure3D`
|
|
196
|
+
Figure where to have the plane
|
|
197
|
+
origin : 3D Vector
|
|
198
|
+
3D coordinates of the 0, 0 point of the plane
|
|
199
|
+
normal : 3D vector
|
|
200
|
+
Normal vector to the plane (does not need to be a unit vector)
|
|
201
|
+
alpha : float, default : 0.
|
|
202
|
+
Rotation of the plane aroung its normal vector
|
|
203
|
+
anticlockwise. By default, the second vector of the plane
|
|
204
|
+
will be as vertical as possible upwards
|
|
205
|
+
"""
|
|
206
|
+
self.master = master
|
|
207
|
+
""":py:obj:`Figure3D`: Figure the Plane is drawn"""
|
|
208
|
+
self._children = []
|
|
209
|
+
master._children.append(self)
|
|
210
|
+
self.origin = origin
|
|
211
|
+
"""3D Vector: Position of the center of the plane in 3D space"""
|
|
212
|
+
self._normal = normal
|
|
213
|
+
self._unit_vector()
|
|
214
|
+
self.alpha = alpha
|
|
215
|
+
"""float: Anticlockwise rotation of the plane around its normal
|
|
216
|
+
vector, the second vector of the plane will be as vertical as
|
|
217
|
+
possible by default"""
|
|
218
|
+
|
|
219
|
+
def _unit_vector(self) :
|
|
220
|
+
#unit vector on the plane
|
|
221
|
+
self._ex = np.array([-self._normal[1], self._normal[0], 0.]) #Ez x normal
|
|
222
|
+
self._ex /= np.linalg.norm(self._ex)
|
|
223
|
+
self._ey = np.array([-self._normal[0]*self._normal[2],
|
|
224
|
+
-self._normal[1]*self._normal[2],
|
|
225
|
+
self._normal[0]**2 + self._normal[1]**2]).astype(np.float64) #normal x ex
|
|
226
|
+
self._ey /= np.linalg.norm(self._ey)
|
|
227
|
+
self._position_matrix = np.array([self._ex, self._ey])
|
|
228
|
+
|
|
229
|
+
self._lambda0 = None
|
|
230
|
+
self._lambda1 = None
|
|
231
|
+
self._alpha0 = None
|
|
232
|
+
self._alpha1 = None
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def normal(self) :
|
|
236
|
+
"""
|
|
237
|
+
3D Vector: Unit vector normal to the plane
|
|
238
|
+
"""
|
|
239
|
+
return self._normal/np.linalg.norm(self._normal)
|
|
240
|
+
|
|
241
|
+
@normal.setter
|
|
242
|
+
def normal(self, value) :
|
|
243
|
+
self._normal = value
|
|
244
|
+
self._unit_vector()
|
|
245
|
+
|
|
246
|
+
def _position3d(self, coordinates : np.ndarray) :
|
|
247
|
+
return np.matvec(self._position_matrix.T, coordinates) + self.origin
|
|
248
|
+
|
|
249
|
+
def _coordinate(self, file) :
|
|
250
|
+
file.write("{} {} translate\n".format(self.origin[0], self.origin[2]))
|
|
251
|
+
|
|
252
|
+
#matrix computing
|
|
253
|
+
if self._lambda0 == None :
|
|
254
|
+
ex = np.dot(self.master._positionmatrix, self._ex)
|
|
255
|
+
ey = np.dot(self.master._positionmatrix, self._ey)
|
|
256
|
+
M= np.array([ex, ey]).T
|
|
257
|
+
D0, R0 = np.linalg.eig(M.T@M)
|
|
258
|
+
D1, R1 = np.linalg.eig(M@M.T)
|
|
259
|
+
D0 = np.real(D0)
|
|
260
|
+
D1 = np.real(D1)
|
|
261
|
+
R0 = np.real(R0)
|
|
262
|
+
R1 = np.real(R1)
|
|
263
|
+
|
|
264
|
+
if not (np.isclose(D0, D1).all()) :
|
|
265
|
+
R1 = np.array([R1[:,1], R1[:,0]])
|
|
266
|
+
if np.linalg.det(R0) < 0 :
|
|
267
|
+
R0[:,1] = -R0[:,1]
|
|
268
|
+
if np.linalg.det(R1) < 0 :
|
|
269
|
+
R1[:,1] = -R1[:,1]
|
|
270
|
+
|
|
271
|
+
self._lambda0 = np.dot(R1.T@M, R0[:,0])[0]
|
|
272
|
+
self._lambda1 = np.dot(R1.T@M, R0[:,1])[1]
|
|
273
|
+
self._alpha0 = -np.atan2(-R0[0,1], R0[0,0])
|
|
274
|
+
self._alpha1 = np.atan2(-R1[0,1], R1[0,0])
|
|
275
|
+
|
|
276
|
+
file.write("{} rotate\n".format(np.rad2deg(self._alpha1)))
|
|
277
|
+
file.write("{} {} scale\n".format(self._lambda0, self._lambda1))
|
|
278
|
+
file.write("{} rotate\n".format(np.rad2deg(self._alpha0)))
|
|
279
|
+
file.write("{} rotate\n".format(np.rad2deg(self.alpha)))
|
|
280
|
+
|
|
281
|
+
def _export(self, file) :
|
|
282
|
+
self._coordinate(file)
|
|
283
|
+
for child in self._children :
|
|
284
|
+
child._export(file)
|
|
285
|
+
file.write("grestore\n")
|
|
286
|
+
|
|
287
|
+
def point(self, *args, **kwargs) :
|
|
288
|
+
"""
|
|
289
|
+
Method to create a :obj:`Point2D` on the plane
|
|
290
|
+
"""
|
|
291
|
+
return Point2D(self, *args, **kwargs)
|
|
292
|
+
|
|
293
|
+
def line(self, *args, **kwargs) :
|
|
294
|
+
"""
|
|
295
|
+
Method to create a :obj:`Line2D` on the plane
|
|
296
|
+
"""
|
|
297
|
+
return Line2D(self, *args, **kwargs)
|
|
298
|
+
|
|
299
|
+
def polygon(self, *args, **kwargs) :
|
|
300
|
+
"""
|
|
301
|
+
Method to create a :obj:`Polygon2D` on the plane
|
|
302
|
+
"""
|
|
303
|
+
return Polygon2D(self, *args, **kwargs)
|
|
304
|
+
|
|
305
|
+
def rectangle(self, *args, **kwargs) :
|
|
306
|
+
"""
|
|
307
|
+
Method to create a :obj:`Rectangle2D` on the plane
|
|
308
|
+
"""
|
|
309
|
+
return Rectangle2D(self, *args, **kwargs)
|
|
310
|
+
|
|
311
|
+
def ellipse(self, *args, **kwargs) :
|
|
312
|
+
"""
|
|
313
|
+
Method to create an :obj:`Ellipse2D` on the plane
|
|
314
|
+
"""
|
|
315
|
+
return Ellipse2D(self, *args, **kwargs)
|
|
316
|
+
|
|
317
|
+
def arc(self, *args, **kwargs) :
|
|
318
|
+
"""
|
|
319
|
+
Method to create an :obj:`Arc2D` on the plane
|
|
320
|
+
"""
|
|
321
|
+
return Arc2D(self, *args, **kwargs)
|
|
322
|
+
|
|
323
|
+
def angle(self, *args, **kwargs) :
|
|
324
|
+
"""
|
|
325
|
+
Method to create an :obj:`Angle2D` on the plane
|
|
326
|
+
"""
|
|
327
|
+
return Angle2D(self, *args, **kwargs)
|
|
328
|
+
|
|
329
|
+
def _arrowhead(self, *args, **kwargs) :
|
|
330
|
+
return _ArrowHead2D(self, *args, **kwargs)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class _Object2D :
|
|
334
|
+
def __init__(self, master : Plane2D) :
|
|
335
|
+
self._children = []
|
|
336
|
+
self._style_children = []
|
|
337
|
+
self.master = master
|
|
338
|
+
""":py:obj:`Plane2D`: Plane where the object is drawn"""
|
|
339
|
+
self._drawflag = False
|
|
340
|
+
self._dash = None
|
|
341
|
+
master._children.append(self)
|
|
342
|
+
self._label_position = np.array([0., 0.])
|
|
343
|
+
|
|
344
|
+
def style(self, dash : list[float|int] =None) :
|
|
345
|
+
"""
|
|
346
|
+
Edits the drawing style of an object
|
|
347
|
+
|
|
348
|
+
Parameters
|
|
349
|
+
----------
|
|
350
|
+
dash : list[float|int]
|
|
351
|
+
Dashing style of the line
|
|
352
|
+
"""
|
|
353
|
+
if dash != None :
|
|
354
|
+
self._dash = dash.copy()
|
|
355
|
+
for child in self._style_children :
|
|
356
|
+
child.style(dash)
|
|
357
|
+
|
|
358
|
+
def _drawstyle(self, file) :
|
|
359
|
+
if self._dash != None :
|
|
360
|
+
file.write("[")
|
|
361
|
+
for e in self._dash :
|
|
362
|
+
file.write("{} ".format(e))
|
|
363
|
+
file.write("] 0 setdash\n")
|
|
364
|
+
else :
|
|
365
|
+
file.write("[] 0 setdash\n")
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def draw(self) -> bool :
|
|
369
|
+
"""
|
|
370
|
+
bool: Whether or not an element will be drawn where exporting
|
|
371
|
+
"""
|
|
372
|
+
return self._drawflag
|
|
373
|
+
|
|
374
|
+
@draw.setter
|
|
375
|
+
def draw(self, value) :
|
|
376
|
+
self._drawflag = value
|
|
377
|
+
for child in self._children :
|
|
378
|
+
child.draw = value
|
|
379
|
+
|
|
380
|
+
def label(self,
|
|
381
|
+
text : str,
|
|
382
|
+
offset3d : np.ndarray=np.array([0., 0., 0.]),
|
|
383
|
+
offset2d : np.ndarray=np.array([0., 0.]),
|
|
384
|
+
**kwargs) :
|
|
385
|
+
"""
|
|
386
|
+
Create a label positionned somewhat correctly whereas the object
|
|
387
|
+
|
|
388
|
+
Parameters
|
|
389
|
+
----------
|
|
390
|
+
text : str
|
|
391
|
+
Text to print on the label
|
|
392
|
+
offset3d : 3D Vector
|
|
393
|
+
Displacement in the 3D space of the label relative to its
|
|
394
|
+
default position
|
|
395
|
+
offset2d : 2D Vector
|
|
396
|
+
Displacement on the screen of the label relative to its
|
|
397
|
+
position
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
out : :py:obj:`Label`
|
|
402
|
+
Label object created
|
|
403
|
+
|
|
404
|
+
Note
|
|
405
|
+
----
|
|
406
|
+
``offset2D`` and ``offset3d`` are compatible with one another,
|
|
407
|
+
the usage of both will result in both the offsets being applied
|
|
408
|
+
"""
|
|
409
|
+
return Label(self.master.master,
|
|
410
|
+
text,
|
|
411
|
+
self.master._position3d(self._label_position)+offset3d,
|
|
412
|
+
offset2d,
|
|
413
|
+
**kwargs)
|
|
414
|
+
|
|
415
|
+
def _export(self, file) :
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class Point2D(_Object2D) :
|
|
420
|
+
"""
|
|
421
|
+
2D point on a plane
|
|
422
|
+
"""
|
|
423
|
+
@staticmethod
|
|
424
|
+
def _compatibilitycheck(master, pointlike) :
|
|
425
|
+
if isinstance(pointlike, Point2D) :
|
|
426
|
+
return pointlike
|
|
427
|
+
elif isinstance(pointlike, np.ndarray) :
|
|
428
|
+
return Point2D(master, pointlike)
|
|
429
|
+
elif isinstance(pointlike, np.ndarray) :
|
|
430
|
+
return Point2D(master, np.array(pointlike))
|
|
431
|
+
else :
|
|
432
|
+
raise TypeError("expected Point2D, numpy ndarray or list got {} instead".format(type(pointlike)))
|
|
433
|
+
|
|
434
|
+
def __init__(self, master : Plane2D, pos : np.ndarray) :
|
|
435
|
+
"""
|
|
436
|
+
Parameters
|
|
437
|
+
----------
|
|
438
|
+
master : :obj:`Plane2D`
|
|
439
|
+
Plane on which the point will be placed
|
|
440
|
+
pos : 2D Vector
|
|
441
|
+
Position on the point on the plane
|
|
442
|
+
"""
|
|
443
|
+
self._position = pos
|
|
444
|
+
super().__init__(master)
|
|
445
|
+
self._label_position = pos
|
|
446
|
+
|
|
447
|
+
@property
|
|
448
|
+
def position(self) :
|
|
449
|
+
"""2D Vector: Where the point is located on the plane"""
|
|
450
|
+
return self._position
|
|
451
|
+
|
|
452
|
+
@position.setter
|
|
453
|
+
def position(self, pos) :
|
|
454
|
+
self._position = pos
|
|
455
|
+
self._label_position = pos
|
|
456
|
+
|
|
457
|
+
def line(self, end : np.ndarray|Point2D) :
|
|
458
|
+
"""
|
|
459
|
+
Create a line between the point and another one
|
|
460
|
+
|
|
461
|
+
Parameters
|
|
462
|
+
----------
|
|
463
|
+
end : :obj:`Point2D`
|
|
464
|
+
End of the line to draw
|
|
465
|
+
|
|
466
|
+
Returns
|
|
467
|
+
-------
|
|
468
|
+
:obj:`Line2D`
|
|
469
|
+
Line between the two points
|
|
470
|
+
"""
|
|
471
|
+
return Line2D(self.master, self, end)
|
|
472
|
+
|
|
473
|
+
def __sub__(self, other : Point2D|np.ndarray) :
|
|
474
|
+
if isinstance(other, Point2D) :
|
|
475
|
+
return self.position - other.position
|
|
476
|
+
else :
|
|
477
|
+
return self.position - other
|
|
478
|
+
|
|
479
|
+
class Line2D(_Object2D):
|
|
480
|
+
"""
|
|
481
|
+
Line to draw on a 2D plane
|
|
482
|
+
"""
|
|
483
|
+
def __init__(self,
|
|
484
|
+
master : Plane2D,
|
|
485
|
+
origin : np.ndarray|Point2D,
|
|
486
|
+
end : np.ndarray|Point2D) :
|
|
487
|
+
"""
|
|
488
|
+
Parameters
|
|
489
|
+
----------
|
|
490
|
+
master : :obj:`Plane2D`
|
|
491
|
+
Plane on which the line will be drawn
|
|
492
|
+
origin : :obj:`Point2D`
|
|
493
|
+
Start of the line
|
|
494
|
+
end : :obj:`Point2D`
|
|
495
|
+
End of the line
|
|
496
|
+
"""
|
|
497
|
+
super().__init__(master)
|
|
498
|
+
self._start = Point2D._compatibilitycheck(master, origin)
|
|
499
|
+
self._end = Point2D._compatibilitycheck(master, end)
|
|
500
|
+
self._label_position = (self._end._position + self._start._position)/2
|
|
501
|
+
|
|
502
|
+
@property
|
|
503
|
+
def end(self) :
|
|
504
|
+
"""
|
|
505
|
+
:py:obj:`Point2D`: End of the line
|
|
506
|
+
"""
|
|
507
|
+
return self._end
|
|
508
|
+
|
|
509
|
+
@end.setter
|
|
510
|
+
def end(self, end : Point2D) :
|
|
511
|
+
self._end = end
|
|
512
|
+
self._label_position = (self._end._position + self._start._position)/2
|
|
513
|
+
|
|
514
|
+
@property
|
|
515
|
+
def start(self) :
|
|
516
|
+
"""
|
|
517
|
+
:py:obj:`Point2D`: Start of the line
|
|
518
|
+
"""
|
|
519
|
+
return self._start
|
|
520
|
+
|
|
521
|
+
@start.setter
|
|
522
|
+
def start(self, start : Point2D) :
|
|
523
|
+
self._start = start
|
|
524
|
+
self._label_position = (self._end._position + self._start._position)/2
|
|
525
|
+
|
|
526
|
+
def _export(self, file) :
|
|
527
|
+
if self._drawflag :
|
|
528
|
+
self._drawstyle(file)
|
|
529
|
+
file.write("newpath {} {} moveto {} {} lineto stroke\n".format(self.start.position[0], self.start.position[1], self.end.position[0], self.end.position[1]))
|
|
530
|
+
|
|
531
|
+
def intersection(self, other : Line2D) :
|
|
532
|
+
"""
|
|
533
|
+
Compute the intersection of two lines on the same plane
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
536
|
+
----------
|
|
537
|
+
other : :obj:`Line2D`
|
|
538
|
+
Other line with which to compute the intersection
|
|
539
|
+
|
|
540
|
+
Returns
|
|
541
|
+
-------
|
|
542
|
+
:obj:`Point2D`
|
|
543
|
+
Point of the intersection of the two lines
|
|
544
|
+
"""
|
|
545
|
+
a, b = (self.end - self.start)[0], (self.end - self.start)[1]
|
|
546
|
+
c, d = (other.end - other.start)[0], (other.end - other.start)[1]
|
|
547
|
+
v0 = np.array([b, -a])
|
|
548
|
+
v1 = np.array([d, -c])
|
|
549
|
+
e = np.dot(self.start.position, v0)
|
|
550
|
+
f = np.dot(other.start.position, v1)
|
|
551
|
+
return self.master.point(np.linalg.solve(np.array([v0, v1]),
|
|
552
|
+
np.array([e,f])))
|
|
553
|
+
|
|
554
|
+
def angle(self, other : Line2D, **kwargs) :
|
|
555
|
+
"""
|
|
556
|
+
Return an angle object to show the angle between to lines
|
|
557
|
+
|
|
558
|
+
Parameters
|
|
559
|
+
----------
|
|
560
|
+
other : :obj:`Line2D`
|
|
561
|
+
Other line to draw the angle with
|
|
562
|
+
**kwargs :
|
|
563
|
+
Arguments passed down to :obj:`Angle2D`
|
|
564
|
+
|
|
565
|
+
Returns
|
|
566
|
+
-------
|
|
567
|
+
:obj:`Angle2D`
|
|
568
|
+
Angle between the two lines
|
|
569
|
+
"""
|
|
570
|
+
center = self.intersection(other)
|
|
571
|
+
v0 = self.end - self.start
|
|
572
|
+
v1 = other.end - other.start
|
|
573
|
+
alpha0 = np.atan2(v0[1], v0[0])
|
|
574
|
+
alpha1 = np.atan2(v1[1], v1[0])
|
|
575
|
+
return self.master.angle(center, alpha0, alpha1, 15., **kwargs)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class Polygon2D(_Object2D) :
|
|
579
|
+
"""
|
|
580
|
+
Polygon on a 2D plane
|
|
581
|
+
"""
|
|
582
|
+
def __init__(self, master : Plane2D,
|
|
583
|
+
verteces : list[np.ndarray]|list[Point2D]) :
|
|
584
|
+
"""
|
|
585
|
+
Parameters
|
|
586
|
+
----------
|
|
587
|
+
master : :obj:`Plane2D`
|
|
588
|
+
Plane on which to plot the polygon
|
|
589
|
+
verteces : list[:obj:`Point2D`]
|
|
590
|
+
Verteces of the polygon
|
|
591
|
+
"""
|
|
592
|
+
n = len(verteces)
|
|
593
|
+
self._verteces = n*[None]
|
|
594
|
+
self._edges = n*[None]
|
|
595
|
+
for i in range(n) :
|
|
596
|
+
self._verteces[i] = Point2D._compatibilitycheck(master, verteces[i])
|
|
597
|
+
super().__init__(master)
|
|
598
|
+
for i in range(n) :
|
|
599
|
+
self._edges[i] = self._verteces[i].line(self.verteces[i-n+1])
|
|
600
|
+
temp = [pt._position for pt in self._verteces]
|
|
601
|
+
self._label_position = np.mean(temp, axis=0)
|
|
602
|
+
self._children = self._edges
|
|
603
|
+
self._style_children = self._edges
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class Rectangle2D(Polygon2D) :
|
|
607
|
+
"""
|
|
608
|
+
Rectangle on a 2D plane
|
|
609
|
+
"""
|
|
610
|
+
def __init__(self, master : Plane2D,
|
|
611
|
+
point0 : np.ndarray|Point2D,
|
|
612
|
+
point1 : np.ndarray|Point2D,
|
|
613
|
+
point3 : np.ndarray|Point2D) :
|
|
614
|
+
"""
|
|
615
|
+
Parameters
|
|
616
|
+
----------
|
|
617
|
+
master : :obj:`Plane2D`
|
|
618
|
+
Plane on which to plot the rectangle
|
|
619
|
+
point0 : :obj:`Point2D`
|
|
620
|
+
A vertex of the rectangle
|
|
621
|
+
point1 : :obj:`Point2D`
|
|
622
|
+
An adjacent vertex to point0 of the rectangle
|
|
623
|
+
point3 : :obj:`Point2D`
|
|
624
|
+
An other adjacent vertex to point0 of the rectangle
|
|
625
|
+
"""
|
|
626
|
+
point3 = Point2D._compatibilitycheck(master, point3)
|
|
627
|
+
point0 = Point2D._compatibilitycheck(master, point0)
|
|
628
|
+
point1 = Point2D._compatibilitycheck(master, point1)
|
|
629
|
+
if np.abs(np.dot(point1 - point0, point3 - point0)) > 1.e-6 :
|
|
630
|
+
raise GM3DpsError("The two sides of the rectangle are not perpendicular to each other")
|
|
631
|
+
super().__init__(master,
|
|
632
|
+
[point0,
|
|
633
|
+
point1,
|
|
634
|
+
master.point(point1.position + (point3 - point0)),
|
|
635
|
+
point3])
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
class Ellipse2D(_Object2D) :
|
|
639
|
+
"""
|
|
640
|
+
Ellipse on a 2D plane
|
|
641
|
+
"""
|
|
642
|
+
def __init__(self, master : Plane2D,
|
|
643
|
+
center : Point2D|np.ndarray,
|
|
644
|
+
point0 : Point2D|np.ndarray,
|
|
645
|
+
point1 : Point2D|np.ndarray) :
|
|
646
|
+
"""
|
|
647
|
+
Parameters
|
|
648
|
+
----------
|
|
649
|
+
master : :obj:`Plane2D`
|
|
650
|
+
Plane on which to plot the ellipse
|
|
651
|
+
center : :obj:`Point2D`
|
|
652
|
+
Center of the ellipse
|
|
653
|
+
point0 : :obj:`Point2D`
|
|
654
|
+
Point at the end of the major axis
|
|
655
|
+
point1 : :obj:`Point2D`
|
|
656
|
+
Point at the end of the minor axis
|
|
657
|
+
"""
|
|
658
|
+
self._center = Point2D._compatibilitycheck(master, center)
|
|
659
|
+
self._point0 = Point2D._compatibilitycheck(master, point0)
|
|
660
|
+
self._point1 = Point2D._compatibilitycheck(master, point1)
|
|
661
|
+
self._a = np.linalg.norm(self._point0-self._center)
|
|
662
|
+
self._b = np.linalg.norm(self._point1-self._center)
|
|
663
|
+
if self._a < self._b :
|
|
664
|
+
self._b, self._a = self._a, self._b
|
|
665
|
+
self._point0, self._point1 = self._point1, self._point0
|
|
666
|
+
self._eccentricity = np.sqrt(1-(self._b/self._a)**2)
|
|
667
|
+
if np.abs(np.dot(self._point0-self._center,
|
|
668
|
+
self._point1-self._center)) > 1.e-6 :
|
|
669
|
+
raise GM3DpsError("The minor axis and the major axis are not perpendicular to each other")
|
|
670
|
+
super().__init__(master)
|
|
671
|
+
self._label_position = self._center
|
|
672
|
+
|
|
673
|
+
def _export(self, file) :
|
|
674
|
+
if self._drawflag :
|
|
675
|
+
Maxis = self._point0 - self._center
|
|
676
|
+
alpha = np.rad2deg(np.atan2(Maxis[1], Maxis[0]))
|
|
677
|
+
file.write("{} {} translate ".format(self._center.position[0],
|
|
678
|
+
self._center.position[1]))
|
|
679
|
+
file.write("{} rotate ".format(alpha))
|
|
680
|
+
file.write("1 {} scale\n".format(self._b/self._a))
|
|
681
|
+
self._drawstyle(file)
|
|
682
|
+
file.write("newpath 0 0 {} 0 360 arc stroke\ngrestore\ngsave\n".format(np.linalg.norm(Maxis)))
|
|
683
|
+
self.master._coordinate(file)
|
|
684
|
+
|
|
685
|
+
def foci(self) :
|
|
686
|
+
"""
|
|
687
|
+
Returns the foci of the ellipse
|
|
688
|
+
|
|
689
|
+
Returns
|
|
690
|
+
-------
|
|
691
|
+
focus0 : :obj:`Point2D`
|
|
692
|
+
Focus point closest to point0
|
|
693
|
+
focus1 : :obj:`Point2D`
|
|
694
|
+
Focus point furthest to point0
|
|
695
|
+
"""
|
|
696
|
+
focus0 = self.master.point(self._center.position+self._eccentricity*(self._point0-self._center))
|
|
697
|
+
focus1 = self.master.point(self._center.position-self._eccentricity*(self._point0-self._center))
|
|
698
|
+
return focus0, focus1
|
|
699
|
+
|
|
700
|
+
def point(self, alpha : float, mode : str = "eccentric") :
|
|
701
|
+
"""
|
|
702
|
+
Return a point on the ellipse corresponding to an angle.
|
|
703
|
+
|
|
704
|
+
Parameters
|
|
705
|
+
----------
|
|
706
|
+
alpha : float
|
|
707
|
+
Angle to position the point
|
|
708
|
+
mode : str, default ``"eccentric"``
|
|
709
|
+
mode to mesure the angle
|
|
710
|
+
|
|
711
|
+
* ``"eccentric"`` : using the eccentric angle to
|
|
712
|
+
position the point, starting from ``point0`` and going
|
|
713
|
+
towards ``point1``
|
|
714
|
+
|
|
715
|
+
* ``"true0"`` : using the true anomaly starting from
|
|
716
|
+
``point0`` going towards ``point1``
|
|
717
|
+
|
|
718
|
+
* ``"true1"`` : using the true anomaly starting from
|
|
719
|
+
the opposite point to ``point0`` and going the same
|
|
720
|
+
way as ``"true0"``
|
|
721
|
+
|
|
722
|
+
Returns
|
|
723
|
+
-------
|
|
724
|
+
out : :obj:`Point2D`
|
|
725
|
+
Corresponding to the angle
|
|
726
|
+
"""
|
|
727
|
+
if mode == "eccentric" :
|
|
728
|
+
return self.master.point(np.cos(alpha)*(self._point0-self._center) + np.sin(alpha)*(self._point1-self._center))
|
|
729
|
+
elif mode == "true0" :
|
|
730
|
+
theta = 2.*np.atan2(np.sqrt((1.-self._eccentricity)/(1.+self._eccentricity))*np.sin(alpha/2.),np.cos(alpha/2.))
|
|
731
|
+
return self.point(theta, mode="eccentric")
|
|
732
|
+
elif mode == "true1" :
|
|
733
|
+
theta = 2.*np.atan2(np.sqrt((1.-self._eccentricity)/(1.+self._eccentricity))*np.sin(alpha/2.),np.cos(alpha/2.)) + np.pi
|
|
734
|
+
return self.point(theta, mode="eccentric")
|
|
735
|
+
else :
|
|
736
|
+
raise GM3DpsError("mode should be eccentric, true0 or true1 recieved {} instead".format(mode))
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
class _ArrowHead2D(_Object2D) :
|
|
740
|
+
def __init__(self, master : Plane2D, end : Point2D|np.ndarray, length : float, width : float, alpha : float) :
|
|
741
|
+
super().__init__(master)
|
|
742
|
+
self._end = Point2D._compatibilitycheck(master, end)
|
|
743
|
+
self.length = length
|
|
744
|
+
self.width = width
|
|
745
|
+
self.alpha = alpha
|
|
746
|
+
self._label_position = self._end._position
|
|
747
|
+
|
|
748
|
+
@property
|
|
749
|
+
def end(self) :
|
|
750
|
+
return self._end
|
|
751
|
+
|
|
752
|
+
@end.setter
|
|
753
|
+
def end(self, end : Point2D) :
|
|
754
|
+
self._end = end
|
|
755
|
+
self._label_position = self.end._position
|
|
756
|
+
|
|
757
|
+
def back(self) :
|
|
758
|
+
return self.master.point(self._end - self.length*np.array([np.cos(self.alpha), np.sin(self.alpha)]))
|
|
759
|
+
|
|
760
|
+
def _export(self, file) :
|
|
761
|
+
if self._drawflag :
|
|
762
|
+
file.write("{} {} moveto\n".format(self.end.position[0], self.end.position[1]))
|
|
763
|
+
file.write("{} dup rotate\n".format(np.rad2deg(self.alpha)))
|
|
764
|
+
self._drawstyle(file)
|
|
765
|
+
file.write("0 {} dup 2 div {} neg exch rlineto neg rlineto closepath fill\nneg rotate\n".format(self.width, self.length))
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
class Vector2D(_Object2D) :
|
|
769
|
+
"""
|
|
770
|
+
Vector to plot on a 2D plane
|
|
771
|
+
"""
|
|
772
|
+
def __init__(self, master : Plane2D,
|
|
773
|
+
start : Point2D|np.ndarray,
|
|
774
|
+
end : Point2D|np.ndarray) :
|
|
775
|
+
"""
|
|
776
|
+
Parameters
|
|
777
|
+
----------
|
|
778
|
+
master : :obj:`Plane2D`
|
|
779
|
+
Plane on which the vector will be plotted
|
|
780
|
+
start : :obj:`Point2D`
|
|
781
|
+
Start of the vector
|
|
782
|
+
end : :obj:`Point2D`
|
|
783
|
+
End of the vector
|
|
784
|
+
"""
|
|
785
|
+
super().__init__(master)
|
|
786
|
+
self.start = Point2D._compatibilitycheck(master, start)
|
|
787
|
+
""":py:obj:`Point2D`: Start of the vector on the 2D plane"""
|
|
788
|
+
self.end = Point2D._compatibilitycheck(master, end)
|
|
789
|
+
""":py:obj:`Point2D`: End of the vector on the 2D plane"""
|
|
790
|
+
self.line = master.line(self.start, self.end)
|
|
791
|
+
""":py:obj:`Line2D`: Line body of the vector"""
|
|
792
|
+
temp = self.end - self.start
|
|
793
|
+
self.arrowhead = master._arrowhead(self.end, 10., 5., np.rad2deg(np.atan2(temp[1], temp[0])))
|
|
794
|
+
""":py:obj:`_Arrowhead2D`: Arrow head of the vector"""
|
|
795
|
+
self._label_position = self.line._label_position
|
|
796
|
+
self._children = [self.line, self.arrowhead]
|
|
797
|
+
self._style_children = [self.line]
|
|
798
|
+
|
|
799
|
+
class Arc2D(_Object2D) :
|
|
800
|
+
"""
|
|
801
|
+
Arc to show an angle on a 2D plane
|
|
802
|
+
"""
|
|
803
|
+
def __init__(self, master : Plane2D,
|
|
804
|
+
center : Point2D|np.ndarray,
|
|
805
|
+
alpha0 : float,
|
|
806
|
+
alpha1 : float,
|
|
807
|
+
radius : float) :
|
|
808
|
+
"""
|
|
809
|
+
Parameters
|
|
810
|
+
----------
|
|
811
|
+
master : :obj:`Plane2D`
|
|
812
|
+
Plane to plot the arc on
|
|
813
|
+
center : :obj:`Point2D`
|
|
814
|
+
Center of the arc
|
|
815
|
+
alpha0 : float
|
|
816
|
+
Start of the arc
|
|
817
|
+
alpha1 : float
|
|
818
|
+
End of the arc
|
|
819
|
+
radius : float
|
|
820
|
+
Radius of the arc
|
|
821
|
+
"""
|
|
822
|
+
super().__init__(master)
|
|
823
|
+
self.center = Point2D._compatibilitycheck(self.master, center)
|
|
824
|
+
""":py:obj:`Point2D`: Center of the arc"""
|
|
825
|
+
self.radius = radius
|
|
826
|
+
"""float: Radius of the arc"""
|
|
827
|
+
self.alpha0 = alpha0
|
|
828
|
+
"""float: Starting angle of the arc"""
|
|
829
|
+
self.alpha1 = alpha1
|
|
830
|
+
"""float: Ending angle of the arc"""
|
|
831
|
+
self._adjustement()
|
|
832
|
+
|
|
833
|
+
def _adjustement(self) :
|
|
834
|
+
self._label_position = self.center.position + self.radius*(np.array([np.cos((self.alpha0+self.alpha1)/2), np.sin((self.alpha0+self.alpha1)/2)]))
|
|
835
|
+
|
|
836
|
+
def _export(self, file) :
|
|
837
|
+
if self._drawflag :
|
|
838
|
+
self._drawstyle(file)
|
|
839
|
+
file.write("newpath {} {} {} {} {} arc stroke\n".format(self.center.position[0],
|
|
840
|
+
self.center.position[1],
|
|
841
|
+
self.radius,
|
|
842
|
+
np.rad2deg(self.alpha0),
|
|
843
|
+
np.rad2deg(self.alpha1)))
|
|
844
|
+
|
|
845
|
+
class Angle2D(_Object2D) :
|
|
846
|
+
"""
|
|
847
|
+
Angle to show an angle on a 2D plane
|
|
848
|
+
"""
|
|
849
|
+
def __init__(self, master : Plane2D,
|
|
850
|
+
center : Point2D|np.ndarray,
|
|
851
|
+
alpha0 : float,
|
|
852
|
+
alpha1 : float,
|
|
853
|
+
radius : float,
|
|
854
|
+
anticlockwise : bool = True) :
|
|
855
|
+
"""
|
|
856
|
+
Parameters
|
|
857
|
+
----------
|
|
858
|
+
master : :obj:`Plane2D`
|
|
859
|
+
Plane on which to plot the angle
|
|
860
|
+
center : :obj:`Point2D`
|
|
861
|
+
Center of the angle
|
|
862
|
+
alpha0 : float
|
|
863
|
+
Starting *angle* of the angle
|
|
864
|
+
alpha1 : float
|
|
865
|
+
Ending *angle* of the angle
|
|
866
|
+
radius : float
|
|
867
|
+
Radius of the angle
|
|
868
|
+
anticlockwise : bool, default : ``True``
|
|
869
|
+
Whether or not the angle will anticlockwise
|
|
870
|
+
"""
|
|
871
|
+
super().__init__(master)
|
|
872
|
+
self.center = Point2D._compatibilitycheck(master, center)
|
|
873
|
+
""":py:obj:`Point2D`: Center of the angle on the plane"""
|
|
874
|
+
self._radius = radius
|
|
875
|
+
self._alpha0 = alpha0
|
|
876
|
+
self._alpha1 = alpha1
|
|
877
|
+
self._anticlockwise = anticlockwise
|
|
878
|
+
temp = master.point(center.position+self._radius*np.array([np.cos(alpha1), np.sin(alpha1)]))
|
|
879
|
+
if anticlockwise :
|
|
880
|
+
temp_arrowhead = master._arrowhead(temp, 6., 11., alpha1 + np.pi/2)
|
|
881
|
+
temp_arc = self.master.arc(self.center, self._alpha0, self._alpha1, self._radius)
|
|
882
|
+
else :
|
|
883
|
+
temp_arrowhead = master._arrowhead(temp, 6., 11., alpha1 - np.pi/2)
|
|
884
|
+
temp_arc = self.master.arc(self.center, self._alpha1, self._alpha0, self._radius)
|
|
885
|
+
self.arrowhead = temp_arrowhead
|
|
886
|
+
""":py:obj:`_Arrowhead2D`: Arrow head of the angle. Its ``width``,
|
|
887
|
+
``lenght`` and ``style`` can be modified like other drawn object"""
|
|
888
|
+
self.arc = temp_arc
|
|
889
|
+
""":py:obj:`Arc2D`: Arc body of the angle"""
|
|
890
|
+
self._label_position = self.arc._label_position
|
|
891
|
+
self._children = [self.arrowhead, self.arc]
|
|
892
|
+
|
|
893
|
+
def _adjustement(self) :
|
|
894
|
+
if np.abs(np.sin(self._alpha1-self._alpha0)) > 2*self.arrowhead.length/self._radius :
|
|
895
|
+
self.arc.center = self.master.point(self.center.position -
|
|
896
|
+
self.arrowhead.length*np.array([np.cos(self._alpha0), np.sin(self._alpha0)])/np.sin(self._alpha1-self._alpha0)
|
|
897
|
+
)
|
|
898
|
+
self.arc.radius = self.radius - np.abs(self.arrowhead.length/np.tan(self._alpha1-self._alpha0))
|
|
899
|
+
self.arc._adjustement()
|
|
900
|
+
|
|
901
|
+
@property
|
|
902
|
+
def radius(self) :
|
|
903
|
+
"""
|
|
904
|
+
float: Radius of the angle
|
|
905
|
+
"""
|
|
906
|
+
return self._radius
|
|
907
|
+
|
|
908
|
+
@radius.setter
|
|
909
|
+
def radius(self, value : float) :
|
|
910
|
+
self._radius = value
|
|
911
|
+
self.arrowhead.end.position = self.center.position + self._radius*np.array([np.cos(self._alpha1), np.sin(self._alpha1)])
|
|
912
|
+
self._adjustement()
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
class _ObjectPseudo3D :
|
|
916
|
+
def __init__(self, master : Figure3D, origin : np.ndarray, normal : np.ndarray) :
|
|
917
|
+
self.master = master
|
|
918
|
+
self.origin = origin
|
|
919
|
+
self.plane = Plane2D(master, origin, normal, 0.)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: GM3Dps
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A package to create 3D (and 2D) Postscript figure in Python
|
|
5
|
+
Author-email: Yannis Benchaib--Langon <yannis.benchaib-langon@student.isae-supaero.fr>
|
|
6
|
+
Project-URL: Homepage, https://github.com/Gutenews/GM3Dps
|
|
7
|
+
Keywords: geometry,postscript
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: numpy>=2.4.0
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# GM3Dps
|
|
16
|
+
A python module to draw 3D (or 2D) figure in postscript.
|
|
17
|
+
Documentation available at : https://gm3dps.readthedocs.io/en/latest/index.html
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
./GM3Dps/__init__.py
|
|
5
|
+
./GM3Dps/core.py
|
|
6
|
+
GM3Dps/__init__.py
|
|
7
|
+
GM3Dps/core.py
|
|
8
|
+
GM3Dps.egg-info/PKG-INFO
|
|
9
|
+
GM3Dps.egg-info/SOURCES.txt
|
|
10
|
+
GM3Dps.egg-info/dependency_links.txt
|
|
11
|
+
GM3Dps.egg-info/requires.txt
|
|
12
|
+
GM3Dps.egg-info/top_level.txt
|
|
13
|
+
tests/test.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy>=2.4.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GM3Dps
|
gm3dps-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yannis Benchaib
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gm3dps-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: GM3Dps
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A package to create 3D (and 2D) Postscript figure in Python
|
|
5
|
+
Author-email: Yannis Benchaib--Langon <yannis.benchaib-langon@student.isae-supaero.fr>
|
|
6
|
+
Project-URL: Homepage, https://github.com/Gutenews/GM3Dps
|
|
7
|
+
Keywords: geometry,postscript
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: numpy>=2.4.0
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# GM3Dps
|
|
16
|
+
A python module to draw 3D (or 2D) figure in postscript.
|
|
17
|
+
Documentation available at : https://gm3dps.readthedocs.io/en/latest/index.html
|
gm3dps-0.0.1/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "GM3Dps"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "A package to create 3D (and 2D) Postscript figure in Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{name = "Yannis Benchaib--Langon", email = "yannis.benchaib-langon@student.isae-supaero.fr"}]
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"geometry",
|
|
18
|
+
"postscript"
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"numpy>=2.4.0"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/Gutenews/GM3Dps"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
packages = ["GM3Dps"]
|
|
29
|
+
package-dir = {"" = "."}
|
gm3dps-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Sun Oct 11 11:58:41 2026
|
|
4
|
+
|
|
5
|
+
@author: yanni
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import GM3Dps as gm
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
draw = gm.Figure3D(200, 200)
|
|
12
|
+
plan = gm.Plane2D(draw, np.array([0,0,0]), np.array([-1,-1,1]), 0.)
|
|
13
|
+
O = gm.Point2D(plan, np.array([0,0]))
|
|
14
|
+
droitex = gm.Line2D(plan, O, np.array([100,0]))
|
|
15
|
+
droitex.draw()
|
|
16
|
+
droitey = gm.Line2D(plan, O, np.array([0,100]))
|
|
17
|
+
droitey.style(dash = [5,5])
|
|
18
|
+
droitey.draw()
|
|
19
|
+
ell = gm.Ellipse2D(plan, O, droitex.end, droitey.end)
|
|
20
|
+
ell.draw()
|
|
21
|
+
|
|
22
|
+
draw.export("test.ps")
|