Перейти к содержанию

Контекст AnimaGeo для генерации стилей с помощью AI

Используйте этот документ как стабильный контекст для языковой модели, которая должна создавать JSON-файлы стилей AnimaGeo по запросу на естественном языке. Он написан для моделей, не знакомых с кодовой базой. Машиночитаемая схема лежит рядом: docs/ai_style_json_schema.json.

Задача для модели

Дано:

  1. Запрос на стиль на естественном языке.
  2. Этот файл контекста.
  3. При желании — компактная сводка конструкции.

Вернуть:

  1. Корректный объект JSON-стиля AnimaGeo.
  2. При необходимости — код на Python DSL AnimaGeo, но только когда запрос требует явной стилизации именованных элементов или процедурной логики, которой не место в общем файле стиля. Этот код должен быть самостоятельным файлом .py на DSL, загружаемым через scene.loadCode(path) после scene.loadGGB(...); это не должен быть код внутри наследника AnimaGeoScene, и он не должен вызывать scene.element(...).
  3. Короткие заметки о принятых допущениях, о том, что осталось за рамками запроса, и о том, как применять стиль.

Когда пользователь просит «только JSON», верните только объект JSON-стиля без Markdown. В остальных случаях используйте такую форму ответа:

STYLE_JSON:
```json
{ ... }
```

PYTHON_DSL:
```python
# опустите эту секцию, если она не нужна; если она есть — это ровно
# содержимое файла .py, передаваемого в scene.loadCode(...), а не управление сценой
...
```

NOTES:
- ...

Не выдумывайте новые ключи стиля. Используйте только канонические ключи из этого документа и JSON Schema. Не вставляйте комментарии внутрь JSON.

Контракт секции NOTES

NOTES — это не пересказ каждого ключа стиля. В них фиксируются решения, допущения, риски и ограничения, которые могут понадобиться вызывающей стороне для последующих правок. Держите каждую заметку короткой и конкретной. Где уместно, указывайте имена элементов, вычисленные значения и выбранный слой.

Добавляйте NOTES всякий раз, когда происходит что-то из перечисленного:

  • Запрос неоднозначен. Укажите принятую трактовку — например, какая именно окружность, треугольник, угол или группа подписей имелась в виду.
  • Цель выбрана из construction_summary. Скажите, что выбрано и на каком основании, например «выбран отрезок a как самый длинный видимый отрезок, длина 5.98».
  • Элемент конструкции может визуально накладываться на другой. В частности, если выделяемый отрезок одновременно является стороной многоугольника, упомяните, что контур многоугольника дублирует отрезок и что порядок слоёв имеет значение.
  • Возвращается код Python DSL. Объясните, почему одного JSON-стиля не хватило, например: «маркеров углов треугольника не существовало, поэтому DSL создаёт их через Angle(...)».
  • Python DSL сознательно не использован для вычислимого запроса, потому что предоставленной сводки хватило. Объясните, что результат сведён к именованным элементам и реализован через overlay.per_name.
  • Пользователь просит что-то за пределами генерации стиля. Скажите, что управление сценой, анимация, рендер и код экспорта не генерируются; можно вернуть только JSON-стиль и, при необходимости, DSL, совместимый с loadCode.
  • Формулировки на естественном языке требуют численной или цветовой трактовки: «слегка», «тонкий», «крупный», «школьный стиль», «акцент», «светлая палитра» и подобные. Укажите выбранные значения или палитру.
  • Применяются относительные изменения. Укажите исходное и целевое значения, например font_size.main: 8.67 -> 5.78.
  • Стилизация GeoGebra сохраняется, игнорируется или выборочно перебивается. Скажите, какие свойства переопределены, а какие остались из слоя импорта.
  • Меняется видимость. Назовите точные затронутые имена или типы — особенно когда скрываются подписи или объекты.
  • Включаются или настраиваются блоки автоматики, такие как overlay.angle_radius или overlay.label_placement.
  • Есть несколько допустимых стратегий, и выбор влияет на дальнейшие правки. Назовите выбранную стратегию и осмысленную альтернативу.
  • Требуемый выбор невозможно сделать надёжно, потому что construction_summary не предоставлена или потому что цель отсутствует, скрыта либо описана недостаточно.

Не используйте NOTES, чтобы замаскировать некорректный результат. Если запрошенный эффект нельзя выразить ни JSON-стилем, ни DSL, совместимым с loadCode, — скажите об этом в NOTES и оставьте неподдерживаемую часть неприменённой.

Основная модель

AnimaGeo сначала загружает пакетный builtin.json, а затем накладывает пользовательский JSON-стиль глубоким слиянием. Поэтому сгенерированный JSON-стиль может быть коротким. В нём должны быть значения, важные для запроса, а не копия всех встроенных умолчаний.

Действующий стиль отрисованного элемента определяется по приоритету:

elem.style
→ overlay.per_name
→ overlay.per_type
→ elem.ggb_style
→ defaults.<type>
→ внутренний стиль геометрии
→ запасное значение рендерера

