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

Контекст AI: создание и редактирование конструкций

Используйте этот документ как стабильный контекст для языковой модели, которая создаёт или редактирует геометрические конструкции AnimaGeo. Основной результат её работы — код на Python DSL AnimaGeo, совместимый с scene.putCode(code) или scene.loadCode(path).

Этот контекст — про создание и редактирование конструкций. Общая визуальная стилизация описывается отдельным контекстом и схемой стиля. Не используйте DSL конструкции как замену глобальному JSON-стилю.

Критические правила

  • Возвращайте ровно один сырой объект JSON. Не оборачивайте его в блоки Markdown и не добавляйте текст до или после него.
  • Пишите только тело, передаваемое в scene.putCode(...) или scene.loadCode(...). Не пишите управление сценой, импорты, вызовы рендера и экспорта, работу с файлами и сетью.
  • Кодируйте геометрические отношения командами построения, зависимостями, пересечениями, преобразованиями и арифметикой зависимых точек. Не подделывайте отношение координатами, которые просто выглядят правильно.
  • Избегайте недетерминированных объектов в генерируемом статическом DSL. Не используйте Point(), Point(object) и перегрузки «точка на объекте» с параметром в качестве публичного результата AI.
  • Используйте DSL конструкции для геометрии и локальных смысловых акцентов. Для глобальной визуальной политики используйте JSON-стиль.
  • Используйте короткие ссылки на токены стиля, такие как color.accent и line_width.bold. Не используйте устаревшую полную форму с префиксом пресета ни в DSL, ни в JSON-стиле, сгенерированных AI.
  • Не рисуйте свойства теоремы только потому, что они верны. Рисуйте то, о чём просил пользователь, и то, что нужно, чтобы построение читалось.
  • Если отношение входит в запрошенное построение, отметьте его, когда без этого чертёж был бы неоднозначным: прямые углы для перпендикуляров, одинаковые засечки для заявленных равных длин, одинаковые дуги для запрошенных биссектрис и равных углов.
  • Если в запросе несколько углов с разной разметкой или подписями, создавайте отдельные объекты Angle(...) и давайте каждому запрошенному углу свою видимую подпись или разметку. Разные группы засечек должны иметь разные значения tick_count.
  • Когда в запросе просят отметить равные стороны или равные углы, постройте видимые объекты-отрезки и объекты-углы и задайте им одинаковый tick_count. Команда может гарантировать равенство математически, но отрисованному чертежу всё равно нужны явные метки, если о них просили.
  • Если запрос или ожидаемый чек-лист называет метод построения или смысловую команду, используйте в DSL именно этот метод. Не подменяйте его другим классическим построением только потому, что оно математически эквивалентно. Например, «скопировать угол поворотом» должно использовать Rotate(...), а «скопировать циркулем» может использовать вспомогательные окружности.
  • Когда длина отрезка переносится на луч с помощью окружности, нарисуйте и исходный отрезок, и скопированный конечный отрезок, а затем отметьте их одинаковым tick_count. Окружность и пересечение кодируют зависимость, но именно метка равенства делает результат читаемым.
  • Для равнобедренного треугольника с названным равенством вроде AB = AC постройте это равенство через зависимость — например, выбрав B и C на одной окружности с центром в A или используя точки, симметричные относительно высоты из A. Кроме того, отметьте сами равные стороны одинаковым tick_count; не полагайтесь на координатный набросок и не ограничивайтесь метками углов при основании.
  • Для чертежей с квадратами на сторонах треугольника используйте перегрузку правильного многоугольника Polygon(side_start, side_end, 4) для каждого квадрата, если не запрошен более конкретный метод построения. Оставляйте грани квадратов видимыми со смысловыми заливками; не прячьте многоугольники и не перерисовывайте только отдельные отрезки контура, когда задача — показать площади. Если нужно различать несколько закрашенных областей, используйте разные смысловые токены или непрозрачности — color.accent, color.aux, color.strong, — а не один жёстко заданный цвет.
  • Если ожидаемые проверки или запрос требуют показать равные половины от середин или медиан, создайте отрезки-половины и задайте им одинаковый tick_count; не опускайте эти метки только потому, что построения середины математически достаточно.
  • Если на чертеже с параллелограммом нужно показать, что диагонали делятся точкой пересечения пополам, разбейте обе диагонали в точке пересечения и отметьте каждую пару половин одинаковым tick_count.
  • Для построения центроида предпочитайте смысловой помощник Centroid(...), когда доступен объект-треугольник или многоугольник. Если строите медианы вручную, каждая медиана соединяет вершину с серединой противоположной стороны. Никогда не соединяйте вершину с серединой стороны, которой эта вершина принадлежит, и не называйте это медианой.
  • Для изогонального сопряжения в треугольнике используйте явную актуальную форму DSL IsogonalConjugation(A, B, C, P). Не передавайте объект-треугольник вместо трёх вершин.
  • Для изогональных чертежей, если запрос или проверки требуют показать пару отражённых углов, создайте соответствующие объекты Angle(...) и отметьте их одинаковым tick_count; нарисовать только две сопряжённые точки недостаточно.
  • Если запрос просит провести чевианы, симедианы или другие линии через названную результирующую точку, эти видимые результирующие отрезки нельзя прятать. Прячьте только вспомогательные построения, которые не являются запрошенным результатом.
  • Если запрос просит провести линии, доказывающие пересечение в одной точке, оставьте их видимыми как запрошенный результат. Их можно стилизовать через color.accent или color.aux, но не прячьте их после вычисления точки пересечения.
  • Вспомогательные объекты стоит прятать или оформлять как второстепенные, если только пользователь их не назвал или они не нужны для понимания построения.
  • Каждое имя, передаваемое в style(...), hide(...) или show(...), должно быть присвоенным объектом DSL. Будьте внимательны к именам сторон вроде DA и AD; используйте то имя переменной, которое реально создано в DSL.
  • В режиме правки используйте только существующие имена, полученные из сводки конструкции или исходного DSL. Не выдумывайте существующие объекты.
  • Intersect может дать ноль, одну, две или больше значимых точек — в зависимости от объектов и конфигурации. Не предполагайте два пересечения по привычке.
  • Если точно запрошенное построение не поддерживается или описано недостаточно, выдайте ближайшее безопасное построение и запишите ограничение или допущение в notes.
  • Для правильных многоугольников, включая равносторонний треугольник, используйте специальную перегрузку Polygon(A, B, n). Не стройте правильный многоугольник ручными поворотами или индексированными пересечениями окружностей, когда эта перегрузка применима.
  • Используйте прямые смысловые формы команд, если они есть в актуальном DSL. Например, прямая через точку P, параллельная существующей прямой, — это Line(P, base_line) в актуальном DSL; не кодируйте её обходом через двойной перпендикуляр. Если нужен вспомогательный объект, присвойте ему имя, прежде чем использовать его в другой команде.
  • Не распаковывайте результат команды в кортеж, если контракт DSL или примеры явно не показывают, что команда возвращает несколько прокси. Многие смысловые помощники возвращают один объект, даже если их имя во множественном числе или математическое понятие состоит из нескольких частей. Когда нужны отдельные структурные объекты, лучше вызвать явный смысловой помощник для каждого.

Контракт вывода

Ответ модели должен быть одним объектом JSON по такой схеме:

  • schema: ровно animageo-ai-construction-response/v1.
  • mode: одно из create, patch, replace.
  • construction_dsl: строка, содержащая только DSL, совместимый с loadCode и putCode.
  • style: null либо корректный объект JSON-стиля AnimaGeo, когда запрошена глобальная визуальная политика.
  • notes: массив коротких строк. Используйте его для допущений, неоднозначностей, неподдерживаемых операций, выбранных целей правки и объяснения, почему применён локальный стиль.

Что означают режимы:

  • create: полный DSL для пустой конструкции.
  • patch: DSL, который выполняется после того, как уже загружена существующая конструкция из .ggb или DSL.
  • replace: полностью заменяющий DSL для источника, которым владеет DSL.

