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

1""" 

2Geometry Primitives 

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

4 

5Define various geometry primitives and their generation methods for 

6colour 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 

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

20 

21from __future__ import annotations 

22 

23import typing 

24 

25import numpy as np 

26 

27from colour.constants import DTYPE_FLOAT_DEFAULT, DTYPE_INT_DEFAULT 

28 

29if typing.TYPE_CHECKING: 

30 from colour.hints import ( 

31 Any, 

32 DTypeFloat, 

33 DTypeInt, 

34 Literal, 

35 NDArray, 

36 Tuple, 

37 Type, 

38 ) 

39 

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) 

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 

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. 

78 

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

83 

84 

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. 

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 

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. 

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 

458PRIMITIVE_METHODS: CanonicalMapping = CanonicalMapping( 

459 { 

460 "Grid": primitive_grid, 

461 "Cube": primitive_cube, 

462 } 

463) 

464PRIMITIVE_METHODS.__doc__ = """ 

465Supported geometry primitive generation methods. 

466""" 

467 

468 

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