Что это значит:

  • elem.style — явный код на Python или DSL, он всегда важнее остальных.
  • overlay.per_name нацелен на один именованный элемент и важнее правил по типу.
  • overlay.per_type нацелен на все элементы типа и важнее визуального импорта из GeoGebra.
  • elem.ggb_style — нормализованный визуальный стиль, импортированный из GeoGebra.
  • Если import.enabled равно false, resolver полностью пропускает elem.ggb_style. Геометрия, имена, зависимости и elem.ggb_raw остаются доступными.
  • defaults.<type> — базовые значения, которые используются, когда ключ не задан ни на одном вышестоящем слое.
  • Внутренний стиль геометрии — низкоуровневый стиль, уже сохранённый на элементе помощниками геометрии или парсера, но не помеченный как явная запись пользователя.
  • visible — особый случай: сначала побеждает явный стиль из DSL или API, затем видимость времени выполнения из Show/Hide/addAllGeometry (она важнее слоёв стиля), а импортированная видимость конструкции живёт отдельно от elem.ggb_style.

Короткое правило:

presets = переиспользуемые токены
defaults = базовые значения по типам элементов
import = как читать и адаптировать визуальные значения GeoGebra
overlay = стилизация проекта после импорта
reference = метаданные эталонного холста
rendering = флаги сцены, экспорта и рендера, а не стилизация объектов
elem.style / Python DSL = явные локальные правки

Секции верхнего уровня JSON-стиля

Разрешённые ключи верхнего уровня:

  • name: необязательная строка.
  • version: необязательное число или строка.
  • presets: переиспользуемые семантические константы.
  • defaults: базовый стиль по типам.
  • reference: необязательные метаданные эталонного холста.
  • overlay: переопределения поверх импорта и автоматика.
  • rendering: флаги рендера уровня сцены.
  • import: правила импорта и адаптации визуального стиля GeoGebra.

Избегайте произвольных полей с метаданными. При загрузке во время выполнения неизвестные ключи верхнего уровня вызывают предупреждение и игнорируются, но соответствующая JSON Schema их отвергает.

Единицы

Все визуальные значения, оканчивающиеся на _px, заданы в пиксельных единицах стиля. Сюда входят:

  • диаметр точки: size_px
  • толщина линии и дуги: stroke_width_px
  • радиус дуги угла: arc_size_px
  • сдвиг между несколькими дугами угла: arc_shift_px
  • размер маркера прямого угла: right_angle_size_px
  • размеры засечек: tick_length_px, tick_width_px, tick_shift_px, tick_radius_px
  • размеры наконечника стрелки: arrow_length_px, arrow_width_px
  • смещения подписей: label_offset_px, label_radial_offset_px
  • размер шрифта подписи: font_size_px

Значения непрозрачности — числа от 0 до 1. Цвета — строки hex вида #rrggbb. Ссылки на пресеты — строки вроде color.main.

Если пользователь просит сделать размер точки ровно вдвое больше толщины линии, помните, что size_px — это диаметр точки. Задайте point_size.main = 2 * line_width.main, если только из запроса явно не следует другой локальный смысл.

Имена типов элементов

Используйте эти имена типов в defaults и overlay.per_type:

  • point
  • segment
  • line
  • ray
  • vector
  • angle
  • polygon
  • circle
  • arc
  • circlesector
  • conic
  • function
  • implicitcurve

decoration встречается во встроенных стилях как общий помощник для пресетов засечек, но отрисовываемым типом элемента не является.

Канонические ключи стиля

Видимость и подписи (для всех типов)

  • visible: bool; отрисовывается ли объект. Изменения видимости во время выполнения могут перебить слои стиля.
  • label_visible: bool.
  • label_text: строка TeX, часто "$A$" или "$\\alpha$".
  • label_mode: label, value или label_value; определяет, что именно отрисовывается — подпись, вычисленное значение или label = value.
  • label_value_precision: целое число знаков после запятой для вычисленных значений.
  • label_value_strip_zeros: bool.
  • label_angle_unit: degree или radian.
  • label_value_separator: строка, обычно " = ".
  • label_color: hex или ссылка на пресет.
  • label_anchor: одно из TL, TC, TR, ML, MC, MR, BL, BC, BR. Подпись из GeoGebra без явного label_anchor сохраняет размещение GeoGebra для типа своего элемента; заданный label_anchor переключает её на общее размещение.
  • label_offset_px: [dx, dy].
  • label_radial_offset_px: радиальное смещение для подписей углов.
  • label_placement_locked: bool; запрещает автоматическому размещению двигать подпись.
  • label_hint_px: [dx, dy], ось y вверх; где должен быть центр подписи относительно опорной точки элемента (точка, середина отрезка, вершина угла). Мягкая просьба: авторазмещение сохраняет подсказанную сторону и по-прежнему обходит наложения. Удобнее, чем label_offset_px + label_placement_locked, когда известна только сторона.
  • font_size_px: размер шрифта подписи.