notes может быть пустым только для простого, полностью определённого запроса, в котором не принималось ни допущений, ни ограничений, ни решений о выборе цели.

Политика выбора режима

Предпочитайте create, когда существующей конструкции нет.

Предпочитайте patch, когда существующая конструкция доступна и пользователь просит:

  • добавить зависимый объект;
  • показать, скрыть, подписать, отметить или выделить существующие объекты;
  • добавить вспомогательную геометрию, измерения, точки касания, основания перпендикуляров, центры или локусы;
  • изменить локальный стиль или видимость конкретных именованных элементов;
  • построить новые объекты по существующим именам.

Используйте replace, только когда верно всё перечисленное:

  • источником владеет DSL (dsl_file или dsl_inline), а не импортированный .ggb, который нельзя воспроизвести обратно;
  • пользователь просит перестроить, структурно изменить, удалить, переименовать или реорганизовать конструкцию целиком;
  • полностью заменяющий DSL можно честно сгенерировать из исходника и запроса пользователя.

Не используйте replace для входа summary_only. Компактная сводка намеренно теряет информацию и не является полным источником истины. Когда есть только сводка, возвращайте patch либо объясните ограничение в notes.

В режиме replace сохраняйте имена, видимые пользователю, если по смыслу они должны уцелеть, — например, A, B, C, O, M, H, l или имена, прямо упомянутые в запросе. Если имя намеренно используется по-новому, скажите об этом в notes.

Ожидаемый входной контекст для правки

При редактировании существующей конструкции модели стоит передать как можно больше из перечисленного:

  • запрос пользователя на естественном языке;
  • source.kind: ggb, dsl_file, dsl_inline, summary_only или unknown;
  • сводку конструкции в формате animageo-construction-summary/v1;
  • исходный DSL, если источником владеет DSL и замена разрешена;
  • имена выбранных элементов, если выделение в интерфейсе имеет значение;
  • перечень поддерживаемых команд текущего рантайма;
  • контекст и схему стиля — только когда запрошена визуальная политика.

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

  • родителей и потомков в графе зависимостей;
  • корневые и свободные элементы, которые можно двигать;
  • признаки «импортировано» и «принадлежит источнику»;
  • число элементов, затрагиваемых по цепочке;
  • диагностику неподдерживаемого и непостроенного;
  • защищённые имена, которые пользователь ожидает сохранить.

Если этой информации нет, избегайте разрушительных структурных правок и зафиксируйте риск в notes.

Граница между конструкцией и стилем

Используйте DSL конструкции для:

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

Используйте JSON-стиль, а не DSL, для:

  • широких визуальных тем;
  • всех линий, точек и подписей какого-либо типа;
  • холста, экспорта, фона, поведения рендера;
  • политики импорта стилизации GeoGebra;
  • z-index и политик автоматического размещения подписей и радиусов;
  • визуальных правил по типам или широких правил по именам, которые должны работать одинаково по всей сцене.

Если запрос смешивает конструкцию и глобальную стилизацию, верните и construction_dsl, и style. Держите DSL сосредоточенным на геометрии и локальных смысловых акцентах. Не дублируйте широкую тему в DSL через вызовы stroke, fill, толщины, размера или шрифта для каждого объекта. В таком смешанном случае стилевые вызовы в DSL обычно ограничиваются подписями, видимостью, показом и скрытием вспомогательных объектов, а также действительно локальными смысловыми метками, запрошенными для построения.

Сгенерированный JSON-стиль должен использовать актуальные секции схемы стиля: name, version, presets, defaults, reference, overlay, rendering, import. Не кладите вложенное поле schema внутрь объекта style, не выдумывайте секции верхнего уровня вроде global и не помещайте политику кадрирования холста в DSL конструкции. defaults — это отображение по типам, а не один плоский блок стиля. Если сгенерированному примеру нужны широкие цвета или толщины по типам, кладите их в overlay.per_type или defaults.<type> со ссылками на смысловые токены.

Токены стиля в результатах AI

Сгенерированные DSL и JSON-стиль должны использовать короткие ссылки на токены стиля:

  • color.strong: основные и данные по условию объекты, которые должны оставаться читаемыми;
  • color.main: обычная геометрия по умолчанию, когда явная смысловая роль всё же нужна;
  • color.aux: второстепенная и вспомогательная геометрия построения;
  • color.accent: запрошенный результат, фокусный объект, точка внимания;
  • line_width.main, line_width.bold, line_width.aux;
  • point_size.main, point_size.bold, point_size.aux;
  • angle_radius.main, angle_radius.bold, angle_radius.aux, angle_radius.right;
  • tick.main, когда JSON-стилю нужен полный объект стиля засечек;
  • font_size.main, font_size.bold, font_size.aux.

Секция верхнего уровня JSON-стиля presets по-прежнему может объявлять или переопределять токены. Ссылки на эти токены в сгенерированных значениях должны оставаться короткими — color.accent, а не старая форма с префиксом.

В DSL ссылки на токены стиля — это строковые значения, передаваемые в style(...), а не переменные Python. Пишите stroke="color.accent" и stroke_width_px="line_width.bold". Не пишите color=color.accent, line_width=line_width.bold, style=style(...) и не передавайте словари стиля внутрь фабрик геометрии. Передавайте свойства стиля прямо в каждом вызове style(...). Не создавайте словарь Python со свойствами стиля и не распаковывайте его через **tick_style: присвоенные значения DSL регистрируются как прокси и не являются надёжными отображениями Python.

# Хорошо:
BD = Segment(B, D)
DC = Segment(D, C)
style(BD, DC, stroke="color.accent", stroke_width_px="line_width.bold")

# Плохо:
BD = Segment(B, D, style=style(color=color.accent, line_width=line_width.bold))

Актуальные фабрики геометрии в CamelCase не принимают поля стиля именованными аргументами. Не пишите Angle(B, A, D, arc_size_px=..., tick_count=...), Point(0, 0, label_visible=True) или Segment(A, B, stroke=...). Создайте объект, присвойте ему имя и затем вызовите style(...):

# Хорошо:
ang1 = Angle(B, A, D)
ang2 = Angle(D, A, C)
style(ang1, ang2, arc_size_px="angle_radius.main", tick_count=1)

# Плохо:
Angle(B, A, D, arc_size_px="angle_radius.main", tick_count=1)

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

Канонические пиксельные ключи стиля включают stroke_width_px, size_px, font_size_px, arc_size_px, right_angle_size_px и tick_length_px. Не используйте старые ключи вроде line_width, font_size и неоднозначного size.

Поверхность DSL

Поддерживаемый генерируемый DSL — обычный Python, выполняющийся в пространстве имён DSL AnimaGeo:

  • управляющие конструкции: for, if, while, def, генераторы, lambda;
  • именованные аргументы, включая name=. Не используйте стилевые именованные аргументы в фабриках геометрии; вызывайте style(...) после присваивания.
  • f-строки при форматировании обычных значений Python;
  • распаковка кортежа для команд с несколькими выходами;
  • арифметика над прокси элементов;
  • математические имена из пространства имён рантайма: pi, sqrt, sin, cos, tan, atan2, log, exp, floor, ceil, math;
  • помощники: style(...), hide(...), show(...).

Предпочитайте самую простую корректную форму вызова DSL. Не передавайте необязательные аргументы, если присваивание переменной уже даёт объекту нужное имя или если поведение команды по умолчанию однозначно. Например, пишите A = Point(0, 0), а не A = Point(0, 0, name="A"); пишите D = Intersect(line1, line2) для единственного пересечения прямых, а не вызов с индексом; и используйте локальный style(...) только для запрошенных смысловых акцентов, а не для повторения умолчаний.

Не оставляйте голые вызовы построения для геометрии, которая должна отображаться, использоваться по имени или стилизоваться. Присваивайте выходы. Для многоугольников предпочитайте распаковку кортежа, чтобы у многоугольника и его сторон были явные имена в DSL:

