Eng-Tips is the largest engineering community on the Internet

Intelligent Work Forums for Engineering Professionals

  • Congratulations waross on being selected by the Eng-Tips community for having the most helpful posts in the forums last week. Way to Go!

VBA Create a formula in a user property to get material name 3

Status
Not open for further replies.

ArnaudG

Mechanical
Dec 5, 2019
25
CA
Hi all,
I'm writing a macro to create a few user properties and one of the properties must have a formula to get the material name.

When you add a material to a part, a parameter is created name "Material". You can can easily create a formula inside a user property (in this case a string property) to link the value to the "Material" parameter. That's for the context.

I'm able to create my user property (name it MATERIAL), I can recover the parameter object associated to it (let's say my part is named Part1, the parameter name will be Part1\Properties\MATERIAL).

So I have my parameter object stored in a variable named propMat, I have my product in which the relations are (oProd = CATIA.ActiveDocument.Product). I created a small sub to generate the formula and it's like this :
In the rest of my code I call it with CreateFormula propMat, oProd (I could probably use Call in front of this but it works anyway).

strMat is a string containing the name of the user property "MATERIAL".

Code:
Sub CreateFormula(oParam As Parameter, produit As Product)

Dim oRels As Relations

On Error Resume Next

Set oRels = produit.Relations

If Err.Number <> 0 Then
    MsgBox "Impossible de récupérer l'ensemble des relations pour créer la formule."
    Exit Sub
End If

If InStr(1, oParam.Name, strMat, vbBinaryCompare) <> 0 Then
    oRels.CreateFormula "FormuleMat", "", oParam, "'Matériau'"
End If

End Sub
The formula itself is created, I can see it with the debugger but it's not visible in the relations in the tree nor the parameters window and the value is empty. I tried using "'Matériau'" (I'm working on a french version but I plan to make it work on english where it should be "Material" and probably german and italian later).
So I tried "'Material'", "='Material'", "Material" but every time the value is empty in the formula.

If I create it manually the formula shows 'Material'. I don't understand where I'm wrong.
 
Replies continue below

Recommended for you

Small update here but I'm not more advanced. I though it was because I didn't created a variable for the formula neither the keyword "Set" and maybe even the wrong characters to embed the Matériau text but not. So here's my latest iteration which still doesn't work :

Code:
Sub CreateFormula(oParam As Parameter, produit As Product)

