Coverage for geometry/primitives.py: 83%

1"""

2 Geometry Primitives

3===================

4

5 Define various geometry primitives and their generation methods for

6 colour science visualizations and computations.

7

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`

13

14 References

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"""

20

21 from __future__ import annotations

22

23 import typing

24

25 import numpy as np

26

27 from colour.constants import DTYPE_FLOAT_DEFAULT, DTYPE_INT_DEFAULT

28

29 if typing.TYPE_CHECKING:

30 from colour.hints import (

31 Any,

32 DTypeFloat,

33 DTypeInt,

34 Literal,

35 NDArray,

36 Tuple,

37 Type,

38 )

39

40 from colour.hints import NDArrayFloat, cast

41 from colour.utilities import (

42 CanonicalMapping,

43 as_int_array,

44 filter_kwargs,

45 ones,

46 optional,

47 validate_method,

48 zeros,

49)

50

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"

57

58 __all__ = [

59 "MAPPING_PLANE_TO_AXIS",

60 "primitive_grid",

61 "primitive_cube",

62 "PRIMITIVE_METHODS",

63 "primitive",

64]

65

66 MAPPING_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)

76 MAPPING_PLANE_TO_AXIS.__doc__ = """

77 Mapping from coordinate planes to their perpendicular axes.

78

79 Maps two-letter plane identifiers (e.g., 'xy', 'yz') to their

80 corresponding perpendicular axis with sign indicating the direction

81 following the right-hand rule convention.

82"""

83

84

85 def 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.

99

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.

121

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.

128

129 References

130 ----------

131 :cite:`Cabello2015`

132

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 """

150

151 axis = MAPPING_PLANE_TO_AXIS.get(axis, axis).lower()

152

153 dtype_vertices = optional(dtype_vertices, DTYPE_FLOAT_DEFAULT)

154 dtype_indexes = optional(dtype_indexes, DTYPE_INT_DEFAULT)

155

156 x_grid = width_segments

157 y_grid = height_segments

158

159 x_grid1 = int(x_grid + 1)

160 y_grid1 = int(y_grid + 1)

161

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)

166

167 y = np.arange(y_grid1) * height / y_grid - height / 2

168 x = np.arange(x_grid1) * width / x_grid - width / 2

169

170 positions[::3] = np.tile(x, y_grid1)

171 positions[1::3] = -np.repeat(y, x_grid1)

172

173 normals[2::3] = 1

174

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)

177

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

187

188 faces_indexes.extend([(a, b, d), (b, c, d)])

189 outline_indexes.extend([(a, b), (b, c), (c, d), (d, a)])

190

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))

193

194 positions = np.reshape(positions, (-1, 3))

195 uvs = np.reshape(uvs, (-1, 2))

196 normals = np.reshape(normals, (-1, 3))

197

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

204

205 sign = -1 if "-" in axis else 1

206

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

224

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 )

234

235 vertices["position"] = positions

236 vertices["uv"] = uvs

237 vertices["normal"] = normals

238 vertices["colour"] = vertex_colours

239

240 return vertices, faces, outline

241

242

243 def 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.

272

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.

297

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.

303

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 """

371

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 )

377

378 dtype_vertices = optional(dtype_vertices, DTYPE_FLOAT_DEFAULT)

379 dtype_indexes = optional(dtype_indexes, DTYPE_INT_DEFAULT)

380

381 w_s, h_s, d_s = width_segments, height_segments, depth_segments

382

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

391

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

399

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

407

408 positions = zeros((0, 3))

409 uvs = zeros((0, 2))

410 normals = zeros((0, 3))

411

412 faces = zeros((0, 3), dtype=dtype_indexes)

413 outline = zeros((0, 2), dtype=dtype_indexes)

414

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"]])

420

421 faces = np.vstack([faces, faces_p + offset])

422 outline = np.vstack([outline, outline_p + offset])

423 offset += vertices_p["position"].shape[0]

424

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 )

434

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 )

449

450 vertices["position"] = positions

451 vertices["uv"] = uvs

452 vertices["normal"] = normals

453 vertices["colour"] = vertex_colours

454

455 return vertices, faces, outline

456

457

458 PRIMITIVE_METHODS: CanonicalMapping = CanonicalMapping(

459 {

460 "Grid": primitive_grid,

461 "Cube": primitive_cube,

462 }

463)

464 PRIMITIVE_METHODS.__doc__ = """

465 Supported geometry primitive generation methods.

466"""

467

468

469 def primitive(

470 method: Literal["Cube", "Grid"] | str = "Cube", **kwargs: Any

471) -> Tuple[NDArray, NDArray, NDArray]:

472 """

473 Generate a geometry primitive.

474

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.

479

480 Parameters

481 ----------

482 method

483 Generation method for the primitive. Supported methods are:

484

485 - ``'Cube'``: Generate a 3D cube primitive

486 - ``'Grid'``: Generate a 2D grid primitive

487

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.

531

532 Returns

533 -------

534 :class:`tuple`

535 Tuple containing three arrays:

536

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

541

542 References

543 ----------

544 :cite:`Cabello2015`

545

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 """

627

628 method = validate_method(method, tuple(PRIMITIVE_METHODS))

629

630 function = PRIMITIVE_METHODS[method]

631

632 return function(**filter_kwargs(function, **kwargs))