Laden...

Architektur eines CAD-Systems zur 2D-Darstellung von Fensteranlagen (für Häuser)

Erstellt von Ayke vor 12 Jahren Letzter Beitrag vor 12 Jahren 2.490 Views
Ayke Themenstarter:in
643 Beiträge seit 2006
vor 12 Jahren
Architektur eines CAD-Systems zur 2D-Darstellung von Fensteranlagen (für Häuser)

Ich habe die Aufgabe ein Modul zu erstellen, was Fenster CAD Zeichnung erstellen soll. Ich habe bereits 2 Lösungen erstellt die ich verwerfen musste.

[B]1 Versuch [/B]Alles in eine Klasse gestopft und direkt gezeichnet. 

[FRAME]
[B][COLOR]Problem[/COLOR][/B]
Der Code war sehr unübersichtlich. Das Erweitern oder Verändern war kaum möglich. Ich poste hier mal nicht den Quellcode  :D

[/frame]

[B]2 Versuch[/B]

Drawing Klasse => Fenster Klasse => FensterContext Klasse
Fenster Klasse zeichnet mehrere Drawing Objekte.
Fenster Context zeichnet mehrere Fenster Objekte

[FRAME]
 [color][B]Klassen Konstruktoren[/B][/COLOR]

Drawing Objekt
[csharp]DrawingRectangle rectangle = new DrawingRectangle(Points coordinates, Painting style);[/csharp]

Fenster Objekt
[csharp]Fenster fenster1 = new Fenster(float x, float y, float width, float height, BedienseitenFlag bedienungsSeiten, OeffnungsSeitenFlag oeffnungsSeiten, float rahmenBreite, float beschlagBreite, float stelzenBreite);[/csharp]

FensterContext Objekt
[csharp]FensterContext context = new FensterContext(Fenster[] fenster);[/csharp]

 [color][B]Basisklassen für den Überblick[/B][/COLOR]

Drawing 
[csharp]    public class DrawingBase
    {
        public DrawingBase() { IsVisible = false;}
        public Image _Image { get { return DrawImage(); } }
        public Points Coordinates { get; set; }
        public Painting Style { get; set; }
        public bool IsVisible { get; set; }
        public string Description { get; set; }
        public virtual void OnDraw(Graphics g) {}

        private Image DrawImage()
        {
            Bitmap bitmap = new Bitmap(IsVisible ? (int)Coordinates.Width : 1, IsVisible ? (int)Coordinates.Height : 1);
            if (IsVisible)
            {
                Graphics graphics = Graphics.FromImage(bitmap);
                graphics.FillRectangle(Style.BackgroundBrush, new Rectangle(0, 0, (int)Coordinates.Width, (int)Coordinates.Height));
                OnDraw(graphics);
                graphics.Dispose();
                bitmap.MakeTransparent(Color.Magenta);
            }
            return bitmap;
        }
    }[/csharp]

Fenster
[csharp]    public abstract class FensterBase
    {
        public FensterBase()
        {
            DrawingItems = new List<Drawing>();
        }

        public Image WindowImage { get { return DrawImage(); } }
        public Points Coordinates { get; set; }
        public Painting Style { get; set; }
        public List<Drawing> DrawingItems { get; private set; }
        public virtual void InitializeImage() { }

        public Image DrawImage()
        {
            Bitmap bitmap = new Bitmap((int)Coordinates.Width, (int)Coordinates.Height);
            Graphics graphics = Graphics.FromImage(bitmap);
            foreach (Drawing drawing in DrawingItems)
                if (drawing != null && drawing.IsVisible)
                    graphics.DrawImage(drawing.DrawingImage, new PointF(drawing.Coordinates.X, drawing.Coordinates.Y));
            graphics.Dispose();
            return bitmap;
        }
    }[/csharp]

