crystal-geometry
Generate 3D crystal geometry from CDL descriptions using half-space intersection and crystallographic symmetry operations. Version 2.0.0.
pip install gemmology-crystal-geometry Core Functions
cdl_to_geometry(description: CrystalDescription) → CrystalGeometry Convert a parsed CDL description into 3D geometry. Uses desc.flat_forms()
internally to handle FormGroup nodes.
from cdl_parser import parse_cdl
from crystal_geometry import cdl_to_geometry
desc = parse_cdl("cubic[m3m]:{111}@1.0 + {100}@1.3")
geom = cdl_to_geometry(desc)
print(f"Vertices: {len(geom.vertices)}") # 24
print(f"Faces: {len(geom.faces)}") # 14 Parameters:
description- ParsedCrystalDescriptionfrom cdl-parser
Returns: CrystalGeometry object with vertices, faces, and normals
cdl_string_to_geometry(cdl_string: str) → CrystalGeometry Convenience function that parses a CDL string and generates geometry in one call.
from crystal_geometry import cdl_string_to_geometry
geom = cdl_string_to_geometry("cubic[m3m]:{111}")
print(len(geom.vertices)) # 6
print(len(geom.faces)) # 8 halfspace_intersection_3d(normals, distances, interior_point=None) → ndarray | None Compute the vertices of the convex polyhedron defined by half-space intersection
(each half-space normal · x ≤ distance). Returns an Nx3 vertex array,
or None if the intersection is empty/unbounded. Use
compute_face_vertices() to derive the ordered face index list for
each plane from the resulting vertices.
compute_face_vertices(vertices, normal, distance, tolerance=1e-6) → list[int] Find the vertex indices lying on a single face plane, ordered counter-clockwise
when viewed from outside. Called once per face normal/distance pair to build up
a polyhedron's faces list from the vertices returned by
halfspace_intersection_3d.
Convenience Constructors
from crystal_geometry import (
create_octahedron,
create_cube,
create_dodecahedron,
create_truncated_octahedron,
)
# Create common crystal forms directly
geom = create_octahedron() # {111} in m3m
geom = create_cube() # {100} in m3m
geom = create_dodecahedron() # {110} in m3m
geom = create_truncated_octahedron() # {111} + {100} Symmetry Operations
get_point_group_operations(point_group: str) → list[np.ndarray] Get the 3x3 rotation/reflection matrices for a crystallographic point group.
from crystal_geometry import get_point_group_operations
matrices = get_point_group_operations("m3m")
print(len(matrices)) # 48 operations
matrices = get_point_group_operations("-3m")
print(len(matrices)) # 12 operations generate_equivalent_faces(miller, point_group: str) → list[ndarray] Generate all symmetry-equivalent face normals from a single Miller index.
miller_to_normal(h, k, l, lattice) → ndarray Convert Miller indices to a Cartesian normal vector using lattice parameters.
get_lattice_for_system(system: str) → LatticeParams Get default lattice parameters for a crystal system.
Modification Functions
apply_modifications(vertices, modifications) → ndarray Apply morphological modifications (elongate, flatten, taper, twist) to vertices.
Each modifier takes a params dict (matching the parsed CDL
Modification.params), not individual keyword arguments.
from crystal_geometry import apply_elongation, apply_flatten, apply_taper, apply_twist
# Elongate along c-axis
vertices = apply_elongation(vertices, {'axis': 'c', 'ratio': 1.5})
# Flatten along c-axis
vertices = apply_flatten(vertices, {'axis': 'c', 'ratio': 0.5})
# Taper toward one end
vertices = apply_taper(vertices, {'direction': '+c', 'factor': 0.3})
# Twist around an axis
vertices = apply_twist(vertices, {'axis': 'c', 'angle': 30}) Amorphous & Aggregate Geometry
generate_amorphous_shape(shape: str, radius: float = 1.0, seed: int = 42) → CrystalGeometry Generate non-crystalline geometry (e.g. massive, botryoidal,
reniform, stalactitic, mammillary, nodular,
conchoidal) for CDL amorphous[...] descriptions. The returned
CrystalGeometry has is_amorphous=True. seed makes the
procedural mesh deterministic.
generate_aggregate(base_geometry: CrystalGeometry, arrangement: str, count: int, spacing: float | None = None, orientation: str | None = None, seed: int = 42) → CrystalGeometry Instance a base geometry into a crystal aggregate (parallel, random,
radial, epitaxial, druse, cluster
arrangements), matching CDL's ~ aggregate operator. The result's
aggregate_metadata records the arrangement, instance count, spacing, and
orientation; component_ids attributes each face back to its instance.
count is capped by AGGREGATE_MAX_INSTANCES.
from crystal_geometry import generate_amorphous_shape, generate_aggregate, create_octahedron
# Botryoidal amorphous mass (e.g. precious opal)
geom = generate_amorphous_shape("botryoidal", radius=1.0)
print(geom.is_amorphous) # True
# Cluster of octahedra
base = create_octahedron()
cluster = generate_aggregate(base, arrangement="cluster", count=6)
print(cluster.aggregate_metadata.arrangement) # 'cluster'
print(cluster.aggregate_metadata.n_instances) # 6 Twin System
from crystal_geometry import (
get_twin_law,
list_twin_laws,
get_gemstone_twins,
TwinLaw,
)
# List all available twin laws (14 total)
laws = list_twin_laws()
print(laws) # ['albite', 'baveno', 'brazil', 'carlsbad', 'dauphine', ...]
# Get a specific twin law (note: 'spinel_law', not 'spinel')
law = get_twin_law("spinel_law")
print(law.name) # 'Spinel Law (Macle)'
print(law.axis) # array([...]) - twin rotation/reflection axis
print(law.angle) # 180.0
# Get all twin laws for a specific gemstone
twins = get_gemstone_twins("quartz")
print(twins) # ['brazil', 'dauphine', 'japan'] Habit System
from crystal_geometry import (
get_habit,
list_habits,
get_gemstone_habits,
CrystalHabit,
)
# List all registered habits (15 total)
habits = list_habits()
print(habits) # ['barrel', 'cube', 'dodecahedron', 'feldspar_tabular', ...]
# Get a specific habit - CrystalHabit exposes vertices/faces directly
# (there is no .generate() method)
habit = get_habit("hexagonal_prism", scale=1.0)
print(habit.name) # 'Hexagonal Prism'
print(habit.vertices.shape) # (12, 3)
print(len(habit.faces)) # 8
# Get habits for a specific gemstone
gem_habits = get_gemstone_habits("diamond")
print(gem_habits) # ['octahedron', 'dodecahedron', 'cube'] Classes
CrystalGeometry
Container for 3D crystal geometry data.
| Attribute | Type | Description |
|---|---|---|
vertices | np.ndarray | Nx3 array of vertex coordinates |
faces | list[list[int]] | List of faces (each face is a list of vertex indices) |
face_normals | list[np.ndarray] | Normal vectors for each face |
face_forms | list[int] | Form index for each face (for colour-by-form rendering) |
face_millers | list[tuple[int, int, int]] | Miller indices for each face |
forms | list[CrystalForm] | Original form definitions from CDL |
component_ids | list[int] | None | Component ID for each face, for twins/aggregates |
twin_metadata | TwinMetadata | None | Twin metadata if geometry is twinned |
is_amorphous | bool | Whether this is an amorphous (non-crystalline) geometry (default False) |
aggregate_metadata | AggregateMetadata | None | Aggregate metadata if geometry was generated via generate_aggregate() |
LatticeParams
Unit cell lattice parameters.
| Attribute | Type | Description |
|---|---|---|
a | float | Unit cell parameter a (default 1.0) |
b | float | Unit cell parameter b (default 1.0) |
c | float | Unit cell parameter c (default 1.0) |
alpha | float | Angle alpha, in radians (default π/2) |
beta | float | Angle beta, in radians (default π/2) |
gamma | float | Angle gamma, in radians (default π/2) |
Backend Acceleration
from crystal_geometry import get_backend, get_backend_info
# Check which backend is active
print(get_backend()) # 'native' or 'python'
print(get_backend_info()) # Detailed backend information