Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ClaudeRevit.CliTests/ClaudeRevit.CliTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<AssemblyName>codex</AssemblyName><RollForward>Major</RollForward>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\ClaudeRevit\Services\TaskJournal.cs" Link="TaskJournal.cs" />
<Compile Include="..\ClaudeRevit\Services\CodexUsage.cs" Link="CodexUsage.cs" />
<Compile Include="..\ClaudeRevit\Services\ClaudeCodeBackend.cs" Link="ClaudeCodeBackend.cs" />
<Compile Include="..\ClaudeRevit\Services\ClaudeModels.cs" Link="ClaudeModels.cs" />
Expand Down
3 changes: 3 additions & 0 deletions ClaudeRevit.NativeTests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,6 @@ catalogue description or the presence of a fixture generator cannot do so.
See [executed October 2026 cases](../docs/native-acceptance-2026-10.md) for results
and the [BIMStarter findings](../docs/bimstarter-reference-cases.md) for expected
minimum-count behavior and remaining visibility checks.


`review_fixes` also contains the four independent October 20 audit cases (total multilayer width, derived Length units, material density and placed zero-area rooms). See [audit validation notes](../docs/audit-fixes-2026-10-20.md); their native execution is pending and is not implied by a successful build.
82 changes: 82 additions & 0 deletions ClaudeRevit.NativeTests/ReviewFixtures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,91 @@ void Check(string name, Func<object> body)
Check("change_element_type_actually_changes_type", () => WithProject(app, ChangeTypeSticks));
Check("execute_csharp_result_serialization_never_throws", () => WithProject(app, ScriptResultsSerialize));
Check("set_parameter_reaches_material_named_like_a_type", () => WithProject(app, MaterialBeatsSameNamedType));
Check("audit_total_wall_thickness", () => WithProject(app, TotalThickness));
Check("audit_derived_length_internal_units", () => WithProject(app, DerivedLength));
Check("audit_material_density_1234", () => WithProject(app, MaterialDensity));
Check("audit_placed_unbounded_room_survives_cleanup", () => WithProject(app, PlacedRoom));
return new { passed = checks.Count(c => (bool)c.GetType().GetProperty("ok")!.GetValue(c)!), total = checks.Count, checks };
}

private static object TotalThickness(UIApplication app, Document doc)
{
using var tx = new Transaction(doc, "QA total width"); tx.Start();
var original = new FilteredElementCollector(doc).OfClass(typeof(WallType)).Cast<WallType>().First(t => t.Kind == WallKind.Basic);
var basis = (WallType)original.Duplicate("QA_Multilayer_Base");
var structure = CompoundStructure.CreateSimpleCompoundStructure(new List<CompoundStructureLayer>
{
new(20 / 304.8, MaterialFunctionAssignment.Finish1, ElementId.InvalidElementId),
new(200 / 304.8, MaterialFunctionAssignment.Structure, ElementId.InvalidElementId),
new(30 / 304.8, MaterialFunctionAssignment.Finish2, ElementId.InvalidElementId)
});
structure.SetNumberOfShellLayers(ShellLayerType.Exterior, 1);
structure.SetNumberOfShellLayers(ShellLayerType.Interior, 1);
basis.SetCompoundStructure(structure);
using var result = JsonDocument.Parse(new CreateWallType().Execute(SyntheticFixtures.Args(new { name = "QA_Total_300", based_on = basis.Name, thickness_mm = 300 }), app));
var created = (WallType)doc.GetElement(new ElementId(result.RootElement.GetProperty("id").GetInt64()));
doc.Regenerate();
var actual = created.Width * 304.8;
if (Math.Abs(actual - 300) > .001) throw new InvalidOperationException($"Expected total 300 mm, got {actual}.");
tx.RollBack(); return new { expected_mm = 300, actual_mm = actual };
}