FensterContext
[csharp]    public abstract class FensterContextBase
    {
        public Image ContextImage { get { return DrawImage(); } }
        public Painting Style { get; set; }
        public List<Fenster> FensterItems { get; set; }
        public int Width { get; private set; }
        public int Height { get; private set; }

        public Image DrawImage()
        {
            float width = 0;
            float height = 0;

            foreach (Fenster fenster in FensterItems)
            {
                float fensterWidth = fenster.Coordinates.X + fenster.Coordinates.Width;
                float fensterHeight = fenster.Coordinates.Y + fenster.Coordinates.Height;
                if (fensterWidth > width)
                    width = fensterWidth;
                if (fensterHeight > height)
                    height = fensterHeight;
            }

            Bitmap bitmap = new Bitmap((int)width, (int)height);
            Graphics graphics = Graphics.FromImage(bitmap);
            foreach (Window window in FensterItems)
                graphics.DrawImage(window.WindowImage, new PointF(window.Coordinates.X, window.Coordinates.Y));
            graphics.Dispose();
            return bitmap;
        }
    }[/csharp]

 [color][B]Problem[/B][/COLOR]
Die Linien werden nicht immer gezeichnet oder sie haben einen Schatten.
Ich versuche ich immer die Linien in der Mitte zu zeichnen, weil es für mich keinen Sinn ergibt das sie immer nach unten Rechts gezeichnet werden. Deshalb Zeichne nur über Graphics.DrawLine oder Graphics.DrawPolygon und ziehe Linien Breite / 2 bei Width und Height ab und addiere sie bei X und Y. Das setzt voraus das ich float statt int benutze. Hier liegt wohl das Problem.
[csharp]
graphics.DrawLine(myPen, x + myPen.Width / 2, y + myPen.Width / 2, width - myPen.Width / 2, height - myPen.Width / 2);
[/csharp]

Durch das Zeichnen in der Mitte wird gibt es fälle wo die Zeichenlogik schwer zu durchschauen ist z.B simple Pfeile die nicht Width x Height sind werden völlig krunkelig. Linien nach innen wäre wohl auch eine möglichkeit. Was mach ich aber wenn ich kein Rectangle zeichne sondern eine Diagonale Linie.

[/frame]

Irgendwie hab ich das Gefühl das die 2 Lösung auch nicht das Gelbe vom Ei ist. WPF wäre wohl eine Alternative die sehr vieles erleichtern würde oder ? Sieht auch schöner aus da alles geglättet ist ohne zu verschwimmen.
Habt ihr schon Erfahrungen gemacht die mir weiterhelfen könnte, anderen Aufbau, die Lienen- Sache. unsw.. unsw...

Bin für jede Anregung und Kritik offen.

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo,

kannst du etwas genauer beschreiben was das Ziel sein soll? V.a. definiere was du unter CAD verstehst - der Begriff ist ziemlich allegemein.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
2.121 Beiträge seit 2010
vor 12 Jahren

Du willst also verhindern dass ein Pen mit Breite > 1 den angegebenen Punkt nicht in der Mitte zeichnet sondern am Eck? Wusste bisher gar nicht dass das so ist..
Du müsstest eine Senkrechte zu der zu zeichnenden Linie bilden und dann entsprechend Pen.Width / 2 Pixel weiter raus gehen.
Aber obs das wirklich wert ist? Vielleicht gehts ja einfacher wenn du die Punkte entsprechend der Linienbreite diagonal verschiebst und dann doch den breiten Pen selber zeichnen lässt.

C
1.214 Beiträge seit 2006
vor 12 Jahren

Ich verstehe dein Problem ebenfalls nicht, aber wegen deiner Klassenhierarchie... Warum gibt dein DrawingBase ein Image zurück? Ich würds eher so machen, dass sie eine Draw Methode hat, die ein Graphics Objekt übergeben bekommt und sie zeichnet da drauf. Dann solltest du natürlich deine Daten und die Darstellung trennen. Eine Rechteck Klasse kannst du für vieles verwenden, z.B. laden, speichern, exportieren etc.. Hat alles nichts mit der Darstellung zu tun.
Nein, WPF würde es nicht einfacher machen. Erst brauchst du ein sinnvolles Datenmodell. Dann kannst du verschiedene Implementierungen der Darstellung erstellen. Ich hab auch mal was ähnliches gemacht und da hatte ich drei Implementierungen der Darstellung, GDi+, WPF und OpenGL. Eigentlich war das nur Spielerei, OpenGL hätt völlig gereicht.