Обводка

  • stroke: hex или ссылка на пресет.
  • stroke_width_px: толщина линии, кривой, дуги.
  • stroke_opacity: 0..1.
  • stroke_dash_ratio: null или число от 0 до 1; не-null означает пунктир (доля штриха в периоде).
  • stroke_dash_period_px: штрих + промежуток в px (по умолчанию rendering.dash_period_px, 10). 0.65 при 10 даёт штрих 6.5 px и промежуток 3.5 px.
  • stroke_linecap: butt, round или square.
  • right_angle_joint: auto, bevel, miter или round.

Заливка

  • fill: hex или ссылка на пресет.
  • fill_opacity: 0..1.

Точки

  • size_px: диаметр точки.
  • point_shape: circle, square, diamond, triangle_up, triangle_down, triangle_left, triangle_right, cross или plus.

Углы

  • angle_range: minor или reflex.
  • arc_size_px: базовый радиус дуги угла.
  • arc_shift_px: радиальный сдвиг между концентрическими дугами угла при tick_count > 1.
  • right_angle_size_px: размер квадратного маркера.
  • right_angle_marker: bool; принудительно включает квадратный маркер. Не задавайте это глобально в overlay.per_type.angle только потому, что пользователь просит изменить размер маркера прямого угла; для размера есть right_angle_size_px, а определение прямого угла оставьте автоматическим, если только пользователь явно не просит превратить все или названные углы в маркеры прямого угла.
  • auto_radius: bool; при false элемент выходит из-под overlay.angle_radius.
  • tick_count: для углов означает число концентрических дуг.

Засечки отрезков и векторов

  • tick_count: число меток равенства.
  • tick_style: line или wave.
  • tick_length_px: длина засечки.
  • tick_width_px: толщина обводки засечки.
  • tick_shift_px: расстояние между несколькими засечками.
  • tick_radius_px: радиус скругления для tick_style = "wave".

Наконечники векторов

  • arrow_length_px
  • arrow_width_px

Слои

  • z_index: основной слой объекта.
  • z_index_fill: слой заливки для circlesector.

За что отвечают секции

presets

Используйте для переиспользуемых токенов: именованных цветов, размеров точек, толщин линий, радиусов углов, размеров шрифта, пресетов засечек и стрелок. Сами по себе пресеты ничего не делают. На них должны ссылаться defaults, overlay, rendering или import.

Предпочитайте семантические ссылки на пресеты дублированию литеральных цветов и размеров в JSON-стиле. Встроенный стиль уже определяет распространённые семантические токены: color.main, color.accent, color.aux, color.strong, line_width.main, line_width.bold, line_width.aux, point_size.main, point_size.bold, point_size.aux и соответствующие токены font_size. Используйте эти роли, чтобы конструкция сохраняла смысловое назначение, а пользователь мог поменять саму палитру или масштаб в одном месте.

Рекомендуемые смыслы:

  • main: обычная геометрия, линии по умолчанию;
  • strong: основные объекты из условия задачи, когда на плотном чертеже им нужно оставаться заметными;
  • aux: вспомогательные и второстепенные объекты построения;
  • accent: искомый результат, фокусный элемент, точка внимания.

Типичные группы:

{
  "presets": {
    "color": {
      "main": "#000000",
      "accent": "#d05456",
      "aux": "#808080",
      "strong": "#000000",
      "angle_fill": "#eeeeee",
      "background": "#ffffff"
    },
    "point_size": { "main": 6, "bold": 9, "aux": 4 },
    "line_width": { "main": 1.5, "bold": 2.5, "aux": 0.75 },
    "angle_radius": { "main": 20, "shift": 3, "right": 17 },
    "font_size": { "main": 14, "bold": 16, "aux": 12 },
    "tick": {
      "main": {
        "tick_length_px": 9,
        "tick_width_px": 1.5,
        "tick_shift_px": 2
      }
    },
    "arrow": {
      "main": {
        "arrow_length_px": 11,
        "arrow_width_px": 7.5
      }
    }
  }
}

defaults

Используйте для базового стиля по типам. Это удобно, когда пользователь хочет стилевую тему для сцен, созданных в Python или DSL, либо запасные значения на случай, когда импорт GeoGebra отключён. defaults имеет более низкий приоритет, чем импортированный стиль GeoGebra, поэтому надёжно перебить визуальные решения из .ggb через него не получится.

Используйте defaults для:

  • «По умолчанию точки — чёрные кружки».
  • «Базовая толщина линии — 1.5 px».
  • «У углов радиус по умолчанию 20 px, если GeoGebra его не задаёт».

Не используйте defaults, когда пользователь говорит, что импортированные значения GeoGebra нужно перебить. Для этого есть overlay.per_type.

overlay.per_type

Используйте для правил уровня проекта, которые применяются после импорта ко всем элементам типа. Это лучший слой для запросов вроде:

  • «Сделать все точки одного размера и чёрными».
  • «Сделать у всех прямых, отрезков, лучей и окружностей одинаковую толщину».
  • «Задать дугам углов отдельную толщину».
  • «Сделать заливку всех углов одного цвета».
  • «Использовать один размер шрифта для всех подписей».
  • «Сделать метки равенства на отрезках одного размера».

Поскольку overlay.per_type стоит выше elem.ggb_style, он перебивает визуальные решения, импортированные из GeoGebra, и действует также на элементы, созданные в DSL.

overlay.per_name