Dim oRels As Relations
[highlight #FCE94F][b]Dim fMat As Formula[/b][/highlight]

On Error Resume Next

Set oRels = produit.Relations

If Err.Number <> 0 Then
    MsgBox "Impossible de récupérer l'ensemble des relations pour créer la formule."
    Exit Sub
End If

If InStr(1, oParam.Name, strMat, vbBinaryCompare) <> 0 Then
    [highlight #FCE94F][b]Set fMat = oRels.CreateFormula("FormuleMat", "", oParam, "`Matériau`")[/b][/highlight]
End If

End Sub
 
Try this way:

Code:
Sub Test()

    Dim oProd As Product
    Set oProd = CATIA.ActiveDocument.Product
    
    Dim oParam As Parameter
    Set oParam = oProd.UserRefProperties.CreateString("MATERIAL", "")
    
    Dim oRels As Relations
    Set oRels = oProd.Relations
    
    Dim oRel As Relation
    Set oRel = oRels.CreateFormula("FormuleMat", "", oParam, oProd.PartNumber & "\Material")

End Sub
 
Hi.

There are three key moments here:
- use language specific parameter name (paramName variable)
- use PartDocument.Product, not PartDocument.Part object to create formula
- address parameter using it's full name provided by Parameters.GetNameToUseInRelation

Code:
Option Explicit
Sub CATMain()
    ' prepare test model
    Dim prtDoc: Set prtDoc = CATIA.Documents.Add("Part")
    Dim prt: Set prt = prtDoc.part
    Dim prd: Set prd = prtDoc.product
    Dim matFilePath: matFilePath = GetFileFromEnvironment("CATStartupPath", "materials\Catalog.CATMaterial")
    Dim matDoc: Set matDoc = CATIA.Documents.Read(CStr(matFilePath))
    Dim matManager: Set matManager = prt.GetItem("CATMatManagerVBExt")
    Dim mat: Set mat = matDoc.families.item(1).materials.item(1)
    matManager.ApplyMaterialOnPart prt, mat, 0
    
    ' create formula
    Dim paramName: paramName = "Material"
    Dim matParam: Set matParam = prt.Parameters.SubList(prt, False).item(paramName)
    Dim userParam: Set userParam = prd.UserRefProperties.CreateString("PartMaterialName", "")
    Dim paramFormula: Set paramFormula = prd.Relations.CreateFormula("MaterialFormula", "Comment", userParam, prd.Parameters.GetNameToUseInRelation(matParam))
End Sub

' Searches for file with path relative to a directories specified in a given environment variable
Private Function GetFileFromEnvironment(variableName, relativePath)
    Dim env: env = CATIA.SystemService.Environ(CStr(variableName))
    Dim dir, path
    For Each dir In Split(env, ";")
        path = CATIA.fileSystem.ConcatenatePaths(CStr(dir), CStr(relativePath))
        If 1 = CATIA.fileSystem.FileExists(CStr(path)) Then
            GetFileFromEnvironment = path
            Exit Function
        End If
    Next
    GetFileFromEnvironment = Null
End Function
 
Thanks all, I made it work anyway yesterday but Little Cthulhu gave some interesting clues.

First the "Parameters.GetNameToUseInRelation" is awesome. I found a way to detect Catia UI language in order to adapt to 4 languages because I already knew that the name in the parameters list is language dependent, but it would not have addressed every case (I'm dealing with the part\Propriétés - Part\Properties\ - Part\Eigenschaften... case but not the name of the material parameter itselft so it should help a lot).
Then I can't agree with one statement because it's precisely what wss wrong for me : - use PartDocument.Product, not PartDocument.Part object to create formula

If the file is a part, you must create the relation in the .Part object, if it's a product you must of course use the .Product object (which I was already using on the part and that's probably why it wasn't working).

Another thing that helped apparently is the sub-typing of the parameter. My MATERIAL parameter is a string and I created it as a generic parameter like this
Code:
Dim propMat as Parameter
Set propMat = oUserProps.CreateString(strMat, "-")

I changed simply the statement :
Code:
Dim propMat as StrParam

So early in my code I detect if I have a part or product and switch a boolean according to this iIsPart = true if it'S a part, false if it's a product. I'm doing this while trying to assign the .part object from the active document to the variable oPart as Part (if it doesn't work I have an error, then I'm using the error number <> 0 to switch my variable.

Code:
'on associe le document actif à sa variable
Set oDoc = CATIA.ActiveDocument

On Error Resume Next

'on associe l'objet part à sa variable si c'est une pièce
Set oPart = oDoc.Part

'sinon on switche le booléen à false vu que c'est un produit
If Err.Number <> 0 Then
    bIsPart = False
End If

By the way, to check Catia language, I'm using a trick I saw somewhere else but modified because I was not satisfied. The guy was creating a new part and checking the name of the main body. Good idea but it means create a part and it's a long process, plus it will do something on screen and the user can wonder what's happening. So I used a similar idea but as my macro work only with an opened document (part or assembly), I create a new property and check its name because the name will depend on the language of the UI.

I only deal with the 4 main languages that my client will use, it could probably be a problem with that method on specific alphabets like chinese or japanese. But here's the function which return a string based on the language. Maybe not the best but it works. I also tried to check the FrameGeneral.CATSettings and read it as a binary file. I can see the different values (I used an internet found code to parse the file into a column in excel and compare based on the different settings) but they are not always at the same line on different computers so I forgot the idea (I didn't want to spend too much time on this).

Code:
Public Function CatiaLanguage2() As String
Dim oDocProd As Product 'Produit du document actif
Dim paramTemp As Parameter 'paramètres temporaire créé pour déterminer la langue
Dim sParamName As String 'nom d'affichage du paramètre
Dim iUserCount As Integer 'nombre de propriétés personnalisées de oDocProd

On Error Resume Next
Set oDocProd = CATIA.ActiveDocument.Product

If Err.Number <> 0 Then
MsgBox "Le fichier actif n'est pas une pièce ou un produit. Impossible de continuer."
CatiaLanguage2 = "ERR"
Set oDocProd = Nothing
Exit Function
End If

Set paramTemp = oDocProd.UserRefProperties.CreateString("MacroTemp", "-")
sParamName = paramTemp.Name

If sParamName Like "*Properties*" Then
    CatiaLanguage2 = "EN"
ElseIf sParamName Like "*Propriétés*" Then
    CatiaLanguage2 = "FR"
ElseIf sParamName Like "*Eigenschaften*" Then
    CatiaLanguage2 = "DE"
ElseIf sParamName Like "*Proprietà*" Then
    CatiaLanguage2 = "IT"
Else
    CatiaLanguage2 = "ERR"
End If

iUserCount = oDocProd.UserRefProperties.Count
oDocProd.UserRefProperties.Remove (iUserCount)

Set paramTemp = Nothing
Set oDocProd = Nothing

End Function

It can probably be improved with the function GetNameToUseInRelation but it works as is.
 
To check document type use [tt]TypeName(doc) = "ProductDocument"[/tt] condition as error forcing is a bad practice and should be used as a mean of last resort.

Language-specific parameter name is stored in .CATNls file that CATIA loads automatically according to selected language. And this is exactly the approach you want to use - try to find and read current .CATNLS. There's knowledgeware function to do that ([tt]BuldMessageNLS[/tt]) and you can invoke it by creating a temporary formula.

Code:
Sub CATMain()
    MsgBox GetNLSString(CATIA.ActiveDocument.part, "CATMatMaterial", "MaterialParameterName", Null)
    'MsgBox GetNLSString(CATIA.ActiveDocument.part, "CATIAKeys", "MessageBoxFormat", Array("First message parameter"))
End Sub

Function GetNLSString(partOrProduct, catalog, key, args)
    Dim formulaText: formulaText = "BuildMessageNLS(""" + catalog + """, """ + key + """"
    If (VarType(args) And vbArray) = vbArray Then
        Dim arg: For Each arg In args
            formulaText = formulaText + ", """ + arg + """"
        Next
    End If
    formulaText = formulaText + ")"

    Dim uniqueKey: uniqueKey = CStr(Timer())
    Dim tempParam: Set tempParam = partOrProduct.Parameters.CreateString("nlsparam" + uniqueKey, catalog + "." + key)
    Dim tempFormula: Set tempFormula = partOrProduct.Relations.CreateFormula("nlsformula" + uniqueKey, "Temporary formula to retrieve NLS string", tempParam, formulaText)
    GetNLSString = tempParam.value
    
    ' cleanup, formula first
    partOrProduct.Relations.Remove tempFormula.name
    partOrProduct.Parameters.Remove tempParam.name
End Function

As for language detection in general the code below is pretty reliable accross all the machines with the same CATIA release. However, it cannot detect actual "default" language in which case you have to obtain OS language with other API:

Code:
'==============================================================================
'   Gets current CATIA UI language by reading FrameGeneral.CATSettings file
'==============================================================================
Private Function GetCATIALanguageFromSettings()
    ' set the default return value
    GetCATIALanguageFromSettings = ""
    
    ' read FrameGeneral.CATSettings from settings as binary
    Dim fso: Set fso = CreateObject("Scripting.FileSystemObject")
    Dim settFile: Set settFile = fso.GetFile(fso.BuildPath(CATIA.SystemService.Environ("CATUserSettingPath"), "FrameGeneral.CATSettings"))
    Dim nbChars: nbChars = settFile.Size
    With settFile.OpenAsTextStream()
        Dim chars: chars = .Read(nbChars)
    End With
    
    '---------------------------------------------------------
    ' SEARCH FOR UserInterfaceLanguage STRING IN SETTINGS FILE
    '---------------------------------------------------------
    ' start reading from the first byte
    Dim str: str = ""
    Dim filePos: filePos = 1
    Dim c: c = Mid(chars, filePos, 1)
    
    Do While (InStrRev(str, "UserInterfaceLanguage") = 0 And filePos < nbChars)
        ' flush text in sFileContent each time we face a zero byte
        If Asc(c) = 0 Then
            str = ""
        Else
            ' append another character to the text
            str = str + c
        End If
        
        ' read next byte from the file
        filePos = filePos + 1
        c = Mid(chars, filePos, 1)
    Loop
    If filePos >= nbChars Then
        ' something went wrong
        Exit Function
    End If

    '------------------------------------
    ' GET CURRENT USER INTERFACE LANGUAGE
    '------------------------------------
    ' read 30th byte after UserInterfaceLanguage that stores language
    Select Case Asc(Mid(chars, filePos + 33, 1))
        Case &HCA:      GetCATIALanguageFromSettings = "EN"
        Case &H56:      GetCATIALanguageFromSettings = "FR"
		Case &H5A:      GetCATIALanguageFromSettings = "DE"
		Case &HC2:      GetCATIALanguageFromSettings = "IT"
		Case &HE5:      GetCATIALanguageFromSettings = "RU"
		Case 0:			GetCATIALanguageFromSettings = ""
    End Select
End Function
 
Wow, thanks a lot.
That's probably what I didn't know about the framegeneral.CATsettings : ' read 30th byte after UserInterfaceLanguage that stores language
However I'm not sure I would have found the way to do that even knowing this thing.

The other Sub/Function to get the local specific names is also amazing ! How can I use that and credit you about it because If I do use it, it will be in a macro that is paid by our customer so I can't take credit for that part.
I can still use what I did (which works) but your way is much more clever and would avoid some painful code.

One more question : how did you figure this out ? I would never have found the knowledge function to get the local NLS file and I watched in the local files and there are thousands of them so how do you know where to look at ?
I understand how your code works, but if I want to get better I also have to understand how to figure this kind of stuff by myself. I've looked on the help but it's not very helpful sometimes (particularly for knowledge rules... I find difficult to get the information about stuff).
 
> One more question : how did you figure this out ?

Well, they say you can get very far by asking the right questions and that, my friend, is one of those :)

I'm an (somewhat) experienced software engineer, so I'm aware of basic principles used in software development.

So, when we talk about language, it's obvious that since it's presistent (saved between launches) it's most likely stored in a some sort of a file (or system registry available in Windows, but since CATIA also works on Unix storage implementation has to be portable, so file it is).
So we start CATIA, navigate to settings directory (CATUserSettings environment variable, there're not that many of them) and change as minimal as we need not to provoke modifications of other settings. So we run Tools - Customize, select language, close window and CATIA and observe what files got modified. A couple of iterations later we found out that it's FrameGeneral.CATSettings and it's 34th (not 30th) that get's modified. You can use any file editor with hexademical view to get enough information on file contents.

As for the knowledgeware function and it's usage. Use it without any restrictions, credits not required (can't forbid it though) as I currently prefer to use CAA-based extensions that eliminate the need in such "hacks". I'd be happy to see more high-quality code in CATIA domain.
Knowledgeware itself is well-described in CATIA's user manual and is one of the three "languages" (frameworks, whatever) that you can use for programming, along with CAA and Automation (VB). So you should be aware of it if you really want to get into CATIA automation.
There's an amazing tool called Knowledgeware Browser that can be started from formula edition window (first button from the left in a set of five located in top-right corner) that displays both Knowledgeware properties (that often contain data not exposed in Automation) and functions such as aforementioned [tt]BuildMessageNLS[/tt]. The rest is up to your imagination!
 
Thanks for the code then, and for the answer.

So it's basically what I'm doing (to guess stuff) but you're more experienced than I so you probably can figure out stuff faster :D I also find the knowledgeware browser not very friendly but I can use it when needed (I just think it would be more helpful with some examples).

I found the FrameGeneral.CATsettings the same way and as I said I found an excel macro to parse the binary values to an excel file and compared the values after changing the language but the problem was that I had the values on different lines if I ran the macro on different computers so I was stuck. I didn't even think of comparing through an hexadecimal program (whereas I opened the file with one...) but that's probably because I'm not used to manipulate binary files.
Anyway, I learn everyday :D

I will open a new thread because I'm stuck somewhere else with a law...
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top