Ayke Themenstarter:in
643 Beiträge seit 2006
vor 12 Jahren

Hallo gfoidl,

das Ziel ist über einen Dialog verschiedene Infomationen zu Fenstern
einzugeben. Danach soll ein Bild einer Fensteranlage erstellt werden.
Siehe Anhang...

Erstmal nur eckige Fenster, später auch Bogenfenster und weitere Exoten.
Bei den Bild sollen Abmessung automatisch für die ganze Anlage und pro Fenster erstellt werden.

Bei jedem Fenster sollen folgende Angaben zur verfügung stehen.

  1. X Postion oder Column
  2. Y Postion oder Row
  3. Breite
  4. Höhe
  5. Auf welcher Seiten kann das Fenster geöffnet werden.
  6. Auf welcher Seiten ist die Bedienung um das Fenster zu öffnen.
  7. Ist überhaupt ein "Beschlag? auf den Rahmen". Ohne Öffnung wäre der nicht nötig.
  8. Wie Breit ist der Beschlag (Ich bin mir nicht sicher wie das Ding heißt was auf den Rahmen sitzt.)

Beim zusammenführen der Fenster zu einer Fensteranlage müssen noch kleinigkeiten berücksichtigt werden. z.B Wenn 2 Fenster die selbe Höhe haben und genau nebeneinander sind, wird auf der Seite wo sich diese berühren nicht mit einen Rahmen abgeschlossen sondern durch eine Stelze getrennt.

Das Ziel meines Beitrags soll sein, herauszufinden ob mein Klassendesin sinn macht. Ob es da nicht schönere oder einfachere Umsetzung gibt. Vill findet mann ja Opensource oder jemand von euch hatte schon eine ähnliche Lösung die mir helfen kann. Vielleicht sollte mann eine ganz andere Technologie verwenden. Am besten alles was einen einfällt um mir die Sache zu erleichtern.

Die Lösung wirkt recht Simple, mann stößt aber stendig an irgenwelche Probleme. Damit meine ich die Sache mit den Linien.
Auserdem stört mich das ich mit jeder weiteren Drawing Klasse mehr zu rechnen habe, das weist auf eine Design Schwäche hin oder nicht. Hier mal ein Auschnitt von diesen Wirwar...

drawingContainer.RahmenInside.Coordinates
   = new Points(HaveStelzeLeft ? Context.BreiteStelzen / 2 : drawingContainer.RahmenOutside.Coordinates.X + Context.BreiteRahmen, drawingContainer.RahmenOutside.Coordinates.Y + Context.BreiteRahmen,
                drawingContainer.RahmenOutside.Coordinates.Width - Context.BreiteRahmen * 2 +
                (HaveStelzeLeft ? (Context.BreiteRahmen - Context.BreiteStelzen / 2) : 0) +
                (HaveStelzeRight ? (Context.BreiteRahmen - Context.BreiteStelzen / 2) : 0),
                drawingContainer.RahmenOutside.Coordinates.Height - Context.BreiteRahmen * 2,
                0, 0, drawingContainer.RahmenOutside.Coordinates.Width - Context.BreiteRahmen * 2 +
                (HaveStelzeLeft ? (Context.BreiteRahmen - Context.BreiteStelzen / 2) : 0) +
                (HaveStelzeRight ? (Context.BreiteRahmen - Context.BreiteStelzen / 2) : 0), drawingContainer.RahmenOutside.Coordinates.Height - Context.BreiteRahmen * 2);