Используйте только для именованных исключений, которые входят в запрошенный переиспользуемый стиль или опираются на предоставленную сводку конструкции.

Хорошие примеры:

  • «Сделать точку A красной».
  • «Скрыть подписи у xAxis и yAxis».
  • «Сделать у угла alpha двойную дугу».
  • «Поставить на отрезке AB две метки равенства».

Если сводки конструкции нет, не выдумывайте имена элементов. Предлагаемые правила для именованных элементов кладите в секцию Python DSL AnimaGeo только как закомментированные заготовки — или используйте overlay.per_type, если правило действительно глобальное.

overlay.angle_radius

Используйте для автоматического масштабирования радиусов углов. Это не прямой ключ визуального стиля, а блок автоматики:

"overlay": {
  "angle_radius": {
    "enabled": true,
    "exp": 0.3,
    "min_px": 14,
    "max_arm_fraction": 0.55,
    "apply_to_right": false
  }
}

Применяйте, когда в запросе сказано, что дуги углов должны оставаться читаемыми и на маленьких, и на больших углах. Для конкретного угла, который должен сохранить фиксированный радиус, используйте auto_radius: false. Если пользователь просит автоматическое согласование или подбор размера углов и при этом просит, чтобы один названный маркер угла был чуть крупнее другого, оставьте overlay.angle_radius.enabled = true и добавьте небольшое переопределение overlay.per_name.<angle>.arc_size_px для этого угла. Не отключайте слой автоматики углов, если пользователь явно не просит вручную зафиксировать размеры.

overlay.label_placement

Используйте для автоматической раскладки подписей. Её можно включить и для статических сцен:

"overlay": {
  "label_placement": {
    "enabled": true,
    "distance_px": 6,
    "padding_px": 2,
    "angle_gap_arc_px": 3,
    "angle_gap_sides_px": 3
  }
}

Для анимаций используйте:

  • keyframe_snapshots: true — раскладка подписей для анимации по ключевым кадрам; работает только вместе с enabled: true.
  • dynamic_angles: true — только вместе со снимками ключевых кадров или явным динамическим авторазмещением в Python.
  • canonicalize_anchor: true — только когда важна плавная интерполяция подписей.

reference

Используйте для эталонного холста, под который настраивался стиль. Это метаданные в JSON-стиле, и во время выполнения их можно переопределить:

"reference": {
  "size": { "width": 800, "height": 600 },
  "source": "source_view"
}

Допустимые значения reference.source — manual, source_view и ggb_view. Сам прямоугольник источника, размещаемый на этом эталонном холсте, задаётся параметром content во время выполнения, а не полем JSON-стиля. Физический размер вывода — это параметр export времени выполнения:

scene.loadGGB(
    "scene.ggb",
    style="style.json",
    content={"source": "rendered_bounds", "padding": 12},
    export={"size": {"width": 1600, "height": 1200}},
)

Используйте reference, когда пользователь просит переиспользуемый стиль, откалиброванный под известный размер холста. Не кладите итоговые размеры SVG или MP4 в JSON-стиль; физический вывод описывается блоком export времени выполнения.

Параметры content времени выполнения (не ключи JSON-стиля)

Начиная с версии библиотеки 1.6.2 блок content времени выполнения принимает ещё три параметра масштабирования декораций. Это параметры API времени выполнения, как и content.source; никогда не выводите их внутри JSON-стиля. Если запрос требует такого поведения, упомяните в NOTES, что это параметр content времени выполнения:

  • content.prominence: число, по умолчанию 1.0 (ограничено диапазоном 0.01..100). Единый множитель размера декораций, применяемый во время рендера. Раскладка, кадрирование и размещение подписей вычисляются при номинальном значении (1.0) и не затрагиваются; prominence масштабирует только ту плотность, относительно которой определяются размеры декораций (точки, обводки, шрифты подписей, маркеры).
  • content.decoration_scale_source: frame (по умолчанию), reference, output или ggb. Определяет, относительно какой плотности вычисляются декорации: frame следует за фактическим кадром экспорта (на этом построен fitView); reference пересчитывает плотность rendered_bounds от эталона стиля по всему исходному виду, поэтому заметность декораций не зависит от выбранного кадрирования; output привязывает размер декораций к масштабу экспорта геометрии, поэтому пиксельный размер декораций фиксирован и задаётся только через prominence; ggb масштабирует декорации равномерно с холстом вывода, как аплет GeoGebra (decoration_px = authored_px × (output_width / ggb_view_width) × prominence).
  • content.label_bounds: reserve (по умолчанию) или exclude. При content.source = "rendered_bounds" значение exclude кадрирует только по геометрии, игнорируя вынесенные наружу подписи; reserve не даёт подписям у края обрезаться.

Все три параметра включаются явно; умолчания библиотеки не изменились.

rendering

