Coverage for colour/geometry/primitives.py: 100%
120 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 23:01 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 23:01 +1300
1"""
2Geometry Primitives
3===================
5Define various geometry primitives and their generation methods for
6colour science visualizations and computations.
8- :attr:`colour.geometry.MAPPING_PLANE_TO_AXIS`
9- :func:`colour.geometry.primitive_grid`
10- :func:`colour.geometry.primitive_cube`
11- :func:`colour.PRIMITIVE_METHODS`
12- :func:`colour.primitive`
14References
15----------
16- :cite:`Cabello2015` : Cabello, R. (n.d.). PlaneGeometry.js. Retrieved May
17 12, 2015, from
18 https://github.com/mrdoob/three.js/blob/dev/src/geometries/PlaneGeometry.js
19"""
21from __future__ import annotations
23import typing
25import numpy as np
27from colour.constants import DTYPE_FLOAT_DEFAULT, DTYPE_INT_DEFAULT
29if typing.TYPE_CHECKING:
30 from colour.hints import (
31 Any,
32 DTypeFloat,
33 DTypeInt,
34 Literal,
35 NDArray,
36 Tuple,
37 Type,
38 )
40from colour.hints import NDArrayFloat, cast
41from colour.utilities import (
42 CanonicalMapping,
43 as_int_array,
44 filter_kwargs,
45 ones,
46 optional,
47 validate_method,
48 zeros,
49)
51__author__ = "Colour Developers"
52__copyright__ = "Copyright 2013 Colour Developers"
53__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
54__maintainer__ = "Colour Developers"
55__email__ = "colour-developers@colour-science.org"
56__status__ = "Production"
58__all__ = [
59 "MAPPING_PLANE_TO_AXIS",
60 "primitive_grid",
61 "primitive_cube",
62 "PRIMITIVE_METHODS",
63 "primitive",
64]
66MAPPING_PLANE_TO_AXIS: CanonicalMapping = CanonicalMapping(
67 {
68 "yz": "+x",
69 "zy": "-x",
70 "xz": "+y",
71 "zx": "-y",
72 "xy": "+z",
73 "yx": "-z",
74 }
75)
76MAPPING_PLANE_TO_AXIS.__doc__ = """
77Mapping from coordinate planes to their perpendicular axes.
79Maps two-letter plane identifiers (e.g., 'xy', 'yz') to their
80corresponding perpendicular axis with sign indicating the direction
81following the right-hand rule convention.
82"""
85def primitive_grid(
86 width: float = 1,
87 height: float = 1,
88 width_segments: int = 1,
89 height_segments: int = 1,
90 axis: Literal[
91 "-x", "+x", "-y", "+y", "-z", "+z", "xy", "xz", "yz", "yx", "zx", "zy"
92 ] = "+z",
93 dtype_vertices: Type[DTypeFloat] | None = None,
94 dtype_indexes: Type[DTypeInt] | None = None,
95) -> Tuple[NDArray, NDArray, NDArray]:
96 """
97 Generate vertices and indexes for a filled and outlined grid
98 primitive.
100 Parameters
101 ----------
102 width
103 Width of the primitive.
104 height
105 Height of the primitive.
106 width_segments
107 Number of segments along the width.
108 height_segments
109 Number of segments along the height.
110 axis
111 Axis to which the primitive will be normal, or plane with
112 which the primitive will be co-planar.
113 dtype_vertices
114 :class:`numpy.dtype` to use for the grid vertices. Defaults
115 to the :class:`numpy.dtype` defined by the
116 :attr:`colour.constant.DTYPE_FLOAT_DEFAULT` attribute.
117 dtype_indexes
118 :class:`numpy.dtype` to use for the grid indexes. Defaults
119 to the :class:`numpy.dtype` defined by the
120 :attr:`colour.constant.DTYPE_INT_DEFAULT` attribute.
122 Returns
123 -------
124 :class:`tuple`
125 Tuple of grid vertices, face indexes to produce a filled
126 grid, and outline indexes to produce an outline of the grid
127 faces.
129 References
130 ----------
131 :cite:`Cabello2015`
133 Examples
134 --------
135 >>> vertices, faces, outline = primitive_grid()
136 >>> print(vertices)
137 [([-0.5, 0.5, 0. ], [ 0., 1.], [ 0., 0., 1.], [ 0., 1., 0., 1.])
138 ([ 0.5, 0.5, 0. ], [ 1., 1.], [ 0., 0., 1.], [ 1., 1., 0., 1.])
139 ([-0.5, -0.5, 0. ], [ 0., 0.], [ 0., 0., 1.], [ 0., 0., 0., 1.])
140 ([ 0.5, -0.5, 0. ], [ 1., 0.], [ 0., 0., 1.], [ 1., 0., 0., 1.])]
141 >>> print(faces)
142 [[0 2 1]
143 [2 3 1]]
144 >>> print(outline)
145 [[0 2]
146 [2 3]
147 [3 1]
148 [1 0]]
149 """
151 axis = MAPPING_PLANE_TO_AXIS.get(axis, axis).lower()
153 dtype_vertices = optional(dtype_vertices, DTYPE_FLOAT_DEFAULT)
154 dtype_indexes = optional(dtype_indexes, DTYPE_INT_DEFAULT)
156 x_grid = width_segments
157 y_grid = height_segments
159 x_grid1 = int(x_grid + 1)
160 y_grid1 = int(y_grid + 1)
162 # Positions, normals and uvs.
163 positions = zeros(x_grid1 * y_grid1 * 3)
164 normals = zeros(x_grid1 * y_grid1 * 3)
165 uvs = zeros(x_grid1 * y_grid1 * 2)
167 y = np.arange(y_grid1) * height / y_grid - height / 2
168 x = np.arange(x_grid1) * width / x_grid - width / 2
170 positions[::3] = np.tile(x, y_grid1)
171 positions[1::3] = -np.repeat(y, x_grid1)
173 normals[2::3] = 1
175 uvs[::2] = np.tile(np.arange(x_grid1) / x_grid, y_grid1)
176 uvs[1::2] = np.repeat(1 - np.arange(y_grid1) / y_grid, x_grid1)
178 # Faces and outline.
179 faces_indexes = []
180 outline_indexes = []
181 for i_y in range(y_grid):
182 for i_x in range(x_grid):
183 a = i_x + x_grid1 * i_y
184 b = i_x + x_grid1 * (i_y + 1)
185 c = (i_x + 1) + x_grid1 * (i_y + 1)
186 d = (i_x + 1) + x_grid1 * i_y
188 faces_indexes.extend([(a, b, d), (b, c, d)])
189 outline_indexes.extend([(a, b), (b, c), (c, d), (d, a)])
191 faces = np.reshape(as_int_array(faces_indexes, dtype_indexes), (-1, 3))
192 outline = np.reshape(as_int_array(outline_indexes, dtype_indexes), (-1, 2))
194 positions = np.reshape(positions, (-1, 3))
195 uvs = np.reshape(uvs, (-1, 2))
196 normals = np.reshape(normals, (-1, 3))
198 if axis in ("-x", "+x"):
199 shift, zero_axis = 1, 0
200 elif axis in ("-y", "+y"):
201 shift, zero_axis = -1, 1
202 elif axis in ("-z", "+z"):
203 shift, zero_axis = 0, 2
205 sign = -1 if "-" in axis else 1
207 positions = np.roll(positions, shift, -1)
208 normals = cast("NDArrayFloat", np.roll(normals, shift, -1)) * sign
209 vertex_colours = np.ravel(positions)
210 vertex_colours = np.hstack(
211 [
212 np.reshape(
213 np.interp(
214 cast("NDArrayFloat", vertex_colours),
215 (np.min(vertex_colours), np.max(vertex_colours)),
216 (0, 1),
217 ),
218 positions.shape,
219 ),
220 ones((positions.shape[0], 1)),
221 ]
222 )
223 vertex_colours[..., zero_axis] = 0
225 vertices = zeros(
226 positions.shape[0],
227 [
228 ("position", dtype_vertices, 3),
229 ("uv", dtype_vertices, 2),
230 ("normal", dtype_vertices, 3),
231 ("colour", dtype_vertices, 4),
232 ], # pyright: ignore
233 )
235 vertices["position"] = positions
236 vertices["uv"] = uvs
237 vertices["normal"] = normals
238 vertices["colour"] = vertex_colours
240 return vertices, faces, outline
243def primitive_cube(
244 width: float = 1,
245 height: float = 1,
246 depth: float = 1,
247 width_segments: int = 1,
248 height_segments: int = 1,
249 depth_segments: int = 1,
250 planes: (
251 Literal[
252 "-x",
253 "+x",
254 "-y",
255 "+y",
256 "-z",
257 "+z",
258 "xy",
259 "xz",
260 "yz",
261 "yx",
262 "zx",
263 "zy",
264 ]
265 | None
266 ) = None,
267 dtype_vertices: Type[DTypeFloat] | None = None,
268 dtype_indexes: Type[DTypeInt] | None = None,
269) -> Tuple[NDArray, NDArray, NDArray]:
270 """
271 Generate vertices and indexes for a filled and outlined cube primitive.
273 Parameters
274 ----------
275 width
276 Cube width.
277 height
278 Cube height.
279 depth
280 Cube depth.
281 width_segments
282 Cube segments count along the width.
283 height_segments
284 Cube segments count along the height.
285 depth_segments
286 Cube segments count along the depth.
287 planes
288 Grid primitives to include in the cube construction.
289 dtype_vertices
290 :class:`numpy.dtype` to use for the grid vertices. Defaults to
291 the :class:`numpy.dtype` defined by the
292 :attr:`colour.constant.DTYPE_FLOAT_DEFAULT` attribute.
293 dtype_indexes
294 :class:`numpy.dtype` to use for the grid indexes. Defaults to
295 the :class:`numpy.dtype` defined by the
296 :attr:`colour.constant.DTYPE_INT_DEFAULT` attribute.
298 Returns
299 -------
300 :class:`tuple`
301 Tuple of cube vertices, face indexes to produce a filled cube and
302 outline indexes to produce an outline of the faces of the cube.
304 Examples
305 --------
306 >>> vertices, faces, outline = primitive_cube()
307 >>> print(vertices)
308 [([-0.5, 0.5, -0.5], [ 0., 1.], [-0., -0., -1.], [ 0., 1., 0., 1.])
309 ([ 0.5, 0.5, -0.5], [ 1., 1.], [-0., -0., -1.], [ 1., 1., 0., 1.])
310 ([-0.5, -0.5, -0.5], [ 0., 0.], [-0., -0., -1.], [ 0., 0., 0., 1.])
311 ([ 0.5, -0.5, -0.5], [ 1., 0.], [-0., -0., -1.], [ 1., 0., 0., 1.])
312 ([-0.5, 0.5, 0.5], [ 0., 1.], [ 0., 0., 1.], [ 0., 1., 1., 1.])
313 ([ 0.5, 0.5, 0.5], [ 1., 1.], [ 0., 0., 1.], [ 1., 1., 1., 1.])
314 ([-0.5, -0.5, 0.5], [ 0., 0.], [ 0., 0., 1.], [ 0., 0., 1., 1.])
315 ([ 0.5, -0.5, 0.5], [ 1., 0.], [ 0., 0., 1.], [ 1., 0., 1., 1.])
316 ([ 0.5, -0.5, -0.5], [ 0., 1.], [-0., -1., -0.], [ 1., 0., 0., 1.])
317 ([ 0.5, -0.5, 0.5], [ 1., 1.], [-0., -1., -0.], [ 1., 0., 1., 1.])
318 ([-0.5, -0.5, -0.5], [ 0., 0.], [-0., -1., -0.], [ 0., 0., 0., 1.])
319 ([-0.5, -0.5, 0.5], [ 1., 0.], [-0., -1., -0.], [ 0., 0., 1., 1.])
320 ([ 0.5, 0.5, -0.5], [ 0., 1.], [ 0., 1., 0.], [ 1., 1., 0., 1.])
321 ([ 0.5, 0.5, 0.5], [ 1., 1.], [ 0., 1., 0.], [ 1., 1., 1., 1.])
322 ([-0.5, 0.5, -0.5], [ 0., 0.], [ 0., 1., 0.], [ 0., 1., 0., 1.])
323 ([-0.5, 0.5, 0.5], [ 1., 0.], [ 0., 1., 0.], [ 0., 1., 1., 1.])
324 ([-0.5, -0.5, 0.5], [ 0., 1.], [-1., -0., -0.], [ 0., 0., 1., 1.])
325 ([-0.5, 0.5, 0.5], [ 1., 1.], [-1., -0., -0.], [ 0., 1., 1., 1.])
326 ([-0.5, -0.5, -0.5], [ 0., 0.], [-1., -0., -0.], [ 0., 0., 0., 1.])
327 ([-0.5, 0.5, -0.5], [ 1., 0.], [-1., -0., -0.], [ 0., 1., 0., 1.])
328 ([ 0.5, -0.5, 0.5], [ 0., 1.], [ 1., 0., 0.], [ 1., 0., 1., 1.])
329 ([ 0.5, 0.5, 0.5], [ 1., 1.], [ 1., 0., 0.], [ 1., 1., 1., 1.])
330 ([ 0.5, -0.5, -0.5], [ 0., 0.], [ 1., 0., 0.], [ 1., 0., 0., 1.])
331 ([ 0.5, 0.5, -0.5], [ 1., 0.], [ 1., 0., 0.], [ 1., 1., 0., 1.])]
332 >>> print(faces)
333 [[ 1 2 0]
334 [ 1 3 2]
335 [ 4 6 5]
336 [ 6 7 5]
337 [ 9 10 8]
338 [ 9 11 10]
339 [12 14 13]
340 [14 15 13]
341 [17 18 16]
342 [17 19 18]
343 [20 22 21]
344 [22 23 21]]
345 >>> print(outline)
346 [[ 0 2]
347 [ 2 3]
348 [ 3 1]
349 [ 1 0]
350 [ 4 6]
351 [ 6 7]
352 [ 7 5]
353 [ 5 4]
354 [ 8 10]
355 [10 11]
356 [11 9]
357 [ 9 8]
358 [12 14]
359 [14 15]
360 [15 13]
361 [13 12]
362 [16 18]
363 [18 19]
364 [19 17]
365 [17 16]
366 [20 22]
367 [22 23]
368 [23 21]
369 [21 20]]
370 """
372 axis = (
373 sorted(MAPPING_PLANE_TO_AXIS.values())
374 if planes is None
375 else [MAPPING_PLANE_TO_AXIS.get(plane, plane).lower() for plane in planes]
376 )
378 dtype_vertices = optional(dtype_vertices, DTYPE_FLOAT_DEFAULT)
379 dtype_indexes = optional(dtype_indexes, DTYPE_INT_DEFAULT)
381 w_s, h_s, d_s = width_segments, height_segments, depth_segments
383 planes_p = []
384 if "-z" in axis:
385 planes_p.append(list(primitive_grid(width, depth, w_s, d_s, "-z")))
386 planes_p[-1][0]["position"][..., 2] -= height / 2
387 planes_p[-1][1] = np.fliplr(planes_p[-1][1])
388 if "+z" in axis:
389 planes_p.append(list(primitive_grid(width, depth, w_s, d_s, "+z")))
390 planes_p[-1][0]["position"][..., 2] += height / 2
392 if "-y" in axis:
393 planes_p.append(list(primitive_grid(height, width, h_s, w_s, "-y")))
394 planes_p[-1][0]["position"][..., 1] -= depth / 2
395 planes_p[-1][1] = np.fliplr(planes_p[-1][1])
396 if "+y" in axis:
397 planes_p.append(list(primitive_grid(height, width, h_s, w_s, "+y")))
398 planes_p[-1][0]["position"][..., 1] += depth / 2
400 if "-x" in axis:
401 planes_p.append(list(primitive_grid(depth, height, d_s, h_s, "-x")))
402 planes_p[-1][0]["position"][..., 0] -= width / 2
403 planes_p[-1][1] = np.fliplr(planes_p[-1][1])
404 if "+x" in axis:
405 planes_p.append(list(primitive_grid(depth, height, d_s, h_s, "+x")))
406 planes_p[-1][0]["position"][..., 0] += width / 2
408 positions = zeros((0, 3))
409 uvs = zeros((0, 2))
410 normals = zeros((0, 3))
412 faces = zeros((0, 3), dtype=dtype_indexes)
413 outline = zeros((0, 2), dtype=dtype_indexes)
415 offset = 0
416 for vertices_p, faces_p, outline_p in planes_p:
417 positions = np.vstack([positions, vertices_p["position"]])
418 uvs = np.vstack([uvs, vertices_p["uv"]])
419 normals = np.vstack([normals, vertices_p["normal"]])
421 faces = np.vstack([faces, faces_p + offset])
422 outline = np.vstack([outline, outline_p + offset])
423 offset += vertices_p["position"].shape[0]
425 vertices = zeros(
426 positions.shape[0],
427 [
428 ("position", dtype_vertices, 3),
429 ("uv", dtype_vertices, 2),
430 ("normal", dtype_vertices, 3),
431 ("colour", dtype_vertices, 4),
432 ], # pyright: ignore
433 )
435 vertex_colours = np.ravel(positions)
436 vertex_colours = np.hstack(
437 [
438 np.reshape(
439 np.interp(
440 cast("NDArrayFloat", vertex_colours),
441 (np.min(vertex_colours), np.max(vertex_colours)),
442 (0, 1),
443 ),
444 positions.shape,
445 ),
446 ones((positions.shape[0], 1)),
447 ]
448 )
450 vertices["position"] = positions
451 vertices["uv"] = uvs
452 vertices["normal"] = normals
453 vertices["colour"] = vertex_colours
455 return vertices, faces, outline
458PRIMITIVE_METHODS: CanonicalMapping = CanonicalMapping(
459 {
460 "Grid": primitive_grid,
461 "Cube": primitive_cube,
462 }
463)
464PRIMITIVE_METHODS.__doc__ = """
465Supported geometry primitive generation methods.
466"""
469def primitive(
470 method: Literal["Cube", "Grid"] | str = "Cube", **kwargs: Any
471) -> Tuple[NDArray, NDArray, NDArray]:
472 """
473 Generate a geometry primitive.
475 This function creates geometric primitives such as cubes or grids with
476 configurable dimensions and segmentation. The generated primitive includes
477 vertices with position, texture coordinates, normal vectors, and colour
478 data, along with face and outline indexes for rendering.
480 Parameters
481 ----------
482 method
483 Generation method for the primitive. Supported methods are:
485 - ``'Cube'``: Generate a 3D cube primitive
486 - ``'Grid'``: Generate a 2D grid primitive
488 Other Parameters
489 ----------------
490 axis
491 {:func:`colour.geometry.primitive_grid`},
492 Axis to which the primitive will be normal, or plane with which the
493 primitive will be co-planar.
494 depth
495 {:func:`colour.geometry.primitive_cube`},
496 Cube depth.
497 depth_segments
498 {:func:`colour.geometry.primitive_cube`},
499 Cube segments count along the depth.
500 dtype_indexes
501 {:func:`colour.geometry.primitive_grid`,
502 :func:`colour.geometry.primitive_cube`},
503 :class:`numpy.dtype` to use for the grid indexes. Defaults to the
504 :class:`numpy.dtype` defined by the
505 :attr:`colour.constant.DTYPE_INT_DEFAULT` attribute.
506 dtype_vertices
507 {:func:`colour.geometry.primitive_grid`,
508 :func:`colour.geometry.primitive_cube`},
509 :class:`numpy.dtype` to use for the grid vertices. Defaults to the
510 :class:`numpy.dtype` defined by the
511 :attr:`colour.constant.DTYPE_FLOAT_DEFAULT` attribute.
512 height
513 {:func:`colour.geometry.primitive_grid`,
514 :func:`colour.geometry.primitive_cube`},
515 Height of the primitive.
516 planes
517 {:func:`colour.geometry.primitive_cube`},
518 Grid primitives to include in the cube construction.
519 width
520 {:func:`colour.geometry.primitive_grid`,
521 :func:`colour.geometry.primitive_cube`},
522 Width of the primitive.
523 width_segments
524 {:func:`colour.geometry.primitive_grid`,
525 :func:`colour.geometry.primitive_cube`},
526 Number of segments along the width.
527 height_segments
528 {:func:`colour.geometry.primitive_grid`,
529 :func:`colour.geometry.primitive_cube`},
530 Number of segments along the height.
532 Returns
533 -------
534 :class:`tuple`
535 Tuple containing three arrays:
537 - **vertices**: Structured array of vertex data including position,
538 texture coordinates, normal vectors, and colour values
539 - **faces**: Face indexes for rendering a filled primitive
540 - **outline**: Outline indexes for rendering the edges of the primitive
542 References
543 ----------
544 :cite:`Cabello2015`
546 Examples
547 --------
548 >>> vertices, faces, outline = primitive()
549 >>> print(vertices)
550 [([-0.5, 0.5, -0.5], [ 0., 1.], [-0., -0., -1.], [ 0., 1., 0., 1.])
551 ([ 0.5, 0.5, -0.5], [ 1., 1.], [-0., -0., -1.], [ 1., 1., 0., 1.])
552 ([-0.5, -0.5, -0.5], [ 0., 0.], [-0., -0., -1.], [ 0., 0., 0., 1.])
553 ([ 0.5, -0.5, -0.5], [ 1., 0.], [-0., -0., -1.], [ 1., 0., 0., 1.])
554 ([-0.5, 0.5, 0.5], [ 0., 1.], [ 0., 0., 1.], [ 0., 1., 1., 1.])
555 ([ 0.5, 0.5, 0.5], [ 1., 1.], [ 0., 0., 1.], [ 1., 1., 1., 1.])
556 ([-0.5, -0.5, 0.5], [ 0., 0.], [ 0., 0., 1.], [ 0., 0., 1., 1.])
557 ([ 0.5, -0.5, 0.5], [ 1., 0.], [ 0., 0., 1.], [ 1., 0., 1., 1.])
558 ([ 0.5, -0.5, -0.5], [ 0., 1.], [-0., -1., -0.], [ 1., 0., 0., 1.])
559 ([ 0.5, -0.5, 0.5], [ 1., 1.], [-0., -1., -0.], [ 1., 0., 1., 1.])
560 ([-0.5, -0.5, -0.5], [ 0., 0.], [-0., -1., -0.], [ 0., 0., 0., 1.])
561 ([-0.5, -0.5, 0.5], [ 1., 0.], [-0., -1., -0.], [ 0., 0., 1., 1.])
562 ([ 0.5, 0.5, -0.5], [ 0., 1.], [ 0., 1., 0.], [ 1., 1., 0., 1.])
563 ([ 0.5, 0.5, 0.5], [ 1., 1.], [ 0., 1., 0.], [ 1., 1., 1., 1.])
564 ([-0.5, 0.5, -0.5], [ 0., 0.], [ 0., 1., 0.], [ 0., 1., 0., 1.])
565 ([-0.5, 0.5, 0.5], [ 1., 0.], [ 0., 1., 0.], [ 0., 1., 1., 1.])
566 ([-0.5, -0.5, 0.5], [ 0., 1.], [-1., -0., -0.], [ 0., 0., 1., 1.])
567 ([-0.5, 0.5, 0.5], [ 1., 1.], [-1., -0., -0.], [ 0., 1., 1., 1.])
568 ([-0.5, -0.5, -0.5], [ 0., 0.], [-1., -0., -0.], [ 0., 0., 0., 1.])
569 ([-0.5, 0.5, -0.5], [ 1., 0.], [-1., -0., -0.], [ 0., 1., 0., 1.])
570 ([ 0.5, -0.5, 0.5], [ 0., 1.], [ 1., 0., 0.], [ 1., 0., 1., 1.])
571 ([ 0.5, 0.5, 0.5], [ 1., 1.], [ 1., 0., 0.], [ 1., 1., 1., 1.])
572 ([ 0.5, -0.5, -0.5], [ 0., 0.], [ 1., 0., 0.], [ 1., 0., 0., 1.])
573 ([ 0.5, 0.5, -0.5], [ 1., 0.], [ 1., 0., 0.], [ 1., 1., 0., 1.])]
574 >>> print(faces)
575 [[ 1 2 0]
576 [ 1 3 2]
577 [ 4 6 5]
578 [ 6 7 5]
579 [ 9 10 8]
580 [ 9 11 10]
581 [12 14 13]
582 [14 15 13]
583 [17 18 16]
584 [17 19 18]
585 [20 22 21]
586 [22 23 21]]
587 >>> print(outline)
588 [[ 0 2]
589 [ 2 3]
590 [ 3 1]
591 [ 1 0]
592 [ 4 6]
593 [ 6 7]
594 [ 7 5]
595 [ 5 4]
596 [ 8 10]
597 [10 11]
598 [11 9]
599 [ 9 8]
600 [12 14]
601 [14 15]
602 [15 13]
603 [13 12]
604 [16 18]
605 [18 19]
606 [19 17]
607 [17 16]
608 [20 22]
609 [22 23]
610 [23 21]
611 [21 20]]
612 >>> vertices, faces, outline = primitive("Grid")
613 >>> print(vertices)
614 [([-0.5, 0.5, 0. ], [ 0., 1.], [ 0., 0., 1.], [ 0., 1., 0., 1.])
615 ([ 0.5, 0.5, 0. ], [ 1., 1.], [ 0., 0., 1.], [ 1., 1., 0., 1.])
616 ([-0.5, -0.5, 0. ], [ 0., 0.], [ 0., 0., 1.], [ 0., 0., 0., 1.])
617 ([ 0.5, -0.5, 0. ], [ 1., 0.], [ 0., 0., 1.], [ 1., 0., 0., 1.])]
618 >>> print(faces)
619 [[0 2 1]
620 [2 3 1]]
621 >>> print(outline)
622 [[0 2]
623 [2 3]
624 [3 1]
625 [1 0]]
626 """
628 method = validate_method(method, tuple(PRIMITIVE_METHODS))
630 function = PRIMITIVE_METHODS[method]
632 return function(**filter_kwargs(function, **kwargs))