drawingContainer.BeschlagOutside.Coordinates
   = new Points(drawingContainer.RahmenInside.Coordinates.X - Context.BreiteBeschlag / 2, drawingContainer.RahmenInside.Coordinates.Y - Context.BreiteBeschlag / 2,
                drawingContainer.RahmenInside.Coordinates.Width + Context.BreiteBeschlag, drawingContainer.RahmenInside.Coordinates.Height + Context.BreiteBeschlag,
                0, 0, drawingContainer.RahmenInside.Coordinates.Width + Context.BreiteBeschlag, drawingContainer.RahmenInside.Coordinates.Height + Context.BreiteBeschlag);
drawingContainer.BeschlagInside.Coordinates
   = new Points(drawingContainer.RahmenInside.Coordinates.X + Context.BreiteBeschlag / 2, drawingContainer.RahmenInside.Coordinates.Y + Context.BreiteBeschlag / 2,
                drawingContainer.RahmenInside.Coordinates.Width - Context.BreiteBeschlag, drawingContainer.RahmenInside.Coordinates.Height - Context.BreiteBeschlag,
                0, 0, drawingContainer.RahmenInside.Coordinates.Width - Context.BreiteBeschlag, drawingContainer.RahmenInside.Coordinates.Height - Context.BreiteBeschlag);
drawingContainer.BedienungLeft.Coordinates
   = new Points(drawingContainer.BeschlagOutside.Coordinates.X + Context.BreiteBeschlag / 2 - BreiteBedienung / 2,
                drawingContainer.BeschlagOutside.Coordinates.Y + drawingContainer.BeschlagOutside.Coordinates.Height / 2 - HöheBedienung / 2 + BedienungLeftVersatz,
                BreiteBedienung, HöheBedienung, 0, 0, BreiteBedienung, HöheBedienung);
drawingContainer.BedienungTop.Coordinates
   = new Points(drawingContainer.BeschlagOutside.Coordinates.X + drawingContainer.BeschlagOutside.Coordinates.Width / 2 - HöheBedienung / 2 + BedienungTopVersatz,
                drawingContainer.BeschlagOutside.Coordinates.Y + BreiteBedienung / 2,
                HöheBedienung, BreiteBedienung, 0, 0, HöheBedienung, BreiteBedienung);
drawingContainer.BedienungRight.Coordinates
   = new Points(drawingContainer.BeschlagOutside.Coordinates.X + drawingContainer.BeschlagOutside.Coordinates.Width - BreiteBedienung / 2 - Context.BreiteBeschlag / 2,
                drawingContainer.BeschlagOutside.Coordinates.Y + drawingContainer.BeschlagOutside.Coordinates.Height / 2 - HöheBedienung / 2 + BedienungRightVersatz,
                BreiteBedienung, HöheBedienung, 0, 0, BreiteBedienung, HöheBedienung);
drawingContainer.BedienungBottom.Coordinates
   = new Points(drawingContainer.BeschlagOutside.Coordinates.X + drawingContainer.BeschlagOutside.Coordinates.Width / 2 - HöheBedienung / 2 + BedienungBottomVersatz,
                drawingContainer.BeschlagOutside.Coordinates.Y + drawingContainer.BeschlagOutside.Coordinates.Height - Context.BreiteBeschlag / 2 - BreiteBedienung / 2,
                HöheBedienung, BreiteBedienung, 0, 0, HöheBedienung, BreiteBedienung);
drawingContainer.KreuzCenter.Coordinates
   = new Points(this.Coordinates.Width / 2 - BreiteKreuz / 2, this.Coordinates.Height / 2 - BreiteKreuz / 2,
                BreiteKreuz, BreiteKreuz,
                0, 0, BreiteKreuz, BreiteKreuz);

Hallo Coder007,
danke für deinen Tip.

