Back to blog
C# voor AutoCAD: Bouw een boutgatenpatroon-commando
Expert

C# voor AutoCAD: Bouw een boutgatenpatroon-commando

David De Lombaerde David De Lombaerde · juli 1, 2026 · 10 min read

Wat we bouwen

Typ BOUTGATEN in AutoCAD, kies een middelpunt, voer een steekcirkeldiameter, het aantal gaten en de gatdiameter in — en het commando tekent het volledige patroon: elk gat op de juiste positie, de steekcirkel als hartlijn, en een kruismarkering in het midden. Precies wat je handmatig zou tekenen op een flens, lagerbehuizing of voetplaat, gedaan in seconden.

Dit is een realistische eerste add-in. Het behandelt alles wat je nodig hebt in 90% van het AutoCAD-automatiseringswerk: de gebruiker om invoer vragen, lagen programmatisch aanmaken, de geometrieberekeningen uitvoeren en entiteiten naar de tekening schrijven.

Vereisten

Heb je je project en referenties nog niet ingesteld, volg dan eerst de eerste helft van onze installatiegids en kom daarna terug.

Het eindresultaat

Voor de code: dit is precies wat het commando produceert voor een 6-gatenpatroon, steekcirkeldiameter 100 mm, gatdiameter 8 mm:

Elk element landt automatisch op de juiste laag — geen handmatig laagwisselen nodig.

Volledige code

Maak een nieuw class-bestand aan in je project en plak dit erin:

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Colors;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

[assembly: CommandClass(typeof(KleverCAD.BoltHoleCommand))]

namespace KleverCAD
{
    public class BoltHoleCommand
    {
        // ── Laagnamen ────────────────────────────────────────────────────────
        // Als constanten opslaan: een typfout wordt opgemerkt door de compiler,
        // niet als een stille verkeerde-laag-fout bij uitvoering.
        private const string LayerObject     = "OBJECT";
        private const string LayerCentreLine = "CENTRE-LINE";

        /// <summary>
        /// BOUTGATEN-commando — tekent een boutgatenpatroon op basis van gebruikersinvoer.
        ///
        /// Werkwijze:
        ///   1. Gebruiker kiest het middelpunt
        ///   2. Gebruiker voert de steekcirkeldiameter in
        ///   3. Gebruiker voert het aantal gaten in
        ///   4. Gebruiker voert de gatdiameter in
        ///   5. Commando tekent het patroon en rapporteert terug
        ///
        /// Escape indrukken bij een prompt annuleert netjes — er wordt niets getekend.
        /// </summary>
        [CommandMethod("BOUTGATEN")]
        public void DrawBoltHoles()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db  = doc.Database;
            Editor   ed  = doc.Editor;

            // ── Stap 1: middelpunt ───────────────────────────────────────────
            PromptPointOptions centerOpt = new PromptPointOptions(
                "\nKies het middelpunt van het boutgatenpatroon: ");

            PromptPointResult centerRes = ed.GetPoint(centerOpt);
            if (centerRes.Status != PromptStatus.OK) return;
            Point3d center = centerRes.Value;

            // ── Stap 2: steekcirkeldiameter ──────────────────────────────────
            PromptDistanceOptions pcdOpt = new PromptDistanceOptions(
                "\nSteekcirkeldiameter (SCD): ");
            pcdOpt.BasePoint     = center;
            pcdOpt.UseBasePoint  = true;
            pcdOpt.AllowNegative = false;
            pcdOpt.AllowZero     = false;

            PromptDoubleResult pcdRes = ed.GetDistance(pcdOpt);
            if (pcdRes.Status != PromptStatus.OK) return;

            double pcdRadius = pcdRes.Value;
            double pcd       = pcdRadius * 2;

            // ── Stap 3: aantal gaten ─────────────────────────────────────────
            PromptIntegerOptions countOpt = new PromptIntegerOptions(
                "\nAantal gaten: ");
            countOpt.AllowNegative = false;
            countOpt.AllowZero     = false;
            countOpt.LowerLimit    = 2;

            PromptIntegerResult countRes = ed.GetInteger(countOpt);
            if (countRes.Status != PromptStatus.OK) return;
            int numHoles = countRes.Value;

            // ── Stap 4: gatdiameter ──────────────────────────────────────────
            PromptDistanceOptions holeOpt = new PromptDistanceOptions(
                "\nGatdiameter: ");
            holeOpt.AllowNegative = false;
            holeOpt.AllowZero     = false;

            PromptDoubleResult holeRes = ed.GetDistance(holeOpt);
            if (holeRes.Status != PromptStatus.OK) return;
            double holeDiameter = holeRes.Value;
            double holeRadius   = holeDiameter / 2.0;

            // ── Stap 5: alles tekenen in één transactie ──────────────────────
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                // Zorg dat de lagen bestaan voor we erop tekenen.
                EnsureLayer(tr, db, LayerObject,     colorIndex: 7, linetype: "Continuous");
                EnsureLayer(tr, db, LayerCentreLine, colorIndex: 1, linetype: "CENTER");

                BlockTableRecord modelSpace = GetModelSpace(tr, db);

                // Steekcirkel (hartlijn)
                DrawCircle(tr, modelSpace, center, pcdRadius, LayerCentreLine);

                // Kruismarkering in het midden
                DrawCentreCross(tr, modelSpace, center, pcdRadius * 0.08, LayerCentreLine);

                // Gaten gelijkmatig verdelen over de steekcirkel
                double angleStep  = (2 * Math.PI) / numHoles;
                double startAngle = Math.PI / 2.0; // start bovenaan (90°)

                for (int i = 0; i < numHoles; i++)
                {
                    double angle = startAngle + (i * angleStep);

                    // Poolcoördinaten (hoek + straal) omzetten naar cartesisch (x, y)
                    double x = center.X + pcdRadius * Math.Cos(angle);
                    double y = center.Y + pcdRadius * Math.Sin(angle);
                    Point3d holeCenter = new Point3d(x, y, center.Z);

                    DrawCircle(tr, modelSpace, holeCenter, holeRadius, LayerObject);
                    DrawCentreCross(tr, modelSpace, holeCenter, holeRadius * 0.4, LayerCentreLine);
                }

                tr.Commit(); // pas hier wordt alles opgeslagen in de tekening
            }

            ed.WriteMessage(
                $"\nBoutgatenpatroon getekend: {numHoles}x Ø{holeDiameter:F1} op SCD Ø{pcd:F1}");
        }

        // ── Hulpmethode: cirkel tekenen ──────────────────────────────────────
        private static void DrawCircle(
            Transaction tr, BlockTableRecord btr,
            Point3d center, double radius, string layer)
        {
            Circle c = new Circle();
            c.Center = center;
            c.Radius = radius;
            c.Layer  = layer;
            btr.AppendEntity(c);
            tr.AddNewlyCreatedDBObject(c, true);
        }

        // ── Hulpmethode: kruismarkering tekenen (twee korte lijnen) ──────────
        private static void DrawCentreCross(
            Transaction tr, BlockTableRecord btr,
            Point3d center, double halfSize, string layer)
        {
            Line h = new Line(
                new Point3d(center.X - halfSize, center.Y, center.Z),
                new Point3d(center.X + halfSize, center.Y, center.Z));
            h.Layer = layer;
            btr.AppendEntity(h);
            tr.AddNewlyCreatedDBObject(h, true);

            Line v = new Line(
                new Point3d(center.X, center.Y - halfSize, center.Z),
                new Point3d(center.X, center.Y + halfSize, center.Z));
            v.Layer = layer;
            btr.AppendEntity(v);
            tr.AddNewlyCreatedDBObject(v, true);
        }

        // ── Hulpmethode: laag aanmaken als die nog niet bestaat ──────────────
        private static void EnsureLayer(
            Transaction tr, Database db,
            string name, short colorIndex, string linetype)
        {
            LayerTable lt = (LayerTable)tr.GetObject(
                db.LayerTableId, OpenMode.ForRead);

            if (lt.Has(name))
                return; // laag bestaat al — niets te doen

            lt.UpgradeOpen();

            LayerTableRecord ltr = new LayerTableRecord();
            ltr.Name  = name;
            ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, colorIndex);

            LinetypeTable ltt = (LinetypeTable)tr.GetObject(
                db.LinetypeTableId, OpenMode.ForRead);

            if (ltt.Has(linetype))
                ltr.LinetypeObjectId = ltt[linetype];

            lt.Add(ltr);
            tr.AddNewlyCreatedDBObject(ltr, true);
        }

        // ── Hulpmethode: modelruimte ophalen ─────────────────────────────────
        private static BlockTableRecord GetModelSpace(Transaction tr, Database db)
        {
            BlockTable bt = (BlockTable)tr.GetObject(
                db.BlockTableId, OpenMode.ForRead);
            return (BlockTableRecord)tr.GetObject(
                bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
        }
    }
}

Code-uitleg — regel voor regel

De using-statements

using Autodesk.AutoCAD.ApplicationServices;  // Application, Document
using Autodesk.AutoCAD.Colors;               // Color (voor laagkleuren)
using Autodesk.AutoCAD.DatabaseServices;     // Database, Transaction, Circle, Line, LayerTable...
using Autodesk.AutoCAD.EditorInput;          // Editor, PromptPointOptions, PromptDistanceOptions...
using Autodesk.AutoCAD.Geometry;             // Point3d, Vector3d
using Autodesk.AutoCAD.Runtime;              // CommandMethod-attribuut

Elke using-regel importeert een namespace uit de AutoCAD .NET API. DatabaseServices bevat alle tekeningobjecten, EditorInput alles wat te maken heeft met de opdrachtregel en gebruikersinvoer, en Geometry alle wiskundetypen (punten, vectoren).

Het commando registreren

[assembly: CommandClass(typeof(KleverCAD.BoltHoleCommand))]

Deze regel vertelt AutoCAD: "zoek in BoltHoleCommand naar methoden met [CommandMethod]". Zonder deze regel vindt AutoCAD je commando niet, ook al is de DLL geladen. Hij staat buiten de namespace, bovenaan het bestand.

Laagnaamconstanten

private const string LayerObject     = "OBJECT";
private const string LayerCentreLine = "CENTRE-LINE";

Laagnamen als constanten opslaan zorgt ervoor dat een typfout door de compiler wordt opgemerkt in plaats van stilzwijgend op de verkeerde laag te tekenen. Wijzig de naam op één plek en alle verwijzingen worden automatisch bijgewerkt.

De drie objecten die je altijd nodig hebt

Document doc = Application.DocumentManager.MdiActiveDocument;
Database db  = doc.Database;
Editor   ed  = doc.Editor;

doc is het huidige open tekeningbestand. db is de database van de tekening — alle entiteiten (lijnen, cirkels, lagen) leven hier. ed is de opdrachtregel en gebruikersinvoer. Deze drie regels staan aan het begin van vrijwel elk AutoCAD-commando dat je ooit schrijft.

Vragen om een punt

PromptPointOptions centerOpt = new PromptPointOptions("\nKies het middelpunt: ");
PromptPointResult  centerRes = ed.GetPoint(centerOpt);
if (centerRes.Status != PromptStatus.OK) return;
Point3d center = centerRes.Value;

De \n aan het begin zorgt voor een nieuwe regel in het opdrachtvenster. ed.GetPoint() pauzeert het commando en wacht tot de gebruiker klikt of coördinaten intypt. Als de status niet OK is (bijv. Escape ingedrukt), keren we direct terug — er is nog niets getekend, dus niets om ongedaan te maken.

Vragen om een afstand met rubber-band lijn

pcdOpt.BasePoint    = center;
pcdOpt.UseBasePoint = true;

Door BasePoint en UseBasePoint = true in te stellen tekent AutoCAD een rubber-band lijn van het middelpunt terwijl de gebruiker de muis beweegt — dezelfde visuele terugkoppeling als bij het tekenen van een cirkel. AllowNegative en AllowZero voorkomen ongeldige invoer.

Vragen om een geheel getal met limiet

countOpt.LowerLimit = 2;

LowerLimit = 2 zorgt ervoor dat AutoCAD zelf alles onder 2 afwijst en een foutmelding toont — je hoeft die validatie niet zelf te schrijven. De API handelt het af vóór het resultaat je code bereikt.

Een transactie openen

using (Transaction tr = db.TransactionManager.StartTransaction())
{
    // ... alle tekenopdrachten hier ...
    tr.Commit();
}

Een transactie is een beschermende envelop om al je tekenbewerkingen. Niets wordt permanent naar de tekening geschreven totdat tr.Commit() wordt aangeroepen. Als er een uitzondering optreedt vóór de commit, wordt de transactie automatisch teruggedraaid en blijft de tekening onaangeroerd — net als een SQL-transactie: alles of niets.

Lagen aanmaken met EnsureLayer

EnsureLayer(tr, db, LayerObject,     colorIndex: 7, linetype: "Continuous");
EnsureLayer(tr, db, LayerCentreLine, colorIndex: 1, linetype: "CENTER");

De hulpmethode controleert eerst of de laag al bestaat. Als de gebruiker BOUTGATEN tweemaal uitvoert, zijn de lagen er al — geen fout, geen duplicaat. Kleurindex 1 = rood, 7 = wit. Het lijntype CENTER geeft de steekcirkel zijn streeplijn-uiterlijk.

De boutgaten-lus

double angleStep  = (2 * Math.PI) / numHoles;
double startAngle = Math.PI / 2.0;

for (int i = 0; i < numHoles; i++)
{
    double angle = startAngle + (i * angleStep);
    double x = center.X + pcdRadius * Math.Cos(angle);
    double y = center.Y + pcdRadius * Math.Sin(angle);
    ...
}

2 * Math.PI is een volledige cirkel in radialen (360°). Delen door het aantal gaten geeft de hoek tussen elk gat. Math.Cos en Math.Sin zetten de hoek om naar X- en Y-coördinaten — standaard poolcoördinaten-naar-cartesisch. Beginnen bij 90° (boven) is een conventie in het mechanisch tekenen.

Een entiteit toevoegen aan de tekening

Circle c = new Circle();
c.Center = center;
c.Radius = radius;
c.Layer  = layer;
btr.AppendEntity(c);
tr.AddNewlyCreatedDBObject(c, true);

Een Circle-object aanmaken in C# voegt hem nog niet toe aan de tekening. btr.AppendEntity(c) voegt hem toe aan de modelruimte. tr.AddNewlyCreatedDBObject(c, true) registreert hem bij de transactie. Beide regels zijn altijd verplicht — sla er één over en de entiteit verschijnt niet of lekt geheugen.

Hoe de wiskunde werkt

double x = center.X + pcdRadius * Math.Cos(angle);
double y = center.Y + pcdRadius * Math.Sin(angle);

Dit converteert poolcoördinaten — een afstand van het middelpunt (pcdRadius) en een hoek — naar de cartesische X/Y-coördinaten die AutoCAD nodig heeft. Voor een 6-gatenpatroon zijn de hoeken 90°, 150°, 210°, 270°, 330° en 30° — gelijkmatig op 60° afstand. De formule werkt voor elk aantal gaten.

Probeer het uit te breiden

Volgende stap in deze cursus

Volgende les: werken met blokken en attributen — een titelblok invoegen, attribuutvelden (tekening­nummer, revisie, schaal) vanuit code invullen en exporteren naar een spreadsheet.

De volledige C# voor AutoCAD-cursus is beschikbaar voor Standard- en Full Access-abonnees in de E-Learning-sectie.

Share this article

LinkedIn WhatsApp X

Comments

No comments yet — be the first!

Leave a comment

Sign in to skip moderation — anonymous comments are reviewed before appearing.