Используйте для поведения сцены и экспорта, а не для стилизации объектов:

  • background: фон сцены.
  • line_cap: окончание линий по умолчанию.
  • dash_period_px: период пунктира по умолчанию в px (штрих + промежуток), 10.
  • right_angle_joint: стиль соединения по умолчанию для маркеров прямого угла.
  • polygon_boundary_layer: "top", чтобы рисовать стороны многоугольника поверх заливок; иначе null.
  • points_display: auto, only_labels или only_points.
  • label_anchor: якорь подписи по умолчанию.
  • label_value_precision: точность числовых подписей по умолчанию на уровне сцены.
  • fast_value_labels: true (по умолчанию); анимированные подписи со значением не компилируют LaTeX на каждом кадре.
  • label_contrast: off (по умолчанию) или auto (перекрашивать подписи на заливках близкой яркости); label_contrast_threshold: разница яркости, 0.35.
  • color_interpolation: oklab (по умолчанию) или srgb, для анимации цветов.

Не кладите в rendering размеры точек, толщины обводок, цвета заливок, размеры шрифта и параметры засечек. Не кладите туда angle_radius и label_placement: эти блоки автоматики живут в overlay.

import

Используйте только для визуальной адаптации, специфичной для GeoGebra. На элементы, созданные в DSL, эта секция не влияет.

Используйте import.colors, import.point_size, import.line_width или import.policy, когда запрос о том, как переводить сырые значения GGB:

  • «Переназначить синий GeoGebra на фирменный синий».
  • «Квантовать импортированные толщины линий в 1, 2 или 4».
  • «Умножить импортированные размеры точек на 1.5».
  • «Игнорировать всю визуальную стилизацию GeoGebra и использовать умолчания стиля».

Отключить визуальный импорт:

"import": {
  "enabled": false
}

Если import.enabled равно false, геометрия и elem.ggb_raw остаются доступными, но импортированные цвета, размеры и подписи не становятся базой для рендера.

Выбирайте стратегию импорта осознанно:

  • Сохранить стиль GeoGebra: оставьте import.enabled незаданным или true и добавьте только те ключи overlay, которые пользователь просил изменить. Не перестилизовывайте посторонние ключи.
  • Адаптировать стиль GeoGebra: оставьте слой импорта включённым и используйте import.colors, import.point_size, import.line_width или отдельные поля import.policy, чтобы преобразовать сырые визуальные значения GGB до overlay.
  • Заменить стиль GeoGebra: задайте import.enabled=false, опишите нужную базу в defaults и используйте overlay только для правил уровня проекта и именованных исключений. Это лучше, чем копировать в overlay каждое поле, пришедшее из GeoGebra, лишь бы его нейтрализовать.

import.policy поддерживает литеральные значения и строки мини-DSL:

  • "const:3"
  • "scale:1.5"
  • "quantize:[1,2,4]"
  • "remap:{'#1565c0':'#0066cc'}"
  • "match_element"
  • "auto"

Поля политики:

  • size_px
  • stroke_width_px
  • arc_size_px
  • label_offset_px
  • label_color
  • label_visible
  • visible
  • label_text
  • label_mode
  • label_value_precision
  • label_value_strip_zeros
  • label_angle_unit
  • label_value_separator
  • angle_range
  • tick_count
  • font_size_px
  • stroke
  • fill
  • fill_opacity
  • point_shape
  • stroke_opacity
  • stroke_dash_ratio
  • stroke_dash_period_px
  • stroke_linecap

Как выбрать правильный слой

Пользуйтесь таблицей решений:

Запрос пользователя Куда класть
«Все импортированные точки чёрные и одного размера» overlay.per_type.point
«Все подписи 14 px» overlay.per_type для каждого подходящего типа — или defaults, если перебивать GGB не требуется
«Унифицировать подписи точек» Задавайте стиль подписей только в overlay.per_type.point; не включайте принудительно подписи у прямых, углов и прочих типов, если они не названы явно
«У всех линий одинаковая толщина» overlay.per_type для линейных типов
«У всех линий одинаковая толщина и без прозрачности, остальное из GeoGebra» Задавайте только stroke_width_px и stroke_opacity; не задавайте stroke, если пользователь явно не просил изменить цвет линий
«У дуг углов своя толщина» overlay.per_type.angle.stroke_width_px
«Заливка углов — бледно-жёлтая» overlay.per_type.angle.fill и fill_opacity
«Углы без линий, только заливка» overlay.per_type.angle.stroke_width_px = 0, stroke_opacity = 0, fill_opacity = 1; не меняйте цвет fill, если пользователь не назвал цвет заливки для углов
«r_shift между дугами угла равен 3» overlay.per_type.angle.arc_shift_px
«Маркер прямого угла вдвое крупнее» overlay.per_type.angle.right_angle_size_px; не выставляйте right_angle_marker=true глобально
«У меток на отрезках одинаковая длина, толщина и шаг» Поля засечек в overlay.per_type.segment
«У отрезка AB две засечки» overlay.per_name.AB.tick_count, если AB существует; иначе — Python DSL AnimaGeo
«Переназначить цвета GeoGebra» import.colors или import.policy.stroke/fill
«Игнорировать стилизацию GeoGebra» import.enabled=false плюс defaults с нужной базой
«Чтобы подписи не наезжали друг на друга» overlay.label_placement
«Сделать дуги очень острых углов читаемыми» overlay.angle_radius
«Сдвинуть подпись A на [10, -4]» overlay.per_name.A.label_offset_px, если A существует; иначе — Python DSL AnimaGeo

