Get the Area from the Face Created by "Face from Curves"

Hello all,

The geometry in the cubic below will be cut off by laser, resulting in a recess.

To get the total area of the square, including the recess in the middle, I used workPart.MeasureManager.NewFaceProperties to get the outer frame at first, and now I have to calculate the area of the recess.
I created a face by "face from curves" and measure its area, while recording the journal. However, After I ran the journal again, it didn't create any area, and I couldn't identify the codes related to "face from curves" in the face...
I referred to CurveCollector, CurveCollection, LineCollection...etc. in API Reference, but none of them has methods like CreateFace...

I succeeded with rectangular design by multiplying the edges, but the method doesn't work with circular, triangular and polygonal design...
Another solution I came up with is extruding->measure area ->delete extrusion, which is obviously not better...
Or is there a better way to get the 2D area of the cubic?

Thanks for a reply in advance!

link to the cubic:
https://drive.google.com/file/d/1N78azoMaMldVE77jy29O5KwcSyX-lcRy/view?u...

recorded journal:
' NX 2023
'
Imports System
Imports NXOpen

Module NXJournal
Sub Main (ByVal args() As String)

Dim theSession As NXOpen.Session = NXOpen.Session.GetSession()
Dim workPart As NXOpen.Part = theSession.Parts.Work

Dim displayPart As NXOpen.Part = theSession.Parts.Display

' ----------------------------------------------
' Menü: Werkzeuge->Befehle vorhersagen->Fläche aus Kurven
' ----------------------------------------------
' ----------------------------------------------
' Menü: Einfügen->Oberfläche->Fläche aus Kurven...
' ----------------------------------------------
Dim markId1 As NXOpen.Session.UndoMarkId = Nothing
markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "##36Sheet")

Dim markId2 As NXOpen.Session.UndoMarkId = Nothing
markId2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Start")

theSession.SetUndoMarkName(markId2, "Klassenauswahl-Dialogfenster")

' ----------------------------------------------
' Dialogfenster Anfang Klassenauswahl
' ----------------------------------------------
Dim scaleAboutPoint1 As NXOpen.Point3d = New NXOpen.Point3d(-37.006751754718962, 19.897136657732013, 0.0)
Dim viewCenter1 As NXOpen.Point3d = New NXOpen.Point3d(37.006751754718898, -19.897136657731966, 0.0)
workPart.ModelingViews.WorkView.ZoomAboutPoint(1.25, scaleAboutPoint1, viewCenter1)

Dim scaleAboutPoint2 As NXOpen.Point3d = New NXOpen.Point3d(-29.605401403775161, 15.91770932618563, 0.0)
Dim viewCenter2 As NXOpen.Point3d = New NXOpen.Point3d(29.605401403775112, -15.91770932618557, 0.0)
workPart.ModelingViews.WorkView.ZoomAboutPoint(1.25, scaleAboutPoint2, viewCenter2)

Dim markId3 As NXOpen.Session.UndoMarkId = Nothing
markId3 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Klassenauswahl")

theSession.DeleteUndoMark(markId3, Nothing)

Dim markId4 As NXOpen.Session.UndoMarkId = Nothing
markId4 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Klassenauswahl")

theSession.DeleteUndoMark(markId4, Nothing)

theSession.SetUndoMarkName(markId2, "Klassenauswahl")

theSession.DeleteUndoMark(markId2, Nothing)

Dim scaleAboutPoint3 As NXOpen.Point3d = New NXOpen.Point3d(-10.027387904031908, 30.943411752932814, 0.0)
Dim viewCenter3 As NXOpen.Point3d = New NXOpen.Point3d(10.027387904031849, -30.943411752932747, 0.0)
workPart.ModelingViews.WorkView.ZoomAboutPoint(0.80000000000000004, scaleAboutPoint3, viewCenter3)

Dim scaleAboutPoint4 As NXOpen.Point3d = New NXOpen.Point3d(-12.534234880039907, 38.525470398159364, 0.0)
Dim viewCenter4 As NXOpen.Point3d = New NXOpen.Point3d(12.53423488003981, -38.525470398159314, 0.0)
workPart.ModelingViews.WorkView.ZoomAboutPoint(0.80000000000000004, scaleAboutPoint4, viewCenter4)

' ----------------------------------------------
' Menü: Analyse->Messen...
' ----------------------------------------------
Dim markId5 As NXOpen.Session.UndoMarkId = Nothing
markId5 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start")

theSession.SetUndoMarkName(markId5, "Messen-Dialogfenster")

workPart.MeasureManager.SetPartTransientModification()

Dim scCollector1 As NXOpen.ScCollector = Nothing
scCollector1 = workPart.ScCollectors.CreateCollector()

scCollector1.SetMultiComponent()

workPart.MeasureManager.SetPartTransientModification()

Dim selectionIntentRuleOptions1 As NXOpen.SelectionIntentRuleOptions = Nothing
selectionIntentRuleOptions1 = workPart.ScRuleFactory.CreateRuleOptions()

selectionIntentRuleOptions1.SetSelectedFromInactive(False)

Dim body1 As NXOpen.Body = CType(workPart.Bodies.FindObject("UNPARAMETERIZED_FEATURE(11)"), NXOpen.Body)

Dim faceBodyRule1 As NXOpen.FaceBodyRule = Nothing
faceBodyRule1 = workPart.ScRuleFactory.CreateRuleFaceBody(body1, selectionIntentRuleOptions1)

selectionIntentRuleOptions1.Dispose()
Dim rules1(0) As NXOpen.SelectionIntentRule
rules1(0) = faceBodyRule1
scCollector1.ReplaceRules(rules1, False)

workPart.MeasureManager.SetPartTransientModification()

Dim scCollector2 As NXOpen.ScCollector = Nothing
scCollector2 = workPart.ScCollectors.CreateCollector()

scCollector2.SetMultiComponent()

Dim selectionIntentRuleOptions2 As NXOpen.SelectionIntentRuleOptions = Nothing
selectionIntentRuleOptions2 = workPart.ScRuleFactory.CreateRuleOptions()

selectionIntentRuleOptions2.SetSelectedFromInactive(False)

Dim features1(0) As NXOpen.Features.Feature
Dim brep1 As NXOpen.Features.Brep = CType(workPart.Features.FindObject("UNPARAMETERIZED_FEATURE(11)"), NXOpen.Features.Brep)

features1(0) = brep1
Dim nullNXOpen_DisplayableObject As NXOpen.DisplayableObject = Nothing

Dim bodyFeatureRule1 As NXOpen.BodyFeatureRule = Nothing
bodyFeatureRule1 = workPart.ScRuleFactory.CreateRuleBodyFeature(features1, True, nullNXOpen_DisplayableObject, selectionIntentRuleOptions2)

selectionIntentRuleOptions2.Dispose()
Dim rules2(0) As NXOpen.SelectionIntentRule
rules2(0) = bodyFeatureRule1
scCollector2.ReplaceRules(rules2, False)

workPart.MeasureManager.SetPartTransientModification()

Dim scCollector3 As NXOpen.ScCollector = Nothing
scCollector3 = workPart.ScCollectors.CreateCollector()

scCollector3.SetMultiComponent()

Dim markId6 As NXOpen.Session.UndoMarkId = Nothing
markId6 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Messen")

theSession.DeleteUndoMark(markId6, Nothing)

Dim markId7 As NXOpen.Session.UndoMarkId = Nothing
markId7 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Messen")

Dim markId8 As NXOpen.Session.UndoMarkId = Nothing
markId8 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Measurement Apply")

workPart.MeasureManager.ClearPartTransientModification()

workPart.MeasureManager.SetPartTransientModification()

theSession.DeleteUndoMark(markId8, "Measurement Apply")

Dim datadeleted1 As Boolean = Nothing
datadeleted1 = theSession.DeleteTransientDynamicSectionCutData()

theSession.DeleteUndoMark(markId7, Nothing)

theSession.SetUndoMarkName(markId5, "Messen")

scCollector1.Destroy()

scCollector2.Destroy()

scCollector3.Destroy()

workPart.MeasureManager.ClearPartTransientModification()

' ----------------------------------------------
' Menü: Werkzeuge->Journal->Aufzeichnung stoppen
' ----------------------------------------------

End Sub
End Module

Best regards,
Ray

NX has an old function that will report the area from curves ("area using curves"), but it is a bit quirky. There should be a corresponding function in the API, but I could not find it after doing a quick search.

A possible alternative for getting the area directly from the curves would be to use a section inertia analysis builder. The last time that I used it was in NX 9 and the results were reported via a PMI note. If this is still the case, you could extract the info from the note text then delete the note. An example of using the builder can be found here (see the AnalyzeSection subroutine):
https://nxjournaling.com/content/area-moment-inertia-0

Creating a sheet body from the curves then using a measure tool to find the area is a good idea. I'm not familiar with "face from curves", perhaps you are using "bounded plane" or "fill surface"? It looks like your NX is set to use a language other than English (German?), so maybe it is just a translation issue.

I would suggest recording a journal while creating the sheet body only - don't worry about performing the measurement in the same journal. The more you record, the more difficult it gets to separate out the useful code.

Hello NXJournaling,

Thanks for your advice!

I tried the code in your link, and nothing happened. Suppose the user has to create at least one associative intersection curve, which means the closed curve in my cubic isn't enough, right?
If so, I can't use this method because there is no associative intersection curve in every design.

I recorded only "area using curves" (the word "Fläche" in German means both area and face LOL). This time there are much fewer codes, but the journal still can't generate any face body (Flächenkörper) in the same part...

The picture below is the result from area using curves and measure. To my surprise "area using curves" creates actually a body instead of a face.
https://drive.google.com/file/d/1i3Y79JDTE_IeAVbOWHUHCaAXcG5qNtkY/view?u...

Here are the codes in journal:
Imports System
Imports NXOpen

Module NXJournal
Sub Main (ByVal args() As String)

Dim theSession As NXOpen.Session = NXOpen.Session.GetSession()
Dim workPart As NXOpen.Part = theSession.Parts.Work

Dim displayPart As NXOpen.Part = theSession.Parts.Display

' ----------------------------------------------
' Menü: Werkzeuge->Befehle vorhersagen->Fläche aus Kurven
' ----------------------------------------------
' ----------------------------------------------
' Menü: Einfügen->Oberfläche->Fläche aus Kurven...
' ----------------------------------------------
Dim markId1 As NXOpen.Session.UndoMarkId = Nothing
markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "##36Sheet")

Dim markId2 As NXOpen.Session.UndoMarkId = Nothing
markId2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Start")

theSession.SetUndoMarkName(markId2, "Klassenauswahl-Dialogfenster")

' ----------------------------------------------
' Dialogfenster Anfang Klassenauswahl
' ----------------------------------------------
Dim markId3 As NXOpen.Session.UndoMarkId = Nothing
markId3 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Klassenauswahl")

theSession.DeleteUndoMark(markId3, Nothing)

Dim markId4 As NXOpen.Session.UndoMarkId = Nothing
markId4 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Klassenauswahl")

theSession.DeleteUndoMark(markId4, Nothing)

theSession.SetUndoMarkName(markId2, "Klassenauswahl")

theSession.DeleteUndoMark(markId2, Nothing)

' ----------------------------------------------
' Menü: Werkzeuge->Journal->Aufzeichnung stoppen
' ----------------------------------------------

End Sub
End Module

Best regards,
Ray

Likewise I recorded the measurement of the body face. There is no info of area in the journal, although in interactive NX there is...

Maybe is there only MeasureManager.NewFaceProperties, which can "save" area in NXOpen?

Codes:
Imports System
Imports NXOpen

Module NXJournal
Sub Main (ByVal args() As String)

Dim theSession As NXOpen.Session = NXOpen.Session.GetSession()
Dim workPart As NXOpen.Part = theSession.Parts.Work

Dim displayPart As NXOpen.Part = theSession.Parts.Display

