Crystal Description Language (CDL)
CDL is a domain-specific language for describing crystal morphology using crystallographic notation. It combines crystal systems, point groups, Miller indices, and optional modifiers to precisely define crystal forms for 3D visualization.
Current version
CDL v2.0
Includes comments (v1.1), features and phenomena (v1.2), grouping, labels, and definitions (v1.3), amorphous materials, nested growth, aggregates, and group-level twins (v2.0).
Basic Syntax
A CDL expression follows this general structure:
system[point_group]:{hkl}@scale + {hkl}@scale | modifier Components
| Component | Required | Description | Example |
|---|---|---|---|
system | Yes | Crystal system | cubic, trigonal |
[point_group] | No | Point group symmetry (defaults per system) | [m3m], [-3m] |
: | Yes | Separator between header and forms | |
{hkl} | Yes | Miller index defining a crystal form | {111}, {10-10} |
@scale | No | Distance from origin (default 1.0) | @1.0, @0.8 |
+ | No | Combine multiple forms |
When the point group is omitted, the default for the system is used (e.g., cubic defaults to m3m).
cubic:{111} cubic[m3m]:{111} cubic[m3m]:{111}@1.0 + {100}@1.3
The @scale value controls how far each form sits from the crystal centre.
Larger values push the form outward, reducing its face area relative to other forms.
Smaller values bring it closer, making faces more prominent.
Crystal Systems and Point Groups
CDL supports all seven crystal systems and all 32 crystallographic point groups:
| System | Default | Point Groups |
|---|---|---|
cubic | m3m | m3m, 432, -43m, m-3, 23 |
hexagonal | 6/mmm | 6/mmm, 622, 6mm, -6m2, 6/m, 6, -6 |
trigonal | -3m | -3m, 32, 3m, -3, 3 |
tetragonal | 4/mmm | 4/mmm, 422, 4mm, -42m, 4/m, 4, -4 |
orthorhombic | mmm | mmm, 222, mm2 |
monoclinic | 2/m | 2/m, 2, m |
triclinic | -1 | -1, 1 |
New in v2.0: Amorphous materials
CDL now supports the amorphous system for non-crystalline
materials like opal, obsidian, and turquoise. Amorphous materials use shape descriptors instead of Miller
indices. See Amorphous Materials below.
Miller Indices
Miller indices define crystal face orientations using curly braces. CDL supports several notations:
| Format | Example | Description |
|---|---|---|
| Condensed 3-index | {111} | Single-digit indices concatenated |
| Separated 3-index | {1 1 1} | Space-separated (for multi-digit indices) |
| 4-index (Miller-Bravais) | {10-10} | For hexagonal and trigonal systems |
| Negative indices | {10-11} | Minus sign before the digit |
In 4-index Miller-Bravais notation {hkil}, the third index i is
redundant (i = -(h+k)) but included for clarity in hexagonal and trigonal systems.
Named Forms
Common crystal forms can be referenced by name instead of Miller indices:
| Name | Miller Index | System |
|---|---|---|
cube | {100} | Cubic |
octahedron | {111} | Cubic |
dodecahedron | {110} | Cubic |
trapezohedron | {211} | Cubic |
prism | {100} | Hexagonal/Trigonal |
pinacoid / basal | {001} | Hexagonal/Trigonal |
rhombohedron | {101} | Trigonal |
dipyramid | {101} | Hexagonal/Trigonal |
cubic[m3m]:octahedron cubic[m3m]:octahedron@1.0 + cube@1.3 Combining Forms
Use + to combine multiple crystal forms into a single crystal:
# Diamond: octahedron with minor dodecahedron
cubic[m3m]:{111}@1.0 + {110}@0.2
# Quartz: prism with two rhombohedra
trigonal[32]:{10-10}@0.5 + {10-11}@1.2 + {01-11}@1.0
# Garnet: dodecahedron + trapezohedron
cubic[m3m]:{110}@1.0 + {211}@0.6 Twin Laws
Crystal twinning is specified with the | twin(law) syntax after
the form list. The pipe | separates the forms from the twin specification.
# Spinel law twin (diamond macle)
cubic[m3m]:{111}@1.0 + {100}@0.3 | twin(spinel_law)
# Fluorite interpenetrating twin
cubic[m3m]:{100}@1.0 | twin(fluorite)
# Quartz Japan law twin
trigonal[32]:{10-10}@0.5 + {10-11}@1.2 + {01-11}@1.0 + {0001}@2.0 | elongate(c:2.0) | twin(japan)
# Cyclic twin with count
cubic[m3m]:{111} | twin(trilling,3)
# Custom twin axis
cubic[m3m]:{111} | twin([1,1,1],180) Named Twin Laws
| Law | Description |
|---|---|
spinel / spinel_law | Contact twin on {111} (diamond, spinel) |
brazil | Optical twin in quartz (left/right hand) |
dauphine | Penetration twin in quartz (180° about c-axis) |
japan | Contact twin in quartz (~84.5°) |
carlsbad | Penetration twin in feldspar |
baveno | Contact twin in feldspar |
manebach | Contact twin in feldspar |
albite | Polysynthetic twin in plagioclase |
pericline | Polysynthetic twin in plagioclase |
fluorite | Interpenetrating cube twin |
iron_cross | Pyrite cross twin |
trilling | Cyclic triplet twin (chrysoberyl) |
staurolite_60 | 60° cross twin |
staurolite_90 | 90° cross twin |
gypsum_swallow | Swallowtail twin in gypsum |
Modifications
Morphological modifications alter the crystal shape. They follow the form list,
separated by a pipe |. Each takes the form type(param:value).
# Elongated quartz prism
trigonal[32]:{10-10}@0.5 + {10-11}@1.2 + {01-11}@1.0 | elongate(c:2.0)
# Flattened tabular crystal
cubic[m3m]:{111} | flatten(a:0.5)
# Multiple modifications separated by commas
cubic[m3m]:{111} | elongate(c:1.5), taper(top:0.3) Modification Types
| Type | Syntax | Description |
|---|---|---|
elongate | elongate(axis:ratio) | Stretch along an axis (a, b, or c) |
flatten | flatten(axis:ratio) | Compress along an axis |
truncate | truncate(form:depth) | Cut corners or edges by a form |
taper | taper(direction:factor) | Narrow in one direction |
bevel | bevel(edges:width) | Add beveled edges |
Comments (v1.1)
CDL supports three comment styles, stripped before parsing:
# Line comment (to end of line)
cubic[m3m]:{111} # Inline comment
/* Block comment
spanning multiple lines */
#! Mineral: Diamond
#! Habit: Octahedral
cubic[m3m]:{111}@1.0 | Style | Syntax | Purpose |
|---|---|---|
| Line comment | # text | Ignored during parsing |
| Block comment | /* text */ | Multi-line ignored content |
| Doc comment | #! Key: Value | Extracted as structured metadata |
Doc comments (#!) are preserved in the parsed output as structured metadata
and can carry mineral names, habits, or other descriptive information.
Features (v1.2)
Features annotate individual crystal forms with surface markings, growth patterns,
inclusions, or colour properties. Features are placed in square brackets [...]
immediately after the form's Miller index and optional scale.
# Diamond octahedron with trigons
cubic[m3m]:{111}@1.0[trigon:dense]
# Sapphire with silk inclusions
trigonal[-3m]:{10-10}@1.0[striation:horizontal] + {10-11}@0.7
# Multiple features on one form
cubic[m3m]:{111}[trigon:dense, phantom:3]
# Feature with multiple values
cubic[m3m]:{111}[phantom:3, white] Feature Types
Growth
phantom, sector, zoning, skeletal, dendritic
Surface
striation, trigon, etch_pit, growth_hillock
Inclusions
inclusion, needle, silk, fluid, bubble
Colour
colour, colour_zone, pleochroism, lamellar, banding
Phenomena (v1.2)
Optical phenomena are crystal-level annotations (not per-form).
They follow the form list (and any modifications or twin), separated by |,
using the syntax phenomenon[type:value].
# Star sapphire (6-rayed asterism)
trigonal[-3m]:{10-11}@1.0 | phenomenon[asterism:6]
# Cat's eye chatoyancy
orthorhombic[mmm]:{110}@1.0 | phenomenon[chatoyancy:sharp]
# Phenomenon with additional parameters
trigonal[-3m]:{10-11} | phenomenon[asterism:6, intensity:strong]
# Phenomenon after twin
cubic[m3m]:{111} | twin(spinel) | phenomenon[asterism:6]
# Phenomenon after modification
cubic[m3m]:{111} | elongate(c:1.5) | phenomenon[asterism:6]
# Features on forms AND phenomenon on crystal
trigonal[-3m]:{10-11}@1.0[silk:dense] | phenomenon[asterism:6] Phenomenon Types
| Type | Description | Example Gems |
|---|---|---|
asterism | Star effect (4, 6, or 12 rays) | Star sapphire, star ruby |
chatoyancy | Cat's eye band | Chrysoberyl cat's eye |
adularescence | Billowy internal glow | Moonstone |
labradorescence | Spectral colour play on cleavage planes | Labradorite |
play_of_color | Spectral flashes from diffraction | Opal |
colour_change | Hue shift under different lighting | Alexandrite |
aventurescence | Spangled glitter from inclusions | Sunstone, aventurine |
iridescence | Rainbow colours from thin-film interference | Rainbow quartz |
Grouping (v1.3)
Parenthesized groups allow shared features to be applied to multiple forms at once. Features placed after the closing parenthesis apply to all forms in the group.
# Group with shared feature
cubic[m3m]:({111}@1.0 + {100}@1.3)[phantom:3]
# Group alongside a standalone form
cubic[m3m]:({111} + {100})[phantom:3] + {110}@0.8
# Nested groups
cubic[m3m]:(({111} + {100}) + {110})
# Individual features within a group merge with group features
cubic[m3m]:({111}[trigon:dense] + {100})[phantom:3]
When flat_forms() is called on the parsed result, group features are
merged into each child form. In the last example above, the {111} form
would have both trigon:dense (its own) and phantom:3 (from the group).
Labels (v1.3)
Labels assign descriptive names to forms or groups using a label: prefix.
This is useful for documenting which form plays what role in the crystal.
# Label individual forms
cubic[m3m]:core:{111}@1.0 + rim:{100}@1.3
# Label a group
cubic[m3m]:core:({111} + {100})[phantom:3]
Named forms (like octahedron, prism) are not treated as
labels — they are resolved to their Miller indices directly. Labels must be identifiers
that are not known named forms.
Definitions (v1.3)
Named definitions let you assign a form expression to a name with @name = expr,
then reference it with $name. Definitions are resolved by text substitution
before parsing. They must appear on their own lines before the main CDL expression.
# Simple definition and reference
@oct = {111}@1.0
cubic[m3m]:$oct + {100}@1.3
# Multiple definitions
@prism = {10-10}@1.0
@rhomb = {10-11}@0.8
trigonal[-3m]:$prism + $rhomb
# Definitions can reference earlier definitions
@a = {111}@1.0
@b = {100}@1.3
@combo = $a + $b
cubic[m3m]:$combo
# Definitions work with comments and doc comments
#! Mineral: Diamond
@oct = {111}@1.0
# Main crystal form
cubic[m3m]:$oct Referencing an undefined name raises a parse error. Definitions are stored on the parsed result for inspection by tools.
Amorphous Materials (v2.0)
CDL v2.0 introduces the amorphous system for non-crystalline materials.
Amorphous expressions replace Miller indices with shape descriptors and use a subtype
instead of a point group.
amorphous[subtype]:{shape1, shape2}[features] | phenomenon[...] Subtypes
| Subtype | Description | Typical Materials |
|---|---|---|
opalescent | Hydrated silica with play of colour | Precious opal |
glassy | Volcanic glass or obsidian | Obsidian, moldavite |
waxy | Waxy lustre, microcrystalline | Turquoise, variscite |
resinous | Organic or resinous lustre | Amber |
cryptocrystalline | Microcrystalline aggregate (not truly amorphous but treated as such) | Chalcedony, agate, jasper |
Shape Descriptors
Instead of Miller indices, amorphous materials use shape descriptors inside curly braces. Multiple shapes can be listed with commas.
| Shape | Description |
|---|---|
massive | No distinct external form |
botryoidal | Grape-like rounded masses |
reniform | Kidney-shaped |
stalactitic | Elongated drip-like forms |
mammillary | Rounded breast-like protrusions |
nodular | Rounded nodules |
conchoidal | Shell-like fracture surfaces |
# Precious opal with play of colour
amorphous[opalescent]:{nodular, botryoidal} | phenomenon[play_of_color:spectral]
# Turquoise massive habit
amorphous[waxy]:{massive, nodular}[colour:blue]
# Pearl nacre (organic amorphous)
amorphous[opalescent]:{mammillary}[nacre:layered] | phenomenon[iridescence:orient]
# Obsidian with conchoidal fracture
amorphous[glassy]:{massive, conchoidal}[colour:black]
Features and phenomena work the same way as for crystalline materials.
Amorphous expressions cannot use modifiers like elongate or flatten,
and they cannot have twin laws.
Nested Growth (v2.0)
The nested growth operator > describes overgrowth relationships where one
crystal form grows over another. The operator is right-associative: a > b > c
means a > (b > c).
base_form > overgrowth_form For multi-form bases or overgrowths, use explicit parenthesized groups:
# Scepter quartz: prismatic base with overgrown cap
trigonal[32]:({10-10}@0.5 + {10-11}@1.2) > ({10-10}@0.8 + {10-11}@0.6) | elongate(c:2.0)
# Phantom diamond: core octahedron with later growth
cubic[m3m]:{111}@1.0[phantom:3] > {111}@0.6
# Multi-stage growth (right-associative)
cubic[m3m]:{111}@1.0 > {111}@0.7 > {111}@0.4
The nested growth operator sits at the same precedence level as + but binds
more tightly. To combine nested growth with other forms, use groups:
# Base with two forms, overgrown by a single form
cubic[m3m]:({111}@1.0 + {100}@1.3) > {111}@0.5
# Nested growth alongside a standalone form
cubic[m3m]:({111} > {111}@0.6) + {100}@1.3 Aggregates (v2.0)
The aggregate operator ~ describes clusters or arrangements of multiple
crystal individuals. The syntax is form ~ arrangement[count], with
optional spacing and orientation parameters.
form ~ arrangement[count]
form ~ arrangement[count]@spacing
form ~ arrangement[count]@spacing[orientation] Arrangements
| Arrangement | Description | Example Use |
|---|---|---|
parallel | Crystals aligned in parallel | Parallel-growth tourmaline |
random | Randomly oriented cluster | General crystal clusters |
radial | Radiating from a centre | Wavellite, pyrite suns |
epitaxial | Oriented overgrowth on substrate | Rutile on hematite |
druse | Coating of small crystals on surface | Druzy quartz, geode linings |
cluster | General multi-crystal grouping | Quartz clusters, fluorite groups |
# Quartz cluster with 12 individuals
trigonal[32]:({10-10}@0.5 + {10-11}@1.2 | elongate(c:2.0)) ~ cluster[12]
# Druzy amethyst geode lining
trigonal[32]:({10-10}@0.3 + {10-11}@1.0) ~ druse[50]@0.2
# Fluorite cluster with 6 cubes
cubic[m3m]:{100}@1.0 ~ cluster[6]
# Radial arrangement of tourmaline needles
trigonal[3m]:({10-10}@1.0 + {10-11}@0.8 | elongate(c:3.0)) ~ radial[8]
# Epitaxial rutile on hematite
tetragonal[4/mmm]:({110}@1.0 + {101}@0.9 | elongate(c:2.5)) ~ epitaxial[3][aligned] Group-Level Twins (v2.0)
In v1.3, twin laws could only be applied globally to the entire crystal. CDL v2.0 adds support for group-level twins, allowing a twin law to be applied to a specific parenthesized group of forms while leaving other groups untwinned.
(form_group) | twin(law) # Twin applied to one group only
cubic[m3m]:(({111}@1.0 + {100}@0.3) | twin(spinel)) + {110}@0.8
# Different twins on different groups
cubic[m3m]:(({111}) | twin(spinel)) + (({100}) | twin(fluorite))
# Group-level twin with global modification
cubic[m3m]:(({111}@1.0 + {100}@0.3) | twin(spinel)) | flatten(c:0.5) Group-level twins are separate from the global twin specification. A crystal can have both group-level and global twins, though this is uncommon. The group-level twin only affects the forms within its parenthesized group.
Full Grammar
The complete CDL grammar, expressed as a production rule summary:
cdl = definitions? (crystal_expr | amorphous_expr)
definitions = ('@' name '=' form_list NEWLINE)*
# Crystalline materials
crystal_expr = system ['[' point_group ']'] ':' form_list modifiers?
# Amorphous materials (v2.0)
amorphous_expr = 'amorphous' '[' subtype ']' ':' '{' shape (',' shape)* '}' features? ('|' phenomenon)?
form_list = form_or_group (('+' | '>') form_or_group)*
form_or_group = label? '(' form_list ')' features? ('|' twin)? # group (twin is v2.0)
| label? form ('~' aggregate)? # single form (aggregate is v2.0)
form = (named_form | '{' miller '}') ['@' scale] features?
features = '[' feature (',' feature)* ']'
feature = name ':' value (',' value)*
aggregate = arrangement '[' count ']' ['@' spacing] ['[' orientation ']']
modifiers = ('|' modification_list)? ('|' twin)? ('|' phenomenon)?
modification_list = modification (',' modification)*
modification = type '(' param ':' value ')'
twin = 'twin' '(' (law [',' count] | '[' axis ']' ',' angle) ')'
phenomenon = 'phenomenon' '[' type [':' value] (',' param ':' value)* ']'
label = IDENTIFIER ':'
miller = INTEGER{3..4}
scale = NUMBER
shape = 'massive' | 'botryoidal' | 'reniform' | 'stalactitic'
| 'mammillary' | 'nodular' | 'conchoidal'
subtype = 'opalescent' | 'glassy' | 'waxy' | 'resinous' | 'cryptocrystalline'
arrangement = 'parallel' | 'random' | 'radial' | 'epitaxial' | 'druse' | 'cluster'
name, type = IDENTIFIER
value = NUMBER | IDENTIFIER Processing Order
CDL text is processed through these stages:
- Doc comment extraction —
#!lines are captured as metadata - Comment stripping —
#and/* */are removed - Definition pre-processing —
@name = ...lines are extracted and$namereferences are resolved by text substitution - Lexing — remaining text is tokenized
- Parsing — tokens are parsed into the AST: system, point group, form tree, modifications, twin, phenomenon
- Validation — point group checked against system