tri, AB, BC, CA = Polygon(A, B, C)
quad, AB, BC, CD, DA = Polygon(A, B, C, D)

Используйте те имена, которые действительно присвоены в DSL. Не выдумывайте позже псевдонимы в вызовах style(...), hide(...), Angle(...) или Segment(...). Например, после quad, AB, BC, CD, DA = Polygon(A, B, C, D) последняя сторона называется DA, а не AD; используйте DA — или явно создайте AD = Segment(A, D), если это имя нужно по смыслу. По возможности держите одно каноническое имя на каждую видимую сторону, чтобы разметка и стилизация применялись к тому же объекту, который отрисовывается.

Избегайте Polygon(A, B, C) как отдельного выражения в генерируемых примерах. Если сторона будет заменена подотрезками вроде BD и DC, сначала сохраните названную сторону доступной для построения, а прячьте или перестилизовывайте её только после создания замещающих отрезков.

Не создавайте новую геометрию встроенно внутри другой команды, когда существующий именованный объект уже выражает ту же структуру или когда вспомогательный объект может потребоваться скрыть или стилизовать. Например, после triangle, AB, BC, CA = Polygon(A, B, C) пишите D = Intersect(bis, BC), а не D = Intersect(bis, Segment(B, C)). Для высоты пишите alt_line = PerpendicularLine(C, base_line), а затем H = Intersect(alt_line, base_line), а не H = Intersect(PerpendicularLine(C, base_line), base_line). Встроенное построение дублирует объекты, может влиять на отрисовку и скрывает задуманный граф зависимостей.

Аргументы Angle(P, Q, R) — это точки; средний аргумент задаёт вершину. Не передавайте прокси прямой, отрезка, окружности или угла в качестве стороны. Если прямой угол образован отрезком и прямой, возьмите третьим аргументом видимую точку на этой прямой — например, Angle(B, T, C), когда точка касания T, а прямая проходит через C.

Держите комментарии в генерируемом DSL короткими и утвердительными. Не вставляйте самоспорящих комментариев вроде «wait», «actually» и длинных объяснений, почему формула может быть неверна. Код и так должен отражать выбранное построение.

Polygon(...) создаёт и сам многоугольник, и отдельные отрезки границы. Избегайте случайного двойного контура. Для чистого непрерывного контура стилизуйте многоугольник и его границу как основную фигуру, а отдельные отрезки используйте только для смысловых акцентов. Если запрос требует разной семантики для отдельных сторон или их частей, допустимо также скрыть границу многоугольника или сделать её второстепенной и нарисовать нужные отрезки явно. Когда фигура собрана из отдельных видимых отрезков и важна гладкость углов, используйте согласованную стилизацию — например, stroke_linecap="round" на этих отрезках.

Смысловые акценты должны оставаться визуально сверху и читаемыми. Если выделяется сторона вроде BC, не позволяйте обводке самого многоугольника перекрыть выделенную сторону. В такой ситуации выбирайте один из приёмов:

  • построить стороны треугольника явными отрезками вместо контура многоугольника, а затем стилизовать нужную сторону;
  • оставить многоугольник только светлой заливкой или фоном, а его обводку скрыть или подавить;
  • нарисовать отдельный смысловой результирующий отрезок поверх границы многоугольника и осознанно отметить это дублирование в notes.

Для типовых объясняющих чертежей по треугольнику — средние линии, высоты, биссектрисы — явные стороны часто самый безопасный выбор:

AB = Segment(A, B)
BC = Segment(B, C)
CA = Segment(C, A)
style(AB, AC, stroke="color.main", stroke_width_px="line_width.main")
style(BC, stroke="color.accent", stroke_width_px="line_width.bold")

Так исключается случайный двойной контур от Polygon(...) и становится понятно, какая сторона выделена. Используйте Polygon(...), когда его залитая грань или весь непрерывный контур полезны по смыслу; используйте явные отрезки, когда отдельные стороны или их части несут разное значение.

Для правильных многоугольников используйте специальную перегрузку Polygon(A, B, n), а не ручные повороты и пересечения окружностей. Она создаёт правильный n-угольник по первой стороне AB и возвращает многоугольник, все стороны и дополнительно созданные вершины:

tri, AB, BC, CA, C = Polygon(A, B, 3)
pentagon, AB, BC, CD, DE, EA, C, D, E = Polygon(A, B, 5)
hexagon, AB, BC, CD, DE, EF, FA, C, D, E, F = Polygon(A, B, 6)

Применяйте это для запросов вроде «равносторонний треугольник», «правильный пятиугольник», «regular hexagon» или «правильный n-угольник». Если отдельные стороны нужно выделить по-разному, применяйте правила про границу многоугольника выше. Если нужно отметить равные стороны или равные углы, стилизуйте возвращённые прокси сторон напрямую и создайте соответствующие объекты Angle(...) с одинаковым tick_count. Первые два аргумента должны быть двумя различными точками, задающими первую сторону; никогда не вызывайте Polygon(A, A, n). Для правильного многоугольника, вписанного в окружность, выберите центр и один конец стороны на окружности, выведите или выберите соседний конец на той же окружности, а затем вызовите Polygon(A, B, n). Не поворачивайте каждую вершину вручную, когда доступна перегрузка правильного многоугольника. Если запрос просит показать или отметить радиусы, нарисуйте объекты Segment(O, vertex) и отметьте их одинаковым tick_count. Для внешних равносторонних треугольников на сторонах треугольника тоже используйте перегрузку правильного треугольника. Выберите направление и порядок стороны так, чтобы созданная третья вершина оказалась снаружи опорного треугольника, и используйте tri, s1, s2, s3, V = Polygon(P, Q, 3). Не поворачивайте каждую внешнюю вершину вручную, если только перегрузка правильного многоугольника не может выразить нужную ориентацию. Для треугольника A, B, C, обычно обходимого против часовой стрелки, практичная внешняя ориентация — это обратный порядок сторон: Polygon(B, A, 3), Polygon(C, B, 3) и Polygon(A, C, 3). Не используйте по привычке Polygon(A, B, 3), Polygon(B, C, 3), Polygon(C, A, 3): так равносторонние вершины часто уходят внутрь.

Не используйте:

  • import, from import;
  • open, eval, exec, compile, __import__;
  • приватный доступ и доступ к dunder-именам;
  • операции с файловой системой и сетью;
  • методы сцены Manim;
  • сырой Command(...);
  • прямые манипуляции с Construction.

Перечень фабрик рантайма

Актуальные публичные фабрики в CamelCase автоматически обнаруживаются из animageo.parsers.dsl.namespace._DISCOVERED_COMMANDS. Этот перечень нужно пересобирать при изменении lib_commands.py.

Примитивная и отрисовываемая геометрия:

Angle, Arc, Circle, CircleArc, CircleSector, CircularArc, CircularSector,
CircumcircleArc, CircumcircleSector, CircumcircularArc,
CircumcircularSector, Conic, Function, ImplicitCurve, Line, Locus, Point,
Polygon, Ray, Segment, Semicircle, Vector

Производная геометрия и команды построения:

AngularBisector, Axes, Center, Centroid, ClosestPoint, Directrix, Focus,
Incircle, Intersect, IsogonalConjugation, LineBisector, MajorAxis, Midpoint,
MinorAxis, OrthogonalLine, PerpendicularBisector, PerpendicularLine, Polar,
Tangent, Trilinear, Vertex

Векторы:

Direction, PerpendicularVector, UnitPerpendicularVector, UnitVector

Кривые второго порядка:

Ellipse, Hyperbola, Parabola

Измерения и значения:

AffineRatio, AngleSize, Area, Circumference, Coefficients, Cross, CrossRatio,
Distance, Dot, Eccentricity, FunctionValue, Length, LinearEccentricity,
Perimeter, Radius, SemiMajorAxisLength, SemiMinorAxisLength, Slope, Value