' ----------------------------------------------
' Menü: Analyse->Messen...
' ----------------------------------------------
Dim markId1 As NXOpen.Session.UndoMarkId = Nothing
markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start")

theSession.SetUndoMarkName(markId1, "Messen-Dialogfenster")

workPart.MeasureManager.SetPartTransientModification()

Dim scCollector1 As NXOpen.ScCollector = Nothing
scCollector1 = workPart.ScCollectors.CreateCollector()

scCollector1.SetMultiComponent()

workPart.MeasureManager.SetPartTransientModification()

Dim selectionIntentRuleOptions1 As NXOpen.SelectionIntentRuleOptions = Nothing
selectionIntentRuleOptions1 = workPart.ScRuleFactory.CreateRuleOptions()

selectionIntentRuleOptions1.SetSelectedFromInactive(False)

Dim body1 As NXOpen.Body = CType(workPart.Bodies.FindObject("UNPARAMETERIZED_FEATURE(15)"), NXOpen.Body)

Dim faceBodyRule1 As NXOpen.FaceBodyRule = Nothing
faceBodyRule1 = workPart.ScRuleFactory.CreateRuleFaceBody(body1, selectionIntentRuleOptions1)

selectionIntentRuleOptions1.Dispose()
Dim rules1(0) As NXOpen.SelectionIntentRule
rules1(0) = faceBodyRule1
scCollector1.ReplaceRules(rules1, False)

workPart.MeasureManager.SetPartTransientModification()

Dim scCollector2 As NXOpen.ScCollector = Nothing
scCollector2 = workPart.ScCollectors.CreateCollector()

scCollector2.SetMultiComponent()

' ----------------------------------------------
' Menü: Werkzeuge->Journal->Aufzeichnung stoppen
' ----------------------------------------------

End Sub
End Module

Please try the journal code below. It will prompt you to select curves, select all the curves that bound the desired area. It will then try to create a bounded plane and measure its area. It will either print the area or an error message to the information window and roll back to before the creation of the bounded plane.

This code prompts you to select curves, but the function that creates the bounded plane only requires a list of curves. If you already have the desired curves, you don't need to prompt the user to select them, you can just pass them into the function.

'NXJournaling.com
'December 18, 2023

'Report area enclosed by selected curves.
'Try to create a bounded plane from the selected curves
' if successful - measure area of bounded plane
' if unsuccessful - report to user and exit

Option Strict Off
Imports System
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF

Module Module3

Dim theSession As Session = Session.GetSession()
Dim theUfSession As UFSession = UFSession.GetUFSession()
Dim theUI As UI = UI.GetUI

Dim lw As ListingWindow = theSession.ListingWindow

Sub Main()

If IsNothing(theSession.Parts.BaseWork) Then
'active part required
Return
End If

Dim workPart As Part = theSession.Parts.Work
lw.Open()

Const undoMarkName As String = "NXJ journal"
Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, undoMarkName)

Dim myCurves As New List(Of Curve)
If SelectCurves(myCurves) = Selection.Response.Cancel Then
'user pressed back or cancel
Return
End If

'lw.WriteLine("number of curves selected: " & myCurves.Count.ToString)

'try to create bounded plane from curves
Dim myBoundedPlane As Features.BoundedPlane = CreateBoundedPlane(myCurves)
If IsNothing(myBoundedPlane) Then
'something went wrong
lw.WriteLine("unable to create bounded plane")
Return
End If

'lw.WriteLine("bounded plane created")

Dim myMeasure As MeasureManager = theSession.Parts.Work.MeasureManager()

Dim myFaceMeasure As MeasureFaces = myMeasure.NewFaceProperties(theSession.Parts.Work.UnitCollection.GetBase("Area"),
theSession.Parts.Work.UnitCollection.GetBase("Length"),
0.99, myBoundedPlane.GetBodies(0).GetFaces)

lw.WriteLine("area bounded by curves: " & myFaceMeasure.Area.ToString)

'undo creation of bounded plane
theSession.UndoToMark(markId1, undoMarkName)

lw.Close()

End Sub

Function SelectBody(ByVal prompt As String, ByRef selObj As TaggedObject) As Selection.Response

Dim title As String = "Select a solid body"
Dim includeFeatures As Boolean = False
Dim keepHighlighted As Boolean = False
Dim selAction As Selection.SelectionAction = Selection.SelectionAction.ClearAndEnableSpecific
Dim cursor As Point3d
Dim scope As Selection.SelectionScope = Selection.SelectionScope.AnyInAssembly
Dim selectionMask_array(0) As Selection.MaskTriple

With selectionMask_array(0)
.Type = UFConstants.UF_solid_type
.SolidBodySubtype = UFConstants.UF_UI_SEL_FEATURE_SOLID_BODY
End With

Dim resp As Selection.Response = theUI.SelectionManager.SelectTaggedObject(prompt,
title, scope, selAction,
includeFeatures, keepHighlighted, selectionMask_array,
selObj, cursor)
If resp = Selection.Response.ObjectSelected OrElse resp = Selection.Response.ObjectSelectedByName Then
Return Selection.Response.Ok
Else
Return Selection.Response.Cancel
End If

End Function

Function SelectCurves(ByRef theCurveList As List(Of Curve)) As Selection.Response

Dim selected() As TaggedObject = Nothing
Dim scope As Selection.SelectionScope = Selection.SelectionScope.WorkPart
Dim action As Selection.SelectionAction = Selection.SelectionAction.ClearAndEnableSpecific
Dim keepHighlighted As Boolean = False
Dim resp As Selection.Response

resp = NXOpen.UI.GetUI.SelectionManager.SelectTaggedObjects("Select Curves", "Select Curves",
scope, keepHighlighted,
New NXOpen.Selection.SelectionType() {Selection.SelectionType.Curves},
selected)

theCurveList.Clear()

For Each aCurve As Curve In selected
theCurveList.Add(aCurve)
Next

'can return OK, back, or cancel
If resp = Selection.Response.Ok Then
Return Selection.Response.Ok
Else
Return Selection.Response.Cancel
End If

End Function

Function CreateBoundedPlane(ByVal boundingCurves As List(Of Curve)) As Features.BoundedPlane

Dim markId11 As NXOpen.Session.UndoMarkId = Nothing
markId11 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "create bounded plane")

Dim nullNXOpen_Features_BoundedPlane As NXOpen.Features.BoundedPlane = Nothing

Dim boundedPlaneBuilder1 As NXOpen.Features.BoundedPlaneBuilder = Nothing
boundedPlaneBuilder1 = theSession.Parts.Work.Features.CreateBoundedPlaneBuilder(nullNXOpen_Features_BoundedPlane)

boundedPlaneBuilder1.BoundingCurves.SetAllowedEntityTypes(NXOpen.Section.AllowTypes.OnlyCurves)

Dim curveDumbRule1 As NXOpen.CurveDumbRule = Nothing
curveDumbRule1 = theSession.Parts.Work.ScRuleFactory.CreateRuleBaseCurveDumb(boundingCurves.ToArray)

boundedPlaneBuilder1.BoundingCurves.AllowSelfIntersection(True)

Dim rules1(0) As NXOpen.SelectionIntentRule
rules1(0) = curveDumbRule1
Dim nullNXOpen_NXObject As NXOpen.NXObject = Nothing

'Dim helpPoint1 As NXOpen.Point3d = New NXOpen.Point3d(-76.031245151676316, 180.3324199849491, 0.00000000000014210854715202004)
Dim helpPoint1 As NXOpen.Point3d = Nothing

For i As Integer = 0 To boundingCurves.Count - 1

boundedPlaneBuilder1.BoundingCurves.AddToSection(rules1, boundingCurves.Item(i), nullNXOpen_NXObject, nullNXOpen_NXObject, helpPoint1, NXOpen.Section.Mode.Create, False)

Next

Dim nXObject1 As NXOpen.NXObject = Nothing

Try
nXObject1 = boundedPlaneBuilder1.Commit()
theSession.DeleteUndoMark(markId11, Nothing)
Return nXObject1
Catch ex As Exception
lw.WriteLine(ex.Message)
Return Nothing
Finally
boundedPlaneBuilder1.Destroy()
End Try

End Function

Public Function GetUnloadOption(ByVal dummy As String) As Integer

'Unloads the image immediately after execution within NX
GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately

End Function

End Module

Hello NXJournaling,

Really appreciate your effort!

