GDI+ vollständige Überschneidung von Regionen

  • VB.NET

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    GDI+ vollständige Überschneidung von Regionen

    Hallo zusammen,

    ich habe ein Programm erstellt, mit dem ich auf automatisierte Weise prüfen kann, ob- und wenn ja wo sich ein bestimmtes Bauteil mit einem Sauggreifer aufnehmen läßt. Dabei erstelle ich grafisch das Bauteil, auch den Greifer und setze die beiden Grafiken dann übereinander. Anhand der Überschneidungen zwischen Bauteil, Bohrungen und Sauger erkenne ich dann, ob an dieser bestimmten Position nun gegriffen werden kann oder nicht.

    Das Bauteil selbst wird als Region, erzeugt aus verschiedenen graphicsPath Objekten, dargestellt. Gleiches gilt für den Sauger.

    Da jetzt ein Bild mehr als 1000 Worte sagt, hier mal eine Abbiildung des Iststandes:



    Der rote Ring in der Abbildung stellt das Bauteil dar, die restlichen Kreise die Sauger. Die vollen grünen Kreise sind Sauger, welche gerade im angesaugten Zustand sind. die "leeren Kreise" sind nicht-angesaugte Sauger.

    Die Abbildung zeigt auch gleich mein Problem:
    Im roten Ring ist der Sauger auf ca. 3 Uhr über einer Bohrung. Wenn ich den Sauger jetzt so verschiebe, dass die Bohrung innerhalb der Außenkante des Saugers ist, dann meint das Programm "alles OK" und saugt an. Das ist es aber natürlich nicht.

    Woher das Problem kommt ist mir klar, allerdings fehlt mir die Lösung dafür noch.... :) Desshalb meine Bitte an die Wissenden hier im Forum nach guten Tips... :)

    Ein paar Informationen noch zur Berechnung, also was ich hier mache und wie ich zu den jeweiligen Werten komme:

    Wie erwähnt besteht das Bauteil aus einer Region. Diese wird erzeugt indem ich zuerst die Region mit der Außenkontur erstelle, und davon dann mittels region.exclude(graphicsPath) die jeweiligen Bohrungen ausschließe.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Sub CreateRegions()
    2. Dim i As Integer
    3. Dim iMin As Integer
    4. Dim iMax As Integer
    5. iMin = PartsInProgrammGeoList.Min(Function(x) x.GeoLfdNr)
    6. iMax = PartsInProgrammGeoList.Max(Function(x) x.GeoLfdNr)
    7. For i = iMin To iMax
    8. partRegion.Add(i, New clsRegionen)
    9. partRegion.Item(i).BauteilNummer = i
    10. partRegion.Item(i).PartBrush = BauteilBrushNotSelected
    11. For Each ge In PartsInProgrammGeoList.Where(Function(y) y.GeoLfdNr = i)
    12. 'Abweichung der errechneten Abbildung zu PARTS_IN_PROGRAM_POS errechnen und über die Matrix angleichen
    13. Dim dx, dy As Single
    14. Dim BohrungsPath As New Drawing2D.GraphicsPath
    15. Dim PathRect As RectangleF = ge.GpAussenkante.GetBounds(ge.GCodeMatrix)
    16. Dim MofPathRect As PointF = RectMiddle(PathRect)
    17. Dim MofPartPos As PointF = RectMiddle(New RectangleF(ge.PosX, ge.PosY, ge.AbInX, ge.AbInY))
    18. dx = MofPartPos.X - MofPathRect.X
    19. dy = MofPartPos.Y - MofPathRect.Y
    20. ge.GCodeMatrix.Translate(dx, dy, MatrixOrder.Append)
    21. 'Grundfläche erzeugen
    22. ge.GpAussenkante.Transform(ge.GCodeMatrix)
    23. partRegion.Item(i).PartRegion = New Region(ge.GpAussenkante)
    24. 'Innenfläche von Region entfernen
    25. ge.GpInnenkante.Transform(ge.GCodeMatrix)
    26. partRegion.Item(i).PartRegion.Exclude(ge.GpInnenkante)
    27. 'Mitnehmerbohrungen einzeichnen
    28. ge.GpMitnehmer.Transform(ge.GCodeMatrix)
    29. partRegion.Item(i).PartRegion.Exclude(ge.GpMitnehmer)
    30. 'Invertierte Flächen speichern --> Zur Prüfung auf Überschneidung Sauger/Bohrung
    31. If ge.GpInnenkante.PointCount > 0 Then
    32. BohrungsPath.AddPath(ge.GpInnenkante, False)
    33. End If
    34. If ge.GpMitnehmer.PointCount > 0 Then
    35. BohrungsPath.AddPath(ge.GpMitnehmer, False)
    36. End If
    37. If BohrungsPath.PointCount > 0 Then
    38. partRegion.Item(i).BohrungsRegion = New Region(BohrungsPath)
    39. End If
    40. Next
    41. Next
    42. End Sub



    Die Sauger werden prinzipiell genauso erstellt, für die eigentliche Berechnung sind aber nur die Pfade interessant, nicht die Regionen. Die Überprüfung erfolgt dann folgendermaßen:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Function getAktSaugerState(SaugerPath As Drawing2D.GraphicsPath) As SaugerState
    2. 'Dim part As PARTS_IN_PROGRAM_POS_CLS = PartsInProgrammGeoList.Item(AktPart)
    3. Dim part As clsRegionen = partRegion.Item(AktPart + 1)
    4. Dim tempPath As Drawing2D.GraphicsPath = TryCast(SaugerPath.Clone, Drawing2D.GraphicsPath)
    5. Dim aktState As SaugerState
    6. tempPath.Flatten()
    7. For Each pt As PointF In tempPath.PathPoints
    8. If part.PartRegion.IsVisible(pt) Then
    9. If Not part.BohrungsRegion.IsVisible(pt) Then
    10. aktState = SaugerState.angesaugt
    11. Else
    12. aktState = SaugerState.entspannt
    13. Exit For
    14. End If
    15. Else
    16. aktState = SaugerState.entspannt
    17. Exit For
    18. End If
    19. Next
    20. Return aktState
    21. End Function



    Ich erzeuge also eine "Polylinie" aus der Kreisbahn, und prüfe dann jeden einzelnen Punkt dieser Linie auf region.isVisible. Und genau da "liegt der Hase im Pfeffer..." :) Denn in der oben beschriebenen Situation, also wenn ein Sauger eine Bohrung vollständig überdeckt, also die Außenkontur des Saugers keine Überschneidung mit der Bohrung hat, dann wird angesaugt. Was natürlich nicht sein darf. Er darf nur ansaugen, wenn sich der Sauger vollständig über der Bauteil - Region befindet.

    Zur Kollisionserkennung hab ich schon ein paar Sachen gefunden, nur eine richtige Kollision erkenne ich ja, eine vollständige Überdeckung leider nicht. Bei meiner Suche bin ich dann noch über eine pixelgenaue- Kollisionserkennung gestolpert, wäre vermutlich eine Möglichkeit - aber doch recht aufwändig denke ich.

    Hat dazu noch jemand eine Idee?

    Vielleicht noch als erklärung wesshalb ich den ganzen Aufwand treibe:
    In der obigen Abbildung wäre dies ja alles nicht nötig, denn da ließe sich das auch einfach rechnen (Abstand zwischen Mittelpunkten<Summe der Radien = Überschneidung) aber leider kann mir als "Bohrung" auch sowas passieren:



    Auch das würde sich ja grundsätzlich rechnen lassen, aber da wird der Aufwand dann schon ganz ordentlich denke ich... :(

    Im Prinzip gehts einzig und alleine darum, ob mein "Kreiserl" (also der Sauger) vollständig auf der Fläche des Bauteils ist oder nicht....

    Mit der Bitte und Hoffnung auf gute Tips,

    LG aus Wien
    Günther
    ich geh mal davon aus, dass dein Sauger immer Kreisrund sein wird.
    Banaler Ansatz wäre also erstmal ein Quadrat der größe 2r x 2r als bounding box deines Saugers zu erstellen. Von diesem Quadrat gehst du jeden Pixel in einer geschachtelten Schleife durch, mit z.B. Zähler i und j.
    von i und j berechnest du den Abstand zum Mittelpunkt(i-r bzw. j-r), diese Quadrierst du und addierst sie auf, wenn dieser Wert kleiner ist als r^2 dann ist der Pixel innerhalb deines Kreises.

    Quellcode

    1. int dx=i-r,dy=j-r;
    2. if (dx*dx+dy*dy <= r*r){
    3. //ist innerhalb des Kreises
    4. }


    Wenn du nun innehalb des Kreises bist musst du von dem jeweiligen Pixel (i,j)+rechteckOffset oder (dx,dy)+circleOffset die Farbe überprüfen, wenn eine Farbe nicht Rot ist, dann kannst du die Schleife abbrechen und weißt, dass es ungültig ist, wenn die Schleife ohne abbruch durchläuft, dann kann das Bauteil an dieser Stelle angesaugt werden.
    Zu Beginn kann man die Farbüberprüfung über Bitmap.GetPixel machen, würde ich dann aber später auf LockBits umbauen.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Hallo & danke für die Antwort,

    Die Sauger sind immer Kreisrund, die Info habe ich unterschlagen.... :whistling:

    ich hätte die Hoffnung gehabt, die Pixel des Saugers irgendwie aus der Region/aus dem Pfad zu bekommen. Dann würde ich mir die Prüfung sparen, ob die Pixel innerhlab des Saugers sind.

    LG
    Hallo,

    @ErfinderDesRades Könntest du vlt. etwas genauer erklären, wie da nach dem xor eine leere Region raus kommen kann?

    Ich hätte das soweit jetzt versucht, ich kann aber nur nach einem region.complement die umschreibenden Rechtecke vergleichen. Funktioniert soweit, nur das hilft mir wieder nix, wenn die Bohrung innerhalb des Rechtecks ist... :(

    Hier ein kleines Testprogramm:

    Wenn das Rechteck dem Ergebnis entspricht, dann färbt sich der Hintergrund blau. Das sollte heißen, alles ok. Ist aber nicht wenn man über dem Kreis steht....

    VB.NET-Quellcode

    1. Private Sub Panel1_Paint(sender As Object, e As PaintEventArgs)
    2. Dim newRect As New Rectangle(50, 50, 300, 400)
    3. Dim newReg As New Region(newRect)
    4. Dim brush As New SolidBrush(Color.Red)
    5. Dim myCircle As New GraphicsPath
    6. myCircle.AddEllipse(New RectangleF(New PointF(150, 125), New SizeF(60, 60)))
    7. newReg.Exclude(myCircle)
    8. e.Graphics.FillRegion(brush, newReg)
    9. e.Graphics.DrawRectangle(New Pen(Color.Black, 2), mouseRect)
    10. Dim chkReg As Region
    11. Dim mousReg As Region
    12. Dim drawReg As Region
    13. drawReg = newReg.Clone
    14. mousReg = New Region(mouseRect)
    15. chkReg = New Region(mouseRect)
    16. drawReg.Xor(mousReg)
    17. drawReg.Complement(mousReg)
    18. Dim eqRect As RectangleF = drawReg.GetBounds(e.Graphics)
    19. Dim regDataFinished As RegionData = drawReg.GetRegionData
    20. Dim hashFinished As Object = drawReg.GetHashCode
    21. Dim hashMouse As Object = mousReg.GetHashCode
    22. Dim regDataMouse As RegionData = mousReg.GetRegionData
    23. If eqRect.Size = mouseRect.Size Then
    24. Me.BackColor = Color.blue
    25. Else
    26. Me.BackColor = myBackColor
    27. End If
    28. Dim newbrush As New SolidBrush(Color.Green)
    29. drawReg.Translate(400, 300)
    30. e.Graphics.FillRegion(newbrush, drawReg)
    31. End Sub
    32. Private Sub panel1_MouseMove(sender As Object, e As MouseEventArgs) Handles panel2.MouseMove
    33. Dim mousepoint As Point
    34. Dim abm As Size
    35. abm = New Size(100, 100)
    36. mousepoint = e.Location
    37. mousepoint.X = mousepoint.X - abm.Width / 2
    38. mousepoint.Y = mousepoint.Y - abm.Height / 2
    39. mouseRect = New Rectangle(mousepoint, abm)
    40. panel2.Invalidate()
    41. End Sub


    Einfach in ein neues Projekt kopieren, in dem Projekt in die OnPaint Methode eines Panels und ins MouseMove.

    LG Günther


    ### EDIT ###

    Ok, also complement ist da das Zauberwort... :) Damit würde es dann klappen... :)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „GuentherA“ ()

    Hallo,

    im Anhang das gezippte Porjekt (VS 2015). Da funktioniert das Thema soweit eigentlich auch schon.

    Ich kanns nur in meiner eigentlichen Applikation so nicht lösen, da ich irgendwie mit der Skalierung nicht klar komm.

    Bin mittlerweile bei einer mathematischen Lösung (Punkt in Polygon) mal sehn ob das der Weisheit letzter Schluß ist... :)

    Danke auf jeden Fall für die Unterstützung.

    LG Günther
    Dateien
    • GraphTest.zip

      (80,12 kB, 114 mal heruntergeladen, zuletzt: )

    GuentherA schrieb:

    Lösung (Punkt in Polygon)
    Gugst Du hier.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    ich hab den Code mal feste aufgeräumt - evtl verstehst du dann dein eigenen Code bisserl besser ;)

    tipps:

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()