Запросы, которые не сводятся к чистому стилю

Когда пользователь говорит «остальное пусть будет из GeoGebra», считайте защищённым слоем импорта GGB каждое неупомянутое визуальное свойство. Например, «одинаковая толщина линий и без прозрачности» меняет stroke_width_px и stroke_opacity, но не должно менять цвет stroke. Чтобы задать stroke у линий, нужна отдельная фраза вроде «сделать линии чёрными».

Прилагательные цвета относятся только к тому классу объектов, который они определяют. Например, «коричневые точки» означает, что fill и stroke точек могут стать коричневыми; это не значит, что коричневыми должны стать заливки углов, заливки многоугольников или обводки линий.

Точно так же запросы про подписи, привязанные к классу объектов, относятся только к этому классу. «Подписи точек» — это overlay.per_type.point.label_*, а не подписи всех типов элементов. Не задавайте label_visible, если в запросе речь только об унификации, перестилизации или изменении размера подписей. Задавайте label_visible только тогда, когда пользователь явно просит показать или скрыть подписи. Когда пользователь просит скрыть подписи у конкретных названных точек, используйте overlay.per_name.<name>.label_visible = false только для этих имён. Не скрывайте подписи у всего типа point.

Если сводка конструкции предоставлена и пользователь просит выделить один существующий объект — например, «окружность», — предпочитайте overlay.per_name для видимого именованного объекта. Используйте overlay.per_type.circle только тогда, когда запрос явно про все окружности.

Некоторые запросы не стоит решать одним лишь переиспользуемым JSON-стилем:

  • Изменения геометрии: добавить, удалить или сдвинуть точки, изменить построение, создать новые отрезки, вычислить пересечения.
  • Выбор, зависящий от содержимого, который нельзя выразить через тип или известное имя: «покрасить самый длинный отрезок», «отметить только равные стороны», «выделить наибольший угол», «покрасить все точки внутри окружности».
  • Динамическая процедурная логика: стиль по условию, зависящий от измерений или других данных, доступных только во время загрузки.
  • Тонкая ручная расстановка подписей для неизвестных имён элементов.

Если в construction_summary достаточно информации, чтобы детерминированно определить имена целевых элементов, предпочитайте overlay.per_name в JSON-стиле, а не Python DSL. Например, если в сводке есть длины отрезков, а в запросе сказано «выделить самый длинный отрезок», выберите самый длинный видимый отрезок из сводки и стилизуйте его по имени. Используйте Python DSL только тогда, когда выбор нельзя разрешить по предоставленной сводке или когда пользователь хочет, чтобы правило пересчитывалось при загрузке другой конструкции. Если пользователь просит выделить углы многоугольника или треугольника, а в сводке есть вершины многоугольника, но нет готовых элементов-углов для этих вершин, — верните DSL, который создаёт недостающие маркеры углов через Angle(...) и стилизует эти новые элементы DSL. Не переиспользуйте посторонние существующие объекты-углы только потому, что они названы греческими буквами. Когда выделяется отрезок, который одновременно является стороной импортированного многоугольника, контур многоугольника может дублировать эту сторону. Поднимите выделенный отрезок над обычной штриховой геометрией с z_index больше уровня обводки — например, 6, — или уберите контур многоугольника, если границу должны задавать отдельные стороны. Не используйте крошечные значения z-index вроде 0.4 для выделенных обводок: они окажутся ниже обычных слоёв обводки. Добавьте запись в NOTES об этом наложении, как предписано контрактом секции NOTES.

Для запросов, которым действительно нужна процедурная логика, верните обычный JSON-стиль плюс код на Python DSL AnimaGeo.

Секция PYTHON_DSL должна содержать только содержимое файла DSL. В неё не должно попадать окружающее управление сценой. Никогда не генерируйте внутри PYTHON_DSL ни AnimaGeoScene, ни construct, ни play, ни wait, ни код анимаций Manim, ни вызовы рендера и экспорта, ни loadGGB, ни loadCode. Веб-сервис или пользователь применит его так:

scene.loadGGB(
    "scene.ggb",
    style="style.json",
    export={"size": {"width": 800, "height": 600}},
)
scene.loadCode("ai_post_style.py")

Пример содержимого ai_post_style.py:

# Это Python DSL AnimaGeo. Файл выполняется через scene.loadCode(...)
# уже после загрузки конструкции. Имена вроде A, AB, CD должны существовать
# в текущей Construction — обычно из loadGGB или более раннего DSL.

for el in [AB, CD]:
    el.style.tick_count = 2
    el.style.tick_length_px = 10

A.style.fill = "#d05456"
A.style.stroke = "#d05456"
A.style.label_color = "#d05456"

Разрешённые формы стилизации в DSL:

A.style.stroke = "#ff0000"
A.style["stroke_width_px"] = 2
style(A, B, C, fill="#000000", size_px=6)
hide(A, B)
show(C)

Не генерируйте:

def restyle(scene): ...
scene.element("A").style["fill"] = "#ff0000"
class MyScene(AnimaGeoScene): ...