Because I can get the curve length by the codes at the bottom (although it can't differentiate composite curve...), I tried to gather the curve collection by the similar way below, and then use them in CreateBoundedPlane.
Somehow I get NullReferenceException, and I can't figure out why...

Dim theCurveList As List(Of Curve)
For Each aCurve As Curve In workPart.Curves
theCurveList.Add(aCurve)
Next

Best regards,
Ray

Code that I use to calculate curve length:

For Each aCurve As Curve In workPart.Curves
'skip COMPOSITE curves because their partial lengths are already calculated
If aCurve.GetType().Name.Contains("COMPOSITE") = False Then
theCurveList.Add(aCurve)
totalCurveLength1 += aCurve.GetLength
Else
theCurveList.Add(aCurve)
totalCurveLength2 += aCurve.GetLength
End If
Next aCurve

Why are we calculating curve length?

What curves are we interested in? All of them? Only those used by other features? Everything except composite curves?

How do we differentiate those that we want to use to create a bounded plane? It seems unreasonable to use all the curves in the part file for this.

Sorry I didn't tell all the details, I also calculate curve length on the selected face, so that I can calculate the operation expenses of laser cutting.

So far my total length calculates all the curves including edges from my design. Actually I don't want to include the edge length, so I try to sum the curve lengths and then subtract the edge lengths. But I can't differentiate edge with curve with my codes below because the curves inside are also cut through by laser.

This one is built by sketch and extrusion, and the four edges and circle are all counted as curves and edges.
https://drive.google.com/file/d/1MFi-ikkWcdx-focQtQ04XGkNzOKSwWiU/view?u...

This is built by BodyBuilder, I can get the curve lengths of the hollow by totalCurveLength1 (edges are not counted by it).
https://drive.google.com/file/d/1LfuTU2BT2V4Xm3S9qlfN-tVT6S2U-0KR/view?u...

The problem with composite curves is, the length is calculated double. And I have to differentiate composite and normal curves. I divide the curve length of composite curves by 2 at the moment.
Details see here:
https://community.sw.siemens.com/s/feed/0D5KZ000000YOk00AG

As for the bounded plane, I should use the curves around the hollow only, for example the circle above. The difficulty is, I can only select a face, instead of the curves around the hollow...

Best regards,
Ray


'Get the edges of a face
Dim edges() As Edge = dispObj1.GetEdges()
'Access the length of the edges
If edges.Length > 0 Then
Dim lengthEdge(edges.Length - 1) As Double 'Erzeuge ein Array, um die Länge zu speichern
For i As Integer = 0 To edges.Length - 1
lengthEdge(i) = edges(i).GetLength()
Next i

Dim upperBound As Integer, lowerBound As Integer, n As Integer 'n = Anzahl der Kanten
upperBound = UBound(lengthEdge)
lowerBound = LBound(lengthEdge)

If lengthEdge.Length > 1 Then
For j As Integer = lowerBound To upperBound
allEdgeLength += lengthEdge(j)
Next j
lw.WriteLine("Kantenlänge des Blechs: " & allEdgeLength.ToString)
lw.WriteLine("letzte Kurve im Blech: " & lengthEdge(upperBound))
ElseIf lengthEdge.Length = 1 Then
allEdgeLength = lengthEdge(0)
lw.WriteLine("Kantenlänge des Blechs: " & allEdgeLength.ToString)
Else
lw.WriteLine("Es gibt keine Kantenlänge")
End If
End If

'Calculate the curve lengths on a metal sheet (not necessarily sheet body)
For Each aCurve As Curve In workPart.Curves
'skip COMPOSITE curve because its parts are already calculated
If aCurve.GetType().Name.Contains("COMPOSITE") = False Then
totalCurveLength1 += aCurve.GetLength
Else
totalCurveLength2 += aCurve.GetLength

End If
Next aCurve
totalCurveLength2 = totalCurveLength2 / 2

Dim curvesCollection As CurveCollection = workPart.Curves
Dim curves() As Curve = curvesCollection.ToArray()
'Differentiate whether a curve is composite
Dim compositeCurveFound As Boolean = False
For Each aCurve As Curve In workPart.Curves
If aCurve.GetType().Name.Contains("COMPOSITE") = True Then
compositeCurveFound = True
Exit For
End If
Next aCurve

'Excluding the edge length
If compositeCurveFound = False Then
Dim gesamt_feature_laenge1 As Double = totalCurveLength1 - allEdgeLength
lw.WriteLine("Gesamtlänge der Kurven: " & gesamt_feature_laenge1.ToString)
Else
Dim gesamt_feature_laenge2 As Double = totalCurveLength2
lw.WriteLine("Gesamtlänge der Kurven: " & gesamt_feature_laenge2.ToString)
End If
lw.WriteLine("totalCurveLength1: " & totalCurveLength1.ToString)
lw.WriteLine("totalCurveLength2: " & totalCurveLength2.ToString)
lw.WriteLine("allEdgeLength: " & allEdgeLength.ToString)

Do you have a good way of correlating the curves to the resulting face edges? If not, I'd suggest working directly with the edges (or curves extracted directly from the edges) of the final part. There may be curves in the file that have no effect on the final face.

Do you have a SNAP license? If so, there are some topological functions that are not otherwise available in the .net API that might help you.
https://community.sw.siemens.com/s/question/0D54O000061xNsCSAU/identify-...

If you do not have a SNAP license and are using the .net API, you could build your own solution for finding inner edge loops of the face, but it may be tricky.

Hello NXJournaling,

I can use SNAP and I can read the outer and inner edges correctly now \^ ^/
Here are some questions about the edge length:

1. In the picture below, I can read the length of inner edges correctly (151.2mm), when I click the face on the (0,0,0) side.
But when I click the opposite side, the length is false (300mm). I tried to delete the composite curve, but the result was the same...
The problem is opposite for outer edges: 251,2mm (false) on the (0,0,0) side, but 400mm (correct) on the opposite side.
(The part is built by BodyBuilder, composite curves, and extrusion.)
Is it related to the convexity mentioned in PLM forum? How can I solve this problem?
Link to the picture:
https://drive.google.com/file/d/1rXAR3kO2UjBNIID7yntD8jJSvT0IHkss/view?u...

2. When I click plate below, the length of the inner edge is 0.164 foot, but it should be 94.2mm or around 0.3ft. I can confirm it's foot, because the outer edges are 0.656ft, which is 200mm.
Why is the measured number correct for outer but not for inner edge?
Even if I changed the measurement unit in NX to mm, it didn’t help. I can’t find something like MeasureManager in the SNAP Reference Guide, and I suppose the Unit Class isn’t relevant to the problem, right?
If so, how to change the measurement unit of edge.ArcLength method?

(The part is built by sketch and extrusion, and the side of face doesn’t influence like the 1. case.)
https://drive.google.com/file/d/10VHmerH36phr2aa-KTOGfdSlSdOGQDVK/view?u...

Thank you and wish you a happy new year in advance!

Best regards
Ray

Here are the code snippets:

Public Sub Run()

Dim dispObj1 As NX.Face = SelectFace() 'It's SNAP Face, not NXOpen!

Dim edges() As NX.Edge = dispObj1.Edges 'It's SNAP Edge, not NXOpen!
Dim allLengthInnerEdge As Double
For Each edgeLoop As Snap.Topology.Loop In dispObj1.Loops
Dim loopType As Snap.Topology.LoopType = edgeLoop.Type
InfoWindow.WriteLine("Type: " & loopType.ToString)

'Dim numVerts As Integer = edgeLoop.Vertices.Length
'InfoWindow.WriteLine(" Vertices: " & numVerts)

Dim numEdges As Integer = edgeLoop.Edges.Length
InfoWindow.WriteLine("Edges: " & numEdges)

Dim lengthInnerEdge() As Double
If loopType = 5413 Then '5412 bedeutet Outer (peripheral loop) in LoopType Enumeration; 5413 ist Inner
ReDim lengthInnerEdge(numEdges - 1)
For i As Integer = 0 To numEdges - 1
lengthInnerEdge(i) = edges(i).ArcLength
allLengthInnerEdge += lengthInnerEdge(i)
Next i

End If

Next edgeLoop
lw.WriteLine("innere Kantenlänge: " & allLengthInnerEdge.ToString)

End Sub

If you are interested in only the inner loop edges, you need to process only the inner loop edges. The code right now is processing a number of edges from the face, but you don't know which ones - it could be inner edges, outer edges, or a mixture of the 2. In the loop that adds the edge lengths, you are processing the "edges()" array, which contains all the edges in the face. Instead, you need to process the "edgeLoop.Edges" array.

So, instead of this:

For i As Integer = 0 To numEdges - 1
lengthInnerEdge(i) = edges(i).ArcLength
allLengthInnerEdge += lengthInnerEdge(i)
Next i

Try something like this (untested):

For Each thisEdge as Edge in edgeLoop.Edges
allLengthInnerEdge += thisEdge.ArcLength
Next

"I tried the code in your link, and nothing happened. Suppose the user has to create at least one associative intersection curve, which means the closed curve in my cubic isn't enough, right?
If so, I can't use this method because there is no associative intersection curve in every design."

The code in the other link won't directly solve your issue. The code looks for an intersection curve feature and uses it as input for the section builder. However, if you look at the AnalyzeSection subroutine, it shows how to input curves to the section builder. If you have all the curves you need to use, perhaps you can modify the code to your purposes. My apologies, I should have been more clear on this point.

I should have started with this question, but what exactly are you trying to accomplish? If you are just looking for the mass and surface area of a given body, the "AskMassProps3d" function is probably the most straight-forward to use.

Here is one example of its use:
https://nxjournaling.com/comment/3297#comment-3297

Hi NXJournaling,

Never mind, I appreciate any kind of help :)

I already got the Mass, area of all faces and Volume of the design with UnitCollection and NewMassProperties for single-body design. For 2 and more body design it get 0 somehow (see my codes at the bottom).
If I realize the API reference correctly, AskMassProps3d also did similarly right?
The problem is, I also need the Mass of the hole in the middle and the hole area within the red side, so I have to "fill" the hole and measure the "total" area of the red side. (see picture in the link)
https://drive.google.com/file/d/1MFi-ikkWcdx-focQtQ04XGkNzOKSwWiU/view?u...

I also tried "fill surface" last weekend, but the recorded journal needs exactly the name of sketch and sketch feature for FindObject. I saved the sketch in an array, and looped them like this, but it doesn't work.

Dim sketchCollection As SketchCollection = workPart.Sketches
Dim sketches() As Sketch = sketchCollection.ToArray()
Dim sketchfeatures() As NXOpen.Features.SketchFeature
For n As Integer = 0 To sketches.Length - 1
sketchfeatures(n) = sketches(n).Feature
Next n
Dim sketchfeaturenames(sketches.Length - 1) As String
For m As Integer = 0 To sketchfeatures.Length - 1
sketchfeaturenames(m) = sketchfeatures(m).Name
Next m

Dim sketchnames(sketches.Length - 1) As String
For k As Integer = 0 To sketches.Length - 1
sketchnames(k) = sketches(k).Name

Next k
Dim sketchnamesstring As String = String.Join(",", sketchnames)

For l As Integer = 0 To sketches.Length - 1
Dim sketchFeature1 As NXOpen.Features.SketchFeature = CType(workPart.Features.FindObject(sketchfeaturenames(l)), NXOpen.Features.SketchFeature)
Dim sketch1 As NXOpen.Sketch = CType(sketchFeature1.FindObject((sketchnames(l))), NXOpen.Sketch)
Next l

Therefore, I tried to extrude a solid face like this (with EdgeChain rule along normal vector).
Dim faceRule As NXOpen.EdgeChainRule = workPart.ScRuleFactory.CreateRuleEdgeChain(edges(0), edges(UBound(edges)), False)
It works most of time but if I click other side, the extrusion isn't complete.
see here: https://drive.google.com/file/d/1LfuTU2BT2V4Xm3S9qlfN-tVT6S2U-0KR/view?u...

And I also have to access the extrusion from the original face. In PLM forum there is a post about "trace_a_ray". But I didn't find it in VB API reference.
Does UF.UFModl.RayHitPointInfo or RayTracedStudioBuilder do the same thing?
https://community.sw.siemens.com/s/question/0D54O00006Z2gfgSAB/how-to-ge...

Thanks for your patience!

theBody = selectedFace.GetBody()
For Each theBody In partBodiesArray
Dim attributes() As NXObject.AttributeInformation = theBody.GetUserAttributes()
' Lese Materialtypen aus
For Each attribute As NXObject.AttributeInformation In attributes
lw.WriteLine(attribute.Title & " = " & attribute.StringValue)
Next attribute

Dim massUnits(4) As Unit
massUnits(0) = theSession.Parts.Display.UnitCollection.GetBase("Area")
massUnits(1) = theSession.Parts.Display.UnitCollection.GetBase("Volume")
massUnits(2) = theSession.Parts.Display.UnitCollection.GetBase("Mass")
massUnits(3) = theSession.Parts.Display.UnitCollection.GetBase("Length")

Dim mb As MeasureBodies = Nothing
'.NewMassProperties(array of units, accuracy parameter, array of bodies to measure)
mb = myMeasure.NewMassProperties(massUnits, 0.999, partBodiesArray)
mb.InformationUnit = MeasureBodies.AnalysisUnit.GramMillimeter
lw.WriteLine("volume: " & mb.Volume.ToString & " mm^3")
lw.WriteLine("surface area: " & mb.Area.ToString & " mm^2")
lw.WriteLine("mass: " & mb.Mass.ToString & " g")

Next theBody

Hello NXJournaling,

After trials for the whole day, the trace_a_ray in the following codes don't show any error. However, no matter I write oppositeFace.blank or oppositeFace.unblank, the face opposite to (0,0,0) doesn't change at all.

In order to get the starting point of the ray, which should be the point on the clicked face, I referred to the article.
https://community.sw.siemens.com/s/question/0D54O00007I0jWjSAJ/how-to-se...

I referred to the following links for UF_EVALSF_ask_minimum_face_dist:
https://solutions.industrysoftware.automation.siemens.com/view.php?sort=...

https://docs.plm.automation.siemens.com/data_services/resources/nx/12/nx...

And finally puzzled these codes:
'Access the point position on the clicked face
Dim evaluator As UF_EVALSF_pc_t
Dim absPoint(2) As Double
Dim surfacePosition As UF_EVALSF_pos3_p_t
Dim faceDistance As Double
Dim uv(1) As Double
Dim pnt3(2) As Double
theUfSession.UF_EVALSF_ask_minimum_face_dist(evaluator, absPoint, & surfacePosition)

After running these errors popped up, and I don't know how to solve them...
Type 'UF_EVALSF_pc_t' is not defined.
Type 'UF_EVALSF_pos3_p_t' is not defined.
'UF_EVALSF_ask_minimum_face_dist' is not a member of 'NXOpen.UF.UFSession'. & Expression expected.

I couldn't find any other example for UF_EVALSF_ask_minimum_face_dist, could you please help?

Thank you very much!

Best regards,
Ray


Dim fromCoords As Double() = {0, 0, 0}
' Access the parallel face by TraceARay
Dim rayDirection As Double() = {norm(0) * -1, norm(1) * -1, norm(2) * -1}
Dim identity(15) As Double
theUfSession.Mtx4.Identity(identity)
Dim num_results As Integer
Dim hits() As UFModl.RayHitPointInfo
theUfSession.Modl.TraceARay(1, New Tag() {theBody.Tag}, fromCoords, rayDirection, identity, 1, num_results, hits)
For Each hit As UFModl.RayHitPointInfo In hits
Dim oppositeFace As Face = DirectCast(NXObjectManager.Get(hit.hit_face), Face)
If oppositeFace IsNot Nothing Then
oppositeFace.blank