private static object DerivedLength(UIApplication app, Document doc)
{
using var tx = new Transaction(doc, "QA derive length"); tx.Start();
var (wall, _, _) = WallWithTwoTypes(doc, "QA_Derive_Type");
doc.Regenerate();
var parameter = wall.get_Parameter(BuiltInParameter.WALL_BASE_OFFSET);
var expectedFeet = ((LocationCurve)wall.Location).Curve.Length;
using var result = JsonDocument.Parse(new DeriveParameters().Execute(SyntheticFixtures.Args(new
{
element_ids = new[] { wall.Id.Value }, parameter_name = parameter.Definition.Name, source = "length_mm", round = 6
}), app));
doc.Regenerate();
var actualFeet = parameter.AsDouble();
if (Math.Abs(actualFeet - expectedFeet) > 1e-7) throw new InvalidOperationException($"Expected {expectedFeet} ft, got {actualFeet} ft.");
tx.RollBack(); return new { expected_feet = expectedFeet, actual_feet = actualFeet };
}

private static object MaterialDensity(UIApplication app, Document doc)
{
// 1234 kg/m3 is no default density in CalculateWeight, so only a correctly read material
// asset gives this mass; Revit converts the value itself, independently of Units.cs.
using var tx = new Transaction(doc, "QA density"); tx.Start();
var materialId = Material.Create(doc, "QA_Density_1234");
var material = (Material)doc.GetElement(materialId);
var asset = new StructuralAsset("QA_Density_Asset", StructuralAssetClass.Generic)
{ Density = UnitUtils.ConvertToInternalUnits(1234, UnitTypeId.KilogramsPerCubicMeter) };
material.SetMaterialAspectByPropertySet(MaterialAspect.Structural, PropertySetElement.Create(doc, asset).Id);
var side = 1 / .3048;
var loop = CurveLoop.Create(new List<Curve>
{
Line.CreateBound(XYZ.Zero, new XYZ(side, 0, 0)), Line.CreateBound(new XYZ(side, 0, 0), new XYZ(side, side, 0)),
Line.CreateBound(new XYZ(side, side, 0), new XYZ(0, side, 0)), Line.CreateBound(new XYZ(0, side, 0), XYZ.Zero)
});
var solid = GeometryCreationUtilities.CreateExtrusionGeometry(new List<CurveLoop> { loop }, XYZ.BasisZ, side,
new SolidOptions(materialId, ElementId.InvalidElementId));
var shape = DirectShape.CreateElement(doc, new ElementId(BuiltInCategory.OST_GenericModel)); shape.SetShape(new GeometryObject[] { solid });
doc.Regenerate();
// The fallback density must not override the material's own.
using var result = JsonDocument.Parse(new CalculateWeight().Execute(SyntheticFixtures.Args(new { element_ids = new[] { shape.Id.Value }, density_kg_m3 = 9999 }), app));
var mass = result.RootElement.GetProperty("total_kg").GetDouble();
if (Math.Abs(mass - 1234) > .02) throw new InvalidOperationException($"A 1 m3 solid of a 1234 kg/m3 material must weigh 1234 kg, got {mass}.");
tx.RollBack(); return new { volume_m3 = 1, expected_kg = 1234, actual_kg = mass };
}

private static object PlacedRoom(UIApplication app, Document doc)
{
using var tx = new Transaction(doc, "QA unbounded room"); tx.Start();
var level = new FilteredElementCollector(doc).OfClass(typeof(Level)).Cast<Level>().First();
var room = doc.Create.NewRoom(level, new UV(1, 1)); doc.Regenerate();
if (room.Location == null || room.Area > 0) throw new InvalidOperationException("Fixture must be placed and unbounded.");
var id = room.Id;
var result = new CleanModel().Execute(SyntheticFixtures.Args(new { targets = new[] { "unplaced_rooms" }, apply = true }), app);
if (doc.GetElement(id) == null) throw new InvalidOperationException("Cleanup deleted a placed room.");
tx.RollBack(); return new { preserved = true, result };
}

