Skip to main content

Crystal Description Language (CDL) parser for crystallographic visualization

Project description

cdl-parser

PyPI version Python License: MIT

Crystal Description Language (CDL) Parser - A Python library for parsing compact string notation describing crystal morphology for gemmological and mineralogical visualization. CDL v2.0 adds support for amorphous materials, nested growth, and crystal aggregates.

Part of the Gemmology Project.

Installation

pip install cdl-parser

Quick Start

from cdl_parser import parse_cdl

# Parse a simple octahedron
desc = parse_cdl("cubic[m3m]:{111}")
print(desc.system)      # 'cubic'
print(desc.point_group) # 'm3m'

# Parse a truncated octahedron (diamond-like)
desc = parse_cdl("cubic[m3m]:{111}@1.0 + {100}@1.3")
print(len(desc.forms))  # 2

# Parse with twin specification
desc = parse_cdl("cubic[m3m]:{111} | twin(spinel)")
print(desc.twin.law)    # 'spinel'

# Parse an amorphous material (v2.0)
desc = parse_cdl("amorphous[opalescent]:{botryoidal}")
print(desc.system)      # 'amorphous'
print(desc.subtype)     # 'opalescent'

# Parse nested growth (v2.0)
desc = parse_cdl("trigonal[32]:({10-10}@1.0 + {10-11}@0.8) > ({10-10}@0.5 + {10-11}@0.4)")

# Parse an aggregate (v2.0)
desc = parse_cdl("trigonal[32]:{10-10}@1.0 + {10-11}@0.8 ~ cluster[12]")

CDL Specification

Syntax Overview

# Crystalline materials
system[point_group]:{form}@scale + {form}@scale | modification | twin(law)

# Amorphous materials (v2.0)
amorphous[subtype]:{shape1, shape2}[features] | phenomenon[type]

# Nested growth (v2.0)
system[point_group]:(forms) > (overgrowth_forms)

# Aggregates (v2.0)
system[point_group]:forms ~ arrangement[count]

Crystal Systems

All 7 crystal systems are supported with their standard point groups, plus amorphous materials:

System Default Point Group All 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, m, 2
triclinic -1 -1, 1
amorphous none (n/a)

Amorphous Materials (v2.0)

Materials without crystalline structure use the amorphous keyword instead of a crystal system:

# Syntax: amorphous[subtype]:{shape1, shape2}[features]
"amorphous[opalescent]:{botryoidal}"
"amorphous[cryptocrystalline]:{massive, nodular}[colour:blue]"
"amorphous[waxy]:{mammillary}[banding:concentric]"

Subtypes: opalescent, glassy, waxy, resinous, cryptocrystalline

Shapes: massive, botryoidal, reniform, stalactitic, mammillary, nodular, conchoidal

Nested Growth (v2.0)

The > operator describes overgrowth relationships (base > overgrowth), where an outer crystal grows on an inner one. Right-associative: a > b > c = a > (b > c).

# Scepter quartz — enlarged head on thin stem
"trigonal[32]:({10-10}@1.0 + {10-11}@0.8) > ({10-10}@0.5 + {10-11}@0.4)"

# Diamond phantom
"cubic[m3m]:{111}@1.0 > {111}@1.0"

Aggregates (v2.0)

The ~ operator describes crystal aggregates — multiple copies of a form arranged in a spatial pattern:

# Syntax: forms ~ arrangement[count]
"trigonal[32]:{10-10}@1.0 + {10-11}@0.8 ~ cluster[12]"   # Quartz cluster
"trigonal[32]:{10-10}@1.0 + {10-11}@0.8 ~ druse[50]"     # Amethyst geode
"cubic[m3m]:{100} ~ cluster[5]"                            # Pyrite cluster
"trigonal[-3m]:rhombohedron ~ parallel[3]"                  # Calcite parallel growth

Arrangements: parallel, random, radial, epitaxial, druse, cluster

Orientations (optional): aligned, random, planar, spherical

Group-Level Twins (v2.0)

Twin specifications can be applied to form groups, allowing twinning of composite morphologies:

# Twin a group of forms
"cubic[m3m]:({111}@1.0 + {100}@0.3) | twin(spinel)"

Miller Indices

Forms are specified using Miller indices in curly braces:

# 3-index notation (hkl)
"{111}"   # Octahedron face
"{100}"   # Cube face
"{110}"   # Dodecahedron face

# 4-index notation for hexagonal/trigonal (hkil where i = -(h+k))
"{10-10}" # Hexagonal prism
"{10-11}" # Rhombohedron
"{0001}"  # Basal pinacoid

Named Forms

Common forms can be referenced by name:

# Cubic named forms
"octahedron"      # → {111}
"cube"            # → {100}
"dodecahedron"    # → {110}
"trapezohedron"   # → {211}