End If
Next

Here is a VB example of using .AskMinimumFaceDist

'December 19, 2023
'find point on given face from reference point
'Example of .AskMinimumFaceDist
'based on gtac nx_api1182 "Sample Open C API program : find point on selected face closest to selected point"

Option Strict Off
Imports System
Imports NXOpen
Imports NXOpen.UF

Module FindPointOnFace

Dim theSession As Session = Session.GetSession()
Dim theUfSession As UFSession = UFSession.GetUFSession()
Dim lw As ListingWindow = theSession.ListingWindow

Sub Main()

If IsNothing(theSession.Parts.Work) Then
'no file open
Return
End If

lw.Open()

Dim theFace As Face = Nothing
If SelectFace("select a face", theFace) = Selection.Response.Cancel Then
Return
End If

Dim thePoint As Point = Nothing
If SelectPointObject("select a point", thePoint) = Selection.Response.Cancel Then
Return
End If
Dim myPt(2) As Double
myPt(0) = thePoint.Coordinates.X
myPt(1) = thePoint.Coordinates.Y
myPt(2) = thePoint.Coordinates.Z

Dim uvMinMax(3) As Double
theUfSession.Modl.AskFaceUvMinmax(theFace.Tag, uvMinMax)

'Dim eval As UFEvalsf.Pos3
Dim eval As IntPtr = Nothing
theUfSession.Evalsf.Initialize(theFace.Tag, eval)

Dim pntLoc(2) As Double
Dim srfPos As UFEvalsf.Pos3 = Nothing

theUfSession.Evalsf.AskMinimumFaceDist(eval, mypt, srfPos)

display_temporary_asterisk(srfPos.pnt3, 186)
DisplayTempLine(myPt, srfPos.pnt3, 186)

lw.WriteLine("selected point: (" & thePoint.Coordinates.X.ToString & ", " & thePoint.Coordinates.Y.ToString & ", " & thePoint.Coordinates.Z.ToString & ")")
lw.WriteLine("point on surface: (" & srfPos.pnt3(0).ToString & ", " & srfPos.pnt3(1).ToString & ", " & srfPos.pnt3(2).ToString & ")")
lw.WriteLine("distance: " & srfPos.distance.ToString)

End Sub

Function SelectFace(ByVal prompt As String, ByRef selObj As TaggedObject) As Selection.Response

Dim theUI As UI = UI.GetUI
Dim title As String = "Select a face"
Dim includeFeatures As Boolean = False
Dim keepHighlighted As Boolean = False
Dim selAction As Selection.SelectionAction = Selection.SelectionAction.ClearAndEnableSpecific
Dim cursor As Point3d
Dim scope As Selection.SelectionScope = Selection.SelectionScope.WorkPart
Dim selectionMask_array(0) As Selection.MaskTriple

With selectionMask_array(0)
.Type = UFConstants.UF_solid_type
.SolidBodySubtype = UFConstants.UF_UI_SEL_FEATURE_ANY_FACE
End With

Dim resp As Selection.Response = theUI.SelectionManager.SelectTaggedObject(prompt,
title, scope, selAction,
includeFeatures, keepHighlighted, selectionMask_array,
selObj, cursor)
If resp = Selection.Response.ObjectSelected OrElse resp = Selection.Response.ObjectSelectedByName Then
Return Selection.Response.Ok
Else
Return Selection.Response.Cancel
End If

End Function

Function SelectPointObject(ByVal prompt As String, ByRef selPoint As Point) As Selection.Response

Dim selObj As TaggedObject
Dim theUI As UI = UI.GetUI
Dim title As String = "Select a Point"
Dim includeFeatures As Boolean = False
Dim keepHighlighted As Boolean = False
Dim selAction As Selection.SelectionAction = Selection.SelectionAction.ClearAndEnableSpecific
Dim cursor As Point3d
Dim scope As Selection.SelectionScope = Selection.SelectionScope.WorkPart
Dim selectionMask_array(0) As Selection.MaskTriple

With selectionMask_array(0)
.Type = UFConstants.UF_point_type
.Subtype = UFConstants.UF_all_subtype
End With

Dim resp As Selection.Response = theUI.SelectionManager.SelectTaggedObject(prompt,
title, scope, selAction,
includeFeatures, keepHighlighted, selectionMask_array,
selObj, cursor)
If resp = Selection.Response.ObjectSelected OrElse resp = Selection.Response.ObjectSelectedByName Then
selPoint = selObj
Return Selection.Response.Ok
Else
Return Selection.Response.Cancel
End If

End Function

Sub display_temporary_asterisk(ByVal coords() As Double, ByVal color As Integer)
Dim attrib As UFObj.DispProps
attrib.blank_status = UFConstants.UF_OBJ_NOT_BLANKED
attrib.color = color
attrib.line_width = UFConstants.UF_OBJ_WIDTH_NORMAL
theUfSession.Disp.DisplayTemporaryPoint(Tag.Null, UFDisp.ViewType.UseActivePlus, coords, attrib, UFDisp.PolyMarker.Asterisk)

End Sub

Sub DisplayTempLine(ByVal lineStart() As Double, ByVal lineEnd() As Double, ByVal color As Integer)

Dim props As UFObj.DispProps
props.blank_status = UFConstants.UF_OBJ_NOT_BLANKED
props.color = color
props.line_width = UFConstants.UF_OBJ_WIDTH_NORMAL
props.font = UFConstants.UF_OBJ_FONT_SOLID

'theUfSession.Disp.DisplayTemporaryLine(theSession.Parts.Work.Views.WorkView.Tag, UFDisp.ViewType.UseWorkView, lineStart, lineEnd, props)
theUfSession.Disp.DisplayTemporaryLine(Tag.Null, UFDisp.ViewType.UseActivePlus, lineStart, lineEnd, props)

End Sub

Public Function GetUnloadOption(ByVal dummy As String) As Integer

'Unloads the image when the NX session terminates
'GetUnloadOption = NXOpen.Session.LibraryUnloadOption.AtTermination

'Unloads the image immediately after execution within NX
GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately

'Unloads the image explicitly, via an unload dialog
'GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Explicitly

End Function

End Module

Hello NXJournaling,

Many thanks for your effort and help!
I realized that I misunderstood the article in PLM...
I actually want to find a point on the clicked face, and shoot a ray from the point along the opposite direction of the face's normal vector.

I apologize that Dim fromCoords As Double() = {0, 0, 0} is a test point because I couldn't find a point on the clicked face this morning...
To get the point on the clicked face, should I use AskPosOnObj in the article?
https://www.nxjournaling.com/content/wrong-positioncoordinates-selected-...

Sincerely,
Ray

I think that you would need to modify the code a bit for your purposes, but it would be a good start.

Hi NXJournaling,

Regarding AskPosOnObj I have some questions:
I'm not sure, whether I understand the AskPosOnObj. correctly.
It use a curve "lp" to define the start and end point, but why do we need MaxNumElemsOfPoint? Start and end point should be 1 each right?
How do we know the start point corresponds to the point which the mouse clicks?
And why do we have to transform between map absolute system and the views?

If I remember correctly, in a previous version of the code, the code's author (Ellie) used a very long line drawn in the current view's Z direction (into/out of the screen) and checked for intersections with the selected face instead of using the TraceARay function. I don't think the line object is strictly necessary in the version that uses TraceARay, but is used as convenience from an earlier version of the code. The line intersection test was scrapped in favor of the TraceARay function.

"why do we need MaxNumElemsOfPoint?"
The .net objects make use of Point and Point3D objects which hold 3 double values representing the X, Y, and Z coordinates of the point. The older UF functions simply use an array of 3 double values to represent the point coordinates value(0) = X, value(1) = Y, and value(2) = Z. Ellie created a constant (MaxNumElemsOfPoint) to help create these arrays.

"How do we know the start point corresponds to the point which the mouse clicks?"
The selection function used, "SelectWithSingleDialog", returns the tag of the object selected, the view it was selected in, and the cursor position of the selection click (if there was one). The line (aka the direction of the ray) makes use of the selection point and the Z axis of the current view.

"And why do we have to transform between map absolute system and the views?"
The help docs state that the cursor position is returned in absolute coordinate space, so the point mapping step might be unnecessary. I have not tested this.

I hope this helps.

Hi NXJournaling,

Yes it does help!
I found the article, whose codes are very similar to Ellie's codes but in Visual Basic.
https://www.eng-tips.com/viewthread.cfm?qid=338507

After comparing your "SelectPointObject" with "select_point_on_face" in the link, and my own implementation, I crafted the codes below.

There are some questions/problems:
1. The selected point remains (0,0,0) in AskMinimumFaceDist, even if I didn't click it. It means PointFeatureOnFace(pnt3d) doesn't work, but I do implement and parameterize the function.
I suppose the dispObj1.Tag doesn't deliver?

2. When I click the face on (0,0,0), it creates one point, but when I click the other face across the (0,0,0), it creates two or more points. This phenomena happens sometime opposite and I can't make an induction. It also makes TraceARay impossible, because the starting point can only be one. Even if I wrote a For-Loop, it doesn't work because it's not a collection type. (For testing I commented the For-loop out.)
Why are there multiple points in my single click?

3. When I run the codes in Eng-Tip forum, it creates a cross on the point I click, but my codes don't.
Is it also because of my face tag?

Wish you a merry Christmas!

Sincerely,
Ray

I excerpted the related codes below:

Option Strict Off
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF
Imports NXOpenUI
Imports NXOpen.Features
Imports NXOpen.Utilities

Public Module GetFace

Dim theSession As Session = Session.GetSession()
Dim workPart As NXOpen.Part = theSession.Parts.Work
Dim theUI As UI = UI.GetUI()
Dim theUfSession As UFSession = UFSession.GetUFSession()
Dim lw As ListingWindow = theSession.ListingWindow

Public Sub Main()

Dim partBodies As BodyCollection
Dim partBodiesArray() As Body

Dim theBody As Body
'Dim unitCollection As NXOpen.UnitCollection = workPart.UnitCollection
Dim myMeasure As MeasureManager = theSession.Parts.Display.MeasureManager()
Dim totalCurveLength1 As Double
Dim totalCurveLength2 As Double
Dim allEdgeLength As Double
Dim displayPart As NXOpen.Part = theSession.Parts.Display
Dim dispObj1 As Face = SelectFace()

'calculate the area of raw material by Extrusion
Dim faceRule As NXOpen.EdgeChainRule = workPart.ScRuleFactory.CreateRuleEdgeChain(edges(0), edges(UBound(edges)), False)
' Create a section
Dim mySection As NXOpen.Section = workPart.Sections.CreateSection(0.0095, 0.01, 0.5)
Dim help As New NXOpen.Point3d(0, 0, 0)
Dim nullObj As NXOpen.NXObject = Nothing
mySection.AddToSection({faceRule}, dispObj1, nullObj, nullObj, help, NXOpen.Section.Mode.Create, False)
'Access the normal vector of the selected face
Dim cp(2) As Double
Dim pt(2) As Double
Dim u1(2) As Double
Dim v1(2) As Double
Dim u2(2) As Double
Dim v2(2) As Double
Dim norm(2) As Double
Dim radii(1) As Double
Dim param(1) As Double
theUfSession.Modl.AskFaceProps(dispObj1.Tag, param, pt, u1, v1, u2, v2, norm, radii)

lw.WriteLine("Normalvektor X:" & formatnumber(norm(0), 2).ToString & " Y:" &
formatnumber(norm(1), 2).ToString & " Z:" &
formatnumber(norm(2), 2).ToString)

'Access the clicked point when selecting a face
Dim point1 As Point = Nothing
Dim pnt3d As Point3d = New Point3d(cp(0), cp(1), cp(2))
point1 = PointFeatureOnFace(pnt3d)
Dim locationOnFace(2) As Double
locationOnFace(0) = point1.Coordinates.X
locationOnFace(1) = point1.Coordinates.Y
locationOnFace(2) = point1.Coordinates.Z

Dim uvMinMax(3) As Double
theUfSession.Modl.AskFaceUvMinmax(theFace.Tag, uvMinMax)

Dim evaluator As IntPtr = Nothing
theUfSession.Evalsf.Initialize(theFace.Tag, evaluator)

Dim pointLocation(2) As Double
Dim surfacePosition As UFEvalsf.Pos3 = Nothing

theUfSession.Evalsf.AskMinimumFaceDist(evaluator, locationOnFace, surfacePosition)

display_temporary_asterisk(surfacePosition.pnt3, 186)

Dim theFace As Face = dispObj1
Dim facetag As Tag = Tag.Null
facetag = dispObj1.Tag

lw.WriteLine("selected point: (" & point1.Coordinates.X.ToString & ", " & point1.Coordinates.Y.ToString & ", " & point1.Coordinates.Z.ToString & ")")
lw.WriteLine("point on surface: (" & surfacePosition.pnt3(0).ToString & ", " & surfacePosition.pnt3(1).ToString & ", " & surfacePosition.pnt3(2).ToString & ")")
lw.WriteLine("distance: " & surfacePosition.distance.ToString)

'Access the parallel face by TraceARay
Dim rayDirection As Double() = {norm(0) * -1, norm(1) * -1, norm(2) * -1}
Dim identity(15) As Double
theUfSession.Mtx4.Identity(identity)
Dim num_results As Integer
Dim hits() As UFModl.RayHitPointInfo
'The troublesome for-loop
Dim point As Point

For Each hit As UFModl.RayHitPointInfo In hits
For Each point In point1
theUfSession.Modl.TraceARay(1, New Tag() {theBody.Tag}, point, rayDirection, identity, 1, num_results, hits)
Dim oppositeFace As Face = DirectCast(NXObjectManager.Get(hit.hit_face), Face)
If oppositeFace IsNot Nothing Then
lw.WriteLine(hit.ToString)

End If
Next

Next

lw.Close()
End If

End If

' Create an ExtrudeBuilder
Dim builder = workPart.Features.CreateExtrudeBuilder(Nothing)
' Define the section for the Extrude
builder.Section = mySection
' Define the direction for the Extrude
Dim origin As New NXOpen.Point3d(0, 0, 0)
Dim normalVector As New NXOpen.Vector3d(norm(0), norm(1), norm(2))
Dim updateOption = SmartObject.UpdateOption.DontUpdate
builder.Direction = workPart.Directions.CreateDirection(origin, normalVector, updateOption)
' Define the extents of the Extrude
builder.Limits.StartExtend.Value.RightHandSide = "0"
builder.Limits.EndExtend.Value.RightHandSide = "20" 'will be replaced by material thickness
Dim extrudeFeature As NXOpen.Features.Extrude = builder.CommitFeature
builder.Destroy

End Sub

Public Function SelectFace() As Face
Dim dispObj1 As Face = Nothing
' Variable for Selection Dialogs

Dim selManager = theUI.SelectionManager
Dim selectedObject As TaggedObject
Dim cursor As Point3d
Dim cue As String = "Bitte wähle die Fläche, auf der gelasert wird, aus."
Dim title As String = "Kosten- und CO2-Rechner"
Dim scope As Selection.SelectionScope = Selection.SelectionScope.WorkPart
Dim action = Selection.SelectionAction.ClearAndEnableSpecific
Dim includeFeatures As Boolean = False
Dim keepHighlighted As Boolean = False
Dim faceMask = New Selection.MaskTriple(NXOpen.UF.UFConstants.UF_face_type, 0, 0)
Dim maskArray As Selection.MaskTriple() = {faceMask}
Dim response As Selection.Response = selManager.SelectTaggedObject(cue, title, scope, action, includeFeatures, keepHighlighted, maskArray, selectedObject, cursor)

If response <> NXOpen.Selection.response.Cancel And response <> NXOpen.Selection.response.Back Then
If TypeOf selectedObject Is Face Then
dispObj1 = CType(selectedObject, Face)
End If
End If

Return dispObj1

End Function

Public Function PointFeatureOnFace(ByVal where As Point3d) As Point
Dim markId1 As Session.UndoMarkId = theSession.SetUndoMark(Session.MarkVisibility.Visible, "Create Point")
Dim point1 As Point
point1 = workPart.Points.CreatePoint(where)
Dim nullFeatures_Feature As Features.Feature = Nothing
Dim pointFeatureBuilder1 As Features.PointFeatureBuilder
pointFeatureBuilder1 = workPart.BaseFeatures.CreatePointFeatureBuilder(nullFeatures_Feature)
pointFeatureBuilder1.Point = point1
Dim nXObject1 As NXObject = pointFeatureBuilder1.Commit()
pointFeatureBuilder1.Destroy()
Return point1
End Function
Public Sub map_view2abs(ByRef c() As Double)
Dim vname As String = ""
Dim abs_mx() As Double = {0, 0, 0, 1, 0, 0, 0, 1, 0}
Dim vw() As Double = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
Dim mx() As Double = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
Dim irc As Integer = 0
theUfSession.Ui.AskLastPickedView(vname)
Dim wp As Part = theSession.Parts.Work
Dim lastViewPicked As View = CType(wp.ModelingViews.FindObject(vname), ModelingView)
Dim vmx As Matrix3x3 = lastViewPicked.Matrix()
vw(3) = vmx.Xx
vw(4) = vmx.Xy
vw(5) = vmx.Xz
vw(6) = vmx.Yx
vw(7) = vmx.Yy
vw(8) = vmx.Yz
vw(9) = vmx.Zx
vw(10) = vmx.Zy
vw(11) = vmx.Zz
theUfSession.Trns.CreateCsysMappingMatrix(vw, abs_mx, mx, irc)
theUfSession.Trns.MapPosition(c, mx)
End Sub

Public Sub map_abs2view(ByRef c() As Double)
Dim vname As String = ""
Dim abs_mx() As Double = {0, 0, 0, 1, 0, 0, 0, 1, 0}
Dim vw() As Double = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
Dim mx() As Double = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
Dim irc As Integer = 0
theUfSession.Ui.AskLastPickedView(vname) '注意Ui要不要換成theUI
Dim wp As Part = theSession.Parts.Work
Dim lastViewPicked As View =
CType(wp.ModelingViews.FindObject(vname), ModelingView)
Dim vmx As Matrix3x3 = lastViewPicked.Matrix()
vw(3) = vmx.Xx
vw(4) = vmx.Xy
vw(5) = vmx.Xz
vw(6) = vmx.Yx
vw(7) = vmx.Yy
vw(8) = vmx.Yz
vw(9) = vmx.Zx
vw(10) = vmx.Zy
vw(11) = vmx.Zz
theUfSession.Trns.CreateCsysMappingMatrix(abs_mx, vw, mx, irc)
theUfSession.Trns.MapPosition(c, mx)
End Sub

Public Sub ask_pos_on_obj(ByVal obj As NXOpen.Tag, ByVal loc() As Double)
Dim aLine As NXOpen.Tag = NXOpen.Tag.Null
Dim cp() As Double = {0, 0, 0}
Dim dist As Double = 0
Dim lp As UFCurve.Line
Dim sp(2) As Double
Dim ep(2) As Double
lp.start_point = sp
lp.end_point = ep
map_abs2view(loc)
lp.start_point(0) = loc(0)
lp.start_point(1) = loc(1)
lp.start_point(2) = loc(2) + 10000
lp.end_point(0) = loc(0)
lp.end_point(1) = loc(1)
lp.end_point(2) = loc(2) - 10000
map_view2abs(lp.start_point)
map_view2abs(lp.end_point)
theUfSession.Curve.CreateLine(lp, aLine)
theUfSession.Modl.AskMinimumDist(obj, aLine, 0, cp, 0, cp, dist, loc, cp)
theUfSession.Obj.DeleteObject(aLine)
End Sub

Public Function mask_for_face(ByVal select_ As IntPtr, ByVal userdata As IntPtr) As Integer

Dim num_triples As Integer = 1
Dim mask_triples(0) As UFUi.Mask
mask_triples(0).object_type = UFConstants.UF_solid_type
mask_triples(0).object_subtype = 0
mask_triples(0).solid_type = UFConstants.UF_UI_SEL_FEATURE_ANY_FACE
theUfSession.Ui.SetSelMask(select_,
UFUi.SelMaskAction.SelMaskClearAndEnableSpecific,
num_triples, mask_triples)
Return UFConstants.UF_UI_SEL_SUCCESS
End Function

Public Function select_point_on_face(ByVal prompt As String,
ByRef cp() As Double) As NXOpen.Tag
Dim resp As Integer = 0
Dim face1 As Face = Nothing
Dim theView As NXOpen.Tag = NXOpen.Tag.Null
Dim mask_face As UFUi.SelInitFnT = AddressOf mask_for_face
Dim facetag As Tag = Tag.Null
theUfSession.Ui.LockUgAccess(UFConstants.UF_UI_FROM_CUSTOM)
theUfSession.Ui.SelectWithSingleDialog("Select a face", prompt, UFConstants.UF_UI_SEL_SCOPE_ANY_IN_ASSEMBLY,
mask_face, Nothing, resp, facetag, cp, theView)
theUfSession.Ui.UnlockUgAccess(UFConstants.UF_UI_FROM_CUSTOM)

If resp = UFConstants.UF_UI_OBJECT_SELECTED Or resp = UFConstants.UF_UI_OBJECT_SELECTED_BY_NAME Then
ask_pos_on_obj(facetag, cp)
theUfSession.Disp.SetHighlight(facetag, 0)
Return facetag
End If
Return Tag.Null
End Function

Sub display_temporary_asterisk(ByVal coords() As Double, ByVal color As Integer)
Dim attrib As UFObj.DispProps
attrib.blank_status = UFConstants.UF_OBJ_NOT_BLANKED
attrib.color = color
attrib.line_width = UFConstants.UF_OBJ_WIDTH_NORMAL
theUfSession.Disp.DisplayTemporaryPoint(Tag.Null, UFDisp.ViewType.UseActivePlus, coords, attrib, UFDisp.PolyMarker.Asterisk)

End Sub

Public Function GetUnloadOption(ByVal dummy As String) As Integer
'delete the data after each run
GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately
End Function

End Module

Hello NXJournaling,

I tried TraceARay and AskMinimumFaceDist, and I have some questions below.
Thanks for your reply and wish you a happy new year in advance :)

1.
When dispObj1 is a selected face as face in NXOpen, how to access it with a face in SNAP?
My code currently has error "'Edges' and "Loops" are not a member of 'NXOpen.Face'."
My code:
Dim selectedFace As NX.Face = dispObj1

Is it the only solution that I set my face selecting function as face in SNAP?

2.
What is the Dim srfPos As UFEvalsf.Pos3 = Nothing

theUfSession.Evalsf.AskMinimumFaceDist(eval, mypt, srfPos)
in you answer on Dec. 19th? Is it a point on a selected face?

3.
I use TraceARay to shoot a ray against the normal vector from the point with minU and minV on the selected face. It works fine with block and sheet bodies, but not with circular and irregular extruded bodies. It can't create a hit point. I can't reason why because I also have the parameters above.
How can I solve the problem?

4.
Owing to the 3. issue I read the normal vectors of all body faces, and try to get the opposite face of the selected face. The for loop does exit and I suppose it finds the opposite face. However, the if loop below says the face list is empty.
Did I make something wrong?