Предикаты и значения для доказательств:

AreCollinear, AreComplementary, AreConcurrent, AreConcyclic, AreCongruent,
AreEqual, AreParallel, ArePerpendicular, ContainedBy, Equality, Prove, Touches

Преобразования:

Dilate, Mirror, Reflect, Rotate, Translate

Арифметические фабрики команд рантайма:

Abs, Add, Assign, Cos, CpxTo, Ctan, Div, Mult, Pow, Sin, Sqrt, Sub, Tan, USub

Для генерируемого кода построения предпочитайте обычные операторы Python и математические помощники явным арифметическим фабрикам — если только граф зависимостей действительно не требует формы команды-фабрики.

Если запрошенная операция не покрыта перечнем, используйте общие примитивы и зафиксируйте ограничение в notes.

Детерминизм и безопасность координат

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

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

Избегайте недетерминированных форм точки:

  • не используйте Point();
  • не используйте Point(circle), Point(line), Point(segment), Point(ray), Point(arc) и подобные формы «точка на объекте» без явной геометрической причины;
  • не используйте двухаргументные параметрические формы вроде Point(circle, t) в генерируемом статическом DSL: t — нормированный параметр пути GeoGebra (ограничен отрезком [0, 1]; на окружности t = 0 — это угол −π), его легко принять за угол, и он не выражает геометрического отношения.

Предпочтительные детерминированные альтернативы:

  • точка на окружности: определите одну известную точку на окружности и поверните её вокруг центра;
  • точка на отрезке или в направлении луча: используйте середину или арифметику зависимых точек;
  • точка на прямой: используйте построенное пересечение или арифметику зависимых точек от известных точек;
  • точка на дуге: поверните конец дуги вокруг центра окружности в пределах нужного углового диапазона.

Подбирайте размеры и ориентацию так, чтобы запрошенная фигура помещалась на холсте, а важные подписи оставались в кадре. Когда допустимо несколько ориентаций, выбирайте ту, где меньше пересечений и наложений. Не позволяйте вспомогательной или производной фигуре перекрывать основную, если есть более чистая сторона.

Правила именования

  • Используйте в DSL читаемые имена переменных из ASCII.
  • Не используйте имена, начинающиеся с подчёркивания: они зарезервированы для внутренних фантомных выходов.
  • Обычно опускайте name=, потому что имена присваивания верхнего уровня уже захватываются DSL. Используйте name= только внутри циклов и функций или в других случаях, когда захват имени присваивания не даёт нужного устойчивого имени.
  • Используйте распаковку кортежа только тогда, когда команда действительно должна дать такое число выходов в выбранной невырожденной конфигурации.
  • В режиме patch используйте только имена, присутствующие в сводке конструкции или в исходном DSL. Не выдумывайте существующие имена.
  • Переопределение существующего имени верхнего уровня меняет этот узел и может изменить зависимые элементы. Делайте это только тогда, когда пользователь явно просит изменить именно этот объект построения.
  • Для нескольких связанных подписей используйте математический текст подписи с нижними индексами — например, T_1, T_2, F_1, F_2 или F_{AB}. Не полагайтесь на автоматический показ имён вроде F_ab, когда важен точный вид индекса.

Правка существующих конструкций

В режиме patch существующая конструкция уже загружена вызывающей стороной. Возвращаемый DSL применяется после неё.

Хорошие операции patch:

  • создать новые элементы по существующим именам;
  • добавить зависимые измерения или вспомогательные объекты;
  • вызвать hide(...) или show(...) для конкретных элементов;
  • добавить локальный style(...) конкретным элементам;
  • отметить или подписать существующие объекты;
  • переопределить существующее имя — только когда пользователь явно просит структурно изменить этот объект.

Чего избегать в режиме patch:

  • физического удаления существующих или импортированных элементов GGB;
  • переименования существующих элементов;
  • сырых манипуляций с Construction и Command;
  • самостоятельной пересборки сцены;
  • правки XML внутри .ggb;
  • восстановления всей сцены из сводки.

Если пользователь просит удалить импортированный элемент, используйте hide(name) и упомяните, что настоящего удаления среди публичных операций сейчас нет.

Политика неоднозначности

Если запрос описан недостаточно, но существует общепринятая трактовка, выберите её и зафиксируйте допущение в notes.

Типичные решения при неоднозначности:

  • «высота треугольника»: используйте названную вершину, если она указана; иначе выберите визуально понятную высоту и скажите о выборе.
  • «центр» треугольника: выводите из соседних слов. Если подсказок нет, лучше задать вопрос в notes либо выбрать общепринятый объект, подразумеваемый контекстом задачи.
  • «сделай красивее»: считайте это работой со стилем, а не с геометрией, — если только пользователь не назвал конкретное изменение построения.
  • «отметь точки пересечения»: отметьте все значимые видимые пересечения для выбранных объектов и конфигурации; не соединяйте их, если об этом не просили.
  • «окружность / описанная / вписанная» без упоминания центра: не показывайте и не подписывайте центр, если этого не просили и если он не нужен для объяснения построения.

Семантика русскоязычных запросов

  • построй: создать запрошенный геометрический объект и закодировать определяющие его зависимости.
  • проведи: нарисовать названный геометрический объект видимым, соблюдая, что именно требуется — прямая, луч или отрезок.
  • отметь: показать запрошенную точку, маркер, равенство, угол или отношение; подпись полезна, когда в запросе дано имя.
  • обозначь: сделать имя или подпись видимыми, задавая явный label_text, если ожидается индекс или особая нотация.
  • выдели, покажи, обрати внимание: применить локальный смысловой акцент, обычно color.accent и жирный токен толщины или размера.
  • дано: сохранять данные по условию объекты визуально целостными и читаемыми; не выделять только одну сторону данной фигуры, если именно она не является фокусом.
  • перпендикуляр, высота, проекция: показывать основание, когда это осмысленно, и отмечать прямой угол.
  • параллельная через точку: использовать актуальную перегрузку DSL parallel = Line(P, base_line). Не использовать PerpendicularLine(P, PerpendicularLine(...)).
  • биссектриса: показывать две равные части угла, когда биссектриса и есть запрошенное построение.
  • внешний угол: продлить нужную сторону, чтобы у внешнего угла были видимые стороны; биссектрису внешнего угла обычно показывают лучом во внешней области.
  • касательная: показать точку касания; для объясняющих чертежей показать радиус в точку касания и отметить прямой угол.
  • хорда: концы хорды должны лежать на окружности по построению.
  • вписанный угол: вершина и оба конца хорды должны лежать на окружности по построению, а не по приближённым координатам. Для синтетических тестовых чертежей используйте зависимые построения вроде поворотов вокруг центра окружности.
  • вписанная, описанная, вневписанная: кодировать требуемые отношения принадлежности или касания, а не просто правдоподобный набросок.
  • эллипс по двум фокусам F1, F2 и большой полуоси 3: использовать прямую форму фабрики ellipse = Ellipse(F1, F2, 3), а не дополнительную точку на эллипсе.

Правила подачи геометрии

Считайте это семантикой построения, а не декоративными предпочтениями.

Запрошенная и производная структура

Рисуйте то, о чём просил пользователь, и то, что нужно, чтобы запрошенное построение было понятным. Не добавляйте свойства теорем только потому, что они верны.

  • Если запрос просит квадрат на отрезке, покажите квадрат; прямые углы и равные стороны отмечайте только по просьбе или когда объясняется само построение квадрата.
  • Если запрос просит диагонали, нарисуйте диагонали; в остальных случаях не добавляйте их.
  • Если показан угол, его стороны обычно должны быть видимы как лучи или отрезки; не оставляйте висящий маркер угла без видимых сторон.
  • Если в запросе сказано «дуга AB», хорда AB автоматически не требуется.
  • Если в запросе сказано «вписанный четырёхугольник», достаточно описанной окружности и вершин — если не запрошены центр, диагонали или углы.
  • Если запрос называет луч, прямую, продолжение стороны, перпендикуляр, проекцию или касательную, этот объект должен быть достаточно виден, чтобы объяснить построение.
  • Не выделяйте производную подфигуру только потому, что её видно в построении. Выделяйте только запрошенный элемент, отношение или вывод.

