passagemath-plot 10.6.31rc3__cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
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.
Potentially problematic release.
This version of passagemath-plot might be problematic. Click here for more details.
- passagemath_plot-10.6.31rc3.dist-info/METADATA +172 -0
- passagemath_plot-10.6.31rc3.dist-info/RECORD +81 -0
- passagemath_plot-10.6.31rc3.dist-info/WHEEL +6 -0
- passagemath_plot-10.6.31rc3.dist-info/top_level.txt +2 -0
- passagemath_plot.libs/libgfortran-e1b7dfc8.so.5.0.0 +0 -0
- passagemath_plot.libs/libgsl-e3525837.so.28.0.0 +0 -0
- passagemath_plot.libs/libopenblasp-r0-4c5b64b1.3.29.so +0 -0
- sage/all__sagemath_plot.py +15 -0
- sage/ext_data/threejs/animation.css +195 -0
- sage/ext_data/threejs/animation.html +85 -0
- sage/ext_data/threejs/animation.js +273 -0
- sage/ext_data/threejs/fat_lines.js +48 -0
- sage/ext_data/threejs/threejs-version.txt +1 -0
- sage/ext_data/threejs/threejs_template.html +597 -0
- sage/interfaces/all__sagemath_plot.py +1 -0
- sage/interfaces/gnuplot.py +196 -0
- sage/interfaces/jmoldata.py +208 -0
- sage/interfaces/povray.py +56 -0
- sage/plot/all.py +42 -0
- sage/plot/animate.py +1796 -0
- sage/plot/arc.py +504 -0
- sage/plot/arrow.py +671 -0
- sage/plot/bar_chart.py +205 -0
- sage/plot/bezier_path.py +400 -0
- sage/plot/circle.py +435 -0
- sage/plot/colors.py +1606 -0
- sage/plot/complex_plot.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/plot/complex_plot.pyx +1446 -0
- sage/plot/contour_plot.py +1792 -0
- sage/plot/density_plot.py +318 -0
- sage/plot/disk.py +373 -0
- sage/plot/ellipse.py +375 -0
- sage/plot/graphics.py +3580 -0
- sage/plot/histogram.py +354 -0
- sage/plot/hyperbolic_arc.py +404 -0
- sage/plot/hyperbolic_polygon.py +416 -0
- sage/plot/hyperbolic_regular_polygon.py +296 -0
- sage/plot/line.py +626 -0
- sage/plot/matrix_plot.py +629 -0
- sage/plot/misc.py +509 -0
- sage/plot/multigraphics.py +1294 -0
- sage/plot/plot.py +4183 -0
- sage/plot/plot3d/all.py +23 -0
- sage/plot/plot3d/base.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/plot/plot3d/base.pxd +12 -0
- sage/plot/plot3d/base.pyx +3378 -0
- sage/plot/plot3d/implicit_plot3d.py +659 -0
- sage/plot/plot3d/implicit_surface.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/plot/plot3d/implicit_surface.pyx +1453 -0
- sage/plot/plot3d/index_face_set.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/plot/plot3d/index_face_set.pxd +32 -0
- sage/plot/plot3d/index_face_set.pyx +1873 -0
- sage/plot/plot3d/introduction.py +131 -0
- sage/plot/plot3d/list_plot3d.py +649 -0
- sage/plot/plot3d/parametric_plot3d.py +1130 -0
- sage/plot/plot3d/parametric_surface.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/plot/plot3d/parametric_surface.pxd +12 -0
- sage/plot/plot3d/parametric_surface.pyx +893 -0
- sage/plot/plot3d/platonic.py +601 -0
- sage/plot/plot3d/plot3d.py +1442 -0
- sage/plot/plot3d/plot_field3d.py +162 -0
- sage/plot/plot3d/point_c.pxi +148 -0
- sage/plot/plot3d/revolution_plot3d.py +309 -0
- sage/plot/plot3d/shapes.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/plot/plot3d/shapes.pxd +22 -0
- sage/plot/plot3d/shapes.pyx +1382 -0
- sage/plot/plot3d/shapes2.py +1512 -0
- sage/plot/plot3d/tachyon.py +1779 -0
- sage/plot/plot3d/texture.py +453 -0
- sage/plot/plot3d/transform.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/plot/plot3d/transform.pxd +21 -0
- sage/plot/plot3d/transform.pyx +268 -0
- sage/plot/plot3d/tri_plot.py +589 -0
- sage/plot/plot_field.py +362 -0
- sage/plot/point.py +624 -0
- sage/plot/polygon.py +562 -0
- sage/plot/primitive.py +249 -0
- sage/plot/scatter_plot.py +199 -0
- sage/plot/step.py +85 -0
- sage/plot/streamline_plot.py +328 -0
- sage/plot/text.py +432 -0
|
@@ -0,0 +1,1873 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-plot
|
|
2
|
+
"""
|
|
3
|
+
Indexed face sets
|
|
4
|
+
|
|
5
|
+
Graphics3D object that consists of a list of polygons, also used for
|
|
6
|
+
triangulations of other objects.
|
|
7
|
+
|
|
8
|
+
Usually these objects are not created directly by users.
|
|
9
|
+
|
|
10
|
+
AUTHORS:
|
|
11
|
+
|
|
12
|
+
- Robert Bradshaw (2007-08-26): initial version
|
|
13
|
+
- Robert Bradshaw (2007-08-28): significant optimizations
|
|
14
|
+
|
|
15
|
+
.. TODO::
|
|
16
|
+
|
|
17
|
+
Smooth triangles using vertex normals
|
|
18
|
+
"""
|
|
19
|
+
# ****************************************************************************
|
|
20
|
+
# Copyright (C) 2007 Robert Bradshaw <robertwb@math.washington.edu>
|
|
21
|
+
#
|
|
22
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
23
|
+
#
|
|
24
|
+
# This code is distributed in the hope that it will be useful,
|
|
25
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
26
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
27
|
+
# General Public License for more details.
|
|
28
|
+
#
|
|
29
|
+
# The full text of the GPL is available at:
|
|
30
|
+
#
|
|
31
|
+
# https://www.gnu.org/licenses/
|
|
32
|
+
# ****************************************************************************
|
|
33
|
+
|
|
34
|
+
from textwrap import dedent
|
|
35
|
+
|
|
36
|
+
from libc.math cimport INFINITY
|
|
37
|
+
from libc.string cimport memset, memcpy
|
|
38
|
+
from cysignals.memory cimport check_calloc, check_allocarray, check_reallocarray, sig_free
|
|
39
|
+
from cysignals.signals cimport sig_check, sig_on, sig_off
|
|
40
|
+
|
|
41
|
+
cdef extern from *:
|
|
42
|
+
int sprintf_3d "sprintf" (char*, char*, double, double, double)
|
|
43
|
+
int sprintf_3i "sprintf" (char*, char*, int, int, int)
|
|
44
|
+
int sprintf_4i "sprintf" (char*, char*, int, int, int, int)
|
|
45
|
+
int sprintf_5i "sprintf" (char*, char*, int, int, int, int, int)
|
|
46
|
+
int sprintf_6i "sprintf" (char*, char*, int, int, int, int, int, int)
|
|
47
|
+
int sprintf_7i "sprintf" (char*, char*, int, int, int, int, int, int, int)
|
|
48
|
+
int sprintf_9d "sprintf" (char*, char*, double, double, double, double, double, double, double, double, double)
|
|
49
|
+
|
|
50
|
+
from cpython.list cimport *
|
|
51
|
+
from cpython.bytes cimport *
|
|
52
|
+
|
|
53
|
+
include "point_c.pxi"
|
|
54
|
+
|
|
55
|
+
from sage.cpython.string cimport bytes_to_str
|
|
56
|
+
|
|
57
|
+
from sage.rings.real_double import RDF
|
|
58
|
+
|
|
59
|
+
from sage.modules.free_module_element import vector
|
|
60
|
+
|
|
61
|
+
from sage.plot.colors import Color, float_to_integer
|
|
62
|
+
from sage.plot.plot3d.base import Graphics3dGroup
|
|
63
|
+
from sage.plot.plot3d.texture import Texture
|
|
64
|
+
|
|
65
|
+
from sage.plot.plot3d.transform cimport Transformation
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# --------------------------------------------------------------------
|
|
69
|
+
# Fast routines for generating string representations of the polygons.
|
|
70
|
+
# --------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
cdef inline format_tachyon_texture(color_c rgb):
|
|
73
|
+
cdef char rs[200]
|
|
74
|
+
cdef Py_ssize_t cr = sprintf_3d(rs,
|
|
75
|
+
"TEXTURE\n AMBIENT 0.3 DIFFUSE 0.7 SPECULAR 0 OPACITY 1.0\n COLOR %g %g %g \n TEXFUNC 0",
|
|
76
|
+
rgb.r, rgb.g, rgb.b)
|
|
77
|
+
return bytes_to_str(PyBytes_FromStringAndSize(rs, cr))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
cdef inline format_tachyon_triangle(point_c P, point_c Q, point_c R):
|
|
81
|
+
cdef char ss[250]
|
|
82
|
+
# PyBytes_FromFormat doesn't do floats?
|
|
83
|
+
cdef Py_ssize_t r = sprintf_9d(ss,
|
|
84
|
+
"TRI V0 %g %g %g V1 %g %g %g V2 %g %g %g",
|
|
85
|
+
P.x, P.y, P.z,
|
|
86
|
+
Q.x, Q.y, Q.z,
|
|
87
|
+
R.x, R.y, R.z )
|
|
88
|
+
return bytes_to_str(PyBytes_FromStringAndSize(ss, r))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
cdef inline format_json_vertex(point_c P):
|
|
92
|
+
cdef char ss[100]
|
|
93
|
+
cdef Py_ssize_t r = sprintf_3d(ss, '{"x":%g,"y":%g,"z":%g}', P.x, P.y, P.z)
|
|
94
|
+
return bytes_to_str(PyBytes_FromStringAndSize(ss, r))
|
|
95
|
+
|
|
96
|
+
cdef inline format_json_face(face_c face):
|
|
97
|
+
s = "[{}]".format(",".join(str(face.vertices[i]) for i in range(face.n)))
|
|
98
|
+
return s
|
|
99
|
+
|
|
100
|
+
cdef inline format_obj_vertex(point_c P):
|
|
101
|
+
cdef char ss[100]
|
|
102
|
+
# PyBytes_FromFormat doesn't do floats?
|
|
103
|
+
cdef Py_ssize_t r = sprintf_3d(ss, "v %g %g %g", P.x, P.y, P.z)
|
|
104
|
+
return bytes_to_str(PyBytes_FromStringAndSize(ss, r))
|
|
105
|
+
|
|
106
|
+
cdef inline format_obj_face(face_c face, int off):
|
|
107
|
+
cdef char ss[100]
|
|
108
|
+
cdef Py_ssize_t r, i
|
|
109
|
+
if face.n == 3:
|
|
110
|
+
r = sprintf_3i(ss, "f %d %d %d", face.vertices[0] + off, face.vertices[1] + off, face.vertices[2] + off)
|
|
111
|
+
elif face.n == 4:
|
|
112
|
+
r = sprintf_4i(ss, "f %d %d %d %d", face.vertices[0] + off, face.vertices[1] + off, face.vertices[2] + off, face.vertices[3] + off)
|
|
113
|
+
else:
|
|
114
|
+
return "f " + " ".join(str(face.vertices[i] + off) for i in range(face.n))
|
|
115
|
+
# PyBytes_FromFormat is almost twice as slow
|
|
116
|
+
return bytes_to_str(PyBytes_FromStringAndSize(ss, r))
|
|
117
|
+
|
|
118
|
+
cdef inline format_obj_face_back(face_c face, int off):
|
|
119
|
+
cdef char ss[100]
|
|
120
|
+
cdef Py_ssize_t r, i
|
|
121
|
+
if face.n == 3:
|
|
122
|
+
r = sprintf_3i(ss, "f %d %d %d", face.vertices[2] + off, face.vertices[1] + off, face.vertices[0] + off)
|
|
123
|
+
elif face.n == 4:
|
|
124
|
+
r = sprintf_4i(ss, "f %d %d %d %d", face.vertices[3] + off, face.vertices[2] + off, face.vertices[1] + off, face.vertices[0] + off)
|
|
125
|
+
else:
|
|
126
|
+
return "f " + " ".join(str(face.vertices[i] + off) for i from face.n > i >= 0)
|
|
127
|
+
return bytes_to_str(PyBytes_FromStringAndSize(ss, r))
|
|
128
|
+
|
|
129
|
+
cdef inline format_pmesh_vertex(point_c P):
|
|
130
|
+
cdef char ss[100]
|
|
131
|
+
# PyBytes_FromFormat doesn't do floats?
|
|
132
|
+
cdef Py_ssize_t r = sprintf_3d(ss, "%g %g %g", P.x, P.y, P.z)
|
|
133
|
+
return bytes_to_str(PyBytes_FromStringAndSize(ss, r))
|
|
134
|
+
|
|
135
|
+
cdef inline format_pmesh_face(face_c face, int has_color):
|
|
136
|
+
cdef char ss[100]
|
|
137
|
+
cdef Py_ssize_t r, i
|
|
138
|
+
cdef int color
|
|
139
|
+
# if the face has an individual color, has_color is -1
|
|
140
|
+
# otherwise it is 1
|
|
141
|
+
if has_color == -1:
|
|
142
|
+
color = float_to_integer(face.color.r,
|
|
143
|
+
face.color.g,
|
|
144
|
+
face.color.b)
|
|
145
|
+
# it seems that Jmol does not like the 0 color at all
|
|
146
|
+
if color == 0:
|
|
147
|
+
color = 1
|
|
148
|
+
|
|
149
|
+
if face.n == 3:
|
|
150
|
+
if has_color == 1:
|
|
151
|
+
r = sprintf_5i(ss, "%d\n%d\n%d\n%d\n%d", has_color * 4,
|
|
152
|
+
face.vertices[0],
|
|
153
|
+
face.vertices[1],
|
|
154
|
+
face.vertices[2],
|
|
155
|
+
face.vertices[0])
|
|
156
|
+
else:
|
|
157
|
+
r = sprintf_6i(ss, "%d\n%d\n%d\n%d\n%d\n%d", has_color * 4,
|
|
158
|
+
face.vertices[0],
|
|
159
|
+
face.vertices[1],
|
|
160
|
+
face.vertices[2],
|
|
161
|
+
face.vertices[0], color)
|
|
162
|
+
elif face.n == 4:
|
|
163
|
+
if has_color == 1:
|
|
164
|
+
r = sprintf_6i(ss, "%d\n%d\n%d\n%d\n%d\n%d", has_color * 5,
|
|
165
|
+
face.vertices[0],
|
|
166
|
+
face.vertices[1],
|
|
167
|
+
face.vertices[2],
|
|
168
|
+
face.vertices[3],
|
|
169
|
+
face.vertices[0])
|
|
170
|
+
else:
|
|
171
|
+
r = sprintf_7i(ss, "%d\n%d\n%d\n%d\n%d\n%d\n%d", has_color * 5,
|
|
172
|
+
face.vertices[0],
|
|
173
|
+
face.vertices[1],
|
|
174
|
+
face.vertices[2],
|
|
175
|
+
face.vertices[3],
|
|
176
|
+
face.vertices[0], color)
|
|
177
|
+
else:
|
|
178
|
+
# Naive triangulation
|
|
179
|
+
all = []
|
|
180
|
+
if has_color == 1:
|
|
181
|
+
for i in range(1, face.n - 1):
|
|
182
|
+
r = sprintf_5i(ss, "%d\n%d\n%d\n%d\n%d", has_color * 4,
|
|
183
|
+
face.vertices[0],
|
|
184
|
+
face.vertices[i],
|
|
185
|
+
face.vertices[i + 1],
|
|
186
|
+
face.vertices[0])
|
|
187
|
+
PyList_Append(all, PyBytes_FromStringAndSize(ss, r))
|
|
188
|
+
else:
|
|
189
|
+
for i in range(1, face.n - 1):
|
|
190
|
+
r = sprintf_6i(ss, "%d\n%d\n%d\n%d\n%d\n%d", has_color * 4,
|
|
191
|
+
face.vertices[0],
|
|
192
|
+
face.vertices[i],
|
|
193
|
+
face.vertices[i + 1],
|
|
194
|
+
face.vertices[0], color)
|
|
195
|
+
PyList_Append(all, PyBytes_FromStringAndSize(ss, r))
|
|
196
|
+
return bytes_to_str(b"\n".join(all))
|
|
197
|
+
# PyBytes_FromFormat is almost twice as slow
|
|
198
|
+
return bytes_to_str(PyBytes_FromStringAndSize(ss, r))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def midpoint(pointa, pointb, w):
|
|
202
|
+
"""
|
|
203
|
+
Return the weighted mean of two points in 3-space.
|
|
204
|
+
|
|
205
|
+
INPUT:
|
|
206
|
+
|
|
207
|
+
- ``pointa``, ``pointb`` -- two points in 3-dimensional space
|
|
208
|
+
|
|
209
|
+
- ``w`` -- a real weight between 0 and 1
|
|
210
|
+
|
|
211
|
+
If the weight is zero, the result is ``pointb``. If the weight is
|
|
212
|
+
one, the result is ``pointa``.
|
|
213
|
+
|
|
214
|
+
EXAMPLES::
|
|
215
|
+
|
|
216
|
+
sage: from sage.plot.plot3d.index_face_set import midpoint
|
|
217
|
+
sage: midpoint((1,2,3),(4,4,4),0.8)
|
|
218
|
+
(1.60000000000000, 2.40000000000000, 3.20000000000000)
|
|
219
|
+
"""
|
|
220
|
+
xa, ya, za = pointa
|
|
221
|
+
xb, yb, zb = pointb
|
|
222
|
+
v = 1 - w
|
|
223
|
+
return ((w * xa + v * xb), (w * ya + v * yb), (w * za + v * zb))
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def cut_edge_by_bisection(pointa, pointb, condition, eps=1.0e-6, N=100):
|
|
227
|
+
"""
|
|
228
|
+
Cut an intersecting edge using the bisection method.
|
|
229
|
+
|
|
230
|
+
Given two points (pointa and pointb) and a condition (boolean
|
|
231
|
+
function), this calculates the position at the edge (defined by
|
|
232
|
+
both points) where the boolean condition switches its value.
|
|
233
|
+
|
|
234
|
+
INPUT:
|
|
235
|
+
|
|
236
|
+
- ``pointa``, ``pointb`` -- two points in 3-dimensional space
|
|
237
|
+
|
|
238
|
+
- ``N`` -- max number of steps in the bisection method (default: 100)
|
|
239
|
+
to cut the boundary triangles that are not entirely within
|
|
240
|
+
the domain.
|
|
241
|
+
|
|
242
|
+
- ``eps`` -- target accuracy in the intersection (default: 1.0e-6)
|
|
243
|
+
|
|
244
|
+
OUTPUT:
|
|
245
|
+
|
|
246
|
+
intersection of the edge defined by ``pointa`` and ``pointb``,
|
|
247
|
+
and ``condition``.
|
|
248
|
+
|
|
249
|
+
EXAMPLES::
|
|
250
|
+
|
|
251
|
+
sage: from sage.plot.plot3d.index_face_set import cut_edge_by_bisection
|
|
252
|
+
sage: cut_edge_by_bisection((0.0,0.0,0.0), (1.0,1.0,0.0),
|
|
253
|
+
....: lambda x,y,z: x**2+y**2+z**2 < 1, eps=1.0E-12)
|
|
254
|
+
(0.7071067811864395, 0.7071067811864395, 0.0)
|
|
255
|
+
"""
|
|
256
|
+
cdef point_c a, b
|
|
257
|
+
cdef point_c midp, b_min_a
|
|
258
|
+
cdef double half = 0.5
|
|
259
|
+
|
|
260
|
+
point_c_set(&a, pointa)
|
|
261
|
+
point_c_set(&b, pointb)
|
|
262
|
+
|
|
263
|
+
itern = 0
|
|
264
|
+
|
|
265
|
+
point_c_sub(&b_min_a, b, a)
|
|
266
|
+
|
|
267
|
+
while point_c_len(b_min_a) > eps:
|
|
268
|
+
itern += 1
|
|
269
|
+
if itern > N:
|
|
270
|
+
break
|
|
271
|
+
# (b+a)/2
|
|
272
|
+
point_c_middle(&midp, b, a, half)
|
|
273
|
+
|
|
274
|
+
if condition(a.x, a.y, a.z) and condition(midp.x, midp.y, midp.z):
|
|
275
|
+
a = midp
|
|
276
|
+
else:
|
|
277
|
+
b = midp
|
|
278
|
+
# (b-a)
|
|
279
|
+
point_c_sub(&b_min_a, b, a)
|
|
280
|
+
|
|
281
|
+
point_c_middle(&midp, b, a, half)
|
|
282
|
+
|
|
283
|
+
return midp.x, midp.y, midp.z
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
cdef class IndexFaceSet(PrimitiveObject):
|
|
287
|
+
"""
|
|
288
|
+
Graphics3D object that consists of a list of polygons, also used for
|
|
289
|
+
triangulations of other objects.
|
|
290
|
+
|
|
291
|
+
Polygons (mostly triangles and quadrilaterals) are stored in the
|
|
292
|
+
c struct ``face_c`` (see transform.pyx). Rather than storing
|
|
293
|
+
the points directly for each polygon, each face consists a list
|
|
294
|
+
of pointers into a common list of points which are basically triples
|
|
295
|
+
of doubles in a ``point_c``.
|
|
296
|
+
|
|
297
|
+
Moreover, each face has an attribute ``color`` which is used to
|
|
298
|
+
store color information when faces are colored. The red/green/blue
|
|
299
|
+
components are then available as floats between 0 and 1 using
|
|
300
|
+
``color.r,color.g,color.b``.
|
|
301
|
+
|
|
302
|
+
Usually these objects are not created directly by users.
|
|
303
|
+
|
|
304
|
+
EXAMPLES::
|
|
305
|
+
|
|
306
|
+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
|
|
307
|
+
sage: S = IndexFaceSet([[(1,0,0),(0,1,0),(0,0,1)], [(1,0,0),(0,1,0),(0,0,0)]])
|
|
308
|
+
sage: S.face_list()
|
|
309
|
+
[[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)],
|
|
310
|
+
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 0.0)]]
|
|
311
|
+
sage: S.vertex_list()
|
|
312
|
+
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0)]
|
|
313
|
+
|
|
314
|
+
sage: def make_face(n): return [(0,0,n),(0,1,n),(1,1,n),(1,0,n)]
|
|
315
|
+
sage: S = IndexFaceSet([make_face(n) for n in range(10)])
|
|
316
|
+
sage: S.show()
|
|
317
|
+
|
|
318
|
+
sage: point_list = [(1,0,0),(0,1,0)] + [(0,0,n) for n in range(10)]
|
|
319
|
+
sage: face_list = [[0,1,n] for n in range(2,10)]
|
|
320
|
+
sage: S = IndexFaceSet(face_list, point_list, color='red')
|
|
321
|
+
sage: S.face_list()
|
|
322
|
+
[[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 0.0)],
|
|
323
|
+
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)],
|
|
324
|
+
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 2.0)],
|
|
325
|
+
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 3.0)],
|
|
326
|
+
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 4.0)],
|
|
327
|
+
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 5.0)],
|
|
328
|
+
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 6.0)],
|
|
329
|
+
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 7.0)]]
|
|
330
|
+
sage: S.show()
|
|
331
|
+
|
|
332
|
+
A simple example of colored IndexFaceSet (:issue:`12212`)::
|
|
333
|
+
|
|
334
|
+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
|
|
335
|
+
sage: from sage.plot.plot3d.texture import Texture
|
|
336
|
+
sage: point_list = [(2,0,0),(0,2,0),(0,0,2),(0,1,1),(1,0,1),(1,1,0)]
|
|
337
|
+
sage: face_list = [[0,4,5],[3,4,5],[2,3,4],[1,3,5]]
|
|
338
|
+
sage: col = rainbow(10, 'rgbtuple')
|
|
339
|
+
sage: t_list = [Texture(col[i]) for i in range(10)]
|
|
340
|
+
sage: S = IndexFaceSet(face_list, point_list, texture_list=t_list)
|
|
341
|
+
sage: S.show(viewer='tachyon')
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
def __init__(self, faces, point_list=None,
|
|
345
|
+
enclosed=False, texture_list=None, **kwds):
|
|
346
|
+
if 'alpha' in kwds:
|
|
347
|
+
opacity = float(kwds.pop('alpha'))
|
|
348
|
+
kwds['opacity'] = opacity
|
|
349
|
+
PrimitiveObject.__init__(self, **kwds)
|
|
350
|
+
self._set_extra_kwds(kwds)
|
|
351
|
+
|
|
352
|
+
self.global_texture = (texture_list is None)
|
|
353
|
+
|
|
354
|
+
self.enclosed = enclosed
|
|
355
|
+
|
|
356
|
+
if point_list is None:
|
|
357
|
+
face_list = faces
|
|
358
|
+
faces = []
|
|
359
|
+
point_list = []
|
|
360
|
+
point_index = {}
|
|
361
|
+
for face in face_list:
|
|
362
|
+
iface = []
|
|
363
|
+
for p in face:
|
|
364
|
+
try:
|
|
365
|
+
ix = point_index[p]
|
|
366
|
+
except KeyError:
|
|
367
|
+
ix = len(point_list)
|
|
368
|
+
point_index[p] = ix
|
|
369
|
+
point_list.append(p)
|
|
370
|
+
iface.append(ix)
|
|
371
|
+
faces.append(iface)
|
|
372
|
+
|
|
373
|
+
cdef Py_ssize_t i
|
|
374
|
+
cdef Py_ssize_t index_len = 0
|
|
375
|
+
for i in range(len(faces)):
|
|
376
|
+
index_len += len(faces[i])
|
|
377
|
+
|
|
378
|
+
self.realloc(len(point_list), len(faces), index_len)
|
|
379
|
+
|
|
380
|
+
for i in range(self.vcount):
|
|
381
|
+
self.vs[i].x, self.vs[i].y, self.vs[i].z = point_list[i]
|
|
382
|
+
|
|
383
|
+
cdef int cur_pt = 0
|
|
384
|
+
for i in range(self.fcount):
|
|
385
|
+
self._faces[i].n = len(faces[i])
|
|
386
|
+
self._faces[i].vertices = &self.face_indices[cur_pt]
|
|
387
|
+
if self.global_texture:
|
|
388
|
+
self._faces[i].color.r, self._faces[i].color.g, self._faces[i].color.b = self.texture.color
|
|
389
|
+
else:
|
|
390
|
+
self._faces[i].color.r, self._faces[i].color.g, self._faces[i].color.b = texture_list[i].color
|
|
391
|
+
for ix in faces[i]:
|
|
392
|
+
self.face_indices[cur_pt] = ix
|
|
393
|
+
cur_pt += 1
|
|
394
|
+
|
|
395
|
+
cdef int realloc(self, Py_ssize_t vcount, Py_ssize_t fcount, Py_ssize_t icount) except -1:
|
|
396
|
+
r"""
|
|
397
|
+
Allocate memory for vertices, faces, and face indices. Can
|
|
398
|
+
only be called from Cython, so the doctests must be indirect.
|
|
399
|
+
|
|
400
|
+
EXAMPLES::
|
|
401
|
+
|
|
402
|
+
sage: # needs sage.symbolic
|
|
403
|
+
sage: var('x,y,z')
|
|
404
|
+
(x, y, z)
|
|
405
|
+
sage: G = implicit_plot3d(x^2+y^2+z^2 - 1,
|
|
406
|
+
....: (x, -2, 2), (y, -2, 2), (z, -2, 2), plot_points=6)
|
|
407
|
+
sage: G.triangulate() # indirect doctest
|
|
408
|
+
sage: len(G.face_list())
|
|
409
|
+
44
|
|
410
|
+
sage: len(G.vertex_list())
|
|
411
|
+
132
|
|
412
|
+
sage: G = implicit_plot3d(x^2+y^2+z^2 - 100,
|
|
413
|
+
....: (x, -2, 2), (y, -2, 2), (z, -2, 2), plot_points=6)
|
|
414
|
+
sage: G.triangulate() # indirect doctest
|
|
415
|
+
sage: len(G.face_list())
|
|
416
|
+
0
|
|
417
|
+
sage: len(G.vertex_list())
|
|
418
|
+
0
|
|
419
|
+
"""
|
|
420
|
+
self.vs = <point_c*>check_reallocarray(self.vs, vcount, sizeof(point_c))
|
|
421
|
+
self.vcount = vcount
|
|
422
|
+
self._faces = <face_c*>check_reallocarray(self._faces, fcount, sizeof(face_c))
|
|
423
|
+
self.fcount = fcount
|
|
424
|
+
self.face_indices = <int*>check_reallocarray(self.face_indices, icount, sizeof(int))
|
|
425
|
+
self.icount = icount
|
|
426
|
+
|
|
427
|
+
def _clean_point_list(self):
|
|
428
|
+
"""
|
|
429
|
+
Clean up the vertices and faces as follows:
|
|
430
|
+
|
|
431
|
+
- Remove all vertices with a coordinate which is NaN or
|
|
432
|
+
infinity.
|
|
433
|
+
|
|
434
|
+
- If a removed vertex occurs in a face, remove it from that
|
|
435
|
+
face, but keep other vertices in that face.
|
|
436
|
+
|
|
437
|
+
- Remove faces with less than 3 vertices.
|
|
438
|
+
|
|
439
|
+
- Remove unused vertices.
|
|
440
|
+
|
|
441
|
+
- Free unused memory for vertices and faces (not indices).
|
|
442
|
+
"""
|
|
443
|
+
cdef Py_ssize_t i, j, v
|
|
444
|
+
|
|
445
|
+
# point_map is an array old vertex index -> new vertex index.
|
|
446
|
+
# The special value -1 means that the vertex is not mapped yet.
|
|
447
|
+
# The special value -2 means that the vertex must be deleted
|
|
448
|
+
# because a coordinate is NaN or infinity.
|
|
449
|
+
# When we are done, all vertices with negative indices are not
|
|
450
|
+
# used and will be removed.
|
|
451
|
+
cdef int* point_map = <int*>check_allocarray(self.vcount, sizeof(int))
|
|
452
|
+
|
|
453
|
+
cdef Py_ssize_t nv = 0 # number of new vertices
|
|
454
|
+
for i in range(self.vcount):
|
|
455
|
+
point_map[i] = -1
|
|
456
|
+
|
|
457
|
+
# Process all faces
|
|
458
|
+
cdef Py_ssize_t nf = 0 # number of new faces
|
|
459
|
+
cdef Py_ssize_t fv # number of new vertices on face
|
|
460
|
+
for i in range(self.fcount):
|
|
461
|
+
face = &self._faces[i]
|
|
462
|
+
|
|
463
|
+
# Process vertices in face
|
|
464
|
+
fv = 0
|
|
465
|
+
for j in range(face.n):
|
|
466
|
+
v = face.vertices[j]
|
|
467
|
+
if point_map[v] == -1:
|
|
468
|
+
if point_c_isfinite(self.vs[v]):
|
|
469
|
+
point_map[v] = nv
|
|
470
|
+
nv += 1
|
|
471
|
+
else:
|
|
472
|
+
point_map[v] = -2
|
|
473
|
+
if point_map[v] == -2:
|
|
474
|
+
continue
|
|
475
|
+
|
|
476
|
+
face.vertices[fv] = point_map[face.vertices[j]]
|
|
477
|
+
fv += 1
|
|
478
|
+
|
|
479
|
+
# Skip faces with less than 3 vertices
|
|
480
|
+
if fv < 3:
|
|
481
|
+
continue
|
|
482
|
+
|
|
483
|
+
# Store in newface
|
|
484
|
+
newface = &self._faces[nf]
|
|
485
|
+
newface.n = fv
|
|
486
|
+
if newface is not face:
|
|
487
|
+
newface.vertices = face.vertices
|
|
488
|
+
newface.color = face.color
|
|
489
|
+
nf += 1
|
|
490
|
+
|
|
491
|
+
# Realloc face array
|
|
492
|
+
if nf < self.fcount:
|
|
493
|
+
self._faces = <face_c*>check_reallocarray(self._faces, nf, sizeof(face_c))
|
|
494
|
+
self.fcount = nf
|
|
495
|
+
|
|
496
|
+
# Realloc and map vertex array
|
|
497
|
+
# We cannot copy in-place since we permuted the vertices
|
|
498
|
+
new_vs = <point_c*>check_allocarray(nv, sizeof(point_c))
|
|
499
|
+
for i in range(self.vcount):
|
|
500
|
+
j = point_map[i]
|
|
501
|
+
if j >= 0:
|
|
502
|
+
new_vs[j] = self.vs[i]
|
|
503
|
+
|
|
504
|
+
sig_free(point_map)
|
|
505
|
+
sig_free(self.vs)
|
|
506
|
+
self.vs = new_vs
|
|
507
|
+
self.vcount = nv
|
|
508
|
+
|
|
509
|
+
def _separate_creases(self, threshold):
|
|
510
|
+
"""
|
|
511
|
+
Some rendering engines Gouraud shading, which is great for smooth
|
|
512
|
+
surfaces but looks bad if one actually has a polyhedron.
|
|
513
|
+
|
|
514
|
+
INPUT:
|
|
515
|
+
|
|
516
|
+
- ``threshold`` -- the minimum cosine of the angle between adjacent
|
|
517
|
+
faces a higher threshold separates more, all faces if >= 1, no
|
|
518
|
+
faces if <= -1
|
|
519
|
+
"""
|
|
520
|
+
cdef Py_ssize_t i, j, k
|
|
521
|
+
cdef face_c *face
|
|
522
|
+
cdef int v, count, total = 0
|
|
523
|
+
cdef int* point_counts = <int *>check_calloc(self.vcount * 2 + 1, sizeof(int))
|
|
524
|
+
# For each vertex, get number of faces
|
|
525
|
+
cdef int* running_point_counts = &point_counts[self.vcount]
|
|
526
|
+
for i in range(self.fcount):
|
|
527
|
+
face = &self._faces[i]
|
|
528
|
+
total += face.n
|
|
529
|
+
for j in range(face.n):
|
|
530
|
+
point_counts[face.vertices[j]] += 1
|
|
531
|
+
# Running used as index into face list
|
|
532
|
+
cdef int running = 0
|
|
533
|
+
cdef int max = 0
|
|
534
|
+
for i in range(self.vcount):
|
|
535
|
+
running_point_counts[i] = running
|
|
536
|
+
running += point_counts[i]
|
|
537
|
+
if point_counts[i] > max:
|
|
538
|
+
max = point_counts[i]
|
|
539
|
+
running_point_counts[self.vcount] = running
|
|
540
|
+
# Create an array, indexed by running_point_counts[v], to the list of faces containing that vertex.
|
|
541
|
+
cdef face_c** point_faces
|
|
542
|
+
try:
|
|
543
|
+
point_faces = <face_c **>check_allocarray(total, sizeof(face_c*))
|
|
544
|
+
except MemoryError:
|
|
545
|
+
sig_free(point_counts)
|
|
546
|
+
raise
|
|
547
|
+
sig_on()
|
|
548
|
+
memset(point_counts, 0, sizeof(int) * self.vcount)
|
|
549
|
+
for i in range(self.fcount):
|
|
550
|
+
face = &self._faces[i]
|
|
551
|
+
for j in range(face.n):
|
|
552
|
+
v = face.vertices[j]
|
|
553
|
+
point_faces[running_point_counts[v]+point_counts[v]] = face
|
|
554
|
+
point_counts[v] += 1
|
|
555
|
+
# Now, for each vertex, see if all faces are close enough,
|
|
556
|
+
# or if it is a crease.
|
|
557
|
+
cdef face_c** faces
|
|
558
|
+
cdef int start = 0
|
|
559
|
+
cdef bint any
|
|
560
|
+
# We compare against face 0, and if it's not flat enough we push it to the end.
|
|
561
|
+
# Then we come around again to compare everything that was put at the end, possibly
|
|
562
|
+
# pushing stuff to the end again (until no further changes are needed).
|
|
563
|
+
while start < self.vcount:
|
|
564
|
+
ix = self.vcount
|
|
565
|
+
# Find creases
|
|
566
|
+
for i in range(self.vcount - start):
|
|
567
|
+
faces = &point_faces[running_point_counts[i]]
|
|
568
|
+
any = 0
|
|
569
|
+
for j from point_counts[i] > j >= 1:
|
|
570
|
+
if cos_face_angle(faces[0][0], faces[j][0], self.vs) < threshold:
|
|
571
|
+
any = 1
|
|
572
|
+
face = faces[j]
|
|
573
|
+
point_counts[i] -= 1
|
|
574
|
+
if j != point_counts[i]:
|
|
575
|
+
faces[j] = faces[point_counts[i]] # swap
|
|
576
|
+
faces[point_counts[i]] = face
|
|
577
|
+
if any:
|
|
578
|
+
ix += 1
|
|
579
|
+
# Reallocate room for vertices at end
|
|
580
|
+
if ix > self.vcount:
|
|
581
|
+
try:
|
|
582
|
+
self.vs = <point_c *>check_reallocarray(self.vs, ix, sizeof(point_c))
|
|
583
|
+
except MemoryError:
|
|
584
|
+
sig_free(point_counts)
|
|
585
|
+
sig_free(point_faces)
|
|
586
|
+
self.vcount = self.fcount = self.icount = 0 # so we don't get segfaults on bad points
|
|
587
|
+
sig_off()
|
|
588
|
+
raise
|
|
589
|
+
ix = self.vcount
|
|
590
|
+
running = 0
|
|
591
|
+
for i in range(self.vcount - start):
|
|
592
|
+
if point_counts[i] != running_point_counts[i+1] - running_point_counts[i]:
|
|
593
|
+
# We have a new vertex
|
|
594
|
+
self.vs[ix] = self.vs[i+start]
|
|
595
|
+
# Update the point_counts and point_faces arrays for the next time around.
|
|
596
|
+
count = running_point_counts[i+1] - running_point_counts[i] - point_counts[i]
|
|
597
|
+
faces = &point_faces[running]
|
|
598
|
+
for j in range(count):
|
|
599
|
+
faces[j] = point_faces[running_point_counts[i] + point_counts[i] + j]
|
|
600
|
+
face = faces[j]
|
|
601
|
+
for k in range(face.n):
|
|
602
|
+
if face.vertices[k] == i + start:
|
|
603
|
+
face.vertices[k] = ix
|
|
604
|
+
point_counts[ix-self.vcount] = count
|
|
605
|
+
running_point_counts[ix-self.vcount] = running
|
|
606
|
+
running += count
|
|
607
|
+
ix += 1
|
|
608
|
+
running_point_counts[ix-self.vcount] = running
|
|
609
|
+
start = self.vcount
|
|
610
|
+
self.vcount = ix
|
|
611
|
+
|
|
612
|
+
sig_free(point_counts)
|
|
613
|
+
sig_free(point_faces)
|
|
614
|
+
sig_off()
|
|
615
|
+
|
|
616
|
+
def _mem_stats(self):
|
|
617
|
+
return self.vcount, self.fcount, self.icount
|
|
618
|
+
|
|
619
|
+
def __dealloc__(self):
|
|
620
|
+
sig_free(self.vs)
|
|
621
|
+
sig_free(self._faces)
|
|
622
|
+
sig_free(self.face_indices)
|
|
623
|
+
|
|
624
|
+
def is_enclosed(self):
|
|
625
|
+
"""
|
|
626
|
+
Whether or not it is necessary to render the back sides of the polygons.
|
|
627
|
+
|
|
628
|
+
One is assuming, of course, that they have the correct orientation.
|
|
629
|
+
|
|
630
|
+
This is may be passed in on construction. It is also
|
|
631
|
+
calculated in
|
|
632
|
+
:class:`sage.plot.plot3d.parametric_surface.ParametricSurface`
|
|
633
|
+
by verifying the opposite edges of the rendered domain either
|
|
634
|
+
line up or are pinched together.
|
|
635
|
+
|
|
636
|
+
EXAMPLES::
|
|
637
|
+
|
|
638
|
+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
|
|
639
|
+
sage: IndexFaceSet([[(0,0,1),(0,1,0),(1,0,0)]]).is_enclosed()
|
|
640
|
+
False
|
|
641
|
+
"""
|
|
642
|
+
return self.enclosed
|
|
643
|
+
|
|
644
|
+
def index_faces(self):
|
|
645
|
+
"""
|
|
646
|
+
Return the list over all faces of the indices of the vertices.
|
|
647
|
+
|
|
648
|
+
EXAMPLES::
|
|
649
|
+
|
|
650
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
651
|
+
sage: S = Box(1,2,3)
|
|
652
|
+
sage: S.index_faces()
|
|
653
|
+
[[0, 1, 2, 3],
|
|
654
|
+
[0, 4, 5, 1],
|
|
655
|
+
[0, 3, 6, 4],
|
|
656
|
+
[5, 4, 6, 7],
|
|
657
|
+
[6, 3, 2, 7],
|
|
658
|
+
[2, 1, 5, 7]]
|
|
659
|
+
"""
|
|
660
|
+
cdef Py_ssize_t i, j
|
|
661
|
+
return [[self._faces[i].vertices[j]
|
|
662
|
+
for j in range(self._faces[i].n)]
|
|
663
|
+
for i in range(self.fcount)]
|
|
664
|
+
|
|
665
|
+
def has_local_colors(self) -> bool:
|
|
666
|
+
"""
|
|
667
|
+
Return ``True`` if and only if every face has an individual color.
|
|
668
|
+
|
|
669
|
+
EXAMPLES::
|
|
670
|
+
|
|
671
|
+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
|
|
672
|
+
sage: from sage.plot.plot3d.texture import Texture
|
|
673
|
+
sage: point_list = [(2,0,0),(0,2,0),(0,0,2),(0,1,1),(1,0,1),(1,1,0)]
|
|
674
|
+
sage: face_list = [[0,4,5],[3,4,5],[2,3,4],[1,3,5]]
|
|
675
|
+
sage: col = rainbow(10, 'rgbtuple')
|
|
676
|
+
sage: t_list=[Texture(col[i]) for i in range(10)]
|
|
677
|
+
sage: S = IndexFaceSet(face_list, point_list, texture_list=t_list)
|
|
678
|
+
sage: S.has_local_colors()
|
|
679
|
+
True
|
|
680
|
+
|
|
681
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
682
|
+
sage: S = Box(1,2,3)
|
|
683
|
+
sage: S.has_local_colors()
|
|
684
|
+
False
|
|
685
|
+
"""
|
|
686
|
+
return not self.global_texture
|
|
687
|
+
|
|
688
|
+
def index_faces_with_colors(self):
|
|
689
|
+
"""
|
|
690
|
+
Return the list over all faces of (indices of the vertices, color).
|
|
691
|
+
|
|
692
|
+
This only works if every face has its own color.
|
|
693
|
+
|
|
694
|
+
.. SEEALSO::
|
|
695
|
+
|
|
696
|
+
:meth:`has_local_colors`
|
|
697
|
+
|
|
698
|
+
EXAMPLES:
|
|
699
|
+
|
|
700
|
+
A simple colored one::
|
|
701
|
+
|
|
702
|
+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
|
|
703
|
+
sage: from sage.plot.plot3d.texture import Texture
|
|
704
|
+
sage: point_list = [(2,0,0),(0,2,0),(0,0,2),(0,1,1),(1,0,1),(1,1,0)]
|
|
705
|
+
sage: face_list = [[0,4,5],[3,4,5],[2,3,4],[1,3,5]]
|
|
706
|
+
sage: col = rainbow(10, 'rgbtuple')
|
|
707
|
+
sage: t_list = [Texture(col[i]) for i in range(10)]
|
|
708
|
+
sage: S = IndexFaceSet(face_list, point_list, texture_list=t_list)
|
|
709
|
+
sage: S.index_faces_with_colors()
|
|
710
|
+
[([0, 4, 5], '#ff0000'),
|
|
711
|
+
([3, 4, 5], '#ff9900'),
|
|
712
|
+
([2, 3, 4], '#cbff00'),
|
|
713
|
+
([1, 3, 5], '#33ff00')]
|
|
714
|
+
|
|
715
|
+
When the texture is global, an error is raised::
|
|
716
|
+
|
|
717
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
718
|
+
sage: S = Box(1,2,3)
|
|
719
|
+
sage: S.index_faces_with_colors()
|
|
720
|
+
Traceback (most recent call last):
|
|
721
|
+
...
|
|
722
|
+
ValueError: the texture is global
|
|
723
|
+
"""
|
|
724
|
+
cdef Py_ssize_t i, j
|
|
725
|
+
if self.global_texture:
|
|
726
|
+
raise ValueError('the texture is global')
|
|
727
|
+
return [([self._faces[i].vertices[j]
|
|
728
|
+
for j in range(self._faces[i].n)],
|
|
729
|
+
Color(self._faces[i].color.r,
|
|
730
|
+
self._faces[i].color.g,
|
|
731
|
+
self._faces[i].color.b).html_color())
|
|
732
|
+
for i in range(self.fcount)]
|
|
733
|
+
|
|
734
|
+
def faces(self):
|
|
735
|
+
"""
|
|
736
|
+
An iterator over the faces.
|
|
737
|
+
|
|
738
|
+
EXAMPLES::
|
|
739
|
+
|
|
740
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
741
|
+
sage: S = Box(1,2,3)
|
|
742
|
+
sage: list(S.faces()) == S.face_list()
|
|
743
|
+
True
|
|
744
|
+
"""
|
|
745
|
+
return FaceIter(self)
|
|
746
|
+
|
|
747
|
+
def face_list(self, render_params=None):
|
|
748
|
+
"""
|
|
749
|
+
Return the list of faces.
|
|
750
|
+
|
|
751
|
+
Every face is given as a tuple of vertices.
|
|
752
|
+
|
|
753
|
+
EXAMPLES::
|
|
754
|
+
|
|
755
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
756
|
+
sage: S = Box(1,2,3)
|
|
757
|
+
sage: S.face_list(S.default_render_params())[0]
|
|
758
|
+
[(1.0, 2.0, 3.0), (-1.0, 2.0, 3.0), (-1.0, -2.0, 3.0), (1.0, -2.0, 3.0)]
|
|
759
|
+
"""
|
|
760
|
+
cdef Transformation transform
|
|
761
|
+
cdef Py_ssize_t i, j
|
|
762
|
+
cdef point_c res
|
|
763
|
+
if render_params is not None:
|
|
764
|
+
transform = render_params.transform
|
|
765
|
+
else:
|
|
766
|
+
transform = None
|
|
767
|
+
if transform is None:
|
|
768
|
+
points = [(self.vs[i].x, self.vs[i].y, self.vs[i].z)
|
|
769
|
+
for i in range(self.vcount)]
|
|
770
|
+
else:
|
|
771
|
+
points = []
|
|
772
|
+
for i in range(self.vcount):
|
|
773
|
+
transform.transform_point_c(&res, self.vs[i])
|
|
774
|
+
PyList_Append(points, (res.x, res.y, res.z))
|
|
775
|
+
|
|
776
|
+
return [[points[self._faces[i].vertices[j]]
|
|
777
|
+
for j in range(self._faces[i].n)]
|
|
778
|
+
for i in range(self.fcount)]
|
|
779
|
+
|
|
780
|
+
def edges(self):
|
|
781
|
+
"""
|
|
782
|
+
An iterator over the edges.
|
|
783
|
+
|
|
784
|
+
EXAMPLES::
|
|
785
|
+
|
|
786
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
787
|
+
sage: S = Box(1,2,3)
|
|
788
|
+
sage: list(S.edges())[0]
|
|
789
|
+
((1.0, -2.0, 3.0), (1.0, 2.0, 3.0))
|
|
790
|
+
"""
|
|
791
|
+
return EdgeIter(self)
|
|
792
|
+
|
|
793
|
+
def edge_list(self):
|
|
794
|
+
"""
|
|
795
|
+
Return the list of edges.
|
|
796
|
+
|
|
797
|
+
EXAMPLES::
|
|
798
|
+
|
|
799
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
800
|
+
sage: S = Box(1,2,3)
|
|
801
|
+
sage: S.edge_list()[0]
|
|
802
|
+
((1.0, -2.0, 3.0), (1.0, 2.0, 3.0))
|
|
803
|
+
"""
|
|
804
|
+
return list(self.edges())
|
|
805
|
+
|
|
806
|
+
def vertices(self):
|
|
807
|
+
"""
|
|
808
|
+
An iterator over the vertices.
|
|
809
|
+
|
|
810
|
+
EXAMPLES::
|
|
811
|
+
|
|
812
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
813
|
+
sage: S = Cone(1,1)
|
|
814
|
+
sage: list(S.vertices()) == S.vertex_list()
|
|
815
|
+
True
|
|
816
|
+
"""
|
|
817
|
+
return VertexIter(self)
|
|
818
|
+
|
|
819
|
+
def vertex_list(self):
|
|
820
|
+
"""
|
|
821
|
+
Return the list of vertices.
|
|
822
|
+
|
|
823
|
+
EXAMPLES::
|
|
824
|
+
|
|
825
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
826
|
+
sage: S = polygon([(0,0,1), (1,1,1), (2,0,1)])
|
|
827
|
+
sage: S.vertex_list()[0]
|
|
828
|
+
(0.0, 0.0, 1.0)
|
|
829
|
+
"""
|
|
830
|
+
cdef Py_ssize_t i
|
|
831
|
+
return [(self.vs[i].x, self.vs[i].y, self.vs[i].z) for i in range(self.vcount)]
|
|
832
|
+
|
|
833
|
+
def x3d_geometry(self):
|
|
834
|
+
"""
|
|
835
|
+
Return the x3d data.
|
|
836
|
+
|
|
837
|
+
EXAMPLES:
|
|
838
|
+
|
|
839
|
+
A basic test with a triangle::
|
|
840
|
+
|
|
841
|
+
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)])
|
|
842
|
+
sage: print(G.x3d_geometry())
|
|
843
|
+
<BLANKLINE>
|
|
844
|
+
<IndexedFaceSet coordIndex='0,1,2,-1'>
|
|
845
|
+
<Coordinate point='0.0 0.0 1.0,1.0 1.0 1.0,2.0 0.0 1.0'/>
|
|
846
|
+
</IndexedFaceSet>
|
|
847
|
+
<BLANKLINE>
|
|
848
|
+
|
|
849
|
+
A simple colored one::
|
|
850
|
+
|
|
851
|
+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
|
|
852
|
+
sage: from sage.plot.plot3d.texture import Texture
|
|
853
|
+
sage: point_list = [(2,0,0),(0,2,0),(0,0,2),(0,1,1),(1,0,1),(1,1,0)]
|
|
854
|
+
sage: face_list = [[0,4,5],[3,4,5],[2,3,4],[1,3,5]]
|
|
855
|
+
sage: col = rainbow(10, 'rgbtuple')
|
|
856
|
+
sage: t_list = [Texture(col[i]) for i in range(10)]
|
|
857
|
+
sage: S = IndexFaceSet(face_list, point_list, texture_list=t_list)
|
|
858
|
+
sage: print(S.x3d_geometry())
|
|
859
|
+
<BLANKLINE>
|
|
860
|
+
<IndexedFaceSet solid='False' colorPerVertex='False' coordIndex='0,4,5,-1,3,4,5,-1,2,3,4,-1,1,3,5,-1'>
|
|
861
|
+
<Coordinate point='2.0 0.0 0.0,0.0 2.0 0.0,0.0 0.0 2.0,0.0 1.0 1.0,1.0 0.0 1.0,1.0 1.0 0.0'/>
|
|
862
|
+
<Color color='1.0 0.0 0.0,1.0 0.6000000000000001 0.0,0.7999999999999998 1.0 0.0,0.20000000000000018 1.0 0.0' />
|
|
863
|
+
</IndexedFaceSet>
|
|
864
|
+
<BLANKLINE>
|
|
865
|
+
"""
|
|
866
|
+
cdef Py_ssize_t i
|
|
867
|
+
vs = self.vs
|
|
868
|
+
fs = self._faces
|
|
869
|
+
points = ",".join("%r %r %r" % (vs[i].x, vs[i].y, vs[i].z)
|
|
870
|
+
for i in range(self.vcount))
|
|
871
|
+
coord_idx = ",-1,".join(",".join(repr(fs[i].vertices[j])
|
|
872
|
+
for j in range(fs[i].n))
|
|
873
|
+
for i in range(self.fcount))
|
|
874
|
+
if not self.global_texture:
|
|
875
|
+
color_idx = ",".join('%r %r %r' % (fs[i].color.r, fs[i].color.g, fs[i].color.b)
|
|
876
|
+
for i in range(self.fcount))
|
|
877
|
+
# Note: Don't use f-strings, since Sage on Python 2 still expects
|
|
878
|
+
# this to return a plain str instead of a unicode
|
|
879
|
+
return dedent("""
|
|
880
|
+
<IndexedFaceSet solid='False' colorPerVertex='False' coordIndex='{coord_idx},-1'>
|
|
881
|
+
<Coordinate point='{points}'/>
|
|
882
|
+
<Color color='{color_idx}' />
|
|
883
|
+
</IndexedFaceSet>
|
|
884
|
+
""".format(coord_idx=coord_idx, points=points, color_idx=color_idx))
|
|
885
|
+
|
|
886
|
+
return dedent("""
|
|
887
|
+
<IndexedFaceSet coordIndex='{coord_idx},-1'>
|
|
888
|
+
<Coordinate point='{points}'/>
|
|
889
|
+
</IndexedFaceSet>
|
|
890
|
+
""".format(coord_idx=coord_idx, points=points))
|
|
891
|
+
|
|
892
|
+
def bounding_box(self):
|
|
893
|
+
r"""
|
|
894
|
+
Calculate the bounding box for the vertices in this object
|
|
895
|
+
(ignoring infinite or NaN coordinates).
|
|
896
|
+
|
|
897
|
+
OUTPUT:
|
|
898
|
+
|
|
899
|
+
a tuple ( (low_x, low_y, low_z), (high_x, high_y, high_z)),
|
|
900
|
+
which gives the coordinates of opposite corners of the
|
|
901
|
+
bounding box.
|
|
902
|
+
|
|
903
|
+
EXAMPLES::
|
|
904
|
+
|
|
905
|
+
sage: x,y = var('x,y') # needs sage.symbolic
|
|
906
|
+
sage: p = plot3d(sqrt(sin(x)*sin(y)), (x,0,2*pi), (y,0,2*pi)) # needs sage.symbolic
|
|
907
|
+
sage: p.bounding_box() # needs sage.symbolic
|
|
908
|
+
((0.0, 0.0, 0.0), (6.283185307179586, 6.283185307179586, 0.9991889981715697))
|
|
909
|
+
"""
|
|
910
|
+
if self.vcount == 0:
|
|
911
|
+
return ((0,0,0),(0,0,0))
|
|
912
|
+
|
|
913
|
+
cdef Py_ssize_t i
|
|
914
|
+
cdef point_c low
|
|
915
|
+
cdef point_c high
|
|
916
|
+
|
|
917
|
+
low.x, low.y, low.z = INFINITY, INFINITY, INFINITY
|
|
918
|
+
high.x, high.y, high.z = -INFINITY, -INFINITY, -INFINITY
|
|
919
|
+
|
|
920
|
+
for i in range(self.vcount):
|
|
921
|
+
point_c_update_finite_lower_bound(&low, self.vs[i])
|
|
922
|
+
point_c_update_finite_upper_bound(&high, self.vs[i])
|
|
923
|
+
return ((low.x, low.y, low.z), (high.x, high.y, high.z))
|
|
924
|
+
|
|
925
|
+
def partition(self, f):
|
|
926
|
+
r"""
|
|
927
|
+
Partition the faces of ``self``.
|
|
928
|
+
|
|
929
|
+
The partition is done according to the value of a map
|
|
930
|
+
`f: \RR^3 \rightarrow \ZZ` applied to the center of each face.
|
|
931
|
+
|
|
932
|
+
INPUT:
|
|
933
|
+
|
|
934
|
+
- ``f`` -- a function from `\RR^3` to `\ZZ`
|
|
935
|
+
|
|
936
|
+
EXAMPLES::
|
|
937
|
+
|
|
938
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
939
|
+
sage: S = Box(1,2,3)
|
|
940
|
+
sage: len(S.partition(lambda x,y,z: floor(x+y+z)))
|
|
941
|
+
6
|
|
942
|
+
"""
|
|
943
|
+
cdef Py_ssize_t i, j, ix, face_ix
|
|
944
|
+
cdef int part
|
|
945
|
+
cdef point_c P
|
|
946
|
+
cdef face_c *face
|
|
947
|
+
cdef face_c *new_face
|
|
948
|
+
cdef IndexFaceSet face_set
|
|
949
|
+
|
|
950
|
+
cdef int *partition = <int *>check_allocarray(self.fcount, sizeof(int))
|
|
951
|
+
|
|
952
|
+
part_counts = {}
|
|
953
|
+
for i in range(self.fcount):
|
|
954
|
+
face = &self._faces[i]
|
|
955
|
+
P = self.vs[face.vertices[0]]
|
|
956
|
+
for j in range(1, face.n):
|
|
957
|
+
point_c_add(&P, P, self.vs[face.vertices[j]])
|
|
958
|
+
point_c_mul(&P, P, 1.0/face.n)
|
|
959
|
+
partition[i] = part = f(P.x, P.y, P.z)
|
|
960
|
+
try:
|
|
961
|
+
count = part_counts[part]
|
|
962
|
+
except KeyError:
|
|
963
|
+
part_counts[part] = count = [0, 0]
|
|
964
|
+
count[0] += 1
|
|
965
|
+
count[1] += face.n
|
|
966
|
+
all = {}
|
|
967
|
+
for part, count in part_counts.iteritems():
|
|
968
|
+
face_set = IndexFaceSet([])
|
|
969
|
+
face_set.realloc(self.vcount, count[0], count[1])
|
|
970
|
+
memcpy(face_set.vs, self.vs, sizeof(point_c) * self.vcount)
|
|
971
|
+
face_ix = 0
|
|
972
|
+
ix = 0
|
|
973
|
+
for i in range(self.fcount):
|
|
974
|
+
if partition[i] == part:
|
|
975
|
+
face = &self._faces[i]
|
|
976
|
+
new_face = &face_set._faces[face_ix]
|
|
977
|
+
new_face.n = face.n
|
|
978
|
+
new_face.vertices = &face_set.face_indices[ix]
|
|
979
|
+
for j in range(face.n):
|
|
980
|
+
new_face.vertices[j] = face.vertices[j]
|
|
981
|
+
face_ix += 1
|
|
982
|
+
ix += face.n
|
|
983
|
+
face_set._clean_point_list()
|
|
984
|
+
all[part] = face_set
|
|
985
|
+
sig_free(partition)
|
|
986
|
+
return all
|
|
987
|
+
|
|
988
|
+
def add_condition(self, condition, N=100, eps=1.0E-6):
|
|
989
|
+
"""
|
|
990
|
+
Cut the surface according to the given condition.
|
|
991
|
+
|
|
992
|
+
This allows to take the intersection of the surface
|
|
993
|
+
with a domain in 3-space, in such a way that the result
|
|
994
|
+
has a smooth boundary.
|
|
995
|
+
|
|
996
|
+
INPUT:
|
|
997
|
+
|
|
998
|
+
- ``condition`` -- boolean function on ambient space, that
|
|
999
|
+
defines the domain
|
|
1000
|
+
|
|
1001
|
+
- ``N`` -- max number of steps used by the bisection method
|
|
1002
|
+
(default: 100) to cut the boundary triangles that are not
|
|
1003
|
+
entirely within the domain.
|
|
1004
|
+
|
|
1005
|
+
- ``eps`` -- target accuracy in the intersection (default: 1.0e-6)
|
|
1006
|
+
|
|
1007
|
+
OUTPUT: an ``IndexFaceSet``
|
|
1008
|
+
|
|
1009
|
+
This will contain both triangular and quadrilateral faces.
|
|
1010
|
+
|
|
1011
|
+
EXAMPLES::
|
|
1012
|
+
|
|
1013
|
+
sage: var('x,y,z') # needs sage.symbolic
|
|
1014
|
+
(x, y, z)
|
|
1015
|
+
sage: P = implicit_plot3d(z-x*y,(-2,2),(-2,2),(-2,2)) # needs sage.symbolic
|
|
1016
|
+
sage: def condi(x, y, z):
|
|
1017
|
+
....: return bool(x*x+y*y+z*z <= Integer(1))
|
|
1018
|
+
sage: R = P.add_condition(condi, 20); R # needs sage.symbolic
|
|
1019
|
+
Graphics3d Object
|
|
1020
|
+
|
|
1021
|
+
.. PLOT::
|
|
1022
|
+
|
|
1023
|
+
x,y,z = var('x,y,z')
|
|
1024
|
+
P = implicit_plot3d(z-x*y,(-2,2),(-2,2),(-2,2))
|
|
1025
|
+
def condi(x, y, z):
|
|
1026
|
+
return bool(x*x+y*y+z*z <= Integer(1))
|
|
1027
|
+
sphinx_plot(P.add_condition(condi,40))
|
|
1028
|
+
|
|
1029
|
+
An example with colors::
|
|
1030
|
+
|
|
1031
|
+
sage: def condi(x, y, z):
|
|
1032
|
+
....: return bool(x*x+y*y <= 1.1)
|
|
1033
|
+
sage: cm = colormaps.hsv
|
|
1034
|
+
sage: cf = lambda x,y,z: float(x+y) % 1
|
|
1035
|
+
sage: P = implicit_plot3d(x**2+y**2+z**2-1-x**2*z+y**2*z, # needs sage.symbolic
|
|
1036
|
+
....: (-2,2),(-2,2),(-2,2),color=(cm,cf))
|
|
1037
|
+
sage: R = P.add_condition(condi,40); R # needs sage.symbolic
|
|
1038
|
+
Graphics3d Object
|
|
1039
|
+
|
|
1040
|
+
.. PLOT::
|
|
1041
|
+
|
|
1042
|
+
x,y,z = var('x,y,z')
|
|
1043
|
+
def condi(x, y, z):
|
|
1044
|
+
return bool(x*x+y*y <= 1.1)
|
|
1045
|
+
cm = colormaps.hsv
|
|
1046
|
+
cf = lambda x,y,z: float(x+y) % 1
|
|
1047
|
+
P = implicit_plot3d(x**2+y**2+z**2-1-x**2*z+y**2*z,(-2,2),(-2,2),(-2,2),color=(cm,cf))
|
|
1048
|
+
sphinx_plot(P.add_condition(condi,40))
|
|
1049
|
+
|
|
1050
|
+
An example with transparency::
|
|
1051
|
+
|
|
1052
|
+
sage: P = implicit_plot3d(x**4+y**4+z**2-4, (x,-2,2), (y,-2,2), (z,-2,2), # needs sage.symbolic
|
|
1053
|
+
....: alpha=0.3)
|
|
1054
|
+
sage: def cut(a, b, c):
|
|
1055
|
+
....: return a*a+c*c > 2
|
|
1056
|
+
sage: Q = P.add_condition(cut,40); Q # needs sage.symbolic
|
|
1057
|
+
Graphics3d Object
|
|
1058
|
+
|
|
1059
|
+
.. PLOT::
|
|
1060
|
+
|
|
1061
|
+
x,y,z = var('x,y,z')
|
|
1062
|
+
P = implicit_plot3d(x**4+y**4+z**2-4,(x,-2,2),(y,-2,2),(z,-2,2),alpha=0.3)
|
|
1063
|
+
def cut(a, b, c):
|
|
1064
|
+
return a*a+c*c > 2
|
|
1065
|
+
sphinx_plot(P.add_condition(cut,40))
|
|
1066
|
+
|
|
1067
|
+
A sombrero with quadrilaterals::
|
|
1068
|
+
|
|
1069
|
+
sage: P = plot3d(-sin(2*x*x+2*y*y)*exp(-x*x-y*y), (x,-2,2), (y,-2,2), # needs sage.symbolic
|
|
1070
|
+
....: color='gold')
|
|
1071
|
+
sage: def cut(x, y, z):
|
|
1072
|
+
....: return x*x+y*y < 1
|
|
1073
|
+
sage: Q = P.add_condition(cut);Q # needs sage.symbolic
|
|
1074
|
+
Graphics3d Object
|
|
1075
|
+
|
|
1076
|
+
.. PLOT::
|
|
1077
|
+
|
|
1078
|
+
x,y,z = var('x,y,z')
|
|
1079
|
+
P = plot3d(-sin(2*x*x+2*y*y)*exp(-x*x-y*y),(x,-2,2),(y,-2,2),color='gold')
|
|
1080
|
+
def cut(x, y, z):
|
|
1081
|
+
return x*x+y*y < 1
|
|
1082
|
+
sphinx_plot(P.add_condition(cut))
|
|
1083
|
+
|
|
1084
|
+
TESTS:
|
|
1085
|
+
|
|
1086
|
+
One test for preservation of transparency :issue:`28783`::
|
|
1087
|
+
|
|
1088
|
+
sage: # needs sage.symbolic
|
|
1089
|
+
sage: x,y,z = var('x,y,z')
|
|
1090
|
+
sage: P = plot3d(cos(x*y),(x,-2,2),(y,-2,2),color='red',opacity=0.1)
|
|
1091
|
+
sage: def condi(x, y, z):
|
|
1092
|
+
....: return not(x*x+y*y <= 1)
|
|
1093
|
+
sage: Q = P.add_condition(condi, 40)
|
|
1094
|
+
sage: L = Q.json_repr(Q.default_render_params())
|
|
1095
|
+
sage: '"opacity":0.1' in L[-1]
|
|
1096
|
+
True
|
|
1097
|
+
|
|
1098
|
+
A test that this works with polygons::
|
|
1099
|
+
|
|
1100
|
+
sage: p = polygon3d([[2,0,0], [0,2,0], [0,0,3]])
|
|
1101
|
+
sage: def f(x, y, z):
|
|
1102
|
+
....: return bool(x*x+y*y+z*z<=5)
|
|
1103
|
+
sage: cut = p.add_condition(f,60,1.0e-12); cut.face_list() # needs sage.symbolic
|
|
1104
|
+
[[(0.556128491210302, 0.0, 2.165807263184547),
|
|
1105
|
+
(2.0, 0.0, 0.0),
|
|
1106
|
+
(0.0, 2.0, 0.0),
|
|
1107
|
+
(0.0, 0.556128491210302, 2.165807263184547)]]
|
|
1108
|
+
|
|
1109
|
+
.. TODO::
|
|
1110
|
+
|
|
1111
|
+
- Use a dichotomy to search for the place where to cut,
|
|
1112
|
+
- Compute the cut only once for each edge.
|
|
1113
|
+
"""
|
|
1114
|
+
index = 0
|
|
1115
|
+
if hasattr(self, 'triangulate'):
|
|
1116
|
+
self.triangulate()
|
|
1117
|
+
local_colored = self.has_local_colors()
|
|
1118
|
+
V = self.vertex_list()
|
|
1119
|
+
old_index_to_index = {}
|
|
1120
|
+
point_list = []
|
|
1121
|
+
for old_index, vertex in enumerate(V):
|
|
1122
|
+
if condition(*vertex):
|
|
1123
|
+
old_index_to_index[old_index] = index
|
|
1124
|
+
point_list.append(vertex)
|
|
1125
|
+
index += 1
|
|
1126
|
+
|
|
1127
|
+
face_list = []
|
|
1128
|
+
if local_colored:
|
|
1129
|
+
texture_list = []
|
|
1130
|
+
index_faces = self.index_faces_with_colors()
|
|
1131
|
+
else:
|
|
1132
|
+
texture = self.texture
|
|
1133
|
+
index_faces = self.index_faces()
|
|
1134
|
+
|
|
1135
|
+
if local_colored:
|
|
1136
|
+
def iter_split_faces():
|
|
1137
|
+
for triple in index_faces:
|
|
1138
|
+
triple, color = triple
|
|
1139
|
+
if len(triple) == 3:
|
|
1140
|
+
yield triple, color
|
|
1141
|
+
else:
|
|
1142
|
+
v0 = triple[0]
|
|
1143
|
+
for i in range(1, len(triple) - 1):
|
|
1144
|
+
yield (v0, triple[i], triple[i + 1]), color
|
|
1145
|
+
else:
|
|
1146
|
+
def iter_split_faces():
|
|
1147
|
+
for triple in index_faces:
|
|
1148
|
+
if len(triple) == 3:
|
|
1149
|
+
yield triple
|
|
1150
|
+
else:
|
|
1151
|
+
v0 = triple[0]
|
|
1152
|
+
for i in range(1, len(triple) - 1):
|
|
1153
|
+
yield (v0, triple[i], triple[i + 1])
|
|
1154
|
+
|
|
1155
|
+
for triple in iter_split_faces():
|
|
1156
|
+
if local_colored:
|
|
1157
|
+
triple, color = triple
|
|
1158
|
+
inside = [x for x in triple if x in old_index_to_index]
|
|
1159
|
+
outside = [x for x in triple if x not in inside]
|
|
1160
|
+
face_degree = len(inside)
|
|
1161
|
+
if face_degree >= 1 and local_colored:
|
|
1162
|
+
texture_list.append(Texture(color=color))
|
|
1163
|
+
if face_degree == 3:
|
|
1164
|
+
face_list.append([old_index_to_index[i] for i in triple])
|
|
1165
|
+
elif face_degree == 2:
|
|
1166
|
+
old_c = outside[0]
|
|
1167
|
+
if old_c == triple[1]:
|
|
1168
|
+
old_b, old_a = inside
|
|
1169
|
+
else:
|
|
1170
|
+
old_a, old_b = inside
|
|
1171
|
+
va = V[old_a]
|
|
1172
|
+
vb = V[old_b]
|
|
1173
|
+
vc = V[old_c]
|
|
1174
|
+
middle_ac = cut_edge_by_bisection(va, vc, condition, eps, N)
|
|
1175
|
+
middle_bc = cut_edge_by_bisection(vb, vc, condition, eps, N)
|
|
1176
|
+
point_list += [middle_ac, middle_bc]
|
|
1177
|
+
face_list.append([index, old_index_to_index[old_a],
|
|
1178
|
+
old_index_to_index[old_b], index + 1])
|
|
1179
|
+
index += 2
|
|
1180
|
+
elif face_degree == 1:
|
|
1181
|
+
old_a = inside[0]
|
|
1182
|
+
if old_a == triple[1]:
|
|
1183
|
+
old_c, old_b = outside
|
|
1184
|
+
else:
|
|
1185
|
+
old_b, old_c = outside
|
|
1186
|
+
va = V[old_a]
|
|
1187
|
+
vb = V[old_b]
|
|
1188
|
+
vc = V[old_c]
|
|
1189
|
+
# Use bisection to find the intersection
|
|
1190
|
+
middle_ab = cut_edge_by_bisection(va, vb, condition, eps, N)
|
|
1191
|
+
middle_ac = cut_edge_by_bisection(va, vc, condition, eps, N)
|
|
1192
|
+
|
|
1193
|
+
point_list += [middle_ac, middle_ab]
|
|
1194
|
+
face_list.append([index, old_index_to_index[old_a], index + 1])
|
|
1195
|
+
index += 2
|
|
1196
|
+
|
|
1197
|
+
if local_colored:
|
|
1198
|
+
return IndexFaceSet(face_list, point_list,
|
|
1199
|
+
texture_list=texture_list)
|
|
1200
|
+
else:
|
|
1201
|
+
opacity = texture.opacity
|
|
1202
|
+
return IndexFaceSet(face_list, point_list, texture=texture,
|
|
1203
|
+
opacity=opacity)
|
|
1204
|
+
|
|
1205
|
+
def tachyon_repr(self, render_params):
|
|
1206
|
+
"""
|
|
1207
|
+
Return a tachyon object for ``self``.
|
|
1208
|
+
|
|
1209
|
+
EXAMPLES:
|
|
1210
|
+
|
|
1211
|
+
A basic test with a triangle::
|
|
1212
|
+
|
|
1213
|
+
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)])
|
|
1214
|
+
sage: s = G.tachyon_repr(G.default_render_params()); s
|
|
1215
|
+
['TRI V0 0 0 1 V1 1 1 1 V2 2 0 1', ...]
|
|
1216
|
+
|
|
1217
|
+
A simple colored one::
|
|
1218
|
+
|
|
1219
|
+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
|
|
1220
|
+
sage: from sage.plot.plot3d.texture import Texture
|
|
1221
|
+
sage: point_list = [(2,0,0),(0,2,0),(0,0,2),(0,1,1),(1,0,1),(1,1,0)]
|
|
1222
|
+
sage: face_list = [[0,4,5],[3,4,5],[2,3,4],[1,3,5]]
|
|
1223
|
+
sage: col = rainbow(10, 'rgbtuple')
|
|
1224
|
+
sage: t_list = [Texture(col[i]) for i in range(10)]
|
|
1225
|
+
sage: S = IndexFaceSet(face_list, point_list, texture_list=t_list)
|
|
1226
|
+
sage: S.tachyon_repr(S.default_render_params())
|
|
1227
|
+
['TRI V0 2 0 0 V1 1 0 1 V2 1 1 0',
|
|
1228
|
+
'TEXTURE... AMBIENT 0.3 DIFFUSE 0.7 SPECULAR 0 OPACITY 1.0... COLOR 1 0 0 ... TEXFUNC 0',...]
|
|
1229
|
+
"""
|
|
1230
|
+
cdef Transformation transform = render_params.transform
|
|
1231
|
+
lines = []
|
|
1232
|
+
cdef point_c P, Q, R
|
|
1233
|
+
cdef face_c face
|
|
1234
|
+
cdef Py_ssize_t i, k
|
|
1235
|
+
sig_on()
|
|
1236
|
+
for i in range(self.fcount):
|
|
1237
|
+
face = self._faces[i]
|
|
1238
|
+
if transform is not None:
|
|
1239
|
+
transform.transform_point_c(&P, self.vs[face.vertices[0]])
|
|
1240
|
+
transform.transform_point_c(&Q, self.vs[face.vertices[1]])
|
|
1241
|
+
transform.transform_point_c(&R, self.vs[face.vertices[2]])
|
|
1242
|
+
else:
|
|
1243
|
+
P = self.vs[face.vertices[0]]
|
|
1244
|
+
Q = self.vs[face.vertices[1]]
|
|
1245
|
+
R = self.vs[face.vertices[2]]
|
|
1246
|
+
PyList_Append(lines, format_tachyon_triangle(P, Q, R))
|
|
1247
|
+
if self.global_texture:
|
|
1248
|
+
PyList_Append(lines, self.texture.id)
|
|
1249
|
+
else:
|
|
1250
|
+
PyList_Append(lines, format_tachyon_texture(face.color))
|
|
1251
|
+
if face.n > 3:
|
|
1252
|
+
for k in range(3, face.n):
|
|
1253
|
+
Q = R
|
|
1254
|
+
if transform is not None:
|
|
1255
|
+
transform.transform_point_c(&R, self.vs[face.vertices[k]])
|
|
1256
|
+
else:
|
|
1257
|
+
R = self.vs[face.vertices[k]]
|
|
1258
|
+
PyList_Append(lines, format_tachyon_triangle(P, Q, R))
|
|
1259
|
+
if self.global_texture:
|
|
1260
|
+
PyList_Append(lines, self.texture.id)
|
|
1261
|
+
else:
|
|
1262
|
+
PyList_Append(lines, format_tachyon_texture(face.color))
|
|
1263
|
+
sig_off()
|
|
1264
|
+
|
|
1265
|
+
return lines
|
|
1266
|
+
|
|
1267
|
+
def json_repr(self, render_params):
|
|
1268
|
+
"""
|
|
1269
|
+
Return a json representation for ``self``.
|
|
1270
|
+
|
|
1271
|
+
TESTS:
|
|
1272
|
+
|
|
1273
|
+
A basic test with a triangle::
|
|
1274
|
+
|
|
1275
|
+
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)])
|
|
1276
|
+
sage: G.json_repr(G.default_render_params())
|
|
1277
|
+
['{"vertices":[{"x":0,"y":0,"z":1},{"x":1,"y":1,"z":1},{"x":2,"y":0,"z":1}], "faces":[[0,1,2]], "color":"#0000ff", "opacity":1.0}']
|
|
1278
|
+
|
|
1279
|
+
A simple colored one::
|
|
1280
|
+
|
|
1281
|
+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
|
|
1282
|
+
sage: from sage.plot.plot3d.texture import Texture
|
|
1283
|
+
sage: point_list = [(2,0,0),(0,2,0),(0,0,2),(0,1,1),(1,0,1),(1,1,0)]
|
|
1284
|
+
sage: face_list = [[0,4,5],[3,4,5],[2,3,4],[1,3,5]]
|
|
1285
|
+
sage: col = rainbow(10, 'rgbtuple')
|
|
1286
|
+
sage: t_list=[Texture(col[i]) for i in range(10)]
|
|
1287
|
+
sage: S = IndexFaceSet(face_list, point_list, texture_list=t_list)
|
|
1288
|
+
sage: S.json_repr(S.default_render_params())
|
|
1289
|
+
['{"vertices":[{"x":2,"y":0,"z":0},..., "faceColors":["#ff0000","#ff9900","#cbff00","#33ff00"], "opacity":1.0}']
|
|
1290
|
+
"""
|
|
1291
|
+
cdef Transformation transform = render_params.transform
|
|
1292
|
+
cdef point_c res
|
|
1293
|
+
|
|
1294
|
+
if transform is None:
|
|
1295
|
+
vertices_str = "[{}]".format(
|
|
1296
|
+
",".join(format_json_vertex(self.vs[i])
|
|
1297
|
+
for i in range(self.vcount)))
|
|
1298
|
+
else:
|
|
1299
|
+
vertices_str = "["
|
|
1300
|
+
for i in range(self.vcount):
|
|
1301
|
+
transform.transform_point_c(&res, self.vs[i])
|
|
1302
|
+
if i > 0:
|
|
1303
|
+
vertices_str += ","
|
|
1304
|
+
vertices_str += format_json_vertex(res)
|
|
1305
|
+
vertices_str += "]"
|
|
1306
|
+
|
|
1307
|
+
faces_str = "[{}]".format(",".join(format_json_face(self._faces[i])
|
|
1308
|
+
for i in range(self.fcount)))
|
|
1309
|
+
opacity = float(self._extra_kwds.get('opacity', 1))
|
|
1310
|
+
|
|
1311
|
+
if self.global_texture:
|
|
1312
|
+
color_str = '"#{}"'.format(self.texture.hex_rgb())
|
|
1313
|
+
json = ['{{"vertices":{}, "faces":{}, "color":{}, "opacity":{}}}'.format(
|
|
1314
|
+
vertices_str, faces_str, color_str, opacity)]
|
|
1315
|
+
else:
|
|
1316
|
+
color_str = "[{}]".format(",".join('"{}"'.format(
|
|
1317
|
+
Color(self._faces[i].color.r,
|
|
1318
|
+
self._faces[i].color.g,
|
|
1319
|
+
self._faces[i].color.b).html_color())
|
|
1320
|
+
for i in range(self.fcount)))
|
|
1321
|
+
json = ['{{"vertices":{}, "faces":{}, "faceColors":{}, "opacity":{}}}'.format(
|
|
1322
|
+
vertices_str, faces_str, color_str, opacity)]
|
|
1323
|
+
|
|
1324
|
+
if 'render_order' in self._extra_kwds:
|
|
1325
|
+
renderOrder = self._extra_kwds.get('render_order')
|
|
1326
|
+
json[0] = json[0][:-1] + ', "renderOrder": {}}}'.format(renderOrder)
|
|
1327
|
+
|
|
1328
|
+
if self._extra_kwds.get('single_side'):
|
|
1329
|
+
json[0] = json[0][:-1] + ', "singleSide": true}'
|
|
1330
|
+
|
|
1331
|
+
if self._extra_kwds.get('threejs_flat_shading'):
|
|
1332
|
+
json[0] = json[0][:-1] + ', "useFlatShading": true}'
|
|
1333
|
+
|
|
1334
|
+
if self._extra_kwds.get('mesh'):
|
|
1335
|
+
json[0] = json[0][:-1] + ', "showMeshGrid": true}'
|
|
1336
|
+
|
|
1337
|
+
return json
|
|
1338
|
+
|
|
1339
|
+
def threejs_repr(self, render_params):
|
|
1340
|
+
r"""
|
|
1341
|
+
Return representation of the surface suitable for plotting with three.js.
|
|
1342
|
+
|
|
1343
|
+
EXAMPLES:
|
|
1344
|
+
|
|
1345
|
+
A simple triangle::
|
|
1346
|
+
|
|
1347
|
+
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)])
|
|
1348
|
+
sage: G.threejs_repr(G.default_render_params())
|
|
1349
|
+
[('surface',
|
|
1350
|
+
{'color': '#0000ff',
|
|
1351
|
+
'faces': [[0, 1, 2]],
|
|
1352
|
+
'opacity': 1.0,
|
|
1353
|
+
'vertices': [{'x': 0.0, 'y': 0.0, 'z': 1.0},
|
|
1354
|
+
{'x': 1.0, 'y': 1.0, 'z': 1.0},
|
|
1355
|
+
{'x': 2.0, 'y': 0.0, 'z': 1.0}]})]
|
|
1356
|
+
|
|
1357
|
+
The same but with more options applied::
|
|
1358
|
+
|
|
1359
|
+
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)], color='red', opacity=0.5,
|
|
1360
|
+
....: render_order=2, threejs_flat_shading=True,
|
|
1361
|
+
....: single_side=True, mesh=True, thickness=10, depth_write=True)
|
|
1362
|
+
sage: G.threejs_repr(G.default_render_params())
|
|
1363
|
+
[('surface',
|
|
1364
|
+
{'color': '#ff0000',
|
|
1365
|
+
'depthWrite': True,
|
|
1366
|
+
'faces': [[0, 1, 2]],
|
|
1367
|
+
'linewidth': 10.0,
|
|
1368
|
+
'opacity': 0.5,
|
|
1369
|
+
'renderOrder': 2.0,
|
|
1370
|
+
'showMeshGrid': True,
|
|
1371
|
+
'singleSide': True,
|
|
1372
|
+
'useFlatShading': True,
|
|
1373
|
+
'vertices': [{'x': 0.0, 'y': 0.0, 'z': 1.0},
|
|
1374
|
+
{'x': 1.0, 'y': 1.0, 'z': 1.0},
|
|
1375
|
+
{'x': 2.0, 'y': 0.0, 'z': 1.0}]})]
|
|
1376
|
+
|
|
1377
|
+
TESTS:
|
|
1378
|
+
|
|
1379
|
+
Transformations apply to the surface's vertices::
|
|
1380
|
+
|
|
1381
|
+
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)]).scale(2,1,-1)
|
|
1382
|
+
sage: G.threejs_repr(G.default_render_params())
|
|
1383
|
+
[('surface',
|
|
1384
|
+
{'color': '#0000ff',
|
|
1385
|
+
'faces': [[0, 1, 2]],
|
|
1386
|
+
'opacity': 1.0,
|
|
1387
|
+
'vertices': [{'x': 0.0, 'y': 0.0, 'z': -1.0},
|
|
1388
|
+
{'x': 2.0, 'y': 1.0, 'z': -1.0},
|
|
1389
|
+
{'x': 4.0, 'y': 0.0, 'z': -1.0}]})]
|
|
1390
|
+
|
|
1391
|
+
Per-face colors::
|
|
1392
|
+
|
|
1393
|
+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
|
|
1394
|
+
sage: from sage.plot.plot3d.texture import Texture
|
|
1395
|
+
sage: point_list = [(2,0,0),(0,2,0),(0,0,2),(0,1,1),(1,0,1),(1,1,0)]
|
|
1396
|
+
sage: face_list = [[0,4,5],[3,4,5],[2,3,4],[1,3,5]]
|
|
1397
|
+
sage: col = rainbow(10, 'rgbtuple')
|
|
1398
|
+
sage: t_list=[Texture(col[i]) for i in range(10)]
|
|
1399
|
+
sage: S = IndexFaceSet(face_list, point_list, texture_list=t_list)
|
|
1400
|
+
sage: S.threejs_repr(S.default_render_params())
|
|
1401
|
+
[('surface',
|
|
1402
|
+
{'faceColors': ['#ff0000', '#ff9900', '#cbff00', '#33ff00'],
|
|
1403
|
+
'faces': [[0, 4, 5], [3, 4, 5], [2, 3, 4], [1, 3, 5]],
|
|
1404
|
+
'opacity': 1.0,
|
|
1405
|
+
'vertices': [{'x': 2.0, 'y': 0.0, 'z': 0.0},
|
|
1406
|
+
{'x': 0.0, 'y': 2.0, 'z': 0.0},
|
|
1407
|
+
{'x': 0.0, 'y': 0.0, 'z': 2.0},
|
|
1408
|
+
{'x': 0.0, 'y': 1.0, 'z': 1.0},
|
|
1409
|
+
{'x': 1.0, 'y': 0.0, 'z': 1.0},
|
|
1410
|
+
{'x': 1.0, 'y': 1.0, 'z': 0.0}]})]
|
|
1411
|
+
"""
|
|
1412
|
+
surface = {}
|
|
1413
|
+
|
|
1414
|
+
vertices = []
|
|
1415
|
+
cdef Transformation transform = render_params.transform
|
|
1416
|
+
cdef point_c res
|
|
1417
|
+
for i in range(self.vcount):
|
|
1418
|
+
if transform is None:
|
|
1419
|
+
res = self.vs[i]
|
|
1420
|
+
else:
|
|
1421
|
+
transform.transform_point_c(&res, self.vs[i])
|
|
1422
|
+
vertices.append(dict(x=float(res.x), y=float(res.y), z=float(res.z)))
|
|
1423
|
+
surface['vertices'] = vertices
|
|
1424
|
+
|
|
1425
|
+
faces = []
|
|
1426
|
+
cdef face_c face
|
|
1427
|
+
for i in range(self.fcount):
|
|
1428
|
+
face = self._faces[i]
|
|
1429
|
+
faces.append([int(face.vertices[j]) for j in range(face.n)])
|
|
1430
|
+
surface['faces'] = faces
|
|
1431
|
+
|
|
1432
|
+
if self.global_texture:
|
|
1433
|
+
surface['color'] = '#' + str(self.texture.hex_rgb())
|
|
1434
|
+
else:
|
|
1435
|
+
face_colors = []
|
|
1436
|
+
for i in range(self.fcount):
|
|
1437
|
+
face = self._faces[i]
|
|
1438
|
+
color = Color(face.color.r, face.color.g, face.color.b)
|
|
1439
|
+
face_colors.append(str(color.html_color()))
|
|
1440
|
+
surface['faceColors'] = face_colors
|
|
1441
|
+
|
|
1442
|
+
surface['opacity'] = float(self._extra_kwds.get('opacity', 1.0))
|
|
1443
|
+
|
|
1444
|
+
if 'render_order' in self._extra_kwds:
|
|
1445
|
+
surface['renderOrder'] = float(self._extra_kwds['render_order'])
|
|
1446
|
+
|
|
1447
|
+
if self._extra_kwds.get('single_side'):
|
|
1448
|
+
surface['singleSide'] = True
|
|
1449
|
+
|
|
1450
|
+
if self._extra_kwds.get('threejs_flat_shading'):
|
|
1451
|
+
surface['useFlatShading'] = True
|
|
1452
|
+
|
|
1453
|
+
if self._extra_kwds.get('mesh'):
|
|
1454
|
+
surface['showMeshGrid'] = True
|
|
1455
|
+
|
|
1456
|
+
if self._extra_kwds.get('thickness'):
|
|
1457
|
+
surface['linewidth'] = float(self._extra_kwds['thickness'])
|
|
1458
|
+
|
|
1459
|
+
if 'depth_write' in self._extra_kwds:
|
|
1460
|
+
surface['depthWrite'] = bool(self._extra_kwds['depth_write'])
|
|
1461
|
+
|
|
1462
|
+
return [('surface', surface)]
|
|
1463
|
+
|
|
1464
|
+
def obj_repr(self, render_params):
|
|
1465
|
+
"""
|
|
1466
|
+
Return an obj representation for ``self``.
|
|
1467
|
+
|
|
1468
|
+
TESTS::
|
|
1469
|
+
|
|
1470
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
1471
|
+
sage: S = Cylinder(1,1)
|
|
1472
|
+
sage: s = S.obj_repr(S.default_render_params())
|
|
1473
|
+
"""
|
|
1474
|
+
cdef Transformation transform = render_params.transform
|
|
1475
|
+
cdef int off = render_params.obj_vertex_offset
|
|
1476
|
+
cdef Py_ssize_t i
|
|
1477
|
+
cdef point_c res
|
|
1478
|
+
|
|
1479
|
+
sig_on()
|
|
1480
|
+
if transform is None:
|
|
1481
|
+
points = [format_obj_vertex(self.vs[i]) for i in range(self.vcount)]
|
|
1482
|
+
else:
|
|
1483
|
+
points = []
|
|
1484
|
+
for i in range(self.vcount):
|
|
1485
|
+
transform.transform_point_c(&res, self.vs[i])
|
|
1486
|
+
PyList_Append(points, format_obj_vertex(res))
|
|
1487
|
+
|
|
1488
|
+
faces = [format_obj_face(self._faces[i], off) for i in range(self.fcount)]
|
|
1489
|
+
if not self.enclosed:
|
|
1490
|
+
back_faces = [format_obj_face_back(self._faces[i], off) for i in range(self.fcount)]
|
|
1491
|
+
else:
|
|
1492
|
+
back_faces = []
|
|
1493
|
+
|
|
1494
|
+
render_params.obj_vertex_offset += self.vcount
|
|
1495
|
+
sig_off()
|
|
1496
|
+
|
|
1497
|
+
return ["g " + render_params.unique_name('obj'),
|
|
1498
|
+
"usemtl " + self.texture.id,
|
|
1499
|
+
points,
|
|
1500
|
+
faces,
|
|
1501
|
+
back_faces]
|
|
1502
|
+
|
|
1503
|
+
def jmol_repr(self, render_params):
|
|
1504
|
+
"""
|
|
1505
|
+
Return a jmol representation for ``self``.
|
|
1506
|
+
|
|
1507
|
+
TESTS::
|
|
1508
|
+
|
|
1509
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
1510
|
+
sage: S = Cylinder(1,1)
|
|
1511
|
+
sage: S.show(viewer='jmol') # indirect doctest
|
|
1512
|
+
"""
|
|
1513
|
+
cdef Transformation transform = render_params.transform
|
|
1514
|
+
cdef Py_ssize_t i
|
|
1515
|
+
cdef point_c res
|
|
1516
|
+
|
|
1517
|
+
self._separate_creases(render_params.crease_threshold)
|
|
1518
|
+
|
|
1519
|
+
sig_on()
|
|
1520
|
+
if transform is None:
|
|
1521
|
+
points = [format_pmesh_vertex(self.vs[i])
|
|
1522
|
+
for i in range(self.vcount)]
|
|
1523
|
+
else:
|
|
1524
|
+
points = []
|
|
1525
|
+
for i in range(self.vcount):
|
|
1526
|
+
transform.transform_point_c(&res, self.vs[i])
|
|
1527
|
+
PyList_Append(points, format_pmesh_vertex(res))
|
|
1528
|
+
|
|
1529
|
+
# activation of coloring in jmol
|
|
1530
|
+
if self.global_texture:
|
|
1531
|
+
faces = [format_pmesh_face(self._faces[i], 1)
|
|
1532
|
+
for i in range(self.fcount)]
|
|
1533
|
+
else:
|
|
1534
|
+
faces = [format_pmesh_face(self._faces[i], -1)
|
|
1535
|
+
for i in range(self.fcount)]
|
|
1536
|
+
|
|
1537
|
+
# If a face has more than 4 vertices, it gets chopped up in
|
|
1538
|
+
# format_pmesh_face
|
|
1539
|
+
cdef Py_ssize_t extra_faces = 0
|
|
1540
|
+
for i in range(self.fcount):
|
|
1541
|
+
if self._faces[i].n >= 5:
|
|
1542
|
+
extra_faces += self._faces[i].n-3
|
|
1543
|
+
|
|
1544
|
+
sig_off()
|
|
1545
|
+
|
|
1546
|
+
all = [str(self.vcount),
|
|
1547
|
+
points,
|
|
1548
|
+
str(self.fcount + extra_faces),
|
|
1549
|
+
faces]
|
|
1550
|
+
|
|
1551
|
+
from sage.plot.plot3d.base import flatten_list
|
|
1552
|
+
name = render_params.unique_name('obj')
|
|
1553
|
+
all = flatten_list(all)
|
|
1554
|
+
if render_params.output_archive:
|
|
1555
|
+
filename = "%s.pmesh" % (name)
|
|
1556
|
+
render_params.output_archive.writestr(filename, '\n'.join(all))
|
|
1557
|
+
else:
|
|
1558
|
+
filename = "%s-%s.pmesh" % (render_params.output_file, name)
|
|
1559
|
+
f = open(filename, 'w')
|
|
1560
|
+
for line in all:
|
|
1561
|
+
f.write(line)
|
|
1562
|
+
f.write('\n')
|
|
1563
|
+
f.close()
|
|
1564
|
+
|
|
1565
|
+
if self.global_texture:
|
|
1566
|
+
s = 'pmesh {} "{}"\n{}'.format(name, filename,
|
|
1567
|
+
self.texture.jmol_str("pmesh"))
|
|
1568
|
+
else:
|
|
1569
|
+
s = 'pmesh {} "{}"'.format(name, filename)
|
|
1570
|
+
|
|
1571
|
+
# Turn on display of the mesh lines or dots?
|
|
1572
|
+
if render_params.mesh:
|
|
1573
|
+
s += '\npmesh %s mesh\n' % name
|
|
1574
|
+
if render_params.dots:
|
|
1575
|
+
s += '\npmesh %s dots\n' % name
|
|
1576
|
+
return [s]
|
|
1577
|
+
|
|
1578
|
+
def stl_binary_repr(self, render_params):
|
|
1579
|
+
"""
|
|
1580
|
+
Return data for STL (STereoLithography) representation of the surface.
|
|
1581
|
+
|
|
1582
|
+
The STL binary representation is a list of binary strings,
|
|
1583
|
+
one for each triangle.
|
|
1584
|
+
|
|
1585
|
+
EXAMPLES::
|
|
1586
|
+
|
|
1587
|
+
sage: G = sphere()
|
|
1588
|
+
sage: data = G.stl_binary_repr(G.default_render_params()); len(data)
|
|
1589
|
+
1368
|
|
1590
|
+
"""
|
|
1591
|
+
import struct
|
|
1592
|
+
from sage.modules.free_module import FreeModule
|
|
1593
|
+
RR3 = FreeModule(RDF, 3)
|
|
1594
|
+
|
|
1595
|
+
if hasattr(self, 'triangulate'):
|
|
1596
|
+
self.triangulate()
|
|
1597
|
+
faces = self.face_list(render_params)
|
|
1598
|
+
faces_iter = iter(faces)
|
|
1599
|
+
|
|
1600
|
+
def chopped_faces_iter():
|
|
1601
|
+
for face in faces_iter:
|
|
1602
|
+
n = len(face)
|
|
1603
|
+
if n == 3:
|
|
1604
|
+
yield face
|
|
1605
|
+
else:
|
|
1606
|
+
# naive cut into triangles
|
|
1607
|
+
v = face[-1]
|
|
1608
|
+
for i in range(n - 2):
|
|
1609
|
+
yield [v, face[i], face[i + 1]]
|
|
1610
|
+
|
|
1611
|
+
main_data = []
|
|
1612
|
+
for i, j, k in chopped_faces_iter():
|
|
1613
|
+
ij = RR3(j) - RR3(i)
|
|
1614
|
+
ik = RR3(k) - RR3(i)
|
|
1615
|
+
n = ij.cross_product(ik)
|
|
1616
|
+
n = n / n.norm()
|
|
1617
|
+
fill = struct.pack('H', 0)
|
|
1618
|
+
# 50 bytes per facet
|
|
1619
|
+
# 12 times 4 bytes (float) for n, i, j, k
|
|
1620
|
+
fill = b''.join(struct.pack('<f', x) for x in n)
|
|
1621
|
+
fill += b''.join(struct.pack('<f', x) for x in i)
|
|
1622
|
+
fill += b''.join(struct.pack('<f', x) for x in j)
|
|
1623
|
+
fill += b''.join(struct.pack('<f', x) for x in k)
|
|
1624
|
+
# plus 2 more bytes
|
|
1625
|
+
fill += b'00'
|
|
1626
|
+
main_data.append(fill)
|
|
1627
|
+
|
|
1628
|
+
return main_data
|
|
1629
|
+
|
|
1630
|
+
def dual(self, **kwds):
|
|
1631
|
+
"""
|
|
1632
|
+
Return the dual.
|
|
1633
|
+
|
|
1634
|
+
EXAMPLES::
|
|
1635
|
+
|
|
1636
|
+
sage: S = cube()
|
|
1637
|
+
sage: T = S.dual()
|
|
1638
|
+
sage: len(T.vertex_list())
|
|
1639
|
+
6
|
|
1640
|
+
"""
|
|
1641
|
+
cdef face_c *face
|
|
1642
|
+
cdef Py_ssize_t i, j, ix, ff
|
|
1643
|
+
cdef IndexFaceSet dual = IndexFaceSet([], **kwds)
|
|
1644
|
+
cdef int incoming, outgoing
|
|
1645
|
+
cdef dict dd
|
|
1646
|
+
|
|
1647
|
+
dual.realloc(self.fcount, self.vcount, self.icount)
|
|
1648
|
+
|
|
1649
|
+
# is using dicts overly-heavy?
|
|
1650
|
+
dual_faces = [{} for i in range(self.vcount)]
|
|
1651
|
+
|
|
1652
|
+
for i in range(self.fcount):
|
|
1653
|
+
sig_check()
|
|
1654
|
+
# Let the vertex be centered on the face according to a simple average
|
|
1655
|
+
face = &self._faces[i]
|
|
1656
|
+
dual.vs[i] = self.vs[face.vertices[0]]
|
|
1657
|
+
for j in range(1, face.n):
|
|
1658
|
+
point_c_add(&dual.vs[i], dual.vs[i], self.vs[face.vertices[j]])
|
|
1659
|
+
point_c_mul(&dual.vs[i], dual.vs[i], 1.0/face.n)
|
|
1660
|
+
|
|
1661
|
+
# Now compute the new face
|
|
1662
|
+
for j in range(face.n):
|
|
1663
|
+
if j == 0:
|
|
1664
|
+
incoming = face.vertices[face.n - 1]
|
|
1665
|
+
else:
|
|
1666
|
+
incoming = face.vertices[j - 1]
|
|
1667
|
+
if j == face.n - 1:
|
|
1668
|
+
outgoing = face.vertices[0]
|
|
1669
|
+
else:
|
|
1670
|
+
outgoing = face.vertices[j + 1]
|
|
1671
|
+
dd = dual_faces[face.vertices[j]]
|
|
1672
|
+
dd[incoming] = i, outgoing
|
|
1673
|
+
|
|
1674
|
+
i = 0
|
|
1675
|
+
ix = 0
|
|
1676
|
+
for dd in dual_faces:
|
|
1677
|
+
sig_check()
|
|
1678
|
+
face = &dual._faces[i]
|
|
1679
|
+
face.n = len(dd)
|
|
1680
|
+
if face.n == 0: # skip unused vertices
|
|
1681
|
+
continue
|
|
1682
|
+
face.vertices = &dual.face_indices[ix]
|
|
1683
|
+
ff, next_ = next(iter(dd.itervalues()))
|
|
1684
|
+
face.vertices[0] = ff
|
|
1685
|
+
for j in range(1, face.n):
|
|
1686
|
+
ff, next_ = dd[next_]
|
|
1687
|
+
face.vertices[j] = ff
|
|
1688
|
+
i += 1
|
|
1689
|
+
ix += face.n
|
|
1690
|
+
|
|
1691
|
+
dual.vcount = self.fcount
|
|
1692
|
+
dual.fcount = i
|
|
1693
|
+
dual.icount = ix
|
|
1694
|
+
|
|
1695
|
+
return dual
|
|
1696
|
+
|
|
1697
|
+
def stickers(self, colors, width, hover):
|
|
1698
|
+
"""
|
|
1699
|
+
Return a group of IndexFaceSets.
|
|
1700
|
+
|
|
1701
|
+
INPUT:
|
|
1702
|
+
|
|
1703
|
+
- ``colors`` -- list of colors/textures to use (in cyclic order)
|
|
1704
|
+
|
|
1705
|
+
- ``width`` -- offset perpendicular into the edge (to create a border)
|
|
1706
|
+
may also be negative
|
|
1707
|
+
|
|
1708
|
+
- ``hover`` -- offset normal to the face (usually have to float above
|
|
1709
|
+
the original surface so it shows, typically this value is very
|
|
1710
|
+
small compared to the actual object
|
|
1711
|
+
|
|
1712
|
+
OUTPUT: Graphics3dGroup of stickers
|
|
1713
|
+
|
|
1714
|
+
EXAMPLES::
|
|
1715
|
+
|
|
1716
|
+
sage: from sage.plot.plot3d.shapes import Box
|
|
1717
|
+
sage: B = Box(.5,.4,.3, color='black')
|
|
1718
|
+
sage: S = B.stickers(['red','yellow','blue'], 0.1, 0.05)
|
|
1719
|
+
sage: S.show()
|
|
1720
|
+
sage: (S+B).show()
|
|
1721
|
+
"""
|
|
1722
|
+
all = []
|
|
1723
|
+
n = self.fcount
|
|
1724
|
+
ct = len(colors)
|
|
1725
|
+
for k in range(len(colors)):
|
|
1726
|
+
if colors[k]:
|
|
1727
|
+
all.append(self.sticker(list(range(k, n, ct)), width, hover,
|
|
1728
|
+
texture=colors[k]))
|
|
1729
|
+
return Graphics3dGroup(all)
|
|
1730
|
+
|
|
1731
|
+
def sticker(self, face_list, width, hover, **kwds):
|
|
1732
|
+
"""
|
|
1733
|
+
Return a sticker on the chosen faces.
|
|
1734
|
+
"""
|
|
1735
|
+
if not isinstance(face_list, (list, tuple)):
|
|
1736
|
+
face_list = (face_list,)
|
|
1737
|
+
faces = self.face_list()
|
|
1738
|
+
all = []
|
|
1739
|
+
for k in face_list:
|
|
1740
|
+
all.append(sticker(faces[k], width, hover))
|
|
1741
|
+
return IndexFaceSet(all, **kwds)
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
cdef class FaceIter:
|
|
1745
|
+
"""
|
|
1746
|
+
A class for iteration over faces
|
|
1747
|
+
|
|
1748
|
+
EXAMPLES::
|
|
1749
|
+
|
|
1750
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
1751
|
+
sage: S = Box(1,2,3)
|
|
1752
|
+
sage: len(list(S.faces())) == 6 # indirect doctest
|
|
1753
|
+
True
|
|
1754
|
+
"""
|
|
1755
|
+
def __init__(self, face_set):
|
|
1756
|
+
self.set = face_set
|
|
1757
|
+
self.i = 0
|
|
1758
|
+
|
|
1759
|
+
def __iter__(self):
|
|
1760
|
+
return self
|
|
1761
|
+
|
|
1762
|
+
def __next__(self):
|
|
1763
|
+
cdef point_c P
|
|
1764
|
+
if self.i >= self.set.fcount:
|
|
1765
|
+
raise StopIteration
|
|
1766
|
+
else:
|
|
1767
|
+
face = []
|
|
1768
|
+
for j in range(self.set._faces[self.i].n):
|
|
1769
|
+
P = self.set.vs[self.set._faces[self.i].vertices[j]]
|
|
1770
|
+
PyList_Append(face, (P.x, P.y, P.z))
|
|
1771
|
+
self.i += 1
|
|
1772
|
+
return face
|
|
1773
|
+
|
|
1774
|
+
|
|
1775
|
+
cdef class EdgeIter:
|
|
1776
|
+
"""
|
|
1777
|
+
A class for iteration over edges
|
|
1778
|
+
|
|
1779
|
+
EXAMPLES::
|
|
1780
|
+
|
|
1781
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
1782
|
+
sage: S = Box(1,2,3)
|
|
1783
|
+
sage: len(list(S.edges())) == 12 # indirect doctest
|
|
1784
|
+
True
|
|
1785
|
+
"""
|
|
1786
|
+
def __init__(self, face_set):
|
|
1787
|
+
self.set = face_set
|
|
1788
|
+
if not self.set.enclosed:
|
|
1789
|
+
raise TypeError("must be closed to use the simple iterator")
|
|
1790
|
+
self.i = 0
|
|
1791
|
+
self.j = 0
|
|
1792
|
+
self.seen = {}
|
|
1793
|
+
|
|
1794
|
+
def __iter__(self):
|
|
1795
|
+
return self
|
|
1796
|
+
|
|
1797
|
+
def __next__(self):
|
|
1798
|
+
cdef point_c P, Q
|
|
1799
|
+
cdef face_c face = self.set._faces[self.i]
|
|
1800
|
+
while self.i < self.set.fcount:
|
|
1801
|
+
if self.j == face.n:
|
|
1802
|
+
self.i += 1
|
|
1803
|
+
self.j = 0
|
|
1804
|
+
if self.i < self.set.fcount:
|
|
1805
|
+
face = self.set._faces[self.i]
|
|
1806
|
+
else:
|
|
1807
|
+
if self.j == 0:
|
|
1808
|
+
P = self.set.vs[face.vertices[face.n - 1]]
|
|
1809
|
+
else:
|
|
1810
|
+
P = self.set.vs[face.vertices[self.j - 1]]
|
|
1811
|
+
Q = self.set.vs[face.vertices[self.j]]
|
|
1812
|
+
self.j += 1
|
|
1813
|
+
if self.set.enclosed: # Every edge appears exactly twice, once in each orientation.
|
|
1814
|
+
if point_c_cmp(P, Q) < 0:
|
|
1815
|
+
return ((P.x, P.y, P.z), (Q.x, Q.y, Q.z))
|
|
1816
|
+
else:
|
|
1817
|
+
if point_c_cmp(P, Q) > 0:
|
|
1818
|
+
P, Q = Q, P
|
|
1819
|
+
edge = ((P.x, P.y, P.z), (Q.x, Q.y, Q.z))
|
|
1820
|
+
if edge not in self.seen:
|
|
1821
|
+
self.seen[edge] = edge
|
|
1822
|
+
return edge
|
|
1823
|
+
raise StopIteration
|
|
1824
|
+
|
|
1825
|
+
|
|
1826
|
+
cdef class VertexIter:
|
|
1827
|
+
"""
|
|
1828
|
+
A class for iteration over vertices
|
|
1829
|
+
|
|
1830
|
+
EXAMPLES::
|
|
1831
|
+
|
|
1832
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
1833
|
+
sage: S = Box(1,2,3)
|
|
1834
|
+
sage: len(list(S.vertices())) == 8 # indirect doctest
|
|
1835
|
+
True
|
|
1836
|
+
"""
|
|
1837
|
+
def __init__(self, face_set):
|
|
1838
|
+
self.set = face_set
|
|
1839
|
+
self.i = 0
|
|
1840
|
+
|
|
1841
|
+
def __iter__(self):
|
|
1842
|
+
return self
|
|
1843
|
+
|
|
1844
|
+
def __next__(self):
|
|
1845
|
+
if self.i >= self.set.vcount:
|
|
1846
|
+
raise StopIteration
|
|
1847
|
+
else:
|
|
1848
|
+
self.i += 1
|
|
1849
|
+
return (self.set.vs[self.i-1].x,
|
|
1850
|
+
self.set.vs[self.i-1].y,
|
|
1851
|
+
self.set.vs[self.i-1].z)
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
def sticker(face, width, hover):
|
|
1855
|
+
"""
|
|
1856
|
+
Return a sticker over the given face.
|
|
1857
|
+
"""
|
|
1858
|
+
n = len(face)
|
|
1859
|
+
edges = []
|
|
1860
|
+
for i in range(n):
|
|
1861
|
+
edges.append(vector(RDF, [face[i - 1][0] - face[i][0],
|
|
1862
|
+
face[i - 1][1] - face[i][1],
|
|
1863
|
+
face[i - 1][2] - face[i][2]]))
|
|
1864
|
+
sticker = []
|
|
1865
|
+
for i in range(n):
|
|
1866
|
+
v = -edges[i]
|
|
1867
|
+
w = edges[i - 1]
|
|
1868
|
+
N = v.cross_product(w)
|
|
1869
|
+
lenN = N.norm()
|
|
1870
|
+
dv = v * (width * w.norm() / lenN)
|
|
1871
|
+
dw = w * (width * v.norm() / lenN)
|
|
1872
|
+
sticker.append(tuple(vector(RDF, face[i-1]) + dv + dw + N*(hover/lenN)))
|
|
1873
|
+
return sticker
|