5. I referred to the journal and API Referrence, and measured the volume of sheet bodies with Modl.AskMassProps3d. For surface area and mass, it works fine, but somehow, I get 0 volume with the NX example "PlateWithHoles".
Can somebody please help?
https://nxjournaling.com/content/weight-calculations-journal

Best regards,
Ray

my executable codes now:

Option Strict Off
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF
Imports NXOpenUI
Imports NXOpen.Features
Imports NXOpen.Utilities
Imports Snap
'Imports NXOpen.Selection

Public Module GetFace

Dim theSession As Session = Session.GetSession()
Dim workPart As NXOpen.Part = theSession.Parts.Work
Dim theUI As UI = UI.GetUI()
Dim theUfSession As UFSession = UFSession.GetUFSession()
Dim lw As ListingWindow = theSession.ListingWindow
Dim dispObj1 As Face = SelectFace()
Dim minPt As Point3d = PointOnSelectedFace()

Public Sub Main()

Dim partBodies As BodyCollection
Dim partBodiesArray() As Body
Dim theBody As Body
Dim myMeasure As MeasureManager = theSession.Parts.Display.MeasureManager()
Dim displayPart As NXOpen.Part = theSession.Parts.Display

If workPart Is Nothing Then
Guide.InfoWriteLine("No work part for processing")

Else partBodies = workPart.Bodies
If dispObj1 Is Nothing Then

Else
' Achtung: partBodiesArray() speichert verschiedene Koerpers
partBodiesArray = partBodies.ToArray()
lw.Open()
lw.WriteLine("Number of BODIES in part= " + partBodiesArray.Length.ToString)

theBody = dispObj1.GetBody()
For Each theBody In partBodiesArray
Dim attributes() As NXObject.AttributeInformation = theBody.GetUserAttributes()
' Read Material type
For Each attribute As NXObject.AttributeInformation In attributes
lw.WriteLine(attribute.Title & " = " & attribute.StringValue)
Next attribute

Next theBody

If theBody.IsSheetBody = True Then
Dim AccuracyValue(11) As Double
Dim MassProperties(46) As Double
Dim Stats(12) As Double 'Errors are Estimates of the Relative Tolerances

Dim tagList(0) As NXOpen.Tag
tagList(0) = theBody.Tag
AccuracyValue(0) = 0.99
'AskMassProps3d(objects, numObjs, type, units (3 ist Gramm und Centimeter), density, accuracy, accValue)
theUfSession.Modl.AskMassProps3d(tagList, 1, 2, 3, 0.0375, 1, AccuracyValue, MassProperties, Stats)
lw.WriteLine("Volumen: " & MassProperties(1) * 1000.ToString & " mm^3") 'Why is it 0 here?
lw.WriteLine("Surface Area: " & MassProperties(0) * 100.ToString & " mm^2")
lw.WriteLine("Masse: " & MassProperties(3).ToString & " g")

Else

Dim massUnits(4) As Unit
massUnits(0) = theSession.Parts.Display.UnitCollection.GetBase("Area")
massUnits(1) = theSession.Parts.Display.UnitCollection.GetBase("Volume")
massUnits(2) = theSession.Parts.Display.UnitCollection.GetBase("Mass")
massUnits(3) = theSession.Parts.Display.UnitCollection.GetBase("Length")
Dim mb As MeasureBodies = Nothing
'.NewMassProperties(array of units, accuracy parameter, array of bodies to measure)
mb = myMeasure.NewMassProperties(massUnits, 0.999, partBodiesArray)
mb.InformationUnit = MeasureBodies.AnalysisUnit.GramMillimeter
lw.WriteLine("volume: " & mb.Volume.ToString & " mm^3")
lw.WriteLine("surface area: " & mb.Area.ToString & " mm^2")
lw.WriteLine("mass: " & mb.Mass.ToString & " g")

End If

'Read the area and the total edge length of the selected face
Dim areaUnit As Unit = CType(workPart.UnitCollection.FindObject("SquareMilliMeter"), Unit)
Dim lengthUnit As Unit = CType(workPart.UnitCollection.FindObject("MilliMeter"), Unit)
Dim measureFaces1 As MeasureFaces
Dim mFaces = New Face() {dispObj1} 'das Array speichert die einzige ausgewählte Fläche
measureFaces1 = workPart.MeasureManager.NewFaceProperties(areaUnit, lengthUnit, 0.999, mFaces)
Dim x1, x2 As Double
x1 = measureFaces1.Area
x2 = measureFaces1.Perimeter
lw.WriteLine("Flächengröße: " & x1)
lw.WriteLine("Kantenlänge des Blechs + Kurven im Blech: " & x2)

'Get the inner edges and their total length on a face
' Create an instance of FaceLoop
Dim faceLoopInstance As New FaceLoop()
' Call the ReturnInnerEdgeLength method on the instance
'faceLoopInstance.ReturnInnerEdgeLength()
faceLoopInstance.SetDefaultUnit()
Dim allLengthInnerEdge As Double = faceLoopInstance.ReturnInnerEdgeLength()
lw.WriteLine("innere Kantenlänge: " & allLengthInnerEdge.ToString)
lw.WriteLine("äußere Kantenlänge: " & (x2 - allLengthInnerEdge).ToString)

'Calculate the „full“ area of a face by Extrusion
Dim edges() As Edge = dispObj1.GetEdges()
Dim faceRule As NXOpen.EdgeChainRule = workPart.ScRuleFactory.CreateRuleEdgeChain(edges(0), edges(UBound(edges)), False)

' Create a section
Dim mySection As NXOpen.Section = workPart.Sections.CreateSection(0.0095, 0.01, 0.5)
Dim help As New NXOpen.Point3d(0, 0, 0)
Dim nullObj As NXOpen.NXObject = Nothing
mySection.AddToSection({faceRule}, dispObj1, nullObj, nullObj, help, NXOpen.Section.Mode.Create, False)
'Greife den Normalvektor der ausgewählten Fläche zu
Dim cp(2) As Double
Dim pt(2) As Double
Dim u1(2) As Double
Dim v1(2) As Double
Dim u2(2) As Double
Dim v2(2) As Double
Dim norm(2) As Double
Dim radii(1) As Double
Dim param(1) As Double
'theUfSession.Modl.AskFaceParm(face1.Tag, cp, param, pt) return the closest U,V parameter on the face, get the (u,v) values at a point
theUfSession.Modl.AskFaceProps(dispObj1.Tag, param, pt, u1, v1, u2, v2, norm, radii)

lw.WriteLine("Normalvektor X:" & formatnumber(norm(0), 2).ToString & " Y:" &
formatnumber(norm(1), 2).ToString & " Z:" &
formatnumber(norm(2), 2).ToString)

'Messe die Dicke durch Trace_A_Ray

Dim locationOnFace(2) As Double
locationOnFace(0) = minPt.X
locationOnFace(1) = minPt.Y
locationOnFace(2) = minPt.Z

Dim evaluator As IntPtr = Nothing
theUfSession.Evalsf.Initialize(dispObj1.Tag, evaluator)
Dim surfacePosition As UFEvalsf.Pos3 = Nothing
theUfSession.Evalsf.AskMinimumFaceDist(evaluator, locationOnFace, surfacePosition)

'display_temporary_asterisk(surfacePosition.pnt3, 186)
'DisplayTempLine(locationOnFace, surfacePosition.pnt3, 186)

'lw.WriteLine("selected point: (" & point1.Coordinates.X.ToString & ", " & point1.Coordinates.Y.ToString & ", " & point1.Coordinates.Z.ToString & ")")
lw.WriteLine("point with minU & minV on surface: (" & minPt.X.ToString & ", " & minPt.Y.ToString & ", " & minPt.Z.ToString & ")")
lw.WriteLine("distance: " & surfacePosition.distance.ToString)

'Get the parallel face by TraceARay
Dim bodyTag(partBodiesArray.Length - 1) As Tag
For k As Integer = 0 To partBodiesArray.Length - 1
bodyTag(k) = partBodiesArray(k).Tag
Next k

Dim rayDirection As Double() = {norm(0) * -1, norm(1) * -1, norm(2) * -1}
Dim identity(15) As Double
theUfSession.Mtx4.Identity(identity)
Dim num_results As Integer
Dim hits() As UFModl.RayHitPointInfo
theUfSession.Modl.TraceARay(1, bodyTag, locationOnFace, rayDirection, identity, 2, num_results, hits)

Dim hitPoint() As Double
For i As Integer = 0 To hits.Length - 1 'nur 0 zu 1
Dim hitFace As Tag = hits(i).hit_face
Dim hitNorm() As Double = hits(i).hit_normal
hitPoint = hits(i).hit_point
Next
lw.WriteLine("hit point: (" & hitPoint(0).ToString & ", " & hitPoint(1).ToString & ", " & hitPoint(2).ToString & ")")

'Measure the distance betwenn the minU/V Point (i.e. locationOnFace) and hitPoint
Dim temp1(2) As Double 'Output: Minimum distance Point on object1
Dim temp2(2) As Double 'Output: Minimum distance Point on object2
Dim minDist As Double 'Output: Resultant minimum distance.
theUfSession.Modl.AskMinimumDist(Nothing, Nothing, 1, locationOnFace, 1, hitPoint, minDist, temp1, temp2)
lw.WriteLine("Materialdicke: " & minDist.ToString)

'Read the normal vectors of all Faces, and access the opposite face
Dim norm2(2) As Double
Dim oppositeFaces As New List(Of Face)({})

For Each bodyFace As Face In theBody.GetFaces()
theUfSession.Modl.AskFaceProps(bodyFace.Tag, param, pt, u1, v1, u2, v2, norm2, radii)

lw.WriteLine("Normalvektor X:" & formatnumber(norm2(0), 2).ToString & " Y:" &
formatnumber(norm2(1), 2).ToString & " Z:" &
formatnumber(norm2(2), 2).ToString)
If norm2(0) = norm(0) * -1 AndAlso norm2(1) = norm(1) * -1 AndAlso norm2(2) = norm(2) * -1 Then
oppositeFaces.Add(bodyFace)
End If
Exit For
Next

If oppositeFaces.Count > 0 Then
Dim oppositeFace As Face = oppositeFaces(0)
Dim oppositeFaceTag As Tag = oppositeFace.Tag
theUfSession.Modl.AskMinimumDist(Nothing, oppositeFaceTag, 1, locationOnFace, 1, Nothing, minDist, temp1, temp2)
lw.WriteLine("Materialdicke2: " & minDist.ToString)
Else
lw.WriteLine("No opposite face found.")
End If

' Create an ExtrudeBuilder
Dim builder = workPart.Features.CreateExtrudeBuilder(Nothing)
' Define the section for the Extrude
builder.Section = mySection
' Define the direction for the Extrude
Dim origin As New NXOpen.Point3d(0, 0, 0)
Dim normalVector As New NXOpen.Vector3d(norm(0), norm(1), norm(2))
Dim updateOption = SmartObject.UpdateOption.DontUpdate
builder.Direction = workPart.Directions.CreateDirection(origin, normalVector, updateOption)
' Define the extents of the Extrude
builder.Limits.StartExtend.Value.RightHandSide = "0"
builder.Limits.EndExtend.Value.RightHandSide = "20" 'Durch Materialdicke ersetzt
Dim extrudeFeature As NXOpen.Features.Extrude = builder.CommitFeature
builder.Destroy

''Access the Extrusion
'Dim extrudedBodyFace As New List(Of Face)({})
'For Each theBody In partBodiesArray
' If theBody.GetType().Name.Contains("Extrude") = True Then
' extrudedBodyFace
' Dim measureFaces2 As MeasureFaces
' Dim mFaces = New Face() {dispObj1} '
' measureFaces2 = workPart.MeasureManager.NewFaceProperties(areaUnit, lengthUnit, 0.999, mFaces)
' Dim x1, x2 As Double
' End If
'Next

