Skip to content

Commit 26535cf

Browse files
Improve Snippets catalogs, editor behavior, rendering stability, and regression coverage
1 parent 12c2c36 commit 26535cf

95 files changed

Lines changed: 23330 additions & 30754 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
## 2.9.1 - 2026-10-20
4+
5+
- Embed all 15 drilling-cycle SVG diagrams in Qt resources, package the Snippets SVG sources, and verify embedded resources against the originals.
6+
- Add a self-contained drilling-cycle SVG generator based on the approved single-drill drawing. Preserve geometry and arrows, changing only controller-specific parameter labels without numeric defaults.
7+
- Refactor Snippets into controller-specific catalogs, separating default templates from SQLite persistence and rendering. Restore missing FANUC and SINUMERIK templates for dwell, threading, turning cycles, plane selection, and controller-specific home return.
8+
- Restore FANUC G-code System B templates with automatic catalog selection based on the active lathe configuration. Seed new defaults without overwriting user-modified snippets or recreating deleted entries. Add catalog, rendering, and GUI regression tests.
9+
- Include Snippets packages in setuptools distributions, correct FANUC Type B program initialization and turning-cycle classification, and migrate Type B illustrations into existing libraries without replacing user images or restoring removed ones.
10+
- Preserve the active Tool Library SQLite connection when database switching fails, close connections after failed schema initialization, and test switching failures and incompatible schemas.
11+
- Keep the editor operational when the Snippets database is corrupted, locked, or incompatible. Disable unavailable Snippets actions and cover startup failures with regression tests.
12+
- Publish execution results, tool setup, playback indices, and plot geometry atomically after cancellable preparation. Preserve the previous consistent scene when preparation is cancelled or its source becomes outdated, including worker cancellation, Stop, window closing, and trace resampling. Display the current execution duration.
13+
- Simplify block numbering: insert, replace, and remove block prefixes while respecting program delimiters, headers, dialect-specific variables, indentation, and optional-block prefixes. Preserve jump and contour references.
14+
- Apply configured tool-recognition rules in the Tool Library dialog, report export failures, and pass the document's actual encoding to the CLI after legacy-encoding fallback.
15+
- Normalize equivalent literal G-code spellings consistently across Python and native tool discovery, including `G020`, `G20.0`, and `G081`. Preserve recognized tool geometry across repeated Heidenhain `TOOL CALL` statements, allowing later explicit descriptions to refine unspecified tools.
16+
- Insert calculator output without splitting the current editor block. Limit hole patterns to 20,000 points before allocation, report invalid previews, and enable insertion only when input and document state are valid.
17+
- Correct mirrored STL wireframe edges and ruler picking using shared transformed geometry. Reject arrays exceeding 1,000 objects or 2,000,000 triangles before allocation.
18+
- Preflight batch export and HTML report destinations against discovered inputs, other outputs, and batch manifests, resolving directory aliases while allowing safe output paths above the input directory.
19+
- Restore clean Pylint checks for Snippets and Tool Library tests by moving imports to module scope and isolating connection caches without modifying application state.
20+
- Update English and Russian documentation for calculator and STL limits, insertion behavior, and block numbering. Regenerate landing pages for the current version and remove hardcoded release versions from download-link regression tests.
21+
322
## 2.9.0 - 2026-10-19
423

524
- Fix Open in CLI through cmd.exe when the PowerShell executable path contains spaces by preserving cmd's command-line quoting without double escaping.

‎FAQ.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,8 @@ On **Transform**, select an axis and angle to rotate around the current base poi
18341834

18351835
**Rectangular array** creates copies along X, Y and Z. Set the copy count and step for each axis; the resulting array includes the original object. Array creation is one STL history action and can be undone.
18361836

1837+
Array creation is rejected if the resulting scene would exceed **1,000 objects** or **2,000,000 triangles**, including objects already in the scene. The limit is checked before copies are built; rejection leaves the scene and its undo history unchanged.
1838+
18371839
#### How do I make a 3D section through an STL model?
18381840

18391841
On **Section**, choose X, Y or Z for the cutting plane, enter its coordinate and choose which side to keep. The initial coordinate is the midpoint of the selected model's world-space bounding box along that axis. **Apply section** clips the solid in 3D and closes the cut where the mesh produces a closed contour; the cut model remains visible over the source toolpath. **Clear section** restores the full STL. Applying and clearing a section are undoable STL scene actions.
@@ -1850,6 +1852,8 @@ Use **Edit → Uppercase** (`Ctrl+Shift+U`) or **Edit → Lowercase** (`Ctrl+U`)
18501852