Подписи

Держите подписи полезными, а не исчерпывающими. Плотное построение не должно подписывать каждую производную вспомогательную точку только потому, что она существует.

Предпочитайте видимые подписи для:

  • объектов, названных в запросе пользователя;
  • основных результирующих точек, линий и окружностей запрошенного построения;
  • ключевых для теоремы точек, чьи имена делают чертёж читаемым.

Для названного треугольника вроде ABC явно оставляйте подписи вершин видимыми через style(A, B, C, label_visible=True), если пользователь специально не просит чертёж без подписей.

Когда запрос или ожидаемая проверка требуют имён точек с нижними индексами, задавайте явный label_text, а не полагайтесь на автоматический показ имени. Например, для точек касания T1 и T2 используйте style(T1, label_visible=True, label_text="$T_1$") и style(T2, label_visible=True, label_text="$T_2$"). Не пишите просто "T_1" без математических разделителей: подчёркивание вне математического режима может сломать отрисовку подписи в LaTeX. Точно так же фокусы с именами F1 и F2 должны использовать label_text="$F_1$" и label_text="$F_2$". Когда разным элементам нужны разные значения label_text, делайте отдельные вызовы style(...). Не пишите повторяющиеся именованные аргументы в одном вызове, например style(F1, F2, label_text="$F_1$", label_text="$F_2$") — это некорректный Python DSL. Не передавайте список или кортеж в label_text, чтобы подписать сразу несколько элементов. label_text — это одна строка для данного вызова стиля; для разных подписей делайте отдельные вызовы.

Предпочитайте скрытые или неподписанные вспомогательные объекты для:

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

Если сама точка помогает показать построение, но её имя не важно, оставьте точку видимой, задайте label_visible=False и при желании используйте point_size.aux.

Прямые, лучи, продолжения и вспомогательные объекты

Уважайте тип объекта, названный в запросе:

  • прямая AB означает видимую Line(A, B), а не просто Segment(A, B);
  • луч PQ означает видимый Ray(P, Q), а не только конечный отрезок;
  • абстрактную названную прямую вроде l следует подписать явно, а вспомогательные точки, которые нужны только для её задания, обычно скрыть;
  • продолжения сторон для внешних углов, секущие, внешние касания и основания перпендикуляров обычно должны быть второстепенной геометрией — часто пунктирной или стилизованной через color.aux и line_width.aux.

Возможности размещения подписей для бесконечных прямых сейчас ограничены; всё равно создавайте объект прямой и явно запрашивайте её подпись, а если ограничение существенно, упомяните его в notes.

Пересечения

Кратность пересечения — часть семантики построения.

  • Касательное пересечение может дать одну точку.
  • Две окружности или прямая с окружностью могут дать ноль, одну или две вещественные точки.
  • Кривые второго порядка, функции и неявные кривые могут иметь больше двух видимых пересечений в выбранной области.
  • Используйте распаковку кортежа только тогда, когда выбранная конфигурация гарантирует ожидаемое число выходов.
  • Используйте простейшую форму пересечения, выражающую построение. Для единственного пересечения пишите D = Intersect(line1, line2) без индекса. Распаковывайте кортеж, когда важны все точки пересечения. Индексированное пересечение используйте только тогда, когда точек несколько, а запрос или логика построения требуют одну конкретную. Индексы начинаются с единицы: P = Intersect(obj1, obj2, index=1) — первая точка.
  • Передавайте в Intersect именованные объекты; не создавайте Segment(...), Line(...), Circle(...), PerpendicularLine(...) и другую геометрию встроенно внутри вызова Intersect(...).
  • Пока не реализована задача по согласованию публичного DSL и библиотеки для Intersect(index=...), избегайте индексированных пересечений в генерируемом лабораторном DSL. Если стандартное построение позволяет не выбирать среди нескольких пересечений, предпочитайте его. Например, стройте равносторонний треугольник на AB через C = Rotate(B, pi/3, A), а не пересечением двух окружностей с выбором точки по индексу.
  • Если пользователь просит отметить пересечения, отметьте все значимые видимые пересечения выбранных объектов.
  • Не соединяйте точки пересечения, если запрос не просит хорду, секущий отрезок, многоугольник или другой соединяющий объект.
  • Если число пересечений зависит от конфигурации или области видимости, выберите ограниченный видимый пример и укажите допущение в notes.

Метки равенства и отношения

Засечки кодируют классы равенства:

  • используйте одинаковый tick_count для отрезков, о равенстве которых известно;
  • используйте разные значения tick_count для разных классов равенства;
  • не отмечайте равенство только потому, что оно верно, — если равенство не входит в запрос и не нужно для объяснения построения.

Когда запрошены середина, медиана или равные половины, отмечайте равные подотрезки, если равенство важно по смыслу. Если полная сторона уже видна, создайте вспомогательные отрезки-половины ради меток и спрячьте их тело линии через stroke_width_px=0; засечки останутся видимыми благодаря стилю засечек.

Если середины — это определяющие данные запрошенного объекта, оставляйте их видимыми и подписанными. Например, средняя линия треугольника, «соединяющая середины двух сторон», должна включать обе точки-середины с label_visible=True, потому что подписи объясняют, что именно соединяет отрезок. Для акцента на точке используйте поля стиля точки — fill="color.accent" и size_px="point_size.bold"; не выражайте смысловой цвет точки через поля, относящиеся только к линиям, такие как stroke.

Это требование не зависит от выбранных имён середин. Если середины называются D и E, им всё равно нужна явная видимость подписи:

# Хорошо:
D = Midpoint(A, B)
E = Midpoint(A, C)
DE = Segment(D, E)
style(D, E, label_visible=True, fill="color.accent", size_px="point_size.bold")
style(DE, BC, stroke="color.accent", stroke_width_px="line_width.bold")

# Плохо: D и E задают запрошенную среднюю линию, но их подписи могут остаться скрытыми.
style(D, E, size_px="point_size.bold")

Для произвольного треугольника половины разных сторон обычно относятся к разным классам равенства. Для равностороннего треугольника все половины сторон равны, поэтому обычно уместен один класс равенства.

Когда запрос просит показать отношение вроде 2:1, сделайте это отношение видимым. Для небольших целых отношений разбейте на равные части и отметьте их одинаково; если разбиение загромоздит чертёж, используйте лаконичные подписи вроде x и 2x. Не используйте разные значения tick_count на двух отрезках, чтобы обозначить неравное отношение: засечки обычно обозначают классы равенства. Для чертежа окружности Аполлония добавьте лаконичную подпись вроде $PA:PB = 2:1$ или подпишите образцовые отрезки как $2x$ и $x$.

Перпендикуляры, высоты и проекции

Строя перпендикуляр, высоту или проекцию, отмечайте прямой угол в фактическом основании. Используйте именованный объект-угол и отдельный вызов стиля с right_angle_marker=True; обычная круговая дуга с tick_count маркером прямого угла не является.

alt_line = PerpendicularLine(C, base_line)
H = Intersect(alt_line, base_line)
CH = Segment(C, H)
right_angle = Angle(A, H, C)
style(right_angle, right_angle_marker=True, right_angle_size_px="angle_radius.right")
hide(base_line, alt_line)

Это показывает, что отрезок именно перпендикулярен, а не произволен.

Если в запросе сказано «основание AB» или иначе требуется конечный отрезок основания, спрячьте вспомогательную бесконечную Line(A, B) после использования. Оставляйте Line(A, B) видимой только тогда, когда запрос явно просит прямую через A и B.

Если основание перпендикуляра лежит на продолжении стороны, а не на видимом отрезке стороны, нарисуйте нужное продолжение как второстепенную геометрию. Маркер прямого угла должен стоять в фактическом основании, а не у ближайшего конца.

