Scene primitives (povgen)¶
quiltwright.povgen
¶
POV-Ray Scene Generation¶
Writes .pov scenes from analytic primitives, so a scene composed in
Python -- or grown by a geometry engine such as kg_utils.viz3d -- can be
ray-traced by :func:~quiltwright.povray.render_pov_quilt instead of
rasterised by VTK.
Why analytic rather than a mesh dump. By the time geometry reaches a
pv.Plotter it is already tessellated: pv.Sphere is a triangulated
ball, and a swept tube is a strip of quads. Dumping those triangles into a
POV-Ray mesh2 reproduces the scene faithfully but keeps VTK's facets and
costs a great deal of text, re-parsed once per view -- 48 times for a Portrait
quilt. Re-emitting the intent instead -- a limb is a swept path of radii, a
leaf is a ball at a point -- gives POV-Ray its own exact primitives: an exact
silhouette at any zoom, and a bounding hierarchy the ray-tracer is good at.
Measured on a 3000-leaf organic tree from kg_utils.viz3d (192k triangles,
159k vertices once tessellated): 839 KB of analytic SDL with oriented leaf
instances, or 508 KB with plain spheres, against roughly 12.5 MB for the
equivalent mesh2 -- 15x to 25x smaller, and better looking, since the
tessellation facets are gone. That quality difference is the reason to leave
VTK, so this module reaches for the analytic form first and leaves mesh2
as the fallback for geometry that has no analytic description (volumes,
isosurfaces, imported meshes).
Handedness. PyVista, VTK and NumPy are right-handed; POV-Ray is
left-handed. Everything here is authored in right-handed world coordinates
and converted on emission by negating z (:func:to_pov), which is the same
correction pypdb2pov applies to PDB coordinates.
:func:pov_camera_from_plotter applies the same conversion to the camera, so
the two agree and the rendered image matches the PyVista one rather than
mirroring it. Pass handedness="none" to author directly in POV-Ray
coordinates.
A :class:~quiltwright.povray.PovCamera you build yourself is not
converted -- it holds POV-Ray coordinates, and
:func:~quiltwright.povray.camera_block emits it verbatim. Run
:func:to_pov over its location, look-at and sky yourself, or the geometry
lands at negative z while the lens aims at positive z and POV-Ray renders
an immaculate picture of empty space.
(The reflection also reverses triangle winding. That does not matter for the
analytic primitives here, none of which have a winding, but a future
mesh2 emitter must reverse each face's index order or its normals will
point inward.)
Cameras. A scene written by this module deliberately contains no
camera. :func:~quiltwright.povray.render_pov_quilt appends one off-axis
camera per view and POV-Ray uses the last camera it parses; emitting one here
would merely be overridden with a warning. Use
:func:pov_camera_from_plotter to carry a composed plotter's viewpoint over
to a :class:~quiltwright.povray.PovCamera instead -- VTK's view_angle
and PovCamera.fov are both vertical degrees, so that maps one-to-one.
Typical usage::
from quiltwright.quilt import QUILT_PRESETS, save_quilt
from quiltwright.povgen import PovScene, Sphere, Texture, sphere_sweeps_from_paths
from quiltwright.povray import render_pov_quilt
scene = PovScene(background="#101018")
scene.add(sphere_sweeps_from_paths(limbs, Texture("#6b4a2f")))
scene.add(Sphere(centre, 0.4, Texture("#3f7d3f")))
scene.write("tree.pov")
spec = QUILT_PRESETS["portrait"]
quilt = render_pov_quilt("tree.pov", spec, pov_camera_from_plotter(plotter))
save_quilt(quilt, "tree", spec)
Part of Quiltwright -- https://github.com/Flux-Frontiers/quiltwright
Author: Eric G. Suchanek, PhD
Box(corner1, corner2, texture=None)
dataclass
¶
Bases: Primitive
A POV-Ray axis-aligned box.
The two corners are sorted componentwise after the handedness conversion,
because negating z swaps which corner is the lower one and POV-Ray
requires corner1 <= corner2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corner1
|
Sequence[float]
|
One corner, right-handed. |
required |
corner2
|
Sequence[float]
|
The opposite corner, right-handed. |
required |
texture
|
Texture | str | None
|
Texture, a |
None
|
sdl(handedness='flip-z')
¶
Returns:
| Type | Description |
|---|---|
str
|
|
Source code in src/quiltwright/povgen.py
339 340 341 342 343 344 | |
Cylinder(base, cap, radius, open=False, texture=None)
dataclass
¶
Bases: Primitive
A POV-Ray cylinder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base
|
Sequence[float]
|
Centre of the base cap, right-handed. |
required |
cap
|
Sequence[float]
|
Centre of the top cap, right-handed. |
required |
radius
|
float
|
Radius in scene units. |
required |
open
|
bool
|
Omit the end caps. |
False
|
texture
|
Texture | str | None
|
Texture, a |
None
|
sdl(handedness='flip-z')
¶
Returns:
| Type | Description |
|---|---|
str
|
|
Source code in src/quiltwright/povgen.py
312 313 314 315 316 317 318 319 | |
Finish(ambient=0.15, diffuse=0.75, phong=0.25, phong_size=40.0, specular=None, roughness=None, reflection=None)
dataclass
¶
A POV-Ray finish block.
Defaults approximate VTK's default actor shading closely enough that a transcoded scene reads as the same scene, rather than matching it photometrically -- POV-Ray's lighting model is not VTK's, and a scene worth ray-tracing usually wants its own lights anyway.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ambient
|
float
|
Light emitted regardless of the light sources. |
0.15
|
diffuse
|
float
|
Fraction of incident light scattered. |
0.75
|
phong
|
float | None
|
Phong highlight strength; |
0.25
|
phong_size
|
float
|
Phong highlight tightness. |
40.0
|
specular
|
float | None
|
Specular highlight strength; |
None
|
roughness
|
float | None
|
Specular roughness; only meaningful with specular. |
None
|
reflection
|
float | None
|
Mirror reflection fraction; |
None
|
sdl()
¶
Returns:
| Type | Description |
|---|---|
str
|
The |
Source code in src/quiltwright/povgen.py
211 212 213 214 215 216 217 218 219 220 221 222 | |
Instance(name, translate=None, scale=None, matrix=None, texture=None)
dataclass
¶
Bases: Primitive
An object { Name ... } reference to a #declared primitive.
Instancing is what keeps a large crown small: POV-Ray parses the prototype once and the canopy becomes one short line per leaf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The declared identifier. |
required |
translate
|
Vec | None
|
Optional translation, right-handed. |
None
|
scale
|
Vec | float | None
|
Optional per-axis scale, applied before the translation. |
None
|
matrix
|
ndarray | None
|
Optional 3x3 rotation as row vectors (row-vector
convention, matching POV-Ray's |
None
|
texture
|
Texture | str | None
|
Texture override, or |
None
|
sdl(handedness='flip-z')
¶
Returns:
| Type | Description |
|---|---|
str
|
|
Source code in src/quiltwright/povgen.py
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 | |
LightSource(position, color='#ffffff', shadowless=False, area=None)
dataclass
¶
A POV-Ray light_source.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position
|
Sequence[float]
|
Position in right-handed world coordinates. |
required |
color
|
str | Vec
|
Hex string or |
'#ffffff'
|
shadowless
|
bool
|
Emit |
False
|
area
|
tuple[Sequence[float], Sequence[float], int, int] | None
|
|
None
|
sdl(handedness='flip-z')
¶
Returns:
| Type | Description |
|---|---|
str
|
|
Source code in src/quiltwright/povgen.py
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 | |
Mesh2(vertices, faces, normals=None, normal_indices=None, textures=(), face_textures=None, texture=None)
dataclass
¶
Bases: Primitive
A POV-Ray mesh2 -- shared vertex, normal and texture lists.
The fallback this module's docstring names: for geometry with no analytic description -- an isosurface, a volume, a molecular cartoon exported from somewhere else -- there is nothing to re-emit the intent of, and triangles are the honest representation.
Prefer this over many separate one-triangle objects. A generator that
emits a mesh2 per face pays for a vertex list, a normal list and a
texture list on every triangle: PyMOL's cmd.get_povray() does exactly
that, and an OmpF porin trimer's cartoon costs 41 MB that way against
7.2 MB coalesced. :func:coalesce_mesh2 performs that merge on text
already written; this class avoids needing it.
Winding. Negating z is a reflection, and a reflection reverses
triangle orientation, so every face's indices are emitted in reverse under
handedness="flip-z". Without that, POV-Ray sees inward-facing normals
and lights the mesh from behind. normal_indices is reversed in step,
since it is parallel to faces.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vertices
|
Sequence[Sequence[float]]
|
|
required |
faces
|
Sequence[Sequence[int]]
|
Triples of indices into vertices. |
required |
normals
|
Sequence[Sequence[float]] | None
|
|
None
|
normal_indices
|
Sequence[Sequence[int]] | None
|
Triples of indices into normals, parallel to faces. Defaults to faces when normals is given, which is right whenever there is one normal per vertex. |
None
|
textures
|
Sequence[Texture | str]
|
Textures the faces index into, for per-vertex colour. |
()
|
face_textures
|
Sequence[Sequence[int]] | None
|
Triples of indices into textures, one per face corner, parallel to faces. |
None
|
texture
|
Texture | str | None
|
A texture for the whole mesh. Independent of textures; POV-Ray applies it where the per-vertex list does not reach. |
None
|
sdl(handedness='flip-z')
¶
Returns:
| Type | Description |
|---|---|
str
|
|
Source code in src/quiltwright/povgen.py
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 | |
PovScene(background=None, includes=list(), handedness='flip-z', ambient_light=None, comment='', _declares=list(), _lights=list(), _objects=list())
dataclass
¶
A POV-Ray scene under construction.
Holds includes, #declares, lights and objects, and emits a .pov
file. It writes no camera -- see the module docstring.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
background
|
str | Vec | None
|
Hex or |
None
|
includes
|
list[str]
|
|
list()
|
handedness
|
str
|
|
'flip-z'
|
ambient_light
|
str | Vec | None
|
Global |
None
|
comment
|
str
|
Free text written into the file header. |
''
|
__len__()
¶
Returns:
| Type | Description |
|---|---|
int
|
Number of top-level objects. |
Source code in src/quiltwright/povgen.py
1029 1030 1031 | |
add(item)
¶
Add one primitive, or an iterable of them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
Primitive | Iterable[Primitive]
|
A :class: |
required |
Returns:
| Type | Description |
|---|---|
PovScene
|
|
Source code in src/quiltwright/povgen.py
988 989 990 991 992 993 994 995 996 997 998 | |
add_light(light)
¶
Add a light source.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
light
|
LightSource
|
The light. |
required |
Returns:
| Type | Description |
|---|---|
PovScene
|
|
Source code in src/quiltwright/povgen.py
1000 1001 1002 1003 1004 1005 1006 1007 | |
bounds()
¶
Axis-aligned bounds of the scene, in right-handed coordinates.
Only the primitives with an obvious extent contribute
(:class:Sphere, :class:Cylinder, :class:Box,
:class:SphereSweep, and the members of a :class:Union);
:class:Instance cannot be measured without resolving its prototype
and is skipped. Useful for placing lights and for handing
focal_distance_for_range a real depth range.
Instancing is what this method cannot see, and instancing is the
reason to use this module -- so check that the two do not collide in
your scene. A tree gets away with it: its wood is swept and reaches
the crown, so the bounds cover the subject even though every leaf is an
instance. A scene whose subject is the instances does not. Ten
thousand instanced boulders around one measurable marker post return
the bounds of the post, and lights placed from that land inside the
scene while a camera framed from it fills the tile with one prop. An
entirely instanced scene returns None.
Two ways out: keep one measurable primitive that spans the subject --
a :class:Box with no texture is invisible to a render but visible
here -- or track the extent as you place the instances, which the
producer usually knows anyway, and skip this.
Returns:
| Type | Description |
|---|---|
tuple[ndarray, ndarray] | None
|
|
Source code in src/quiltwright/povgen.py
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 | |
declare(name, body)
¶
Add a #declare, for prototypes instanced by :class:Instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Identifier, e.g. |
required |
body
|
Primitive | str
|
A primitive, or raw SDL such as a texture block. |
required |
Returns:
| Type | Description |
|---|---|
PovScene
|
|
Source code in src/quiltwright/povgen.py
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 | |
declare_texture(name, texture)
¶
Declare a named texture, so many objects can share one definition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Identifier, e.g. |
required |
texture
|
Texture
|
The texture to declare. |
required |
Returns:
| Type | Description |
|---|---|
PovScene
|
|
Source code in src/quiltwright/povgen.py
1020 1021 1022 1023 1024 1025 1026 1027 | |
sdl()
¶
Returns:
| Type | Description |
|---|---|
str
|
The whole scene as POV-Ray SDL. |
Source code in src/quiltwright/povgen.py
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 | |
write(path)
¶
Write the scene to path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Destination |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The resolved path written. |
Source code in src/quiltwright/povgen.py
1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 | |
Primitive
¶
Base class for anything that can emit a POV-Ray object statement.
sdl(handedness='flip-z')
¶
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handedness
|
str
|
Passed to :func: |
'flip-z'
|
Returns:
| Type | Description |
|---|---|
str
|
This object as POV-Ray SDL. |
Source code in src/quiltwright/povgen.py
268 269 270 271 272 273 | |
Sphere(centre, radius, texture=None)
dataclass
¶
Bases: Primitive
A POV-Ray sphere.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
centre
|
Sequence[float]
|
Centre in right-handed world coordinates. |
required |
radius
|
float
|
Radius in scene units. |
required |
texture
|
Texture | str | None
|
Texture, a |
None
|
sdl(handedness='flip-z')
¶
Returns:
| Type | Description |
|---|---|
str
|
|
Source code in src/quiltwright/povgen.py
289 290 291 292 | |
SphereSweep(points, radii, kind='linear_spline', tolerance=SWEEP_TOLERANCE, texture=None)
dataclass
¶
Bases: Primitive
A POV-Ray sphere_sweep -- a tapered tube through a polyline.
This is the analytic replacement for a PyVista spline.tube(...): one
statement instead of a few thousand triangles, with an exact silhouette.
linear_spline is the default rather than b_spline because it
interpolates its control points. Callers generally hand over a path
that has already been smoothed (kg_utils.viz3d.smooth_paths splines
each limb before this ever sees it), so a further approximating spline
would pull the surface off the geometry PyVista tubed and cost the
parity that makes a dual render comparable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
ndarray
|
|
required |
radii
|
ndarray | float
|
|
required |
kind
|
str
|
|
'linear_spline'
|
tolerance
|
float
|
POV-Ray sweep solver tolerance; see
:data: |
SWEEP_TOLERANCE
|
texture
|
Texture | str | None
|
Texture, a |
None
|
sdl(handedness='flip-z')
¶
Returns:
| Type | Description |
|---|---|
str
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If kind is unknown, or too few points survive de-duplication for that spline kind. |
Source code in src/quiltwright/povgen.py
650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 | |
Texture(color='#cccccc', opacity=1.0, finish=Finish())
dataclass
¶
A POV-Ray texture block: one pigment plus one finish.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color
|
str | Vec
|
Hex string or |
'#cccccc'
|
opacity
|
float
|
|
1.0
|
finish
|
Finish
|
Shading parameters. |
Finish()
|
sdl()
¶
Returns:
| Type | Description |
|---|---|
str
|
The |
Source code in src/quiltwright/povgen.py
241 242 243 244 245 246 247 248 | |
Union(members, texture=None)
dataclass
¶
Bases: Primitive
A POV-Ray union of other primitives.
Grouping keeps the SDL readable and lets one texture cover many members, which is how a whole tree's foliage becomes a single material.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
members
|
Sequence[Primitive]
|
Primitives to gather. |
required |
texture
|
Texture | str | None
|
Texture applied to the union as a whole, or |
None
|
sdl(handedness='flip-z')
¶
Returns:
| Type | Description |
|---|---|
str
|
|
Source code in src/quiltwright/povgen.py
703 704 705 706 707 708 709 710 711 712 | |
coalesce_mesh2(text)
¶
Merge every mesh2 in text into one with shared lists.
Written for generators that emit one mesh2 per triangle, each carrying
its own three-entry vertex, normal and texture lists. PyMOL's
cmd.get_povray() is the case in hand: a GFP cartoon arrives as 17,140
single-face meshes and 9.3 MB, and leaves as one mesh and 1.5 MB, because
51,420 vertices collapse to 8,654 and 51,420 textures to 215.
The merge is exact. Vertices, normals and textures are deduplicated on
their emitted text, so nothing is fused that was not already identical,
and normal_indices is carried through explicitly rather than being
assumed parallel to the faces -- which preserves per-face normal
assignment, so smooth shading is unchanged.
Text that is not a mesh2 is left exactly where it was; the merged mesh
replaces the first one and the rest are dropped. A block that does not
parse is left alone rather than discarded, so a scene never loses geometry
to this function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
POV-Ray source. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The same source with its meshes merged. |
Source code in src/quiltwright/povgen.py
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 | |
fov_horizontal_to_vertical(fov_h, aspect)
¶
Convert a horizontal FOV to the vertical one this package uses.
POV-Ray's own angle keyword is horizontal, so a FOV lifted from a
hand-written .pov file needs converting before it can be handed to
:class:PovCamera, whose fov is vertical.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fov_h
|
float
|
Horizontal field of view in degrees. |
required |
aspect
|
float
|
Image width divided by height. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Vertical field of view in degrees. |
Source code in src/quiltwright/povgen.py
1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 | |
fov_vertical_to_horizontal(fov_v, aspect)
¶
Convert a vertical FOV to POV-Ray's horizontal angle.
The inverse of :func:fov_horizontal_to_vertical, for writing a scene
that states its lens with the angle keyword, which is horizontal.
Not for :class:~quiltwright.povray.PovCamera, whose fov is
vertical: it emits up <0,1,0> with direction = 0.5 / tan(fov/2)
and scales right by the tile aspect, so a vertical angle is what it
wants and converting first renders the scene at the wrong lens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fov_v
|
float
|
Vertical field of view in degrees. |
required |
aspect
|
float
|
Image width divided by height. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Horizontal field of view in degrees. |
Source code in src/quiltwright/povgen.py
1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 | |
ground_slab(lo, hi, *, up=(0.0, 1.0, 0.0), size=3.0, thickness=0.4, base=None, texture=None)
¶
A finite floor under a subject, for it to cast a shadow onto.
Ray-tracing gives a contact shadow, and a contact shadow is most of what makes a subject look placed rather than floating. VTK's headlight casts nothing, so a transcoded scene that looked fine rasterised will look untethered until it has one of these.
Deliberately finite. An effectively infinite plane guarantees off-budget disparity at the horizon on a light-field panel; a slab a few subject-widths across catches the shadow and stops.
Its top face sits at the subject's base along up -- the minimum of the bounds, not below them -- so the subject stands on the floor rather than hovering over one parked underneath.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lo
|
Vec
|
Lower bound corner of the subject, right-handed. |
required |
hi
|
Vec
|
Upper bound corner of the subject, right-handed. |
required |
up
|
Vec
|
World up direction; the slab lies perpendicular to it. |
(0.0, 1.0, 0.0)
|
size
|
float
|
Slab edge as a multiple of the subject's widest horizontal extent, so one value suits subjects of any scale. |
3.0
|
thickness
|
float
|
Slab depth along up. Only its silhouette shows, but a zero-thickness box is degenerate. |
0.4
|
base
|
float | None
|
Level along up for the top face. |
None
|
texture
|
Texture | str | None
|
Texture, a declared name, or |
None
|
Returns:
| Type | Description |
|---|---|
Box
|
The slab as a :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If up is degenerate. |
Source code in src/quiltwright/povgen.py
1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 | |
instances_by_color(name, points, directions, palette, index, *, scale=None, finish=None, prefix='Tint')
¶
Group instances of one prototype into a union per colour.
A crown of ten thousand blades in five colours is five textures and five unions, not ten thousand of each. POV-Ray parses each texture once and every instance is then a single line.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Declared prototype identifier the instances reference. |
required |
points
|
ndarray
|
|
required |
directions
|
ndarray | None
|
|
required |
palette
|
Sequence[str | Vec]
|
Colours to declare, one texture each. |
required |
index
|
Sequence[int] | ndarray
|
|
required |
scale
|
Sequence[float] | float | None
|
Per-axis or scalar scale applied to the prototype. |
None
|
finish
|
Finish | None
|
Finish shared by every declared texture. |
None
|
prefix
|
str
|
Identifier stem for the declared textures. |
'Tint'
|
Returns:
| Type | Description |
|---|---|
tuple[list[tuple[str, Texture]], list[Union]]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If index does not match points in length. |
Source code in src/quiltwright/povgen.py
1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 | |
instances_from_frames(name, points, directions=None, texture=None)
¶
Instance a #declared prototype once per point, optionally oriented.
Orientation matches VTK's glyph convention: the prototype's +x axis is aligned to each direction vector. The remaining two axes are completed deterministically, so a given input always produces the same file -- but that completion is not VTK's, so glyph roll will differ from a PyVista render even though position, aim and silhouette agree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Declared prototype identifier. |
required |
points
|
ndarray
|
|
required |
directions
|
ndarray | None
|
|
None
|
texture
|
Texture | str | None
|
Texture override applied to every instance. |
None
|
Returns:
| Type | Description |
|---|---|
list[Instance]
|
One :class: |
Source code in src/quiltwright/povgen.py
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 | |
lights_from_bounds(lo, hi, *, up=(0.0, 1.0, 0.0), key_side=None, intensity=1.0, fill=True, rim=False)
¶
A serviceable two-light rig sized to a scene's bounds.
VTK's default is a headlight at the camera, which POV-Ray does not reproduce and which looks flat when ray-traced anyway. This places a key light off the upper-front-right corner at roughly twice the scene radius, plus an optional shadowless fill opposite it -- enough that a transcoded scene renders legibly before anyone tunes the lighting properly.
"Upper" means along up, which defaults to +y. That default
is right for a VTK scene and wrong for a +z-up one -- and +z-up is
what :mod:kg_utils.viz3d builds, so the mismatch is not hypothetical.
Left unchanged there, the key light lands at centre_z - 1.4·radius:
below the ground, lighting the subject from underneath. Pass
up=(0, 0, 1) and it goes overhead where it belongs.
Say which side the camera is on. Bounds cannot tell you: the derived
side is whatever falls out of up, and for a +z-up scene that is
+y -- the far side from a :func:kg_utils.viz3d.frame_tree camera,
which stands off along -y. Leave key_side unset and the rig lights
the back of the subject while the lens looks at its shadow. The scene is
perfectly lit and the picture is dark, which is a hard failure to read
backwards from an image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lo
|
Vec
|
Lower bound corner, right-handed. |
required |
hi
|
Vec
|
Upper bound corner, right-handed. |
required |
up
|
Vec
|
World up direction. Defaults to |
(0.0, 1.0, 0.0)
|
key_side
|
Vec | None
|
Direction from the subject toward the side the key should
come from -- normally the camera's own standoff direction, so the lens
sees the lit face. Only its component across up is used, so it
chooses a side without re-deciding the key's elevation. |
None
|
intensity
|
float
|
Key light brightness multiplier. |
1.0
|
fill
|
bool
|
Add the shadowless fill light. |
True
|
rim
|
bool
|
Add a dim shadowless light behind the subject, so it separates from the background instead of silhouetting into it. Worth it when the background is dark or the subject is intricate at its edges -- a canopy, a wireframe -- and wasted on a solid form against a bright ground. |
False
|
Returns:
| Type | Description |
|---|---|
list[LightSource]
|
The light sources, key first. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If up is degenerate. |
Source code in src/quiltwright/povgen.py
1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 | |
parse_color(color)
¶
Normalise a colour to an (r, g, b) triple in 0..1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color
|
str | Vec
|
|
required |
Returns:
| Type | Description |
|---|---|
tuple[float, float, float]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the string is not a valid hex colour or the sequence is not three components. |
Source code in src/quiltwright/povgen.py
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | |
pov_camera_from_frame(frame, look_at=None, up=(0.0, 0.0, 1.0), *, fov=14.0, zoom=1.0, handedness='flip-z')
¶
Convert a renderer-independent camera frame into a :class:PovCamera.
The sibling of :func:pov_camera_from_plotter, for callers that have no
plotter -- a headless box writing .pov files with no VTK installed, which
is the whole point of this module.
frame may be either three sequences (position, look_at, up) or a
single object carrying .position, .focal_point and .up, which is
what kg_utils.viz3d.frame_tree returns. It is duck-typed on purpose:
this package does not import that one, and must not.
The conversion is the entire point. :class:PovCamera holds POV-Ray
coordinates; a frame computed in the right-handed world the scene was
authored in is not one. Hand an unconverted camera to
:func:~quiltwright.povray.camera_block and the geometry sits at negative
z while the lens aims at positive z, and POV-Ray renders an immaculate
picture of empty space -- with nothing wrong in the scene file and every
assertion that compares right-handed against right-handed passing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
|
required | |
look_at
|
Sequence[float] | None
|
Focal point, when frame is a bare position. |
None
|
up
|
Sequence[float]
|
Up vector, when frame is a bare position. |
(0.0, 0.0, 1.0)
|
fov
|
float
|
Vertical field of view in degrees. |
14.0
|
zoom
|
float
|
Dolly factor toward the focal point applied after framing;
|
1.0
|
handedness
|
str
|
Coordinate conversion; must match the :class: |
'flip-z'
|
Returns:
| Type | Description |
|---|---|
|
The camera, in POV-Ray coordinates. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If zoom is not positive. |
Source code in src/quiltwright/povgen.py
1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 | |
pov_camera_from_plotter(plotter, *, fov=None, handedness='flip-z')
¶
Carry a composed PyVista plotter's viewpoint over to a POV-Ray camera.
VTK's camera.view_angle and :attr:PovCamera.fov are both vertical
field of view in degrees, so the lens transfers one-to-one and the two
renderers frame the scene identically. Both quilt paths then apply the
same dolly arithmetic, so passing the same fov to
:func:~quiltwright.lfd.render_quilt and using this camera with
:func:~quiltwright.povray.render_pov_quilt produces a matched sweep.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plotter
|
A |
required | |
fov
|
float | None
|
Vertical FOV override in degrees; |
None
|
handedness
|
str
|
Coordinate conversion; must match the
:class: |
'flip-z'
|
Returns:
| Type | Description |
|---|---|
PovCamera
|
A camera whose |
Source code in src/quiltwright/povgen.py
1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 | |
sphere_sweeps_from_paths(paths, texture=None, *, kind='linear_spline', tolerance=SWEEP_TOLERANCE, min_radius=0.0001)
¶
Turn [(points, radii), ...] paths into sweeps, skipping degenerate ones.
This is the analytic counterpart of tubing each path in PyVista. It is deliberately generic -- it knows about polylines with radii, not about trees -- so any producer of swept paths can use it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paths
|
Iterable[tuple[ndarray, ndarray]]
|
Pairs of |
required |
texture
|
Texture | str | None
|
Texture applied to every sweep. |
None
|
kind
|
str
|
Spline kind; see :class: |
'linear_spline'
|
tolerance
|
float
|
POV-Ray sweep solver tolerance. |
SWEEP_TOLERANCE
|
min_radius
|
float
|
Radii below this are raised to it. A zero radius makes POV-Ray's sweep solver produce artifacts rather than a sharp tip. |
0.0001
|
Returns:
| Type | Description |
|---|---|
list[SphereSweep]
|
One :class: |
Source code in src/quiltwright/povgen.py
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 | |
spheres_from_points(points, radius, texture=None)
¶
Turn a point cloud into one :class:Sphere each.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
ndarray
|
|
required |
radius
|
float | ndarray
|
Scalar radius, or |
required |
texture
|
Texture | str | None
|
Texture applied to every sphere. |
None
|
Returns:
| Type | Description |
|---|---|
list[Sphere]
|
One :class: |
Source code in src/quiltwright/povgen.py
872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 | |
swept_scene(sweeps, *, sweep_color='#6b4a2f', sweep_finish=None, instances=None, instance_shape=(1.0, 1.0, 1.0), instance_radius=1.0, instance_palette=(), instance_index=None, instance_finish=None, clouds=(), cloud_finish=None, up=(0.0, 0.0, 1.0), sky=None, ambient=None, lights=True, key_side=None, rim_light=False, ground=0.0, ground_base=None, ground_color='#2d4a1e', ground_finish=None, brightness=1.0, comment='')
¶
Compose a lit scene from swept paths, instanced glyphs and point clouds.
Named for its geometry rather than for any subject: it knows swept tubes, oriented instances and scattered spheres, and nothing about what they depict. A tree is one caller -- limbs are the sweeps, leaves the instances, annotation clouds the spheres -- but so is any producer with the same three shapes. It imports no domain package and its arguments are arrays and colours throughout.
What it saves a caller is not the primitives, which are already here, but the assembly: prototype declaration, colour grouping, light rig, floor, and the order those go in.
Lights are placed before the ground. The rig is sized from the scene bounds and the floor is deliberately wider than the subject, so measuring after laying it makes the "scene radius" the slab's half-diagonal -- which pushes the key light far enough out to flatten the subject and shrink its shadow to nothing. Getting that order wrong is silent; the scene is structurally perfect and looks dead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sweeps
|
Iterable[tuple[ndarray, ndarray]]
|
|
required |
sweep_color
|
str | Vec
|
Colour for every sweep. |
'#6b4a2f'
|
sweep_finish
|
Finish | None
|
Finish for the sweeps. |
None
|
instances
|
tuple[ndarray, ndarray | None] | None
|
|
None
|
instance_shape
|
Sequence[float]
|
Per-axis shape of the instanced prototype, before
instance_radius scales it. |
(1.0, 1.0, 1.0)
|
instance_radius
|
float
|
Prototype radius. |
1.0
|
instance_palette
|
Sequence[str | Vec]
|
Colours for the instances. |
()
|
instance_index
|
Sequence[int] | None
|
Per-instance index into instance_palette; |
None
|
instance_finish
|
Finish | None
|
Finish shared by the instance textures. |
None
|
clouds
|
Iterable[tuple[ndarray, float, str | Vec, float]]
|
|
()
|
cloud_finish
|
Finish | None
|
Finish for the clouds. |
None
|
up
|
Sequence[float]
|
World up direction, for the light rig and the floor. |
(0.0, 0.0, 1.0)
|
sky
|
str | Vec | None
|
Background colour, or |
None
|
ambient
|
str | Vec | None
|
Global ambient light colour, or |
None
|
ground
|
float
|
Floor edge as a multiple of the subject's width; |
0.0
|
ground_color
|
str | Vec
|
Floor colour. |
'#2d4a1e'
|
ground_finish
|
Finish | None
|
Floor finish. Remember it is multiplied by brightness: a diffuse tuned for a unit key clips at a high one. |
None
|
brightness
|
float
|
Key-light multiplier. |
1.0
|
lights
|
bool
|
Place the rig. |
True
|
key_side
|
Sequence[float] | None
|
Which side the key comes from -- pass the camera's
standoff direction, or the lens looks at the subject's shadow. See
:func: |
None
|
rim_light
|
bool
|
Add the back light; see :func: |
False
|
ground_base
|
float | None
|
Level along up for the floor's top face; see
:func: |
None
|
comment
|
str
|
Free text for the file header. |
''
|
Returns:
| Type | Description |
|---|---|
PovScene
|
The composed :class: |
Source code in src/quiltwright/povgen.py
1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 | |
to_pov(point, handedness='flip-z')
¶
Convert a right-handed world point to POV-Ray's left-handed world.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point
|
Vec
|
|
required |
handedness
|
str
|
|
'flip-z'
|
Returns:
| Type | Description |
|---|---|
tuple[float, float, float]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If handedness is not one of the two accepted values. |
Source code in src/quiltwright/povgen.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |