ApprenticeFoundryBlazor 23.4.0

dotnet add package ApprenticeFoundryBlazor --version 23.4.0
                    
NuGet\Install-Package ApprenticeFoundryBlazor -Version 23.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ApprenticeFoundryBlazor" Version="23.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ApprenticeFoundryBlazor" Version="23.4.0" />
                    
Directory.Packages.props
<PackageReference Include="ApprenticeFoundryBlazor" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ApprenticeFoundryBlazor --version 23.4.0
                    
#r "nuget: ApprenticeFoundryBlazor, 23.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ApprenticeFoundryBlazor@23.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ApprenticeFoundryBlazor&version=23.4.0
                    
Install as a Cake Addin
#tool nuget:?package=ApprenticeFoundryBlazor&version=23.4.0
                    
Install as a Cake Tool

FoundryBlazor

A comprehensive C# / Blazor diagramming library that combines 2D and 3D visualization capabilities for developers.

Overview

FoundryBlazor is a powerful diagramming and visualization library that brings together the best features of Visio, Three.js, and CesiumJS into the Blazor ecosystem. Originally demonstrated at NDC Oslo 2023, it's now available as a NuGet package supporting both Blazor Server and WebAssembly applications.

Features

  • 2D Diagramming: Complete shape library with advanced layout algorithms
  • 3D Integration: Seamless 2D/3D visualization combining
  • Glued Connections: Dynamic connections that maintain relationships during layout changes
  • Multi-page Diagrams: Scaled diagrams across multiple pages
  • Performance Optimized: Object pooling and efficient rendering
  • Cross-platform: Works in both Blazor Server and WebAssembly

Installation

dotnet add package ApprenticeFoundryBlazor

Quick Start

1. Configure Services

Add FoundryBlazor services to your Program.cs:

using FoundryBlazor;

var builder = WebApplication.CreateBuilder(args);

// Add Blazor services
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor(); // For Blazor Server

// Add FoundryBlazor services
builder.Services.AddFoundryBlazorServices();

var app = builder.Build();

// Configure the HTTP request pipeline
app.UseStaticFiles(); // Essential for static assets
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

2. Add Required Static Assets

Add these references to your _Host.cshtml (Blazor Server) or index.html (WebAssembly):


<link href="_content/ApprenticeFoundryBlazor/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="_content/ApprenticeFoundryBlazor/css/site.css" rel="stylesheet" />
<link href="_content/Radzen.Blazor/css/material-base.css" rel="stylesheet" />


<script src="_content/Blazor.Extensions.Canvas/blazor.extensions.canvas.js"></script>
<script src="_content/ApprenticeFoundryBlazorThreeJS/js/blazor-three-js.js"></script>
<script src="_content/ApprenticeFoundryBlazor/js/app-lib.js"></script>
<script src="_content/Radzen.Blazor/Radzen.Blazor.js"></script>

3. Using 2D Canvas Component

@page "/canvas2d-demo"
@using FoundryBlazor.Shared
@using FoundryBlazor.Shape
@inject IWorkspace Workspace

<h3>2D Canvas Diagramming</h3>

<div class="container-fluid">
    <div class="row">
        
        <div class="col-3">
            <div style="height: 600px; border: 1px solid #ccc;">
                <ShapeTreeView />
            </div>
        </div>
        
        
        <div class="col-9">
            <Canvas2DComponent SceneName="MainCanvas2D"
                               CanvasWidth="1200" 
                               CanvasHeight="600"
                               WithAnimations="true" />
        </div>
    </div>
</div>

<div class="mt-3">
    <button class="btn btn-primary" @onclick="AddRectangle">Add Rectangle</button>
    <button class="btn btn-success" @onclick="AddCircle">Add Circle</button>
    <button class="btn btn-warning" @onclick="ClearCanvas">Clear Canvas</button>
</div>

@code {
    protected override async Task OnInitializedAsync()
    {
        // Initialize with some default shapes
        var drawing = Workspace.GetDrawing();
        
        // Add a rectangle
        var rect = new FoShape2D("Rectangle1", 120, 80, "Blue");
        rect.PinX = 200;
        rect.PinY = 150;
        drawing?.AddShape(rect);
        
        // Add a circle (using rectangle with equal width/height for simplicity)
        var circle = new FoShape2D("Circle1", 100, 100, "Red");
        circle.PinX = 400;
        circle.PinY = 200;
        drawing?.AddShape(circle);
        
        await base.OnInitializedAsync();
    }
    
    private void AddRectangle()
    {
        var drawing = Workspace.GetDrawing();
        var random = new Random();
        
        var rect = new FoShape2D($"Rectangle_{DateTime.Now.Ticks}", 
                                 80 + random.Next(40), 
                                 60 + random.Next(40), 
                                 "Green");
        rect.PinX = 100 + random.Next(800);
        rect.PinY = 100 + random.Next(400);
        
        drawing?.AddShape(rect);
        StateHasChanged();
    }
    
    private void AddCircle()
    {
        var drawing = Workspace.GetDrawing();
        var random = new Random();
        var size = 60 + random.Next(40);
        
        var circle = new FoShape2D($"Circle_{DateTime.Now.Ticks}", size, size, "Orange");
        circle.PinX = 100 + random.Next(800);
        circle.PinY = 100 + random.Next(400);
        
        drawing?.AddShape(circle);
        StateHasChanged();
    }
    
    private void ClearCanvas()
    {
        var drawing = Workspace.GetDrawing();
        drawing?.ClearAll();
        StateHasChanged();
    }
}

4. Using 3D Canvas Component

@page "/canvas3d-demo"
@using FoundryBlazor.Shared
@using FoundryBlazor.Shape
@inject IWorkspace Workspace

<h3>3D Canvas Visualization</h3>

<div class="container-fluid">
    <div class="row">
        
        <div class="col-8">
            <Canvas3DComponent SceneName="MainCanvas3D"
                               CanvasWidth="800"
                               CanvasHeight="600"
                               WithAnimations="true" />
        </div>
        
        
        <div class="col-4">
            <div class="card">
                <div class="card-header">
                    <h5>3D Controls</h5>
                </div>
                <div class="card-body">
                    <button class="btn btn-primary mb-2 w-100" @onclick="Add3DShape">Add 3D Shape</button>
                    <button class="btn btn-success mb-2 w-100" @onclick="AnimateShapes">Animate Shapes</button>
                    <button class="btn btn-warning mb-2 w-100" @onclick="ResetCamera">Reset Camera</button>
                    <button class="btn btn-danger mb-2 w-100" @onclick="Clear3DScene">Clear Scene</button>
                    
                    <hr />
                    
                    <h6>Camera Controls:</h6>
                    <p><small>
                        • Left Mouse: Rotate<br/>
                        • Right Mouse: Pan<br/>
                        • Scroll: Zoom
                    </small></p>
                </div>
            </div>
        </div>
    </div>
</div>

@code {
    private int shapeCounter = 0;
    
    protected override async Task OnInitializedAsync()
    {
        // Initialize 3D scene with some basic shapes
        var arena = Workspace.GetArena();
        
        // Add some initial 3D shapes programmatically
        // Note: Actual 3D shape creation depends on your 3D shape classes
        
        await base.OnInitializedAsync();
    }
    
    private void Add3DShape()
    {
        var arena = Workspace.GetArena();
        var random = new Random();
        
        // Example of adding 3D shapes (adjust based on your actual 3D shape classes)
        shapeCounter++;
        
        // This is conceptual - replace with actual 3D shape creation
        // var shape3D = new FoShape3D($"Shape3D_{shapeCounter}");
        // shape3D.Position = new Vector3(
        //     random.Next(-5, 5), 
        //     random.Next(-5, 5), 
        //     random.Next(-5, 5)
        // );
        // arena?.AddShape(shape3D);
        
        StateHasChanged();
    }
    
    private void AnimateShapes()
    {
        // Trigger animations on 3D shapes
        // Implementation depends on your animation system
        StateHasChanged();
    }
    
    private void ResetCamera()
    {
        // Reset 3D camera to default position
        // Implementation depends on your camera system
        StateHasChanged();
    }
    
    private void Clear3DScene()
    {
        var arena = Workspace.GetArena();
        arena?.ClearAll();
        StateHasChanged();
    }
}

5. Combined 2D and 3D Canvas Example

@page "/combined-canvas"
@using FoundryBlazor.Shared
@using FoundryBlazor.Shape
@inject IWorkspace Workspace

<h3>Combined 2D & 3D Visualization</h3>

<div class="container-fluid">
    <div class="row">
        
        <div class="col-2">
            <div style="height: 500px;">
                <RadzenShapeTreeView />
            </div>
        </div>
        
        
        <div class="col-5">
            <h5>2D Diagram</h5>
            <Canvas2DComponent SceneName="Combined2D"
                               CanvasWidth="500" 
                               CanvasHeight="400"
                               WithAnimations="true" />
        </div>
        
        
        <div class="col-5">
            <h5>3D Visualization</h5>
            <Canvas3DComponent SceneName="Combined3D"
                               CanvasWidth="500"
                               CanvasHeight="400"
                               WithAnimations="true" />
        </div>
    </div>
    
    <div class="row mt-3">
        <div class="col-12">
            <button class="btn btn-primary me-2" @onclick="SyncViews">Sync 2D → 3D</button>
            <button class="btn btn-success me-2" @onclick="AddToBoth">Add to Both</button>
            <button class="btn btn-warning me-2" @onclick="ClearBoth">Clear Both</button>
        </div>
    </div>
</div>

@code {
    private void SyncViews()
    {
        // Example: Convert 2D shapes to 3D representation
        var drawing = Workspace.GetDrawing();
        var arena = Workspace.GetArena();
        
        // Implementation would depend on your specific shape conversion logic
        // This demonstrates the concept of syncing between 2D and 3D
        
        StateHasChanged();
    }
    
    private void AddToBoth()
    {
        var drawing = Workspace.GetDrawing();
        var arena = Workspace.GetArena();
        var random = new Random();
        
        // Add to 2D
        var rect2D = new FoShape2D($"Synced2D_{DateTime.Now.Ticks}", 100, 60, "Purple");
        rect2D.PinX = 100 + random.Next(300);
        rect2D.PinY = 100 + random.Next(200);
        drawing?.AddShape(rect2D);
        
        // Add corresponding 3D shape
        // var shape3D = new FoShape3D($"Synced3D_{DateTime.Now.Ticks}");
        // arena?.AddShape(shape3D);
        
        StateHasChanged();
    }
    
    private void ClearBoth()
    {
        Workspace.GetDrawing()?.ClearAll();
        Workspace.GetArena()?.ClearAll();
        StateHasChanged();
    }
}

Advanced Features

Event Handling and Inter-Component Communication

@using BlazorComponentBus
@using FoundryBlazor.PubSub
@inject ComponentBus PubSub
@implements IDisposable

<Canvas2DComponent SceneName="EventCanvas" />

@code {
    protected override async Task OnInitializedAsync()
    {
        // Subscribe to shape selection events
        PubSub.SubscribeTo<ShapeUIEvent>(OnShapeSelected);
        
        // Subscribe to refresh events
        PubSub.SubscribeTo<RefreshUIEvent>(OnUIRefresh);
        
        await base.OnInitializedAsync();
    }
    
    private async Task OnShapeSelected(ShapeUIEvent eventArgs)
    {
        Console.WriteLine($"Shape selected: {eventArgs.Shape?.Name}");
        // Handle shape selection logic
        StateHasChanged();
    }
    
    private async Task OnUIRefresh(RefreshUIEvent eventArgs)
    {
        // Handle UI refresh
        StateHasChanged();
    }
    
    public void Dispose()
    {
        PubSub?.UnSubscribeFrom<ShapeUIEvent>(OnShapeSelected);
        PubSub?.UnSubscribeFrom<RefreshUIEvent>(OnUIRefresh);
    }
}

Custom Shape Creation

// Create custom 2D shapes
public class CustomShape : FoShape2D
{
    public CustomShape(string name) : base(name, "CustomColor")
    {
        // Custom initialization
        Width = 150;
        Height = 100;
    }
    
    // Override drawing behavior if needed
    // Custom rendering logic can be implemented here
}

// Usage in component
private void AddCustomShape()
{
    var drawing = Workspace.GetDrawing();
    var customShape = new CustomShape("MyCustomShape");
    customShape.PinX = 300;
    customShape.PinY = 200;
    drawing?.AddShape(customShape);
}

Performance Optimization

// For large datasets, disable animations
<Canvas2DComponent SceneName="LargeDataset"
                   CanvasWidth="1200" 
                   CanvasHeight="800"
                   WithAnimations="false" />

// Batch operations for better performance
private void AddMultipleShapes()
{
    var drawing = Workspace.GetDrawing();
    
    // Batch add multiple shapes
    for (int i = 0; i < 100; i++)
    {
        var shape = new FoShape2D($"BatchShape_{i}", 50, 30, "Blue");
        shape.PinX = (i % 10) * 60 + 50;
        shape.PinY = (i / 10) * 40 + 50;
        drawing?.AddShape(shape);
    }
    
    // Trigger single refresh instead of multiple
    StateHasChanged();
}

Troubleshooting

Common Issues

Canvas not rendering:

  • Ensure all required JavaScript files are loaded in the correct order
  • Verify app.UseStaticFiles() is called in Program.cs
  • Check browser console for JavaScript errors

Services not found:

Unable to resolve service for type 'FoundryBlazor.IWorkspace'

Solution: Add services.AddFoundryBlazorServices() to Program.cs

Static assets not loading:

  • Verify the _content/ApprenticeFoundryBlazor/ path is accessible
  • Check that static files middleware is properly configured
  • Ensure NuGet package is properly restored

Browser Compatibility

  • Recommended: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
  • Canvas2D: Supported by all modern browsers
  • WebGL (3D): Required for 3D components, supported by 97%+ of browsers

Demo & Examples

Visit our interactive demo: https://apprenticefoundry.github.io/

Architecture Documentation

Matrix3D Compatibility System

FoundryBlazor uses a compatibility wrapper architecture for 3D matrix operations:

  • Matrix3D Compatibility Documentation - Complete technical documentation of the Matrix3D wrapper pattern that delegates to BlazorThreeJS.Matrix3 while maintaining API compatibility

Key Features

  • Delegation Pattern: Matrix3D serves as a compatibility bridge to BlazorThreeJS mathematics
  • Type Conversion: Seamless bridging between FoVector3D (double precision) and Vector3 (float precision)
  • Object Pooling: Performance optimization through matrix instance reuse
  • Zero Breaking Changes: Existing code works unchanged while benefiting from enhanced math
  • BlazorThreeJS: Provides the core mathematical foundation for 3D operations
  • Three2025: Application project demonstrating integrated 3D capabilities

Package Information

  • Package Name: ApprenticeFoundryBlazor
  • Current Version: 23.3.0
  • Target Framework: .NET 9.0
  • Compatible With: Blazer Server and WebAssembly

Requirements

  • .NET 9.0 or higher
  • Blazor Server or WebAssembly
  • Modern web browser with Canvas2D and WebGL support

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.