Hallo Chilic,
das mit "Pen.Width / 2 Pixel" den mache ich bereits.

            //Int ungerade Pinselgröße
            //Rectangle ist aussen...
            Pen pen1rect = new Pen(Color.FromArgb(100, 34, 117, 76), 5);
            Pen pen1line = new Pen(Color.FromArgb(100, 0, 0, 250), 5);
            e.Graphics.FillRectangle(Brushes.Red, new Rectangle(box1.Location.X, box1.Location.Y, box1.Width, box1.Height));
            e.Graphics.DrawRectangle(pen1rect, new Rectangle(box1.Location.X, box1.Location.Y, box1.Width, box1.Height));
            e.Graphics.DrawLine(pen1line, new Point(box1.Location.X, box1.Location.Y),
                                     new Point(box1.Location.X + box1.Width, box1.Location.Y + box1.Height));

            //Float ungerade Pinselgröße
            //Macht alles komplizierter
            Pen pen2rect = new Pen(Color.FromArgb(100, 34, 117, 76), 5);
            Pen pen2line = new Pen(Color.FromArgb(100, 0, 0, 250), 5);
            e.Graphics.FillRectangle(Brushes.Red, new RectangleF(box3.Location.X + pen2rect.Width / 2, box3.Location.Y + pen2rect.Width / 2, box3.Width - pen2rect.Width / 2, box3.Height - pen2rect.Width / 2));
            e.Graphics.DrawRectangle(pen2rect, (float)box3.Location.X + pen2rect.Width / 2, (float)box3.Location.Y + pen2rect.Width / 2, (float)box3.Width - pen2rect.Width / 2, (float)box3.Height - pen2rect.Width / 2);
            e.Graphics.DrawLine(pen2line, new PointF(box3.Location.X, box3.Location.Y),
                                     new PointF(box3.Location.X + box3.Width, box3.Location.Y + box3.Height));

Über sowas simples will mann sicht doch keine Gedanken machen. Wenn ich sage das ich ein Rectangle von 0 bis 100 Zeichnen will. Nicht von 0 - PenWidth / 2 bis 100 + PenWidth + PenWidth / 2. Das es mit int und 5 als PenWidth unsauber wird verstehe ich ja noch.

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo Ayke,

im Text hast du ja schön die Anforderung beschrieben und danach lässt sich ja gut modellieren. Dabei sollte irgendwas in die Richtung mit Basisklasse Fenster von der die anderen Fenster-Typen erben können (dabei das Liskovsches Substitutionsprinzip beachten) und einer Klasse Fensteranlage rauskommen. Hier würde ich die komplette Zeichenlogik noch weglassen und rein nach den Informationen die du im Text beschrieben hast die Klassen erstellen.

Zum Zeichnen: Egal welche Technologie du letzen Endes verwendest würde ich eine "RenderEngine" erstellen welche die Fensteranlage übergeben wird. Je nach Fenster in der Auflistung der Fenster in der Fensteranlage (ich hab noch nie so oft Fenster hintereinander geschrieben 😉) rendert dann diese Engine mit den Information aus den Fenster-Objekten.

Mit ein solchen Ansatz hast du den Vorteil dass die Anliegen klar getrennt sind, also zum Einen sind es die Daten (Fenster-Klassen) und zum Anderen die Darstellung (RenderEngine). Die hat zusätzlich den Vorteil dass die gleichen Fenster-Klassen auch in anderen Teilen verwendet werden können, wie zB für Buchhaltung, Arbeitsvorbereitung, Fertigung, etc. Zum Beispiel kann eine andere "Engine" dann in der Arbeitsvorbeireitung automatisch die Stücklisten usw. erstellen.

Ich hoffe du verstehst in welche Richtung ich dich lenken will. Es ist alles eigentlich ziemlich straight-forward und gar nicht kompliziert denn die Gesamtaufgabe wird in (sich selbständige) Teile zerlegt und somit gewinnst du stark an Übersicht, Testbarkeit, und Allem anderen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Ayke Themenstarter:in
643 Beiträge seit 2006
vor 12 Jahren

danke gfoidl,
hast das gut auf den Punkt gebracht. 👍