TCSL — типобезопасный DSL, где 732 mm + 45 deg — ошибка, а не 777. LLM генерирует → парсер проверяет за 50 мс → FreeCAD строит
Каждое решение в грамматике восходит к этим принципам. Полный манифест — 10 принципов, 5 обязательств, границы и эволюция
100 — не длина. 100 mm — длина. Система замкнута на L⁰–L³. Сложить мм с градусами — ошибка R001 до рендера.
Нет продолжения строк. Pipeline |> — на той же строке. Парсер синхронизируется по границе строки. Ошибка — всегда одна строка.
32 кода диагностики. E015 — не «type error», а «размерность за L³». Каждый код — адрес в спецификации с примером исправления.
4 зоны = 4 фазы генерации LLM. Нет функций, циклов, условий — не недостаток, а требование. Пространство ошибок сужено до минимума.
62 правила, 23 функции, 5 перечислений. Достаточно для мебели любой сложности. Добавление — 3 фильтра: необходимость, генерируемость, диагностируемость.
Тумба 732×550×900 мм, 4 ящика. Прокрутите — это реальный объём кода
import FreeCAD
import Part
doc = FreeCAD.newDocument("Cabinet_4Drawers")
# ═══════════════════════════════════════════
# ПАРАМЕТРЫ — все числа без единиц!
# Если кто-то передаст дюймы — удачи.
# ═══════════════════════════════════════════
total_width = 732 # мм? дюймы? кто знает
total_depth = 550
side_height = 900
board = 16
back_board = 3
guide_w = 13
niche_h = 150
num_drawers = 4
gap = 3
face_gap = 2
fasad_board = 19
# ═══════════════════════════════════════════
# ПРОИЗВОДНЫЕ — вручную, формулы дублируются
# ═══════════════════════════════════════════
inner_width = total_width - 2 * board
inner_depth = total_depth - back_board
drawer_width = inner_width - 2 * guide_w
usable_height = side_height - 2 * board - niche_h
drawer_step = usable_height / num_drawers
drawer_height = drawer_step - gap
face_width = drawer_width - 2 * face_gap
face_height = drawer_height - face_gap
drawer_box_depth = inner_depth - 30
drawer_box_h = drawer_height - 20
drawer_box_side = 12
drawer_box_back = 12
drawer_box_bot = 4
z_bot = 0
z_bot_top = z_bot + board
z_niche_top = z_bot_top + niche_h
z_d1 = z_niche_top + gap
z_d2 = z_d1 + drawer_step
z_d3 = z_d2 + drawer_step
z_d4 = z_d3 + drawer_step
z_top_bot = side_height - board
# ═══════════════════════════════════════════
# КОРПУС — 5 деталей
# ═══════════════════════════════════════════
# --- Левая боковина ---
left_side = doc.addObject("Part::Box", "LeftSide")
left_side.Length = board
left_side.Width = total_depth
left_side.Height = side_height
left_side.Placement = FreeCAD.Placement(
FreeCAD.Vector(0, 0, 0),
FreeCAD.Rotation(0, 0, 0, 1))
left_side.Label = "Bok_LDSP16mm_1"
# --- Правая боковина ---
right_side = doc.addObject("Part::Box", "RightSide")
right_side.Length = board
right_side.Width = total_depth
right_side.Height = side_height
right_side.Placement = FreeCAD.Placement(
FreeCAD.Vector(total_width - board, 0, 0),
FreeCAD.Rotation(0, 0, 0, 1))
right_side.Label = "Bok_LDSP16mm_2"
# --- Дно ---
bottom = doc.addObject("Part::Box", "Bottom")
bottom.Length = inner_width
bottom.Width = total_depth
bottom.Height = board
bottom.Placement = FreeCAD.Placement(
FreeCAD.Vector(board, 0, z_bot),
FreeCAD.Rotation(0, 0, 0, 1))
bottom.Label = "Dno_LDSP16mm_1"
# --- Крышка ---
top_panel = doc.addObject("Part::Box", "TopPanel")
top_panel.Length = inner_width
top_panel.Width = total_depth
top_panel.Height = board
top_panel.Placement = FreeCAD.Placement(
FreeCAD.Vector(board, 0, z_top_bot),
FreeCAD.Rotation(0, 0, 0, 1))
top_panel.Label = "Krysha_LDSP16mm_1"
# --- Задняя стенка ---
back_panel = doc.addObject("Part::Box", "BackPanel")
back_panel.Length = inner_width
back_panel.Width = back_board
back_panel.Height = side_height - 2 * board
back_panel.Placement = FreeCAD.Placement(
FreeCAD.Vector(board, total_depth - back_board, z_bot_top),
FreeCAD.Rotation(0, 0, 0, 1))
back_panel.Label = "Zadstena_DVP3mm_1"
# ═══════════════════════════════════════════
# ЯЩИКИ 1–4 — копипаст ×4
# (сокращено для читаемости)
# ═══════════════════════════════════════════
# ...ещё ~160 строк копипаста ящиков...
doc.recompute()
doc.saveAs("/tmp/cabinet_4drawers.FCStd")
Та же тумба, те же 9 деталей. Окно в 3× меньше — и код всё равно помещается
# ═══════════════════════════════════
# ЗОНА 1: INPUT — базовые параметры
# ═══════════════════════════════════
input total_width = 732 mm
input total_depth = 550 mm
input side_height = 900 mm
input board = 16 mm
input back_board = 3 mm
input guide_w = 13 mm
input niche_h = 150 mm
input num_drawers = 4
input gap = 3 mm
input face_gap = 2 mm
input fasad_board = 19 mm
# ═══════════════════════════════════
# ЗОНА 2: LET — производные
# ═══════════════════════════════════
let inner_width = total_width - 2 * board
let drawer_width = inner_width - 2 * guide_w
let usable_h = side_height - 2 * board - niche_h
let drawer_step = usable_h / num_drawers
let face_width = drawer_width - 2 * face_gap
let face_height = drawer_step - gap - face_gap
let z_d1 = board + niche_h + gap
let z_d2 = z_d1 + drawer_step
let z_d3 = z_d2 + drawer_step
let z_d4 = z_d3 + drawer_step
# ═══════════════════════════════════
# ЗОНА 3: GEOMETRY
# ═══════════════════════════════════
left_side = box(board, total_depth, side_height)
right_side = box(board, total_depth, side_height) |> translate(total_width - board, 0 mm, 0 mm)
bottom = box(inner_width, total_depth, board) |> translate(board, 0 mm, 0 mm)
top_panel = box(inner_width, total_depth, board) |> translate(board, 0 mm, side_height - board)
back = box(inner_width, back_board, usable_h) |> translate(board, total_depth - back_board, board)
front_1 = box(face_width, fasad_board, face_height) |> translate(board+guide_w+face_gap, 0mm-fasad_board, z_d1+face_gap)
front_2 = box(face_width, fasad_board, face_height) |> translate(board+guide_w+face_gap, 0mm-fasad_board, z_d2+face_gap)
front_3 = box(face_width, fasad_board, face_height) |> translate(board+guide_w+face_gap, 0mm-fasad_board, z_d3+face_gap)
front_4 = box(face_width, fasad_board, face_height) |> translate(board+guide_w+face_gap, 0mm-fasad_board, z_d4+face_gap)
# ═══════════════════════════════════
# ЗОНА 4: EXPORT
# ═══════════════════════════════════
export left_side as Bok_LDSP16mm_1
export right_side as Bok_LDSP16mm_2
export bottom as Dno_LDSP16mm_1
export top_panel as Krysha_LDSP16mm_1
export back as Zadstena_DVP3mm_1
export front_1 as Fasad_MDF19mm_1
export front_2 as Fasad_MDF19mm_2
export front_3 as Fasad_MDF19mm_3
export front_4 as Fasad_MDF19mm_4
Что меняет заказчик → что вычисляет инженер → что строит станок → что считает калькулятор. Принцип №2 манифеста
Заказчик
Литералы с единицами. Переопределяемы из GUI или API. Контракт с внешним миром
input width = 732 mm
input board = 16 mm
Инженер
Формулы. Автопересчёт при изменении input. Алгебра L⁰–L³ проверяется статически
let inner = width - 2 * board
let area = inner * depth
Станок
23 функции. Pipeline |> — цепочка трансформаций
side = box(board, depth, h)
|> translate(0mm,0mm,0mm)
Калькулятор
Имена для FreeCAD и себестоимости. Материал, толщина, номер — автоматически
export side as Bok_LDSP16mm_1
Почему нет if/for/функций? Это не ограничение — это архитектурное решение
Спецификация, EBNF-грамматика, VS Code, парсер Python. Open source
Вопросы: texttocad@ya.ru
CC BY-SA 4.0 · TCSL v1.5 · 2026-03-18