private static object WithProject(UIApplication app, Func<UIApplication, Document, object> body)
{
Document? project = null;
Expand Down
3 changes: 3 additions & 0 deletions ClaudeRevit.PaneTests/ClaudeRevit.PaneTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
<RollForward>Major</RollForward>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\ClaudeRevit\UI\MarkdownProps.cs" Link="MarkdownProps.cs" />
<Compile Include="..\ClaudeRevit\UI\MarkdownInlineRenderer.cs" Link="MarkdownInlineRenderer.cs" />
<PackageReference Include="PdfPig" Version="0.1.16" />
<Compile Include="..\ClaudeRevit\Services\AttachmentReader.cs" Link="AttachmentReader.cs" />
<Compile Include="..\ClaudeRevit\Services\AttachmentStore.cs" Link="AttachmentStore.cs" />
<Compile Include="..\ClaudeRevit\Services\ChatHistory.cs" Link="ChatHistory.cs" />
<Compile Include="..\ClaudeRevit\Services\AttachmentImage.cs" Link="AttachmentImage.cs" />
<Compile Include="..\ClaudeRevit\Services\ChatRequestInbox.cs" Link="ChatRequestInbox.cs" />
<Page Include="..\ClaudeRevit\UI\ChatPaneView.xaml" Link="ChatPaneView.xaml" />
Expand Down
18 changes: 18 additions & 0 deletions ClaudeRevit.PaneTests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@

internal static class Program
{
private static async Task CheckStreamingMarkdown()
{
var target = new TextBlock(); MarkdownProps.SetMarkdown(target, "first");
bool renderedWhileStreaming = false;
for (var i = 0; i < 40; i++)
{
MarkdownProps.SetMarkdown(target, "delta " + i);
await Task.Delay(15);
if (i > 10 && string.Concat(target.Inlines.OfType<System.Windows.Documents.Run>().Select(r => r.Text)).StartsWith("delta", StringComparison.Ordinal)) renderedWhileStreaming = true;
}
Check(renderedWhileStreaming, "Continuous deltas starved markdown rendering");
await Task.Delay(150);
Check(string.Concat(target.Inlines.OfType<System.Windows.Documents.Run>().Select(r => r.Text)) == "delta 39", "Final streaming delta was not rendered");
}

[STAThread]
private static void Main(string[] args)
{
Expand All @@ -19,6 +34,7 @@ private static void Main(string[] args)
{
DocumentHarness.Change("a", false);
await CheckIndependentChat();
await CheckStreamingMarkdown();
var pane = new ChatPaneView();
CheckRepeatedDocumentWrappers(pane);
DocumentHarness.Change("a", false);
Expand Down Expand Up @@ -73,6 +89,8 @@ private static void Main(string[] args)
var noisePath = Path.Combine(attachmentRoot, "noise.png");
using (var output = File.Create(noisePath)) noisyEncoder.Save(output);
Check(new FileInfo(noisePath).Length > 5_000_000, "Dense image fixture did not exercise the image size cap");
var encoded = AttachmentImage.EncodePng(noisePath);
Check(Convert.ToBase64String(encoded).Length <= ChatBlockOrder.MaxInlineImageBase64, "Encoded image exceeds actual chat transport budget");
var smallNoise = await Task.Run(() => AttachmentImage.EncodePng(noisePath));
Check(smallNoise.Length <= 5_000_000, "Dense attachment PNG exceeded the native image content cap");
var attach = typeof(ChatPaneView).GetMethod("AttachFilesAsync", BindingFlags.NonPublic | BindingFlags.Instance)!;
Expand Down
7 changes: 1 addition & 6 deletions ClaudeRevit.PaneTests/Stubs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,5 @@ public static class ElementIdLinker
public static void SetText(DependencyObject target, string value) => target.SetValue(TextProperty, value);
public static string GetText(DependencyObject target) => (string)target.GetValue(TextProperty);
}
public static class MarkdownProps
{
public static readonly DependencyProperty MarkdownProperty = DependencyProperty.RegisterAttached("Markdown", typeof(string), typeof(MarkdownProps));
public static void SetMarkdown(DependencyObject target, string value) => target.SetValue(MarkdownProperty, value);
public static string GetMarkdown(DependencyObject target) => (string)target.GetValue(MarkdownProperty);
}

}
Loading
Loading