Присваивания elem.style, сделанные из DSL, считаются явными и имеют высший приоритет. Генерируйте прямые ссылки только на те имена, которые есть в компактной сводке конструкции. Если имя неизвестно, упомяните это в NOTES или выдайте закомментированную заготовку вместо исполняемого кода.

Формат компактной сводки конструкции

Не отправляйте языковой модели сырой XML из .ggb. Используйте экспортер библиотеки, описанный в docs/construction_summary.md, когда запрос ссылается на реальные имена, типы, импортированные стили или геометрию:

summary = scene.exportStylePromptSummary("scene.summary.json")

Рекомендуемый формат:

{
  "schema": "animageo-construction-summary/v1",
  "source": {
    "kind": "ggb",
    "name": "scene.ggb"
  },
  "viewport": {
    "size": [800, 600],
    "reference_size": [800, 600],
    "ptUnit": 50,
    "ptUnit_style": 50,
    "ptUnit_ggb": 50,
    "ptXZero": 400,
    "ptYZero": 300,
    "contentScale": 1,
    "fontSize": 16
  },
  "stats": {
    "point": 4,
    "segment": 3,
    "angle": 1
  },
  "elements": [
    {
      "name": "A",
      "type": "point",
      "visible": true,
      "label_visible": true,
      "geometry": {
        "coords": [0, 0]
      },
      "ggb_style": {
        "size_px": 10,
        "fill": "#1565c0",
        "stroke": "#000000",
        "label_color": "#1565c0"
      }
    },
    {
      "name": "AB",
      "type": "segment",
      "visible": true,
      "label_visible": false,
      "construction": {
        "command": "Segment",
        "inputs": ["A", "B"],
        "outputs": ["AB"]
      },
      "geometry": {
        "endpoints": [[0, 0], [4, 0]],
        "length": 4
      },
      "ggb_style": {
        "stroke": "#1565c0",
        "stroke_width_px": 2.5
      }
    },
    {
      "name": "alpha",
      "type": "angle",
      "visible": true,
      "label_visible": true,
      "construction": {
        "command": "Angle",
        "inputs": ["B", "A", "C"],
        "outputs": ["alpha"]
      },
      "geometry": {
        "vertex": [0, 0],
        "side1": [1, 0],
        "side2": [0.5, 0.866],
        "size_rad": 1.0472,
        "size_deg": 60.0,
        "start_angle": 0.0,
        "end_angle": 1.0472
      },
      "ggb_style": {
        "arc_size_px": 30,
        "stroke_width_px": 2,
        "fill": "#000000",
        "fill_opacity": 0.1
      }
    }
  ],
  "groups": {
    "points": ["A", "B", "C"],
    "segments": ["AB", "BC", "CA"],
    "angles": ["alpha"]
  },
  "warnings": []
}

Держите сводку компактной:

  • Включайте все имена, типы, видимость и видимость подписей.
  • Включайте только ту геометрию, которая полезна для решений о стиле.
  • Включайте импортированный или разрешённый визуальный стиль только по тем ключам, которые относятся к задаче.
  • Включайте имена зависимостей в construction.inputs и construction.outputs для отрезков, векторов, углов и многоугольников, когда они известны.
  • Включайте неподдерживаемые команды и предупреждения парсера, если они влияют на стилизацию.
  • Опускайте сырой XML, огромные выборки точек кривых и избыточные умолчания.

Для больших конструкций ограничивайте объём стилевых данных и давайте списки типов и имён плюс сводную статистику. Если запрос нацелен на подмножество, подробно описывайте только эти элементы, а остальные — числом.

Рекомендуемое наполнение промпта

Надёжный запрос к API должен включать:

  1. Запрос пользователя на естественном языке.
  2. Этот файл контекста.
  3. JSON Schema или ссылку либо путь к ней.
  4. Контракт вывода:
  5. вернуть только корректный JSON, либо
  6. вернуть STYLE_JSON, при необходимости PYTHON_DSL, и NOTES.
  7. Целевой сценарий:
  8. только импорт из GeoGebra,
  9. только сцены на Python и DSL,
  10. и то, и другое.
  11. Нужно ли сохранять, переназначать или перебивать импортированные визуальные стили GGB.
  12. При желании — компактную сводку конструкции.
  13. При желании — существующий фирменный JSON-стиль, который нужно править или расширить.
  14. Ограничения:
  15. цветовая палитра,
  16. точные размеры,
  17. печать или экран,
  18. фон,
  19. должны ли подписи быть видимы,
  20. анимация или статическая сцена.
  21. Разрешены ли правки именованных элементов в overlay.per_name или их следует вернуть как Python DSL AnimaGeo для loadCode.
  22. При желании — требования к раскладке времени выполнения, отдельно от JSON-стиля:
    • эталонный холст reference для масштаба разработки,
    • источник и вписывание content,
    • физический размер export.

Проверочный список для модели

