Am I overcomplicating this..?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Hi all,
I am working on a customer project and have a tricky trigonometry problem that I need fresh eyes to take a look at 😲 .
Basically, the customer needs to place {widget} around the perimeter of a building (any new-build) starting in the "corners" - which may or may not have an acute/obtuse angle depending on the shape of the building.
Thus far, we have seen example building plans from them that are rectangular, L-Shaped, to residential designs that have exterior elements at 45° (depending upon how Inventor decides it wants to measure the angle! 🙄 ) to everything else.
It was quickly understood that for this to work, the customer needs to spend some time on the AutoCAD plans such that the perimeter of the building has a polyline sketched around it in an anti-clockwise direction*. This provides the Inventor API with the necessary "Direction" vectors we need to determine the "shape" of the corner in order to work out where to place {widget}.
To that end, the attached .ipt file contains the necessary geometry such that the following rule will allow me to calculate (at this time) around 30 different "angles to X axis" in the various quadrants between 0° and 360°, but not all cases are returning accurate results and it's extremely frustrating.
AddReference "System.XML"
'AddReference "C:\Users\alex_\OneDrive\Documents\GitHub\Archive\iLogicExternalDebug\packages\NUnit.3.13.2\lib\net45\nunit.framework.dll"
'AddReference "Microsoft.VisualStudio.TestTools.UnitTesting"
'AddVbFile "GraphicsDataSetsExtensions"
AddVbFile "ExtensionsGeneralExtensions"
AddVbFile "ClassMasonrySupport"
AddVbFile "ClassHelperClasses"
Option Explicit On
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Imports Microsoft.VisualStudio.TestTools.UnitTesting
''' Attempts to place a point and then rotate it to the correct location using the maths here: https://matthew-brett.github.io/teaching/rotation_2d.html
Sub main()
iLogicVb.UpdateWhenDone = True
'iLogic Unit testing framework:
If RuleArguments.Exists("TestValues") Then
Dim RulePassedTestParams = RuleArguments.Value("TestValues")
Dim TestParams As TestParameters = New TestParameters(ThisApplication, ThisDoc.Document)
If Not RulePassedTestParams Is Nothing Then
TestParams = RulePassedTestParams
End If
End If
Dim PartDoc As PartDocument = ThisApplication.ActiveDocument
Dim PartDef As PartComponentDefinition = PartDoc.ComponentDefinition
Dim InventorExteriorWall As Object = ThisApplication.CommandManager.Pick( _
SelectionFilterEnum.kSketchCurveLinearFilter, _
"Select an Exterior Wall to use as the base for the new wall." _
)
Dim InventorAdjoiningWall As Object = ThisApplication.CommandManager.Pick( _
SelectionFilterEnum.kSketchCurveLinearFilter, _
"Select an Adjoining Wall to use as the base for the new wall." _
)
Dim SystemRunDef As MasonrySupport.MasonrySupportRun = New MasonrySupport.MasonrySupportRun
oXAxis = PartDef.WorkAxes.Item(1)
SystemRunDef.ExteriorWall = GetDatumEdgeFromInventorGeom(PartDoc, PartDef, InventorExteriorWall, "exterior AutoCAD (brick/block) line", true, WorkPointPrefix :="ExteriorWall")
SystemRunDef.AdjoiningWall = GetDatumEdgeFromInventorGeom(PartDoc, PartDef, InventorAdjoiningWall, "adjoining AutoCAD wall line", true, WorkPointPrefix :="AdjoiningWall")
Dim oPoint As Point = ThisApplication.TransientGeometry.CreatePoint(SystemRunDef.ExteriorWall.StartPoint.X, _
SystemRunDef.ExteriorWall.StartPoint.Y, _
SystemRunDef.ExteriorWall.StartPoint.Z)
Dim AngleBetweenChosenWalls As Double = RadiansToDegrees(SystemRunDef.ExteriorWall.Direction.AngleTo(SystemRunDef.AdjoiningWall.Direction))
Dim BisectorAngle as Double = 180 - (AngleBetweenChosenWalls / 2)
Dim BisectorAngleAlt As Double = 360 - RadiansToDegrees((SystemRunDef.ExteriorWall.Direction.AngleTo(SystemRunDef.AdjoiningWall.Direction) / 2))
Dim angleTest as double = 360 - AngleBetweenChosenWalls
Dim DefaultOffset As Double = 3.2
Dim DefaultAngle As Double = 0
Dim CalculatedAngle As Double = 0
Dim DefaultAngleRadians As Double = 0
Dim ExtWallDirectionX As Double = SystemRunDef.ExteriorWall.Direction.X
Dim ExtWallDirectionY As Double = SystemRunDef.ExteriorWall.Direction.Y
Dim AdjWallDirectionX As Double = SystemRunDef.AdjoiningWall.Direction.X
Dim AdjWallDirectionY As Double = SystemRunDef.AdjoiningWall.Direction.Y
Dim AngleBetweenWallVectors as Double = RadiansToDegrees(Math.Atan2(AdjWallDirectionY - ExtWallDirectionY, AdjWallDirectionX - ExtWallDirectionX))
Dim WallAngleToXAxis As Double = SystemRunDef.ExteriorWall.AngleToXAxis
Dim Hypotenuse As Double = 0
Dim RotationAngle As Double = 0
' Break
DefaultAngle = WallAngleToXAxis
CalculatedAngle = AngleBetweenWallVectors
Hypotenuse = DefaultOffset / Math.Cos(DegreesToRadians(BisectorAngle))
'if the axis is at 0, 90, 180 or 270 one or the other of these will be zero, or very close to it.
If ExtWallDirectionY > 0 and ExtWallDirectionX > 0 then 'WallAngleToXAxis is located between 12:00 and 3:00
Break
DefaultAngle = 180 - DefaultAngle
If AngleBetweenWallVectors < 0 then
RotationAngle = DefaultAngle + (180 + CalculatedAngle)
Else
RotationAngle = DefaultAngle + CalculatedAngle
End If
Else If ExtWallDirectionY < 0 and ExtWallDirectionX < 0 Then 'WallAngleToXAxis is located between 6:00 and 9:00
Break
DefaultAngle = 180 - DefaultAngle
If AngleBetweenWallVectors < 0 then
RotationAngle = DefaultAngle + (180 + CalculatedAngle)
Else
RotationAngle = DefaultAngle + CalculatedAngle
End If
Else If ExtWallDirectionY < 0 and ExtWallDirectionX > 0 Then 'WallAngleToXAxis is located between 3:00 and 6:00
Break
DefaultAngle = 180 + DefaultAngle
If AngleBetweenWallVectors < 0 then
RotationAngle = DefaultAngle + (180 + CalculatedAngle)
Else
RotationAngle = DefaultAngle + CalculatedAngle
End If
Else If ExtWallDirectionY > 0 and ExtWallDirectionX < 0 Then 'WallAngleToXAxis is located between 9:00 and 12:00
Break
' DefaultAngle = 180 - DefaultAngle
If AngleBetweenWallVectors < 0 then
RotationAngle = DefaultAngle + (180 + CalculatedAngle)
Else
RotationAngle = DefaultAngle + CalculatedAngle
End If
Else
End If
DefaultAngleRadians = DegreesToRadians(DefaultAngle)
'works but not thoroughly enough.
' If AngleBetweenWallVectors < 0 then
' RotationAngle = DefaultAngle + (180 + CalculatedAngle)
' Else
' RotationAngle = DefaultAngle + CalculatedAngle
' End If
If DefaultAngle < 180 then
RotationAngle = RotationAngle + ((180 - DefaultAngle) * 2)
Else
End If
Dim NewPoint As Point = PolarPoints(oPoint, DefaultAngleRadians, Hypotenuse)
Dim NewWPoint As WorkPoint = PartDef.WorkPoints.AddFixed(NewPoint)
' Dim NewAxis As WorkAxis = PartDef.WorkAxes.AddByTwoPoints(PartDef.WorkPoints.Item(1), NewWPoint)
NewWPoint.Name = NewWPoint.Name.Replace("Work Point", "AngleToXAxis-" & Parameter("AngleToXAxis") & "-correct-not-visible")
Dim RotatedPoint As Point = GetRotatedPoint(NewPoint, DegreesToRadians(RotationAngle))
Dim RotatedWPoint As WorkPoint = PartDef.WorkPoints.AddFixed(RotatedPoint)
RotatedWPoint.Name = RotatedWPoint.Name.Replace("Work Point", "AngleBetweenWalls-" & Parameter("AngleBetweenWalls") & "-correct-not-visible")
' Dim RotatedAxis As WorkAxis = PartDef.WorkAxes.AddByTwoPoints(NewWPoint, RotatedWPoint)
End Sub
Public Function BuildPointForTest(ByVal AngleToXAxis As Double, ByVal AngleBetweenWalls As Double)
End Function
Public oXAxis As WorkAxis = Nothing
Public ReadOnly DefaultTolerance As Double = 0.001
Public ReadOnly kRadiansToDegrees As Double = 180.0 / Math.PI
Public ReadOnly kDegreesToRadians As Double = Math.PI / 180.0
Public Function GetDatumEdgeFromInventorGeom(ByVal PartDoc As PartDocument, _
ByVal oDef As PartComponentDefinition, _
ByVal InventorLine As SketchLine, _
ByVal SelectionPrompt As String, _
Optional WorkPointsForConstructionPurposesOnly As Boolean = False, _
Optional WorkPointPrefix As String = "") As MasonrySupport.DatumEdgeFromAutoCAD
Dim Datum As MasonrySupport.DatumEdgeFromAutoCAD = New MasonrySupport.DatumEdgeFromAutoCAD
Dim WorkGeomColl As ObjectCollection = ThisApplication.TransientObjects.CreateObjectCollection()
Dim startPoint As Point = ThisApplication.TransientGeometry.CreatePoint(InventorLine.StartSketchPoint.Geometry.X, _
InventorLine.StartSketchPoint.Geometry.Y, 0)
Dim NeedToAddStartPointWP As Boolean = True
Dim endPoint As Point = ThisApplication.TransientGeometry.CreatePoint(InventorLine.EndSketchPoint.Geometry.X, _
InventorLine.EndSketchPoint.Geometry.Y, 0)
Dim NeedToAddEndPointWP As Boolean = True
Dim oTempWorkAxis As WorkAxis = Nothing
Dim NeedToAddDirectionAxis As Boolean = True
Dim oStartPoint As WorkPoint = Nothing
Dim oEndPoint As WorkPoint = Nothing
Dim LineDirection As UnitVector = ThisApplication.TransientGeometry.CreateUnitVector(InventorLine.Geometry.Direction.X, _
InventorLine.Geometry.Direction.Y, _
0)
If oDef.WorkPoints.Count > 2 Then ' we have work to do. (but isn't the count always > 0 because of the center point..?)
'check if startpoint's point object is used by an existing WorkPoint and use that if so, otherwise add a new workpoint.
For Each wp As WorkPoint In oDef.WorkPoints
If wp.Point.IsEqualTo(startPoint, DefaultTolerance) Then
NeedToAddStartPointWP = False
oStartPoint = wp
oStartPoint.Visible = True 'in case it happens to be the origin point.
Exit For
End If
Next
For Each wp As WorkPoint In oDef.WorkPoints
If wp.Point.IsEqualTo(endPoint, DefaultTolerance) Then
NeedToAddEndPointWP = False
oEndPoint = wp
oEndPoint.Visible = True 'in case it happens to be the origin point.
Exit For
End If
Next
End If
If NeedToAddStartPointWP Then
oStartPoint = oDef.WorkPoints.AddFixed(startPoint, WorkPointsForConstructionPurposesOnly)
oStartPoint.Grounded = True
End If
WorkGeomColl.Add(oStartPoint)
If NeedToAddEndPointWP Then
oEndPoint = oDef.WorkPoints.AddFixed(endPoint, WorkPointsForConstructionPurposesOnly)
oEndPoint.Grounded = True
End If
WorkGeomColl.Add(oEndPoint)
Dim SelectedStartPoint As WorkPoint = oStartPoint 'ThisApplication.CommandManager.Pick(SelectionFilterEnum.kWorkPointFilter, "Select the " & SelectionPrompt & " startpoint (the point with the smallest Y value)")
Dim SelectedEndPoint As WorkPoint = oEndPoint
If Not WorkPointsForConstructionPurposesOnly Then
oStartPoint.Name = oStartPoint.Name.Replace("Work Point", WorkPointPrefix & "StartPoint")
oEndPoint.Name = oEndPoint.Name.Replace("Work Point", WorkPointPrefix & "EndPoint")
End If
If oDef.WorkAxes.Count > 3 Then '3 because X, Y & Z..?
For Each wa As WorkAxis In oDef.WorkAxes
If wa.DefinitionType = WorkAxisDefinitionEnum.kFixedWorkAxis
'probably one we added earlier and need to either delete or reuse
Dim waDef As FixedWorkAxisDef = wa.Definition
If Not waDef Is Nothing Then
If waDef.OriginPoint.IsEqualTo(oStartPoint.Point, DefaultTolerance) Or waDef.OriginPoint.IsEqualTo(oEndPoint.Point, DefaultTolerance) Then
If waDef.Axis.IsEqualTo(LineDirection, DefaultTolerance) Then
NeedToAddDirectionAxis = False
oTempWorkAxis = wa
Exit For
End If
End If
End If
End If
Next
End If
If NeedToAddDirectionAxis Then
oTempWorkAxis = oDef.WorkAxes.AddFixed(SelectedStartPoint.Point, LineDirection, WorkPointsForConstructionPurposesOnly)
oTempWorkAxis.Grounded = "True"
if not WorkPointsForConstructionPurposesOnly then
oTempWorkAxis.AutoResize = "True"
oTempWorkAxis.Name = oTempWorkAxis.Name.Replace("Work Axis", WorkPointPrefix & "Axis")
end if
End If
WorkGeomColl.Add(oTempWorkAxis)
Datum.AngleToXAxis = RadiansToDegrees(oTempWorkAxis.Line.Direction.AngleTo(oXAxis.Line.Direction))
Datum.Direction = oTempWorkAxis.Line.Direction
Datum.StartPoint = New Point3d(SelectedStartPoint.Point)
Datum.EndPoint = New Point3d(SelectedEndPoint.Point)
Datum.Length = Measure.MinimumDistance(SelectedStartPoint.Name, SelectedEndPoint.Name)
'cleanup on aisle three:
If WorkPointsForConstructionPurposesOnly Then
For Each workGeom As Object In WorkGeomColl
If TypeOf workGeom Is WorkPoint Then
Try
Dim wp As WorkPoint = workGeom
wp.Delete()
Catch
Continue For
End Try
Else If TypeOf workGeom Is WorkAxis Then
Try
Dim wa As WorkAxis = workGeom
wa.Delete()
Catch
Continue For
End Try
End If
Next
Else
WorkGeomColl.Clear()
End If
Return Datum
End Function
Public Function RadiansToDegrees(ByVal radians As Double) As Double
Return radians * kRadiansToDegrees
End Function
Public Function DegreesToRadians(ByVal degrees As Double) As Double
Return degrees * kDegreesToRadians
End Function
Public Function GetRotatedPoint(ByVal OGPoint As Point, ByVal RotationAngle As Double) As Point
Dim RotatedPoint As Point = ThisApplication.TransientGeometry.CreatePoint(0,0,0)
RotatedPoint.X = OGPoint.X * Math.Cos(RotationAngle) - OGPoint.Y * Math.Sin(RotationAngle)
RotatedPoint.Y = OGPoint.X * Math.Sin(RotationAngle) + OGPoint.Y * Math.Cos(RotationAngle)
RotatedPoint.Z = OGPoint.Z
Return RotatedPoint
End Function
Public Function PolarPoints(ByVal pPt As Point, ByVal dAng As Double, ByVal dDist As Double) As Point
Dim X As Double = dDist * Math.Cos(dAng)
Dim Y As Double = dDist * Math.Sin(dAng)
Dim NewPoint As Point = ThisApplication.TransientGeometry.CreatePoint(pPt.X + X, pPt.Y + Y, pPt.Z)
Return NewPoint
End Function
The attached AngleGoesBrrr.ipt contains an iLogic form to control the angles and I have begun to investigate how I might perform some automated testing on this because click, click, click "oh it's wrong again" gets VERY tedious, extremely quickly.
My plan for this is to use one of the Unit Testing Frameworks (either Microsoft's own, or NUnit) because in the part file I have "labelled" the lines along with the always-correct Sketch point using @ekinsb's extremely useful "Nifty Attributes" addin, such that I can programmatically select both lines, run my code and test the resulting point against the position of the known-good sketch point.
I'll probably throw that up as a separate forum post when I get to it, but for now, I would love for someone to look at the code ^ and say, "throw that away and do it like {x} instead"
If you do download these files, the rule above and it's three classes are external rules. Without placing them in your External rules folder, the second button on the form will appear with a strikethrough. (the first button I haven't provided code for)
Thanks,
Alex
*I had overlooked this CRITICAL requirement when I threw together this part file late last week and subsequently spent most of Thursday & ALL of Friday fighting with Inventor's API and wondering why I couldn't get angles more than 180° apart for the two lines when I measured them in-code. Cue, me realising my mistake as I was walking home on Friday evening. Ho-hum.
Alex Fielder
Inventor Expert
https://github.com/alexfielder/
LinkedIn - Github Inventor Extension Server - Bonkers polygon iLogic thing
Top ten iLogic Tips - API Shortcut In Google Chrome - Assembly Extrusion Example