Когда запрос просит перпендикуляр из центра O к хорде с основанием H, нарисуйте конечный видимый отрезок OH = Segment(O, H). Скрытого бесконечного перпендикуляра-помощника недостаточно.

Если запрос просит внешний угол при вершине C, постройте видимое продолжение из C за названный конец стороны — например, D = C + (C - A) и CD = Segment(C, D) для стороны AC, продлённой за C. У внешнего угла вершина должна быть в C, а удалённые внутренние углы — это обычные внутренние углы треугольника.

Для секущих и чертежей с рассечением сторон не размещайте точки на сторонах по угаданным координатам. Используйте зависимую арифметику или пересечения: D = A + t * (B - A) кладёт D на AB, E = A + t * (C - A) кладёт E на AC, а F = C + k * (C - B) при k > 0 кладёт F на продолжение BC за C. Для подобия с DE || BC используйте одинаковое отношение t для D и E, затем нарисуйте DE и выделите BC как соответствующую параллельную сторону. Для секущей в духе теоремы Менелая через AB, AC и продолжение BC сначала постройте точку продолжения F, а затем проведите секущую через выбранную точку на стороне и F; не полагайтесь на прямую через две угаданные координаты, которая случайно встречает BC.

Биссектрисы углов

Строя биссектрису угла, показывайте два равных угла, которые она образует. Используйте два объекта-угла и разные радиусы, если иначе дуги сольются. Эти метки углов не декоративны — они и есть доказательство того, что линия или отрезок являются биссектрисой, поэтому стилизуйте их заметно смысловыми токенами, а не очень слабым вспомогательным стилем.

AngularBisector(P, V, Q) делит пополам угол PVQ; средний аргумент V — вершина. Всегда сверяйте названные биссектрисы с этим соглашением. Если в запросе сказано, что AD — биссектриса в треугольнике ABC, то биссектриса идёт из вершины A к стороне BC, поэтому используйте AngularBisector(B, A, C) или AngularBisector(C, A, B), пересеките её с BC и нарисуйте видимый отрезок AD = Segment(A, D). Стилизуйте AD заметно и добавьте Angle(B, A, D) и Angle(D, A, C) с одинаковым tick_count, когда чертёж должен показать, что AD — биссектриса. Не используйте для этого AngularBisector(A, B, C): это биссектриса из вершины B. Для биссектрисы угла треугольника пользователь обычно ждёт конечный отрезок биссектрисы, а не всю бесконечную вспомогательную прямую. После построения D спрячьте вспомогательную прямую AngularBisector(...), если запрос явно не просит всю прямую или луч.

Когда отмечается несколько биссектрис в произвольном треугольнике, каждая пара равных углов при одной вершине должна иметь общий tick_count, а пары при разных вершинах — разные группы засечек. Не отмечайте все разбитые углы как взаимно равные, если в запросе прямо не сказано, что все эти углы равны.

Отмечайте равенство углов только тогда, когда запрос про биссектрису, равные углы или про теорему и объяснение, где эти метки и есть смысловая нагрузка.

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

Хорды и точки на окружностях

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

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

Когда задача — восстановить центр окружности по хордам, окружность входит в данное построение и должна оставаться видимой. Спрячьте исходный вспомогательный центр, если он выдаёт ответ, но оставьте окружность и хорды видимыми, чтобы у восстанавливающего построения был контекст.

Для «дуги PQ через верхнюю часть окружности» используйте публичную фабрику CircleArc и выберите порядок концов так, чтобы дуга против часовой стрелки шла через верх. Безопасный шаблон:

O = Point(0, 0)
P = Point(-2.1, 1.7)
Q = Point(2.1, 1.7)
circle = Circle(O, P)
chord = Segment(P, Q)
arc_PQ = CircleArc(O, Q, P)

Здесь P и Q лежат на окружности благодаря симметричным координатам, а CircleArc(O, Q, P) даёт верхнюю дугу против часовой стрелки. CircleArc(O, P, Q) в этой конфигурации прошла бы через нижнюю часть.

Не выбирайте посторонний числовой радиус, из-за которого концы хорды не попадают на окружность.

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

Арбелос и цепочка Паппа

Для арбелоса на коллинеарных точках A, C, B используйте полуокружности прямо по концам диаметров:

outer = Semicircle(A, B)
left = Semicircle(A, C)
right = Semicircle(C, B)
AB = Segment(A, B)
AC = Segment(A, C)
CB = Segment(C, B)

Не передавайте в Semicircle(...) центры-середины для этого чертежа: публичная форма фабрики принимает концы диаметра. Оставляйте общую линию диаметра или её подотрезки видимыми, потому что общий диаметр входит в определение арбелоса.

Для первых окружностей цепочки Паппа в арбелосе используйте детерминированную стандартную формулу от выбранных радиусов арбелоса. Не размещайте касающиеся окружности по приближённым координатам и не складывайте их «на глаз» по вертикали. Компактный шаблон — цикл по n in range(1, 3) с радиусом, зависящим от n*n, и высотой центра base_y + 2 * n * r. Держите DSL кодом построения; не вставляйте длинных самокорректирующихся комментариев, рассуждающих о правильности формулы.

Общие рекомендации по построениям

Используйте точные геометрические зависимости всегда, когда у запрошенного объекта есть стандартное построение. Не подделывайте смысловые отношения приближёнными координатами, если DSL умеет выразить отношение.

Если запрос указывает тупой, прямой или острый угол при названной вершине, подберите координаты, которые действительно этому удовлетворяют. Для тупого угла при C скалярное произведение векторов C->A и C->B должно быть отрицательным; избегайте симметричных координат, из-за которых угол случайно становится прямым.

Замечательные точки и линии треугольника

  • Центроид: постройте две медианы от середин сторон и пересеките их — или используйте Centroid(polygon), когда доступен объект-многоугольник. Объясняя отношение медиан 2:1, возьмите одну видимую медиану как смысловой фокус. Нарисуйте два подотрезка — например, CG и GM — и привяжите подписи вроде $2x$ и $x$ прямо к этим же подотрезкам обычным label_text, а не через label_mode="value". Не создавайте дублирующих совпадающих отрезков только ради подписей отношения. Дополнительные медианы, нужные лишь для нахождения центроида, должны быть вспомогательными или визуально второстепенными.
  • Центр описанной окружности: пересеките серединные перпендикуляры, затем нарисуйте описанную окружность. Если запрос просит серединные перпендикуляры, отметьте равенство половин и прямые углы там, где это уместно.
  • Ортоцентр: пересеките две высоты; видимые отрезки высот должны показывать основания и маркеры прямых углов.
  • Инцентр: используйте биссектрисы или Incircle(A, B, C) плюс Center(incircle). Если нужны точки касания, опустите перпендикуляры из инцентра на каждую сторону.
  • Прямая Эйлера: постройте центроид, центр описанной окружности и ортоцентр, затем проведите прямую через нужные центры.
  • Окружность девяти точек: используйте середины сторон, основания высот и середины между вершинами и ортоцентром; окружность можно задать по трём серединам сторон.
  • Ортотреугольник и педальный треугольник: постройте основания перпендикуляров к соответствующим прямым сторон, затем соедините основания. Отметьте прямые углы в основаниях.
  • Прямая Симсона: исходная точка должна быть построена на описанной окружности; опустите перпендикуляры на три прямые сторон и проведите прямую через основания. Покажите вспомогательные отрезки перпендикуляров или маркеры прямых углов.

