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 v1.3
Includes comments (v1.1), features and phenomena (v1.2), grouping, labels, and definitions (v1.3).
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 |
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.
Full Grammar
The complete CDL grammar, expressed as a production rule summary:
cdl = definitions? system ['[' point_group ']'] ':' form_list modifiers?
definitions = ('@' name '=' form_list NEWLINE)*
form_list = form_or_group ('+' form_or_group)*
form_or_group = label? '(' form_list ')' features? # group
| label? form # single form
form = (named_form | '{' miller '}') ['@' scale] features?
features = '[' feature (',' feature)* ']'
feature = name ':' value (',' value)*
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
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