feat: drawing_create_standard_views — упрочнение по ревью Codex реализации
#1: непустота проверяется у ВНОВЬ созданных видов (разница IView.Number), не по всей коллекции — прежняя геометрия чертежа не маскирует пустые новые виды. #3: откат созданных видов (IView.Delete) при created!=3 или пустых видах. #7: unit-тест нормализации кириллического пути. #4: повторный вызов (дубликаты) отражён в описании инструмента. #5/#6/#2 — по обоснованию (release RCW отклонён v2-2). Тесты: 116 unit + 61 integration = 177 зелёных. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
# Дизайн: стандартные виды чертежа (drawing_create_standard_views) через API7
|
||||
|
||||
**Дата:** 2026-05-27
|
||||
**Статус:** спайк зелёный; ревью Codex учтено (10 замечаний); реализуется
|
||||
**Статус:** реализовано и проверено (3 интеграционных + 18 unit, всего 177); ревью Codex спека (10) и реализации (7) учтено
|
||||
|
||||
## Правки по ревью реализации Codex (7 замечаний)
|
||||
|
||||
- **#1** непустоту проверяем у ВНОВЬ созданных видов (по разнице `IView.Number` до/после), а не по
|
||||
всей коллекции — на непустом чертеже прежняя геометрия не маскирует пустые новые виды.
|
||||
- **#3** откат: при `created != 3` или пустых видах удаляем созданные виды (`IView.Delete`) → чертёж
|
||||
не остаётся в полу-состоянии.
|
||||
- **#2** ленивость `ObjectCount` опровергнута эмпирически (happy-тест зелёный = геометрия есть сразу,
|
||||
rebuild не нужен). **#7** добавлен unit-тест нормализации кириллического пути.
|
||||
- **#4** повторный вызов намеренно добавляет ещё набор (дубликаты в разных `x,y` легитимны) —
|
||||
отражено в описании инструмента. **#5** обход `IViews` уточнён (по `Number`); точечный release RCW
|
||||
отклонён (v2-2). **#6** доп. негативы (.a3d, пустой валидный .m3d) — маргинальны, отложены.
|
||||
|
||||
## Решения по ревью Codex
|
||||
|
||||
|
||||
@@ -47,24 +47,36 @@ public sealed class DrawingService
|
||||
throw new InvalidOperationException($"Файл модели пуст (не сохранён?): {path}");
|
||||
|
||||
var views = RequireActiveDrawingViews();
|
||||
var before = views.Count;
|
||||
// Снимок номеров существующих видов — чтобы потом отличить созданные ИМЕННО этим вызовом
|
||||
// (на чертеже уже есть системный вид и могут быть прежние виды).
|
||||
var beforeNumbers = CollectViewNumbers(views);
|
||||
|
||||
if (!views.AddStandartViews(path, "#Спереди", StandardProjections, x, y, scale, GapMm, GapMm))
|
||||
throw new InvalidOperationException(
|
||||
$"AddStandartViews вернул FALSE — виды не созданы (файл модели или проекция недоступны): {path}");
|
||||
|
||||
var created = views.Count - before;
|
||||
if (created != ExpectedCreated)
|
||||
throw new InvalidOperationException(
|
||||
$"Ожидалось {ExpectedCreated} вида, создано {created} (было {before}, стало {views.Count}).");
|
||||
var newViews = CollectNewViews(views, beforeNumbers);
|
||||
try
|
||||
{
|
||||
if (newViews.Count != ExpectedCreated)
|
||||
throw new InvalidOperationException(
|
||||
$"Ожидалось {ExpectedCreated} вида, создано {newViews.Count}.");
|
||||
|
||||
// Гарантия непустоты: у спроецированных видов должна быть геометрия (иначе модель не
|
||||
// спроецировалась — пустые виды). Системный вид #0 имеет ObjectCount 0, поэтому суммируем.
|
||||
if (TotalObjectCount(views) <= 0)
|
||||
throw new InvalidOperationException(
|
||||
"Стандартные виды созданы, но пусты (модель не спроецировалась). Проверьте файл модели.");
|
||||
// Гарантия непустоты: у созданных видов должна быть геометрия (иначе модель не
|
||||
// спроецировалась). Проверяем ИМЕННО новые виды, не всю коллекцию.
|
||||
if (newViews.Sum(v => v.ObjectCount) <= 0)
|
||||
throw new InvalidOperationException(
|
||||
"Стандартные виды созданы, но пусты (модель не спроецировалась). Проверьте файл модели.");
|
||||
|
||||
return new DrawingViewsResult { Created = created, Total = views.Count };
|
||||
return new DrawingViewsResult { Created = newViews.Count, Total = views.Count };
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Откат: удаляем виды, созданные этим вызовом, чтобы чертёж не остался в полу-состоянии.
|
||||
foreach (var v in newViews)
|
||||
try { v.Delete(); } catch { /* лучшее усилие */ }
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private IViews RequireActiveDrawingViews()
|
||||
@@ -80,12 +92,21 @@ public sealed class DrawingService
|
||||
?? throw new InvalidOperationException("Не удалось получить коллекцию видов чертежа.");
|
||||
}
|
||||
|
||||
private static int TotalObjectCount(IViews views)
|
||||
private static HashSet<int> CollectViewNumbers(IViews views)
|
||||
{
|
||||
int sum = 0;
|
||||
var set = new HashSet<int>();
|
||||
foreach (var o in views)
|
||||
if (o is IView v)
|
||||
sum += v.ObjectCount;
|
||||
return sum;
|
||||
set.Add(v.Number);
|
||||
return set;
|
||||
}
|
||||
|
||||
private static List<IView> CollectNewViews(IViews views, HashSet<int> beforeNumbers)
|
||||
{
|
||||
var list = new List<IView>();
|
||||
foreach (var o in views)
|
||||
if (o is IView v && !beforeNumbers.Contains(v.Number))
|
||||
list.Add(v);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing)
|
||||
"сохранённой 3D-модели (.m3d деталь / .a3d сборка). Активный документ должен быть чертежом " +
|
||||
"(document_create drawing); модель должна быть сохранена в файл (document_save_as). " +
|
||||
"scale — масштаб (1.0 = 1:1); x,y — точка привязки главного вида на листе (мм). " +
|
||||
"Повторный вызов добавит ещё один набор видов — задайте другие x,y, чтобы не накладывались. " +
|
||||
"Возвращает число созданных видов.")]
|
||||
public async Task<string> CreateStandardViews(
|
||||
[Description("Путь к файлу 3D-модели (.m3d/.a3d)")] string partFilePath,
|
||||
|
||||
@@ -23,6 +23,7 @@ public sealed class DrawingValidationTests
|
||||
[InlineData(@"C:\m\part.m3d", ".m3d")]
|
||||
[InlineData(@"C:\m\frame.a3d", ".a3d")]
|
||||
[InlineData(@" C:\m\part.M3D ", ".m3d")]
|
||||
[InlineData(@"C:\модели\деталь.m3d", ".m3d")] // кириллица в пути
|
||||
public void NormalizeModelPath_accepts_model_files(string path, string expectedExt)
|
||||
{
|
||||
var result = DrawingValidation.NormalizeModelPath(path);
|
||||
@@ -30,6 +31,14 @@ public sealed class DrawingValidationTests
|
||||
Assert.EndsWith(expectedExt, result, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizeModelPath_preserves_cyrillic()
|
||||
{
|
||||
var result = DrawingValidation.NormalizeModelPath(@"C:\модели\деталь.m3d");
|
||||
Assert.Contains("деталь", result);
|
||||
Assert.Contains("модели", result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
|
||||
Reference in New Issue
Block a user