Adding AI Render Variations to a CAD Application
Why I added OpenAI, Gemini, and MyArchitectAI to a CAD workflow, and the small architecture that kept the feature practical.
- CAD
- AI Rendering
- .NET
- OpenAI
- Gemini
Why I added it
This feature was not meant to replace real render tools like V-Ray, D5, or Blender. The goal was much narrower: some customers want to finish a design, generate a near-render-quality preview quickly, and show it to their own client without leaving the CAD workflow.
That made AI rendering a useful middle layer. The CAD application already knows the geometry, camera, and layout. If I could capture the current scene, send it to multiple image providers, and keep the prompt disciplined, users could get fast visual variations without turning the product into a full rendering engine.
The workflow I wanted
I kept the flow small on purpose. The user captures the current scene, picks a provider, writes a short request, receives a rendered variation, and can ask for one more revision from the latest result.
- Capture the active CAD scene as a clean reference image.
- Send the same logical request shape to OpenAI, Gemini, or MyArchitectAI.
- Keep geometry and camera constraints inside the prompt instead of relying on the model to guess.
- Save the latest result so it can be reviewed or exported back into the normal render folder.
Capture the scene first
The most important input is the scene image itself. Before sending anything to an AI provider, I capture the current viewport in a predictable size and hide helper visuals such as grid lines or axis icons.
csharp
CadSceneCapture.cs
The exact viewport type can change, but the idea is to capture one clean image that represents the current design state.
public static SceneCapture Capture(IDesignViewport viewport, string aspectRatio)
{
Size size = aspectRatio switch
{
"1:1" => new Size(1200, 1200),
"9:16" => new Size(900, 1600),
_ => new Size(1600, 900)
};
bool oldGrid = viewport.GridVisible;
bool oldAxis = viewport.AxisIconVisible;
try
{
viewport.GridVisible = false;
viewport.AxisIconVisible = false;
using Bitmap bitmap = viewport.RenderToBitmap(size);
using MemoryStream stream = new();
bitmap.Save(stream, ImageFormat.Png);
return new SceneCapture
{
ImageBytes = stream.ToArray(),
MimeType = "image/png",
AspectRatio = aspectRatio
};
}
finally
{
viewport.GridVisible = oldGrid;
viewport.AxisIconVisible = oldAxis;
}
}Keep providers behind one contract
The integration becomes easier to maintain if every provider returns the same result shape. That lets the UI and conversation flow stay provider-agnostic while only the HTTP payload details change underneath.
csharp
IAiRenderProvider.cs
This is the part that kept OpenAI, Gemini, and MyArchitectAI swappable instead of leaking provider-specific details into the dialog.
public interface IAiRenderProvider
{
Task<RenderResult> GenerateAsync(RenderRequest request, CancellationToken cancellationToken);
}
public sealed class AiRenderProviderFactory
{
public IAiRenderProvider Create(RenderProvider provider) => provider switch
{
RenderProvider.OpenAI => new OpenAiRenderProvider(),
RenderProvider.Gemini => new GeminiRenderProvider(),
RenderProvider.MyArchitectAI => new MyArchitectRenderProvider(),
_ => throw new NotSupportedException()
};
}
public sealed class RenderRequest
{
public required SceneCapture Scene { get; init; }
public required string UserPrompt { get; init; }
public required IReadOnlyList<ConversationMessage> History { get; init; }
}
public sealed class RenderResult
{
public required byte[] ImageBytes { get; init; }
public required string MimeType { get; init; }
public string? TextResponse { get; init; }
}Use prompt constraints to protect the design
A generic prompt like 'make this realistic' is usually not enough. In a CAD workflow, the important thing is preserving layout, cabinet geometry, proportions, and camera framing while improving materials, lighting, and presentation.
For revision turns, I also pass the previous AI render as an extra reference so the next result behaves more like a refinement than a restart.
csharp
RenderPromptComposer.cs
The useful part is not the exact wording. The useful part is forcing the model to respect the design before it starts stylizing the image.
public static string BuildPrompt(RenderRequest request, bool hasPreviousRender)
{
StringBuilder sb = new();
sb.AppendLine("Preserve the cabinet geometry, proportions, camera framing, and overall layout from the CAD reference scene.");
sb.AppendLine("Improve materials, lighting, shadows, and presentation quality.");
sb.AppendLine();
sb.AppendLine("Reference image 1 is the original CAD scene screenshot.");
if (hasPreviousRender)
{
sb.AppendLine("Reference image 2 is the latest AI render. Refine it without changing the design logic from image 1.");
}
sb.AppendLine();
sb.AppendLine($"User request: {request.UserPrompt}");
sb.AppendLine("Return a single polished image.");
return sb.ToString();
}Result handling matters too
The rendering call itself is only half of the feature. I also wanted the result to feel usable immediately, so the latest image is kept in the conversation, shown in a preview area, and can be exported into the normal render/output folder.
csharp
RenderResultExport.cs
A simple export step makes the AI result feel like part of the real workflow instead of a temporary experiment window.
public string SaveResult(byte[] imageBytes, string extension, string outputFolder)
{
Directory.CreateDirectory(outputFolder);
string fileName = $"project_ai_{DateTime.Now:yyyyMMdd_HHmmss}{extension}";
string fullPath = Path.Combine(outputFolder, fileName);
File.WriteAllBytes(fullPath, imageBytes);
return fullPath;
}What this feature is really for
For me, the value of this integration is speed. It helps a designer move from a clean CAD scene to a more client-friendly visual in a few steps, especially during early presentation rounds.
It is still not a replacement for a dedicated rendering pipeline. But as a fast preview layer inside a CAD application, it solves a real communication problem for customers.
Share