'schaue die X-Y Fläche
Dim myView As ModelingView = theSession.Parts.Work.ModelingViews.WorkView
Dim rotAxis As Vector3d = myView.GetAxis(XYZAxis.ZAxis)
Dim rotationAngleDegrees As Double = 0
myView.Concatenate(myView.AbsoluteOrigin, rotAxis, (System.Math.PI / 180) * rotationAngleDegrees)

lw.Close()
End If

End If

End Sub

'Differentiate inner and outer edges by loop
Public Class FaceLoop
Public Sub SetDefaultUnit()
' Constructor - called when an instance of FaceLoop is created
theSession.Parts.Display.UnitCollection.SetDefaultDataEntryUnits(UnitCollection.UnitDefaults.KgMmNDegC)
theSession.Parts.Display.UnitCollection.SetDefaultObjectInformationUnits(UnitCollection.UnitDefaults.KgMmNDegC)
End Sub

Public Function ReturnInnerEdgeLength() As Double
Dim dispObj1 As NX.Face = SelectFace() 'It's SNAP Face, not NXOpen!

' Get information about the loops of this face
'For Each edgeOuterLoop As Snap.Topology.Loop In dispObj1.OuterLoop
' Dim lengthOuterEdges As Integer = edgeOuterLoop.Edges.ArcLength
' InfoWindow.WriteLine("äußere Kantenlänge : " & lengthOuterEdges.ToString)
'Next
Dim edges() As NX.Edge = dispObj1.Edges 'It's SNAP Edge, not NXOpen!
Dim allLengthInnerEdge As Double
For Each edgeLoop As Snap.Topology.Loop In dispObj1.Loops
Dim loopType As Snap.Topology.LoopType = edgeLoop.Type
InfoWindow.WriteLine("Type: " & loopType.ToString)

'Dim numVerts As Integer = edgeLoop.Vertices.Length
'InfoWindow.WriteLine(" Vertices: " & numVerts)

Dim numEdges As Integer = edgeLoop.Edges.Length
InfoWindow.WriteLine("Edges: " & numEdges)

If loopType = 5413 Then '5412 bedeutet Outer (peripheral loop) in LoopType Enumeration; 5413 ist Inner
For Each thisEdge As NX.Edge In edgeLoop.Edges
allLengthInnerEdge += thisEdge.ArcLength
Next thisEdge
End If

Next edgeLoop

Return allLengthInnerEdge
End Function
End Class

Public Function SelectFace() As Face
Dim dispObj1 As Face = Nothing
' Variablen fuer Auswahl-Dialoge (Selection Dialogs)

Dim selManager = theUI.SelectionManager
Dim selectedObject As TaggedObject
Dim cursor As Point3d
Dim cue As String = "Bitte wähle die Fläche, auf der gelasert wird, aus."
Dim title As String = "Kosten- und CO2-Rechner"
Dim scope As Selection.SelectionScope = Selection.SelectionScope.WorkPart
Dim action = Selection.SelectionAction.ClearAndEnableSpecific
Dim includeFeatures As Boolean = False
Dim keepHighlighted As Boolean = False
Dim faceMask = New Selection.MaskTriple(NXOpen.UF.UFConstants.UF_face_type, 0, 0)
Dim maskArray As Selection.MaskTriple() = {faceMask}
'Dim typeArray As NXOpen.Selection.SelectionType() = {SelectionType.Faces}
Dim response As Selection.Response = selManager.SelectTaggedObject(cue, title, scope, action, includeFeatures, keepHighlighted, maskArray, selectedObject, cursor)

If response <> NXOpen.Selection.response.Cancel And response <> NXOpen.Selection.response.Back Then
If TypeOf selectedObject Is Face Then
dispObj1 = CType(selectedObject, Face)
End If
End If

Return dispObj1

End Function

Function PointOnSelectedFace() As Point3d
'Get the point with minU and minV of a face
Dim selectedFace As Face = dispObj1
Dim uvMin(3) As Double
theUfSession.Modl.AskFaceUvMinmax(selectedFace.Tag, uvMin)
Dim ptMin(2) As Double
Dim ptMax(2) As Double

'get mid point of given face
Dim param() As Double = {uvMin(0), uvMin(2)} '[0] - umin[1] - umax [2] - vmin [3] - vmax
Dim pt(2) As Double
Dim u1(2) As Double
Dim v1(2) As Double
Dim u2(2) As Double
Dim v2(2) As Double
Dim unitNormal(2) As Double
Dim radii(1) As Double
theUfSession.Modl.AskFaceProps(selectedFace.Tag, param, pt, u1, v1, u2, v2, unitNormal, radii)

Dim minPt As New Point3d(pt(0), pt(1), pt(2))
Return minPt

End Function

Public Function GetUnloadOption(ByVal dummy As String) As Integer
'lösche die vorherige Daten sofort nach jedem Lauf
GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately
End Function

End Module

1. I don't have access to a SNAP license, but I believe you will need to cast the NXOpen face type to the SNAP face type. The link below is an introduction to casting in VB:
https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/...

2. srfPos is a structure that holds several pieces of information (returned from the AskMinimumFaceDist function) one of which is a point on the face. The "eval" variable is an "evaluator object" for the given face.

3 & 4: I have not looked over the code yet and that will take some time. However, when using the trace a ray function, make sure you do NOT have perspective turned on in the view. The perspective view option will skew the trace a ray results.

5. Sheet bodies have zero volume. IIRC, there is an option in AskMassProps3d to calculate sheet bodies as if they were of a given thickness, but if you have not used this option, the volume will be reported as zero.

I don't have a SNAP license, so I had to comment out all the code that called SNAP functions; I won't be able to help debug those.

Note that the TraceARay function shoots a ray completely through your model (think of it like an X-ray that travels through everything) and reports all the faces that it hits on the way through. The "hits()" array contains all the faces that the ray hit and the coordinates of each hit. I modified your loop slightly so that it would report each hit.

Dim hitPoint() As Double
For i As Integer = 0 To hits.Length - 1 'nur 0 zu 1
Dim hitFace As Tag = hits(i).hit_face
Dim hitNorm() As Double = hits(i).hit_normal
hitPoint = hits(i).hit_point
lw.WriteLine("hit point: (" & hitPoint(0).ToString & ", " & hitPoint(1).ToString & ", " & hitPoint(2).ToString & ")")
Next

You will need to decide which hit point to use in the AskMinimumDist function.

Hello NXJournaling,
Re: 3 & 4
Thanks for your hint! I moved the print into the for-loop, and it can print the starting point and the hit point now. However, it still works only with rectangular parts; for circular and irregular parts it doesn’t print the hit points, but there is also no error…

Another problem/phenomenon with circular and irregular parts is, the material thickness (Materialdicke) measured from TraceARay & AskMinimumDist is larger than opposite face & AskMinimumDist, and both are wrong. Take the “door” from NX for example, its thickness is 1mm, but TraceARay & AskMinimumDist measured 21,4910mm and opposite face & AskMinimumDist measured 6,5446mm…
I wonder why there is such an error and difference, because TraceARay travels 1mm from the point with minU and minV on the selected face against the normal vector, and opposite face is 1mm away from the selected face…
Are the following factors the reasons?
1. “Make sure you do NOT have perspective turned on in the view. The perspective view option will skew the trace a ray results.” Perhaps my perspective is turned on? If so, how to turn it off?
2. Shouldn’t I use the point with minU and minV on the selected face?
3. The hitpoint in the parameter of AskMinimumDist is an array, and notation like hitPoint(0) results in error. How can I decide a point when the hitpoint is not printed out?

Re: 5
What’s the option you said in AskMassProps3d?
I checked the reference but didn’t find it…
https://docs.plm.automation.siemens.com/data_services/resources/nx/10/nx...

Thanks for your reply in advance!

I created a simple cylinder and ran your journal and clicked on the cylindrical face. The code correctly reported the "material thickness", the diameter of the cylinder. It reported that it could not find the opposite face, probably because it is the same as the selected face. Finally, it threw an error because it was unable to "create a body". I have not examined the code closely enough to know exactly what it is looking for.

Picking the point on the minimum U,V coordinate may be an issue as it will likely land on the edge of the face. I don't know how much of an issue this is (if any) for the trace a ray function. It might work just fine; testing will tell. Edit: According to the help docs: "A ray that is tangent to the interior of a face, but does not cross through the face is not considered a hit." I've not tested it myself, but it sounds like a ray that hits the edge of a face would not be considered a "hit".

On my version of NX, the perspective option can be found in the "display" section of the "view" tab. If you search for "perspective" in the command finder, it will give you quick access to it.

"The hitpoint in the parameter of AskMinimumDist is an array, and notation like hitPoint(0) results in error. How can I decide a point when the hitpoint is not printed out?"

In the code that you posted earlier, "hitpoint(0)" would represent the X coordinate of the "hitpoint" array. For the Ask distance function, you would need to pass in the entire array to fully define the point (all 3 coordinates). One of the journals posted creates a temporary point on the given coordinates; you might try using this subroutine to display the points while you debug your code. The subroutine below takes a coordinate array (which you already have) and an integer for the color (the NX color ID).

Sub display_temporary_asterisk(ByVal coords() As Double, ByVal color As Integer)
Dim attrib As UFObj.DispProps
attrib.blank_status = UFConstants.UF_OBJ_NOT_BLANKED
attrib.color = color
attrib.line_width = UFConstants.UF_OBJ_WIDTH_NORMAL
theUfSession.Disp.DisplayTemporaryPoint(Tag.Null, UFDisp.ViewType.UseActivePlus, coords, attrib, UFDisp.PolyMarker.Asterisk)

End Sub

The option that I mentioned for sheet bodies in the AskMassProps3d function is the "type 2" analysis (thin shell). In this type of analysis, the density value you supply to the function will be interpreted as mass per unit area.

Hello NXJournaling,
Really appreciate your detailed reply!

I can get the opposite face whose normal vector is against our selected face at rectangular and circular parts, but not the PlateWithHole example (sheet body). It’s really mysterious XD

When I use type 2 thin shell in the parameter (the second 2 in the bracket), the volume is still not measured. My code which I puzzled from some examples is different from the API reference. Do I implement it correctly (see my code below)?
'AskMassProps3d(objects, numObjs, type, units (3 ist Gramm und Centimeter), density, accuracy, accValue)
theUfSession.Modl.AskMassProps3d(tagList, 1, 2, 2, 0.0375, 1, AccuracyValue, MassProperties, Stats)

I'm not sure if I implement your subroutine correctly, because I can’t see any asterisk neither with rectangular nor circular parts. I put the code body in my Sub Main(), right under my TraceARay. What's my mistake?

'Greife die parallele Fläche durch TraceARay zu
Dim bodyTag(partBodiesArray.Length - 1) As Tag
For k As Integer = 0 To partBodiesArray.Length - 1
bodyTag(k) = partBodiesArray(k).Tag
Next k

Dim rayDirection As Double() = {norm(0) * -1, norm(1) * -1, norm(2) * -1}
Dim identity(15) As Double
theUfSession.Mtx4.Identity(identity)
Dim num_results As Integer
Dim hits() As UFModl.RayHitPointInfo
theUfSession.Modl.TraceARay(1, bodyTag, locationOnFace, rayDirection, identity, 2, num_results, hits)

Dim hitPoint() As Double
For i As Integer = 0 To hits.Length - 1 'nur 0 zu 1
Dim hitFace As Tag = hits(i).hit_face
Dim hitNorm() As Double = hits(i).hit_normal
hitPoint = hits(i).hit_point
lw.WriteLine("hit point: (" & hitPoint(0).ToString & ", " & hitPoint(1).ToString & ", " & hitPoint(2).ToString & ")")
Next

'display_temporary_asterisk
Dim attrib As UFObj.DispProps
Dim color As Integer = 2
attrib.blank_status = UFConstants.UF_OBJ_NOT_BLANKED
attrib.color = color
attrib.line_width = UFConstants.UF_OBJ_WIDTH_NORMAL
theUfSession.Disp.DisplayTemporaryPoint(Tag.Null, UFDisp.ViewType.UseActivePlus, hitPoint, attrib, UFDisp.PolyMarker.Asterisk)