# Hexagonal/Trigonal named forms
"prism"           # → {10-10}
"basal"           # → {0001}
"rhombohedron"    # → {10-11}

Scale Values

The @scale parameter controls the relative prominence of forms:

# Larger scale = form appears more (more truncation)
"{111}@1.0 + {100}@1.3"  # Cube truncates the octahedron
"{111}@1.0 + {100}@0.3"  # Octahedron with small cube facets

Twin Laws

Named twin laws for common crystal twins:

# Contact twins
"twin(spinel)"      # Spinel law (111) twin
"twin(brazil)"      # Brazil law quartz twin
"twin(japan)"       # Japan law quartz twin

# Penetration twins
"twin(fluorite)"    # Fluorite interpenetration twin
"twin(iron_cross)"  # Iron cross pyrite twin

# Cyclic twins
"twin(trilling,3)"  # Three-fold cyclic twin

Complete Examples

# Diamond (octahedron with cube truncation)
"cubic[m3m]:{111}@1.0 + {100}@0.3"

# Quartz prism with rhombohedron termination
"trigonal[-3m]:{10-10}@1.0 + {10-11}@0.8"

# Garnet (dodecahedron + trapezohedron)
"cubic[m3m]:{110}@1.0 + {211}@0.6"

# Spinel-law twinned octahedron
"cubic[m3m]:{111} | twin(spinel)"

# Fluorite cube
"cubic[m3m]:{100}"

# Opal — amorphous with play of color (v2.0)
"amorphous[opalescent]:{botryoidal} | phenomenon[play_of_color:intense]"

# Turquoise — cryptocrystalline massive (v2.0)
"amorphous[cryptocrystalline]:{massive, nodular}[colour:blue]"

# Scepter quartz — nested growth (v2.0)
"trigonal[32]:({10-10}@1.0 + {10-11}@0.8) > ({10-10}@0.5 + {10-11}@0.4)"

# Quartz cluster aggregate (v2.0)
"trigonal[32]:{10-10}@1.0 + {10-11}@0.8 ~ cluster[12]"

# Amethyst geode druse (v2.0)
"trigonal[32]:{10-10}@1.0 + {10-11}@0.8 ~ druse[50]"

API Reference

Core Functions

parse_cdl(text: str) -> CrystalDescription | AmorphousDescription

Parse a CDL string into a structured description. Returns CrystalDescription for crystalline materials or AmorphousDescription for amorphous materials.

from cdl_parser import parse_cdl

# Crystalline — returns CrystalDescription
desc = parse_cdl("cubic[m3m]:{111}@1.0 + {100}@1.3")

# Amorphous — returns AmorphousDescription (v2.0)
desc = parse_cdl("amorphous[opalescent]:{botryoidal}")

validate_cdl(text: str) -> tuple[bool, str | None]

Validate a CDL string without parsing.

from cdl_parser import validate_cdl

is_valid, error = validate_cdl("cubic[m3m]:{111}")
if not is_valid:
    print(f"Error: {error}")

Data Classes

CrystalDescription

Main output of CDL parsing for crystalline materials.

@dataclass
class CrystalDescription:
    system: str                          # Crystal system
    point_group: str                     # Point group symbol
    forms: list[FormNode]                # Form tree (CrystalForm | FormGroup | NestedGrowth | AggregateSpec)
    modifications: list[Modification]    # Morphological mods
    twin: TwinSpec | None                # Twin specification
    phenomenon: PhenomenonSpec | None    # Optical phenomenon
    doc_comments: list[str] | None       # Doc comments (#!)
    definitions: list[Definition] | None # Named definitions (@name = ...)

    def flat_forms(self) -> list[CrystalForm]:
        """Flatten form tree into a list of CrystalForm leaves."""

AmorphousDescription (v2.0)

Output of CDL parsing for amorphous (non-crystalline) materials.

@dataclass
class AmorphousDescription:
    subtype: str                         # 'opalescent', 'glassy', 'waxy', etc.
    shapes: list[str]                    # 'massive', 'botryoidal', etc.
    features: list[Feature] | None       # Feature annotations
    phenomenon: PhenomenonSpec | None    # Optical phenomenon
    doc_comments: list[str] | None       # Doc comments (#!)
    definitions: list[Definition] | None # Named definitions

    @property
    def system(self) -> str:             # Always returns 'amorphous'

NestedGrowth (v2.0)

Represents a base crystal with an overgrowth (the > operator).

@dataclass
class NestedGrowth:
    base: FormNode                       # Base form node
    overgrowth: FormNode                 # Overgrowth form node

AggregateSpec (v2.0)

Represents a crystal aggregate (the ~ operator).

