Yehor's picture
Deploy c9d13553a120bbf9be364e305e4bb02446659b0b from egorsmkv/parametric-2d-chess
c2a88f5 verified
Raw
History Blame Contribute Delete
10.4 kB
"""File export: SVG and DXF for 2D geometry, STEP and STL for optional solids.
Every function is pure output: it takes geometry plus a path and writes a file.
No geometry is generated here beyond grouping already-built shapes.
3MF lives next door in :mod:`chess2d.bambu`, which also lays the pieces out on a
build plate; :func:`generate_all` can include one via ``with_3mf``.
"""
from __future__ import annotations
from pathlib import Path
from build123d import (
Color,
Compound,
ExportDXF,
ExportSVG,
Pos,
Sketch,
export_step,
export_stl,
extrude,
)
from .assembly import ChessComposition, make_initial_position
from .board import BoardGeometry, make_board
from .parameters import (
BLACK_FILL_COLOR,
DARK_SQUARE_COLOR,
LIGHT_SQUARE_COLOR,
SQUARE_SIZE,
WHITE_FILL_COLOR,
ChessStyle,
FigureMode,
PieceStyle,
PieceType,
)
from .pieces import make_piece, make_piece_solid
def _color(rgb: tuple[float, float, float]) -> Color:
return Color(*rgb)
def _ensure_parent(path: str | Path) -> Path:
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
return p
# --------------------------------------------------------------------------
# SVG
# --------------------------------------------------------------------------
def export_piece_svg(
piece_type: PieceType,
path: str | Path,
scale: float = 1.0,
fill: tuple[float, float, float] = WHITE_FILL_COLOR,
mode: FigureMode = FigureMode.TWO_SIDED,
square_size: float = SQUARE_SIZE,
style: PieceStyle = PieceStyle.STAUNTON,
) -> Path:
"""Write a single filled piece silhouette as an SVG."""
out = _ensure_parent(path)
sketch = make_piece(piece_type, scale=scale, mode=mode, square_size=square_size, style=style)
exporter = ExportSVG(fit_to_stroke=True)
exporter.add_layer("fill", fill_color=_color(fill), line_color=Color(0.15, 0.15, 0.15))
exporter.add_shape(sketch, layer="fill")
exporter.write(str(out))
return out
def export_board_svg(board: BoardGeometry, path: str | Path) -> Path:
"""Write the board (light + dark squares, border) as a layered SVG."""
out = _ensure_parent(path)
exporter = ExportSVG(fit_to_stroke=True)
exporter.add_layer(
"light", fill_color=_color(LIGHT_SQUARE_COLOR), line_color=_color(DARK_SQUARE_COLOR),
)
exporter.add_layer(
"dark", fill_color=_color(DARK_SQUARE_COLOR), line_color=_color(DARK_SQUARE_COLOR),
)
exporter.add_shape(board.light_squares, layer="light")
exporter.add_shape(board.dark_squares, layer="dark")
if board.border is not None:
exporter.add_layer("border", fill_color=None, line_color=Color(0.2, 0.2, 0.2))
exporter.add_shape(board.border, layer="border")
exporter.write(str(out))
return out
def export_composition_svg(composition: ChessComposition, path: str | Path) -> Path:
"""Write the full board + both piece layers as a single layered SVG."""
out = _ensure_parent(path)
exporter = ExportSVG(fit_to_stroke=True)
exporter.add_layer(
"light", fill_color=_color(LIGHT_SQUARE_COLOR), line_color=_color(DARK_SQUARE_COLOR),
)
exporter.add_layer(
"dark", fill_color=_color(DARK_SQUARE_COLOR), line_color=_color(DARK_SQUARE_COLOR),
)
exporter.add_layer(
"white_pieces", fill_color=_color(WHITE_FILL_COLOR), line_color=Color(0.2, 0.2, 0.2),
)
exporter.add_layer(
"black_pieces", fill_color=_color(BLACK_FILL_COLOR), line_color=Color(0.1, 0.1, 0.1),
)
exporter.add_shape(composition.board.light_squares, layer="light")
exporter.add_shape(composition.board.dark_squares, layer="dark")
exporter.add_shape(composition.white_pieces, layer="white_pieces")
exporter.add_shape(composition.black_pieces, layer="black_pieces")
exporter.write(str(out))
return out
# --------------------------------------------------------------------------
# DXF
# --------------------------------------------------------------------------
def export_dxf(shapes: dict[str, Sketch], path: str | Path) -> Path:
"""Write a set of named shape layers to a single DXF."""
out = _ensure_parent(path)
exporter = ExportDXF()
for name, shape in shapes.items():
exporter.add_layer(name)
exporter.add_shape(shape, layer=name)
exporter.write(str(out))
return out
# --------------------------------------------------------------------------
# STEP / STL (optional solids)
# --------------------------------------------------------------------------
def export_piece_step(
piece_type: PieceType,
path: str | Path,
thickness: float,
scale: float = 1.0,
mode: FigureMode = FigureMode.TWO_SIDED,
square_size: float = SQUARE_SIZE,
style: PieceStyle = PieceStyle.STAUNTON,
) -> Path:
out = _ensure_parent(path)
solid = make_piece_solid(
piece_type,
thickness=thickness,
scale=scale,
mode=mode,
square_size=square_size,
style=style,
)
export_step(solid, str(out))
return out
def export_piece_stl(
piece_type: PieceType,
path: str | Path,
thickness: float,
scale: float = 1.0,
mode: FigureMode = FigureMode.TWO_SIDED,
square_size: float = SQUARE_SIZE,
style: PieceStyle = PieceStyle.STAUNTON,
) -> Path:
out = _ensure_parent(path)
solid = make_piece_solid(
piece_type,
thickness=thickness,
scale=scale,
mode=mode,
square_size=square_size,
style=style,
)
export_stl(solid, str(out))
return out
def export_flat_pieces_step(
path: str | Path,
thickness: float,
scale: float = 1.0,
mode: FigureMode = FigureMode.TWO_SIDED,
square_size: float = SQUARE_SIZE,
style: PieceStyle = PieceStyle.STAUNTON,
) -> Path:
"""All six flat-piece solids laid out in a row, as one STEP file."""
out = _ensure_parent(path)
solids = []
for i, pt in enumerate(PieceType):
solid = make_piece_solid(
pt,
thickness=thickness,
scale=scale,
mode=mode,
square_size=square_size,
style=style,
)
solids.append(Pos((i - 2.5) * square_size * 0.95, 0, 0) * solid)
compound = Compound(children=solids)
export_step(compound, str(out))
return out
# --------------------------------------------------------------------------
# Batch driver
# --------------------------------------------------------------------------
def generate_all(
output_dir: str | Path = "output",
style: ChessStyle | None = None,
with_solids: bool = True,
with_3mf: bool = False,
) -> list[Path]:
"""Regenerate every deliverable under ``output_dir``. Returns written paths.
``with_3mf`` adds a meshed 3MF of the six pieces, ready to open in Bambu
Studio (or any other slicer) -- see :mod:`chess2d.bambu` for print plates
holding a whole set.
"""
if style is None:
style = ChessStyle()
root = Path(output_dir)
written: list[Path] = []
board = make_board(square_size=style.square_size, border_width=style.border_width)
composition = make_initial_position(style)
# --- SVG ---
svg_dir = root / "svg"
written.append(export_board_svg(board, svg_dir / "board.svg"))
written.extend(
export_piece_svg(
pt,
svg_dir / f"{pt.value}.svg",
style.piece_scale,
mode=style.figure_mode,
square_size=style.square_size,
style=style.piece_style,
)
for pt in PieceType
)
written.append(export_composition_svg(composition, svg_dir / "initial_position.svg"))
# --- DXF ---
dxf_dir = root / "dxf"
written.append(
export_dxf({"board": board.light_squares + board.dark_squares}, dxf_dir / "board.dxf"),
)
# Space the pieces out so they do not overlap in the DXF.
spaced = {
pt.value: Pos((i - 2.5) * style.square_size * 0.95, 0)
* make_piece(
pt,
scale=style.piece_scale,
mode=style.figure_mode,
square_size=style.square_size,
style=style.piece_style,
)
for i, pt in enumerate(PieceType)
}
written.append(export_dxf(spaced, dxf_dir / "pieces.dxf"))
written.append(
export_dxf(
{
"light": composition.board.light_squares,
"dark": composition.board.dark_squares,
"white_pieces": composition.white_pieces,
"black_pieces": composition.black_pieces,
},
dxf_dir / "initial_position.dxf",
),
)
if with_solids:
step_dir = root / "step"
stl_dir = root / "stl"
board_solid_path = step_dir / "board.step"
_ensure_parent(board_solid_path)
export_step(
extrude(board.light_squares + board.dark_squares, amount=style.board_thickness),
str(board_solid_path),
)
written.append(board_solid_path)
written.append(
export_flat_pieces_step(
step_dir / "flat_pieces.step",
style.piece_thickness,
style.piece_scale,
mode=style.figure_mode,
square_size=style.square_size,
style=style.piece_style,
),
)
written.extend(
export_piece_stl(
pt,
stl_dir / f"{pt.value}.stl",
style.piece_thickness,
style.piece_scale,
mode=style.figure_mode,
square_size=style.square_size,
style=style.piece_style,
)
for pt in PieceType
)
if with_3mf:
# Imported here: the 3MF writer is only needed on this branch, and the
# module reaches back into this one's siblings for the plate layout.
from .bambu import export_pieces_3mf # noqa: PLC0415
written.append(
export_pieces_3mf(
root / "3mf" / "pieces.3mf",
style.piece_thickness,
style.piece_scale,
mode=style.figure_mode,
square_size=style.square_size,
style=style.piece_style,
),
)
return written