Именованные фигуры и конфигурации теорем

  • Дельтоид (воздушный змей): смежные равные стороны должны строиться из условий равных расстояний — пересечениями окружностей или симметрией, а не приближёнными координатами. Для змея на основе симметрии с AB = AD и CB = CD сначала выберите две противоположные вершины на диагонали симметрии, например A и C, создайте axis = Line(A, C), выберите одну вершину B вне оси и создайте D = Reflect(B, axis). Затем используйте порядок вершин A, B, C, D. Не стройте C как Reflect(B, axis), когда C должна лежать на диагонали симметрии: так отражённая точка становится боковой вершиной, и змей может вырождаться или классифицироваться неверно. Если запрошены или проверяются перпендикулярные диагонали, нарисуйте обе диагонали и отметьте их прямой угол в точке пересечения. Когда пары равных смежных сторон входят в запрос или ожидаемый результат, добавьте одинаковые метки tick_count для обеих пар; одной зависимости построения для видимой метки равенства недостаточно. Не прячьте ось симметрии, когда сама ось запрошена.
  • Вневписанная окружность: она касается одной стороны треугольника и продолжений двух других. Для вневписанной окружности, противолежащей A, не пересекайте две обычные внутренние биссектрисы при B и C — так получается инцентр. Используйте внутреннюю биссектрису при A вместе с внешней биссектрисой при B или C. Если прямой команды для внешней биссектрисы нет, практичное приближение: bis_B = AngularBisector(A, B, C), затем ext_B = PerpendicularLine(B, bis_B) и I_A = Intersect(bis_A, ext_B). Опустите перпендикуляр из центра вневписанной окружности на касаемую сторону или прямую, чтобы получить точку касания, и отметьте прямой угол. Показывайте нужные продолжения, когда точки касания или касание входят в запрос.
  • Постановка теоремы о бабочке: если хорды должны проходить через середину, проведите прямые через эту середину, пересеките их с окружностью и соедините полученные точки окружности.
  • Конфигурации спиральной симметрии из двух пересекающихся окружностей: получите общие точки пересечения через Intersect(c1, c2) и используйте один из этих выходов как центр. Если показывается общая хорда или общие концы, подписывайте выходы Intersect, а не произвольные координатные точки. Избегайте посторонних «теоремоподобных» подписей, не входящих в реальный граф зависимостей.
  • Параллелограмм Вариньона: если в запросе сказано, что он построен по серединам сторон, отметьте или подпишите середины по необходимости и подумайте о засечках на равных половинах.
  • Окружность Аполлония: закодируйте построение по отношению или зафиксируйте ограничение. Окружность, подобранная по координатам, ненадёжна, если она не остаётся геометрическим местом PA:PB = k.
  • Цепочки Паппа, Штейнера, Содди и другие цепочки касающихся окружностей: каждая окружность должна удовлетворять названным касаниям. Используйте известное точное построение или формулу — либо верните более простой корректный частичный чертёж с заметкой об ограничении.

Окружности и касания

  • Концы хорды должны лежать на окружности по построению.
  • Для чертежей с касательной и хордой точка, задающая направление касательной, должна лежать на касательной. Для окружности с центром O и точкой касания A детерминированный помощник строится как T = Rotate(O, pi / 2, A) или с противоположным знаком; не используйте Rotate(A, pi / 2, O) — эта точка останется на окружности.
  • Для теоремы о центральном и вписанном углах создавайте явные объекты central = Angle(A, O, B) и inscribed = Angle(A, C, B) и используйте запрошенные подписи вроде $2\\alpha$ и $\\alpha$. Оставляйте стороны углов видимыми; не прячьте все OA, OB, CA и CB после создания дуг углов.
  • Для секторов рисуйте два радиуса, хорду и дугу, когда эти объекты запрошены. Используйте каноническое имя фабрики CircleSector(O, A, B) для кругового сектора, даже если есть псевдонимы. Создавайте и подписывайте центральный Angle(A, O, B), когда запрос или проверки требуют подписи центрального угла.
  • Для чертежа с центром описанной окружности прямоугольного треугольника, если равные радиусы запрошены или проверяются, нарисуйте все нужные отрезки радиусов от середины гипотенузы к вершинам треугольника и отметьте их одинаковым tick_count.
  • Для радикальной оси двух окружностей, если отношение к центрам входит в запрос или проверки, нарисуйте линию центров или отрезок Segment(O1, O2) как вспомогательную геометрию в дополнение к радикальной оси. Для чертежей с радикальным центром трёх окружностей, где нужны общие хорды, выводите каждую радикальную ось из двух пересечений окружностей: P12, Q12 = Intersect(c1, c2), затем rad12 = Line(P12, Q12) или видимый отрезок между этими точками. Не заменяйте общие хорды вручную решёнными уравнениями прямых.
  • Для чертежей о степени точки с секущими получайте точки пересечения через Intersect(circle, secant_line) и используйте явные математические подписи с индексами — $A_1$, $B_1$, $C_1$, $D_1$, — когда индексированные подписи запрошены. Не переиспользуйте имена вспомогательных точек для итоговых точек пересечения.
  • Для чертежа к теореме о касательной и секущей включите отрезок касательной, конечный отрезок секущей через обе точки пересечения с окружностью и краткую видимую подпись теоремы вроде $PT^2 = PA \\cdot PB$, привязанную к подходящему смысловому носителю рядом. Не добавляйте длинных пояснений текстом на чертеже.
  • Для поляры точки относительно окружности используйте смысловую команду polar = Polar(P, circle), когда запрос или проверки этого требуют. Можно дополнительно получить точки касания и показать, что polar проходит через них.
  • Если в запросе сказано, что две равные окружности имеют центры A и B, то A и B — именно центры, а не автоматически точки на противоположных окружностях. Используйте явный общий радиус или скрытый вспомогательный радиус, когда это нужно.
  • Для ромба из двух равных окружностей с центрами A и B используйте явное значение или помощник общего радиуса, если в запросе не сказано, что каждый центр лежит на противоположной окружности. Отметьте четыре стороны ромба равными через tick_count; не показывайте отрезки радиусов, если запрос просит этого не делать. Если AB — данная диагональ и C, D = Intersect(cA, cB), порядок вершин ромба — A-C-B-D, а равные стороны — AC, CB, BD и DA. Не отмечайте диагональ AB как одну из равных сторон. Если в запросе явно сказано не предполагать, что центры лежат на противоположных окружностях, не задавайте радиус равным Distance(A, B); выберите независимый общий радиус больше половины AB.
  • Не показывайте отрезки радиусов для построений циркулем и равными окружностями, если радиусы не запрошены и не нужны для объяснения касания или прямого угла.
  • Касательные из внешней точки: постройте касательные прямые, получите точки касания, спрячьте бесконечные вспомогательные прямые, если видимыми должны быть конечные отрезки касательных, и покажите эти отрезки.
  • Для запроса о касательных из внешней точки к окружности используйте t1, t2 = Tangent(P, circle), когда ожидаются две касательные прямые. Получите точки касания через T1 = Intersect(t1, circle) и T2 = Intersect(t2, circle), затем нарисуйте конечные отрезки PT1 и PT2. Спрячьте бесконечные касательные прямые, если видимыми должны быть конечные отрезки. Не заменяйте Tangent(P, circle) ручным построением через вспомогательную окружность.
  • Точки касания обычно стоит сопровождать маркерами прямого угла между радиусом и касательной, когда чертёж объясняющий. Вершина прямого угла — это точка касания, поэтому используйте Angle(P, T1, O) и Angle(P, T2, O) (либо с переставленными сторонами), а не Angle(T1, O, P). Кроме того, нарисуйте отрезки радиусов OT1 и OT2 видимыми, обычно через color.aux, чтобы у маркеров прямого угла были видимые стороны.
  • Радикальная ось пересекающихся окружностей: проведите прямую через их две точки пересечения; это же и есть общая хорда.
  • Поляра внешней точки относительно окружности или кривой второго порядка должна совпадать с хордой касания, соединяющей точки касания.
  • Для восстановления центра окружности по хордам пересеките серединные перпендикуляры двух хорд и спрячьте несмысловые вспомогательные центры, которые нужны были только для задания проверочной окружности.
  • Запрос про сектор обычно подразумевает два радиуса и центральный угол. Добавляйте несущую окружность или дугу, когда это помогает читать сектор как часть круга, оставляя её второстепенной, если фокус — сектор. Если запрос или проверки требуют подписать центральный угол, дуги угла или tick_count недостаточно: задайте явный label_text у central_angle = Angle(A, O, B) — например, $\\theta$ или $120^\\circ$.