@dataclass
class AggregateSpec:
    form: FormNode                       # Form to aggregate
    arrangement: str                     # 'parallel', 'random', 'radial', etc.
    count: int                           # Number of individuals
    spacing: str | None                  # Optional spacing value
    orientation: str | None              # Optional orientation mode
    orientation_param: float | None      # Optional orientation parameter

MillerIndex

Miller index representation.

@dataclass
class MillerIndex:
    h: int
    k: int
    l: int
    i: Optional[int] = None  # For 4-index notation

    def as_tuple(self) -> tuple[int, ...]
    def as_3index(self) -> tuple[int, int, int]

CrystalForm

A crystal form with scale.

@dataclass
class CrystalForm:
    miller: MillerIndex
    scale: float = 1.0
    name: Optional[str] = None

Constants

from cdl_parser import (
    CRYSTAL_SYSTEMS,          # Set of system names
    POINT_GROUPS,             # Dict[system, Set[groups]]
    DEFAULT_POINT_GROUPS,     # Dict[system, default_group]
    NAMED_FORMS,              # Dict[name, (h, k, l)]
    TWIN_LAWS,                # Set of twin law names
    AMORPHOUS_SUBTYPES,       # Set: opalescent, glassy, waxy, resinous, cryptocrystalline
    AMORPHOUS_SHAPES,         # Set: massive, botryoidal, reniform, stalactitic, ...
    AGGREGATE_ARRANGEMENTS,   # Set: parallel, random, radial, epitaxial, druse, cluster
    AGGREGATE_ORIENTATIONS,   # Set: aligned, random, planar, spherical
)

Exceptions

from cdl_parser import ParseError, ValidationError

try:
    desc = parse_cdl("invalid{{{")
except ParseError as e:
    print(f"Syntax error at position {e.position}: {e.message}")

CLI Usage

# Parse and display
cdl parse "cubic[m3m]:{111}@1.0 + {100}@1.3"

# Validate
cdl validate "cubic[m3m]:{111}"

# Output as JSON
cdl parse "cubic[m3m]:{111}" --json

# List available options
cdl --list-systems
cdl --list-point-groups
cdl --list-forms
cdl --list-twins

Integration with Other Packages

cdl-parser is designed to work with the Gemmology Project ecosystem:

from cdl_parser import parse_cdl
from crystal_geometry import cdl_to_geometry
from crystal_renderer import generate_cdl_svg, generate_geometry_svg

# Option 1: Direct CDL string to SVG
cdl = "cubic[m3m]:{111}@1.0 + {100}@1.3"
generate_cdl_svg(cdl, "crystal.svg")

# Option 2: Parse, then generate geometry for custom processing
desc = parse_cdl(cdl)
geometry = cdl_to_geometry(desc)

# Render geometry directly
generate_geometry_svg(geometry.vertices, geometry.faces, "geometry.svg")

Development

# Clone the repository
git clone https://github.com/gemmology-dev/cdl-parser.git
cd cdl-parser

# Install with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Type checking
mypy src/cdl_parser

# Linting
ruff check src/cdl_parser

Documentation

Full documentation is available at cdl-parser.gemmology.dev.

License

MIT License - see LICENSE for details.

Related Projects

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gemmology_cdl_parser-2.0.0.tar.gz (37.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gemmology_cdl_parser-2.0.0-py3-none-any.whl (25.9 kB view details)

Uploaded Python 3

File details

Details for the file gemmology_cdl_parser-2.0.0.tar.gz.

File metadata

  • Download URL: gemmology_cdl_parser-2.0.0.tar.gz
  • Upload date:
  • Size: 37.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for gemmology_cdl_parser-2.0.0.tar.gz
Algorithm Hash digest
SHA256 aba54fd1c11c7765104464424b5c9fb8bf35cbab642fc7cd8c47b8fbd5920cfb
MD5 4c6c964065cf5a6604e005ddc76361e7
BLAKE2b-256 24ca7b2a69445084efc7aff95f6dc4a87939713ea1db6ce94c760708cec5fceb

See more details on using hashes here.

Provenance

The following attestation bundles were made for gemmology_cdl_parser-2.0.0.tar.gz:

Publisher: pypi-publish.yml on gemmology-dev/cdl-parser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gemmology_cdl_parser-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for gemmology_cdl_parser-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 62f9aaad47d5bf8bd8f669280b6237c137808241698d66a6b5eeb5f9d0aa990f
MD5 485d1a4e5d3043682b2640dbe0f27b24
BLAKE2b-256 265f33a62fcdd0b7cfc94d7a3d9597f34ec9cad1ae254a4b4b345685467a92a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for gemmology_cdl_parser-2.0.0-py3-none-any.whl:

Publisher: pypi-publish.yml on gemmology-dev/cdl-parser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page