I was mistaken in my previous post. I was thinking that if you did a "thin shell" analysis there would be an option to give the sheet bodies a thickness. However, this is not how it works. The density value supplied will be a "mass per unit area". The sheet bodies have zero volume and will be reported that way.

I've never used the "bounded by sheet bodies" (type 3) option, but this might give you what you need. Alternately, if you have sheet bodies that enclose a volume, you can use the "sew" command before running the journal. If the sheet bodies enclose a volume and have no gaps, the sew command will result in a solid body. You can then run a normal measurement command on this body (type 1, solid body).

You should copy & paste the entire subroutine to your code, that way you can call it anywhere you need to in your Main subroutine (see below).

Sub Main

'... other code

Dim hitPoint() As Double
For i As Integer = 0 To hits.Length - 1 'nur 0 zu 1
Dim hitFace As Tag = hits(i).hit_face
Dim hitNorm() As Double = hits(i).hit_normal
hitPoint = hits(i).hit_point
display_temporary_asterisk(hitPoint, 186)
lw.WriteLine("hit point: (" & hitPoint(0).ToString & ", " & hitPoint(1).ToString & ", " & hitPoint(2).ToString & ")")
Next

'... other code

End Sub 'Sub Main

Sub display_temporary_asterisk(ByVal coords() As Double, ByVal color As Integer)
Dim attrib As UFObj.DispProps
attrib.blank_status = UFConstants.UF_OBJ_NOT_BLANKED
attrib.color = color
attrib.line_width = UFConstants.UF_OBJ_WIDTH_NORMAL
theUfSession.Disp.DisplayTemporaryPoint(Tag.Null, UFDisp.ViewType.UseActivePlus, coords, attrib, UFDisp.PolyMarker.Asterisk)

End Sub

Hi NXJournaling,

I finally incorporated the subroutine correctly!
Two stars are on the edge which is vertical from the selected face. That's correct!

Nevertheless there is no asterisk on cylindrical and irregular parts, which means the starting point of TraceARay, i.e. the point with minU & minV on a face, is actually not on the face!?

Or is there again some blunders with my function which returns the point with minU & minV on the selected face?


Function PointOnSelectedFace(dispObj1 As Face) As Point3d
'Greife den Punkt mit minU und minV einer Fläche zu / Get the point with minU and minV on a face
Dim selectedFace As Face = dispObj1
Dim uvMin(3) As Double
theUfSession.Modl.AskFaceUvMinmax(selectedFace.Tag, uvMin)
Dim ptMin(2) As Double
Dim ptMax(2) As Double

'get the point of given face
Dim param() As Double = {uvMin(0), uvMin(2)} '[0] - umin[1] - umax [2] - vmin [3] - vmax
Dim pt(2) As Double
Dim u1(2) As Double
Dim v1(2) As Double
Dim u2(2) As Double
Dim v2(2) As Double
Dim unitNormal(2) As Double
Dim radii(1) As Double
theUfSession.Modl.AskFaceProps(selectedFace.Tag, param, pt, u1, v1, u2, v2, unitNormal, radii)

Dim minPt As New Point3d(pt(0), pt(1), pt(2))
Return minPt

End Function

Can you post or email me your latest code?
info@nxjournaling.com

Yes, please find the GetFace0108 in your mail box.
Thank you!

"which means the starting point of TraceARay, i.e. the point with minU & minV on a face, is actually not on the face!?"

This is true. The AskFaceProps function operates on the underlying surface of the given face. The UV parameters are reported against the underlying surface, not the final trimmed surface. Imagine a square, planar surface - the U and V directions are parallel to the horizontal and vertical edges. Now trim away parts of this surface until it is circular instead of square. The UV definition is not obvious on this new circular, planar surface. The UV definition will be based on the original surface before it was trimmed.

NX works much the same way in the background. If you sketch an arbitrary shape and extrude it, NX will internally create a larger rectangular planar surface and trim it back to your desired curves.

The AskMinimumFaceDist function will report a point on the actual, final surface. To fix the current issue, you could use AskMinimumFaceDist given the min UV point or the original selection point (you can skip the call to AskFaceProps if the face is planar). If the normal of the selected face is close to the screen normal, the selected point and the returned point will align well. If the view is skewed, the minimum face point may be quite different from the selected point.

Another option would be to use the selected point and trace a ray in the view normal direction. This will give you a hit point on the selected face. You could then use the point location on the face and the face normal to trace another ray through the part to get the thickness.

"For 2 and more body design it get 0 somehow (see my codes at the bottom)."

The code at the bottom:

theBody = selectedFace.GetBody()
For Each theBody In partBodiesArray
Dim attributes() As NXObject.AttributeInformation = theBody.GetUserAttributes()
' Lese Materialtypen aus
For Each attribute As NXObject.AttributeInformation In attributes
lw.WriteLine(attribute.Title & " = " & attribute.StringValue)
Next attribute

Dim massUnits(4) As Unit
massUnits(0) = theSession.Parts.Display.UnitCollection.GetBase("Area")
massUnits(1) = theSession.Parts.Display.UnitCollection.GetBase("Volume")
massUnits(2) = theSession.Parts.Display.UnitCollection.GetBase("Mass")
massUnits(3) = theSession.Parts.Display.UnitCollection.GetBase("Length")

Dim mb As MeasureBodies = Nothing
'.NewMassProperties(array of units, accuracy parameter, array of bodies to measure)
mb = myMeasure.NewMassProperties(massUnits, 0.999, partBodiesArray)
mb.InformationUnit = MeasureBodies.AnalysisUnit.GramMillimeter
lw.WriteLine("volume: " & mb.Volume.ToString & " mm^3")
lw.WriteLine("surface area: " & mb.Area.ToString & " mm^2")
lw.WriteLine("mass: " & mb.Mass.ToString & " g")

Next theBody

Note that the "NewMassProperties" function will only work on solid bodies (not sheet bodies). Also, the same calculation gets run each time through the for loop; you are measuring the entire array of bodies, not each individual body. If you run this code on a file that has 3 solid bodies, you will get the same result written out 3 times.

If you only need the total of all bodies combined, move the calculation outside of the for loop. If you need each body individually, only pass in the body of the current iteration ("theBody" in your case) to the measure function.

If you need to measure sheet bodies, I suggest the "NewFaceProperties" function. See my code posted earlier in the thread (link for convenience):
https://nxjournaling.com/comment/6572#comment-6572

"The problem is, I also need the Mass of the hole in the middle and the hole area within the red side, so I have to "fill" the hole and measure the "total" area of the red side."

But why?
If you can complete the desired calculation, what does it tell you? Are you looking for the minimum stock size needed to create the part? If so, you can use one of the bounding box functions. Then, if desired, you could compare the starting mass (or volume, surface area, etc) vs. the finished part.

Because I have to calculate the total raw material cost of laser cutting, for example, the the hole and the red side actually come from the same plate:
https://drive.google.com/file/d/1MFi-ikkWcdx-focQtQ04XGkNzOKSwWiU/view

That's why I have to calculate the total area and mass of the plate.

I guess the bounding box is this one right?
https://www.nxjournaling.com/content/creating-bounding-box-arround-solid
Are the offset Negative/Positive in X/Y/Z the edge lengths, and X/Y/ZValue.Value in API reference a reference point (such as a corner of a plate)?

Best regards,
Ray

Dim min_corner(2) As Double
Dim directions(2, 2) As Double
Dim distances(2) As Double
Dim edge_len(2) As String

While select_a_body(a_body) = Selection.Response.Ok

ufs.Csys.AskWcs(csys)

ufs.Modl.AskBoundingBoxExact(a_body, csys, min_corner, directions, distances)

The code above was taken from the first code posted in the thread you linked.

In this case, the calculated bounding box will be aligned to the given csys (the WCS). The "min_corner" tells you the coordinates of the corner of the bounding box. The "directions" array gives you the vector directions of the box. The "distances" array gives you the length of the box sides. I think the min corner and directions are reported with respect to the absolute coordinate system, but I'd have to double check that.

If we run the code on a rectangular block and the WCS is aligned to the block, then the "min_corner" would give the coordinates of one of the block's corners. If the WCS were not aligned to the block, then this would not necessarily be true.

Hi NXJournaling,

I moved the measuring codes out of the loop and they work fine, thank you!

I assume the Siemens example file PlateWithHoles is a sheet body because I still get 0s with surface area, volume and mass. But if I can get the face area and thickness from the discussion above, I can multiple them with material density and get the mass.

Best regards,
Ray

Hello NXJournaling,

1.
I measured the distance between the face center point and the hit point from TraceARay. I also measured the distance between the face center point and the opposite face. They both delivered correct distances (i.e. material thickness) for cylindrical and irregular parts. Thus the point with minU&V is the issue.

Because there may be holes on face center, which results in false distance, I tried to get a point on an face edge (10% of the length) with UF.Eval.EvaluateUnitVectors.
I referred to some sources below, but I don't know why NX says my edge1.Tag is an invalid parameter...
Could you please have a look at the codes at the bottom? Thanks in advance!

Reference:
https://community.sw.siemens.com/s/question/0D54O000061xNA5SAM/get-multi...
https://docs.plm.automation.siemens.com/data_services/resources/nx/12/nx...

2.
Besides, I found the NameLocation at DisplayableObject class, and I thought, maybe it can provide me a Point3d of a face. Am I right?
I tried the codes below, but NX said: NXException: the name space for this type can't be identified in the 2nd line. Since Face also inherits from displayableobject, why isn’t it allowed?
Dim displayableobjface = CType(dispObj1, Displayableobject)
Dim pointonselectedface As point3d = displayableobjface.NameLocation
Dim pointonselectedfacedouble(2) As Double
pointonselectedfacedouble(0) = pointonselectedface.x
pointonselectedfacedouble(1) = pointonselectedface.y
pointonselectedfacedouble(2) = pointonselectedface.z


'Trial "UF_EVAL_evaluate_unit_vectors"
'Dim edges() As Edge = dispObj1.GetEdges()
Dim edge1 As Edge = edges(0)
Dim evaluator2 As IntPtr = Nothing
theUfSession.Evalsf.Initialize(edge1.Tag, evaluator2)

'Ask limits of the edge/curve at first
Dim limits(1) As Double
theUfSession.Eval.AskLimits(evaluator, limits)

Dim param2 As Double = (limits(1) - limits(0)) * 10 / 100.0 + limits(0) 'get a point at 10% of edge
Dim point(2) As Double
Dim tangent(2) As Double
Dim normal(2) As Double
Dim binormal(2) As Double
theUfSession.Eval.EvaluateUnitVectors(evaluator, param2, point, tangent, normal, binormal)
lw.WriteLine("pointOnSelectedFace: " & "(" & point(0).ToString & ", " & point(1).ToString & ", " & point(2).ToString & ")")

You use the initialize function to set up "evaluator2", but the ask limits and evaluate with unit vectors reference one named "evaluator". Make sure that you are referencing the correct evaluator object.

I don't think the name location will give you what you want. The name location is a point in space that may or may not be on the face. It is used when the object has a custom name and the "show object names" option is turned on in the visualization preferences. I've not tested it, but the name location might not be defined unless that option is turned on.

Hello NXJournaling,

I unified the evaluator2 in AskLimits and EvaluateUnitVectors, but it still said edge1.Tag is an invalid parameter...

I also tried the point from AskFaceProps today which should be on the selected face. It worked well with NX example “door”, but it couldn’t measure the distance of my “plate with a hole” correctly. I also found that, when a part is far away from (0,0,0), the point can be surprisingly reported on (0,0,0), which distorts the distance, and TraceARay can’t hit any point.
See the picture below:
https://drive.google.com/file/d/17PeqcPKCqrP537gYcQrF10BJAJDvx8My/view?u...