18511853
Select one or more blocks and press **Ctrl+/** to add `/` at the start of each selected nonempty block. Press **Ctrl+Shift+/** to remove one leading `/` from each selected block. Without a selection, these commands do nothing. Both shortcuts are fixed and cannot be assigned to another command. The slash is the FANUC optional block skip marker; whether such blocks execute depends on the Block Skip setting.
18521854

1855+
The editor's **Renumber** command adds or replaces block prefixes in the selected lines, or the entire document without a selection. It skips `%` delimiters, `O` program headers and dialect-specific variable lines: `#` for FANUC, `R` for SINUMERIK and `Q` for iTNC. Jump and contour references remain unchanged; their presence does not block the command. **Remove Numbers** removes block prefixes without rejecting programs or rewriting references, preserves optional `/` markers and accepts indented or lowercase `N` prefixes. Klartext block prefixes are handled separately from `LBL` labels.
1856+
18531857
### How do I print the plot?
18541858

18551859
Use **File → Print** (`Ctrl+P`) or enable its optional File-toolbar button through Customize Toolbars. The app prints sharp vector lines in the current camera orientation, fitted to a landscape page. Printing ignores the playback slider and does not hide lines behind an imported STL. In **Settings → Options → Plot**, you can show or hide rapid moves, render them dashed or solid, and color all moves by tool; Print follows those choices. **FAQ** opens with `F3`, and **Grid** toggles with `F4` by default.
@@ -1929,6 +1933,8 @@ Hole and Pocket Calculator are milling-only. Snippets is available in both machi
19291933

19301934
It generates coordinate blocks for circular or rectangular/grid hole patterns. It does not invent the complete surrounding drilling-cycle safety logic.
19311935

1936+
Patterns are limited to **20,000 points** in both the preview and generated fragment; for a grid, the limit applies to the product of the X and Y counts. An excessive pattern clears the preview and disables **Insert**, with the reason in the button's tooltip. Reduce the counts to continue. With no selection, insertion inside an existing block places the fragment before that whole block, preserving its commands.
1937+
19321938
### What does Pocket Calculator generate?
19331939

19341940
It can generate circular or rectangular milling fragments with configurable stepover, depths, safe/reference Z, feed, direction, clearing strategy, helical entry and finishing pass.

‎FAQ_RU.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,8 @@ OD, ID и Face — это фильтры UI для доступных ориен
18361836

18371837
**Прямоугольный массив** создаёт копии вдоль X, Y и Z. Задайте количество копий и шаг для каждой оси, затем нажмите **Создать**. Исходный объект входит в массив. Создание массива считается одним действием истории STL и отменяется одной командой.
18381838

1839+
Создание массива отклоняется, если итоговая сцена превысит **1 000 объектов** или **2 000 000 треугольников**, включая уже находящиеся в ней объекты. Проверка выполняется до построения копий; при отказе сцена и история отмены не меняются.
1840+
18391841
#### Как выполнить 3D-сечение модели STL?
18401842

18411843
На странице **Сечение** выберите ось X, Y или Z, координату плоскости и сторону, которую нужно оставить. Изначально координата равна середине ограничивающего параллелепипеда выбранной модели в мировых координатах. Кнопка **Применить** обрезает сплошную модель в 3D и закрывает срез там, где сетка образует замкнутый контур; результат остаётся видим поверх исходной траектории. Кнопка **Очистить** возвращает целую STL-модель. Применение и очистка сечения отменяются через историю STL.
@@ -1852,6 +1854,8 @@ OD, ID и Face — это фильтры UI для доступных ориен
18521854

18531855
Выберите один или несколько блоков и нажмите **Ctrl+/**, чтобы добавить `/` в начало каждого выделенного непустого блока. Нажмите **Ctrl+Shift+/**, чтобы удалить один ведущий `/` из каждого выделенного блока. Без выделения эти команды ничего не делают. Оба сочетания клавиш фиксированы и не могут быть назначены другой команде. Косая черта — это маркер пропуска необязательного блока FANUC; выполняются ли такие блоки, зависит от настройки пропуска блоков.
18541856

1857+
Команда редактора **Перенумерация** добавляет или заменяет номера кадров в выделенных строках, а без выделения — во всём документе. Она пропускает разделители `%`, заголовки программ `O` и строки переменных соответствующего диалекта: `#` для FANUC, `R` для SINUMERIK и `Q` для iTNC. Ссылки переходов и границы контуров остаются без изменений; их наличие не блокирует команду. **Удалить номера** удаляет префиксы кадров без отказа и без изменения ссылок, сохраняет маркеры `/` и учитывает отступы и строчный префикс `n`. Числовые префиксы кадров Klartext обрабатываются отдельно от меток `LBL`.
1858+
18551859
### Как распечатать график?
18561860

18571861
Используйте **Файл → Печать** (`Ctrl+P`) или включите кнопку «Печать» через «Настроить панели». Приложение печатает чёткие векторные линии в текущей ориентации камеры, вписанные в альбомную страницу. Печать игнорирует ползунок воспроизведения и не скрывает линии за импортированным STL. В **Настройки → Параметры → График** можно показывать или скрывать ускоренные перемещения, отображать их пунктирными или сплошными и раскрашивать все перемещения по инструменту; печать следует этим настройкам. **FAQ** открывается по `F3`, а **Сетка** по умолчанию переключается по `F4`.
@@ -1931,6 +1935,8 @@ Hole и Pocket Calculator только для фрезерования. Snippets
19311935

19321936
Он генерирует блоки координат для круговых или прямоугольных/сеточных patterns отверстий. Он не выдумывает полную окружающую логику безопасности цикла сверления.
19331937

1938+
Предпросмотр и генерируемый фрагмент ограничены **20 000 точек**; для сетки проверяется произведение количеств по X и Y. При превышении лимита предпросмотр очищается, а кнопка **Вставить** отключается с пояснением во всплывающей подсказке. Уменьшите количество отверстий, чтобы продолжить. Если выделения нет, вставка при каретке внутри существующего кадра помещает фрагмент перед всем этим кадром, сохраняя его команды.
1939+
19341940
### Что генерирует Pocket Calculator?
19351941

19361942
Он может генерировать круговые или прямоугольные фрагменты фрезерования с настраиваемым перекрытием, глубинами, безопасным/референсным Z, подачей, направлением, стратегией очистки, винтовым входом и чистовым проходом.

‎app/gcode/batch/analysis.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from app.gcode.execution.program import execute_program
1717
from app.gcode.execution.source import language_for_path, source_dialect_for_path
18-
from app.gcode.file_io import atomic_write_text
18+
from app.gcode.file_io import atomic_write_text, validate_batch_output_paths
1919
from app.gcode.kernel import Diagnostic, ExecutionResult
2020
from app.gcode.kernel.frontend.io import read_nc_text
2121
from app.gcode.reports.html import export_execution_statistics
@@ -341,6 +341,11 @@ def analyze_directory(
341341
label="HTML output",
342342
)
343343
paths = discover_nc_files(directory, recursive=recursive, extensions=normalized_extensions)
344+
if html_dir is not None:
345+
validate_batch_output_paths(
346+
paths,
347+
((Path(html_dir) / path.relative_to(directory)).with_name(path.name + ".html") for path in paths),
348+
)
344349
profiles = kinematics_by_file or {}
345350
actual = {path.relative_to(directory).as_posix() for path in paths}
346351
unknown = set(profiles) - actual

‎app/gcode/batch/export.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from app.gcode.execution.source import language_for_path, source_dialect_for_path
1717
from app.gcode.export.common import is_export_limitation
1818
from app.gcode.export.service import ExportRequest, export_file, request_document
19-
from app.gcode.file_io import atomic_write_text
19+
from app.gcode.file_io import atomic_write_text, validate_batch_output_paths
2020
from app.gcode.post_profiles import export_file_suffix
2121
from app.gcode.reports.kinematics import kinematics_report_fields
2222

@@ -134,6 +134,10 @@ def export_directory(
134134
]
135135
if duplicates:
136136
raise ValueError(f"Multiple inputs map to the same output: {duplicates[0]}")
137+
validate_batch_output_paths(
138+
paths,
139+
[*destinations, *(destination_root / f"{REPORT_BASENAME}.{suffix}" for suffix in ("json", "csv"))],
140+
)
137141
files = []
138142
for path, destination in zip(paths, destinations, strict=True):
139143
relative = path.relative_to(directory).as_posix()

‎app/gcode/file_io.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ def validate_output_paths(source, *outputs) -> None:
2525
raise ValueError("Output paths must differ from each other")
2626

2727

28+
def validate_batch_output_paths(sources, outputs) -> None:
29+
"""Protect every batch input and reject aliases before the first write."""
30+
source_paths = {Path(source).resolve() for source in sources}
31+
destinations = set()
32+
for output in outputs:
33+
destination = Path(output).resolve()
34+
if destination in source_paths:
35+
raise ValueError(f"Batch output would overwrite an input file: {output}")
36+
if destination in destinations:
37+
raise ValueError(f"Multiple inputs map to the same output: {output}")
38+
destinations.add(destination)
39+
40+
2841
def atomic_export(path, write, *, cancelled=None) -> int:
2942
target = Path(path)
3043
target.parent.mkdir(parents=True, exist_ok=True)

‎app/gcode/generators/holes.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from .formatting import _number, _signed_number
88
from .types import Point
99

10+
MAX_HOLE_POINTS = 20_000
11+
1012

1113
def _validate_finite(**values: float) -> None:
1214
for name, value in values.items():
@@ -35,6 +37,8 @@ def hole_circle_points(
3537
center_y=center_y,
3638
)
3739
_validate_count("count", count)
40+
if count > MAX_HOLE_POINTS:
41+
raise ValueError("Hole pattern exceeds the 20,000-point limit; reduce the hole count.")
3842
if diameter <= 0:
3943
raise ValueError("diameter must be positive.")
4044

@@ -92,6 +96,8 @@ def hole_grid_points(
9296
_validate_finite(start_x=start_x, start_y=start_y, step_x=step_x, step_y=step_y)
9397
_validate_count("count_x", count_x)
9498
_validate_count("count_y", count_y)
99+
if count_x * count_y > MAX_HOLE_POINTS:
100+
raise ValueError("Hole pattern exceeds the 20,000-point limit; reduce the grid counts.")
95101

96102
points: list[Point] = []
97103
for row in range(count_y):

0 commit comments

Comments
 (0)