Support

  • Demo: https://apprenticefoundry.github.io/
  • Issues: Submit issues on GitHub for bug reports and feature requests
  • Documentation: See the Matrix3D compatibility documentation for technical details
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on ApprenticeFoundryBlazor:

Package Downloads
ApprenticeFoundryMentorModeler

Package Description

SparingEngine

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
23.4.0 64 9/25/2025
23.3.0 64 9/24/2025
23.2.0 89 9/22/2025
23.1.0 88 9/22/2025
23.0.0 99 9/22/2025
21.3.0 127 9/13/2025
21.2.0 110 9/13/2025
21.0.0 159 9/10/2025
20.3.0 177 8/11/2025
20.2.0 96 8/10/2025
20.0.0 122 8/9/2025
19.0.0 143 7/28/2025
18.3.0 284 3/29/2025
18.2.0 157 3/2/2025
18.1.1 139 3/2/2025
18.1.0 133 3/2/2025
18.0.0 173 2/23/2025
17.4.1 143 2/11/2025
17.4.0 159 2/9/2025
17.3.0 133 1/31/2025
17.2.2 139 1/28/2025
17.1.2 188 1/28/2025
17.1.1 132 1/27/2025
16.4.0 186 11/27/2024
16.3.0 123 11/27/2024
16.2.0 174 11/16/2024
16.0.0 140 11/10/2024
15.4.0 145 11/4/2024
15.3.0 152 10/30/2024
15.2.0 168 10/27/2024
15.1.0 221 9/27/2024
15.0.0 199 9/12/2024
14.5.0 203 8/30/2024
14.3.0 142 8/28/2024
14.2.0 175 8/28/2024
14.0.0 240 8/21/2024
13.17.0 225 8/18/2024
13.16.0 159 8/13/2024
13.15.0 158 8/9/2024
13.14.0 161 8/9/2024
13.13.0 159 8/6/2024
13.12.0 158 8/6/2024
13.11.0 165 8/5/2024
13.10.0 120 8/4/2024
13.9.0 120 8/2/2024
13.8.0 114 7/28/2024
13.7.0 118 7/27/2024
13.6.0 129 7/27/2024
13.5.0 122 7/27/2024
13.4.0 133 7/26/2024
13.3.0 149 7/25/2024
13.2.0 115 7/25/2024
13.1.0 130 7/25/2024
12.8.0 164 7/19/2024
12.7.0 148 7/19/2024
12.6.0 152 7/13/2024
12.5.0 167 7/5/2024
12.4.0 173 5/21/2024
12.3.0 171 4/15/2024
12.2.0 163 4/15/2024
12.1.0 160 4/11/2024
12.0.0 177 3/12/2024
11.6.2 185 2/19/2024
11.6.1 149 2/19/2024
11.6.0 162 2/14/2024
11.5.0 174 2/13/2024
11.4.0 174 2/12/2024
11.3.1 163 2/12/2024
11.3.0 174 2/12/2024
11.2.0 164 2/11/2024
11.1.0 181 1/21/2024
11.0.9 228 1/11/2024
11.0.8 155 1/11/2024
11.0.7 171 1/9/2024
11.0.6 184 12/31/2023
11.0.5 224 12/11/2023
11.0.3 185 12/4/2023
11.0.1 162 12/2/2023
11.0.0 174 11/26/2023
10.9.9 200 11/18/2023
10.9.8 160 11/18/2023
10.9.7 158 11/18/2023
10.9.6 159 11/16/2023
10.9.5 163 11/14/2023
10.9.3 153 11/12/2023
10.9.1 138 11/12/2023
10.9.0 1,198 10/31/2023
10.8.2 170 10/30/2023
10.8.0 181 10/20/2023
10.7.0 160 10/19/2023
10.5.0 198 10/15/2023
10.4.0 183 10/14/2023
10.3.0 183 10/14/2023
10.2.0 184 10/14/2023
10.1.6 186 10/2/2023
10.1.5 167 10/2/2023
10.0.5 205 10/1/2023