Прежде чем вернуть ответ, убедитесь:

  • В JSON нет комментариев и висящих запятых.
  • Ключи верхнего уровня допустимы.
  • Каждая ссылка на пресет вида <group>.<name> существует в presets.
  • Имена типов — канонические, в нижнем регистре.
  • Ключи стиля — канонические, с единицами _px там, где это требуется.
  • Нет удалённых секций верхнего уровня (style, technic, ggb_export, palette) и нет удалённых визуальных ключей вроде line_width, font_size, ang_rdefault, label_r_offset, strich_len.
  • Не генерируются удалённые ключи вроде rendering.scale_export и overlay.label_placement.angle_gap_px.
  • Используется font_size_px, в том числе внутри import.policy; не генерируйте import.policy.font_size.
  • overlay.per_type используется для глобальной стилизации после импорта.
  • defaults не используется, когда в запросе явно сказано перебить импортированные значения GGB.
  • import.policy используется только для адаптации сырых значений GGB.
  • Непрозрачности лежат в диапазоне 0..1.
  • Цвета — строки hex.
  • NOTES содержат каждое обязательное решение, допущение, риск, ограничение области и выбор на основе сводки конструкции, как описано в контракте секции NOTES.
  • В строках TeX для label_text одиночная обратная косая черта TeX экранирована ровно один раз в JSON — например, "$\\alpha$" в файле JSON. Не экранируйте дважды как "$\\\\alpha$": в LaTeX это превратится в \\alpha и сломается.
  • Чтобы приблизить автоматические подписи углов к маркеру угла, уменьшайте overlay.label_placement.angle_gap_arc_px и angle_gap_sides_px. Положительный label_radial_offset_px наоборот отодвигает подпись угла наружу, поэтому не используйте положительное радиальное смещение, чтобы выполнить просьбу «ближе к углу».
  • Если запросу нужна процедурная логика или работа с неизвестными именами, выдавайте Python DSL AnimaGeo, загружаемый через scene.loadCode(...), а не выдавайте это за общий стиль.

Полный пример

Запрос:

Импортировать конструкцию GeoGebra. Сделать все точки чёрными кружками одного размера, все линейные объекты — одной толщины, дуги углов тоньше, заливку углов светло-серой, все подписи 14 px, сдвиг дуг угла 3 px, а метки на отрезках — единообразными.

Хороший JSON-стиль:

{
  "name": "uniform_black_geometry",
  "version": 1,
  "presets": {
    "color": {
      "main": "#000000",
      "background": "#ffffff",
      "angle_fill": "#eeeeee"
    },
    "point_size": {
      "main": 6
    },
    "line_width": {
      "main": 1.5,
      "angle": 1
    },
    "angle_radius": {
      "main": 20,
      "shift": 3,
      "right": 17
    },
    "font_size": {
      "main": 14
    },
    "tick": {
      "main": {
        "tick_style": "line",
        "tick_length_px": 9,
        "tick_width_px": 1.5,
        "tick_shift_px": 2
      }
    }
  },
  "overlay": {
    "per_type": {
      "point": {
        "size_px": "point_size.main",
        "point_shape": "circle",
        "fill": "color.main",
        "stroke": "color.main",
        "stroke_width_px": 0,
        "fill_opacity": 1,
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "line": {
        "stroke": "color.main",
        "stroke_width_px": "line_width.main",
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "segment": {
        "$include": "tick.main",
        "stroke": "color.main",
        "stroke_width_px": "line_width.main",
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "ray": {
        "stroke": "color.main",
        "stroke_width_px": "line_width.main",
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "circle": {
        "stroke": "color.main",
        "stroke_width_px": "line_width.main",
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "arc": {
        "stroke": "color.main",
        "stroke_width_px": "line_width.main",
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "conic": {
        "stroke": "color.main",
        "stroke_width_px": "line_width.main",
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "function": {
        "stroke": "color.main",
        "stroke_width_px": "line_width.main",
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "implicitcurve": {
        "stroke": "color.main",
        "stroke_width_px": "line_width.main",
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "polygon": {
        "stroke": "color.main",
        "stroke_width_px": "line_width.main",
        "fill": "color.background",
        "fill_opacity": 0,
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      },
      "angle": {
        "arc_size_px": "angle_radius.main",
        "arc_shift_px": "angle_radius.shift",
        "right_angle_size_px": "angle_radius.right",
        "stroke": "color.main",
        "stroke_width_px": "line_width.angle",
        "fill": "color.angle_fill",
        "fill_opacity": 1,
        "label_color": "color.main",
        "font_size_px": "font_size.main"
      }
    }
  },
  "rendering": {
    "background": "color.background",
    "line_cap": "round",
    "right_angle_joint": "miter",
    "points_display": "auto",
    "label_anchor": "BC"
  }
}

Текущая поддержка и пробелы

Версия библиотеки, по которой сверялся этот контекст: 1.7.0.

Уже доступно:

  • Публичный экспортер сводки конструкции: scene.exportStylePromptSummary(filepath=None, **kwargs).
  • Низкоуровневые помощники экспортера: construction_to_ai_summary(...) и write_ai_summary(...).
  • Машиночитаемая схема стиля: docs/ai_style_json_schema.json.
  • Валидация стиля во время выполнения через StyleConfig.load(...) и StyleConfig.from_dict(...).