Преобразования

  • Отражение: используйте Reflect(point, line) или Mirror(point, line) для осевой симметрии и подписывайте образы штрихами, когда об этом просят. Если требуется соответствие, нарисуйте видимые отрезки от каждой исходной точки к её образу — например, AA1 = Segment(A, A1) — как вспомогательную геометрию.
  • Инверсия относительно окружности: Reflect(point, circle) — текущая операция DSL для инверсии.
  • Поворот: используйте Rotate(point, angle, center) и показывайте представительный угол или дугу, когда это помогает объяснить движение. Чтобы скопировать угол на целевой луч поворотом, создайте исходный Angle(...), поверните точку целевого луча вокруг целевой вершины на этот угол, нарисуйте скопированный луч или отрезок и отметьте исходный и скопированный углы одинаковыми засечками.
  • Перенос: используйте Vector(P, Q) и переносите нужные объекты через Translate(object, vector).
  • Гомотетия: используйте Dilate(object, k, center), например A1 = Dilate(A, k, O); она преобразует и отрезки, прямые, окружности, многоугольники, кривые второго порядка, функции и неявные кривые. Для точек равнозначна прямая арифметика прокси A1 = O + k * (A - O). Не полагайтесь на координатные поля вроде A.x и A.y внутри нового Point(...).

Кривые второго порядка и функции

  • Эллипс и гипербола: предпочитайте фокальные конструкторы, когда в запросе упоминаются фокусы; используйте Center, Focus, Vertex, MajorAxis и MinorAxis для смысловых структурных элементов. Для фокальных радиусов берите произвольную точку кривой, а не особую симметричную, если симметрия не входит в запрос. Если точка на эллипсе нужна для фокальных радиусов, выведите её из пересечения со смысловой осью или вспомогательной прямой — например, используйте minor_axis = MinorAxis(ellipse) и P, P2 = Intersect(ellipse, minor_axis); не поворачивайте фокус вокруг центра в надежде, что результат окажется на эллипсе. Если фокусы запрошены, оставляйте их видимыми и подписывайте явно — например, $F_1$ и $F_2$; не прячьте их после подписывания. Когда обе оси кривой должны быть отдельными отрисовываемыми объектами, создавайте их явными смысловыми помощниками: major_axis = MajorAxis(ellipse) и minor_axis = MinorAxis(ellipse). Не предполагайте, что помощник во множественном числе можно распаковать в несколько прокси.
  • Парабола: используйте Parabola(focus, directrix), Center(parabola) для вершины по текущему соглашению и Axes(parabola) для оси симметрии. Если директриса d запрошена, оставьте прямую видимой и подписанной; прячьте только вспомогательные точки, которые нужны были для её задания. Для фокальной хорды, перпендикулярной оси, нарисуйте конечный отрезок хорды через фокус и добавьте маркер прямого угла между осью и хордой в фокусе.
  • Кривая по пяти точкам: используйте Conic(A, B, C, D, E) только с невырожденным выбором точек.
  • Функции и неявные кривые следует создавать из явных строк уравнений. Отмечайте пересечения через Intersect, а не приближайте их вручную. Синтаксис формул в Function("y = ..."), Conic("..."), ImplicitCurve("..."), Line("y = ...") и строках f(x) = ...: ^ — степень, знак * можно опускать (2x, k x, 2(x + 1), (x + 1)(x - 1)); x(A) и y(A) без пробела перед скобкой — координаты точки A, поэтому произведение с x пишите как x*(x + 1); другую функцию вызывают по имени (h(x) = g(x) + 1); вызывать можно математические функции sin, cos, tan, cot, sec, csc, asin, acos, atan, sqrt, exp, log, ln, abs, floor, ceiling, round, min, max и подобные — любое другое вызываемое имя должно быть объектом конструкции; pi и π — это π, ℯ — число Эйлера везде (2π, πx — произведения); e — число Эйлера в функциях и неявных кривых, но обычное имя в уравнении коники или прямой; −, ·, × и ÷ читаются как -, *, * и /. Формула следует за числами, логическими значениями, точками и функциями, которые в ней названы, поэтому определяйте их до формулы. Уравнение коники или прямой должно быть многочленом в том виде, как записано. Line читает как уравнение только строковый литерал; Line(A, B) и Line(P, base_line) строят прямые как обычно. Когда запрошено несколько пересечений, распакуйте Intersect(...) в именованные переменные точек и стилизуйте эти точки по отдельности или как именованную группу.
  • Для дуги описанной окружности через три точки используйте CircumcircleArc(A, B, C), когда этого требуют запрос или проверки. Оставляйте всю описанную окружность видимой как вспомогательную геометрию, если она запрошена как помощник; в этом случае не прячьте её.
  • Чертежи с единичной окружностью и тригонометрией подразумевают координатные оси, если запрос явно не просит чисто геометрический чертёж окружности. Размещайте подписи синуса и косинуса на соответствующих видимых отрезках проекций, а не только на основаниях проекций. Например, подпишите горизонтальный отрезок, представляющий косинус, через label_text="$\\cos\\theta$", а вертикальный отрезок синуса — через label_text="$\\sin\\theta$". Постройте или покажите координатные оси, чтобы у слова «проекция» была видимая точка отсчёта.
  • Для чертежей с концентрическими окружностями и кольцами внутренний и внешний радиусы — смысловые объекты. Нарисуйте отрезки радиусов и подпишите именно их явными значениями label_text вроде $r_{in}$ и $r_{out}$ или $r_1$ и $r_2$; одних подписей у концов недостаточно, чтобы понять, о каком радиусе речь.

Измерения и текстовый вывод

Создать объекты Area(...), Perimeter(...), Length(...) или Distance(...) недостаточно, когда пользователь просит показать измерение. Значение должно быть где-то видно на чертеже.

Для простых подписей длины, угла, радиуса окружности и площади многоугольника предпочитайте label_mode="value" или label_mode="label_value" на самом измеряемом отрисовываемом элементе.

Объекты Measure сами по себе — числовые прокси, а не самостоятельные отрисовываемые подписи. Не рассчитывайте, что style(area_measure, label_visible=True, ...) или style(perimeter_measure, ...) покажут значение. Привяжите значение к видимому смысловому носителю — многоугольнику, стороне, радиусу или соседнему отрезку.

Не задавайте на одном элементе два конкурирующих значения label_text, чтобы показать два разных измерения: более поздний вызов стиля перебьёт более раннюю подпись. Используйте по одному носителю на каждую видимую подпись — либо объедините сведения в одну лаконичную подпись, если так действительно понятнее.

Для сводных величин вроде периметра в текущем публичном DSL нет идеального самостоятельного объекта живого текста. Если статическая видимая подпись приемлема, прочитайте число из прокси измерения через .data.value и привяжите лаконичную подпись к ближайшему смысловому элементу-носителю. Не форматируйте ElementProxy напрямую как число.

Когда вы вручную форматируете значение измерения в label_text, не задавайте на этом же носителе label_mode="value" или label_mode="label_value". Текст подписи уже содержит нужное число, а режим числовой подписи может дописать собственное значение элемента-носителя вместо сводного измерения.

Если запрошенное самостоятельное значение нельзя представить чисто, не опускайте его молча. Включите ближайшее видимое представление и зафиксируйте ограничение в notes.

Подписи LaTeX

Значения label_text — это строковые литералы Python внутри DSL. Экранируйте обратные косые черты LaTeX для Python, иначе последовательности вроде alpha, beta, theta и команд circ могут превратиться в управляющие символы ещё до того, как их увидит рендерер.

Внутри сгенерированного JSON строка JSON должна раскодироваться в исходник DSL, в котором нужные экранирования Python сохранены.