Assigning Material Per Face causes InvalidInput Error

Assigning Material Per Face causes InvalidInput Error

jason_aPCJML
Explorer Explorer
320 Views
2 Replies
Message 1 of 3

Assigning Material Per Face causes InvalidInput Error

jason_aPCJML
Explorer
Explorer

I am trying to assign different materials to different faces of a Solid3d but when I try and assign the material to the face, the Brep.Faces I am using to traverse the faces is getting corrupted for some reason. Here is the snippet of code that I am using to set Material Textures to a specific face of a solid. In my actual code, the materialName is a valid Material that exists within the MdiActiveDocument and materials is populated with the existing materialName's and the ObjectId's are valid ObjectId's of materials within the MdiActiveDocument. The plugin I am working on works on a different CAD platform with the same API as AutoCAD and works as expected. So I am not sure if there is a bug in ACAD where this code should work or if there is a bug in the other CAD platform that allows this to work when in reality it shouldn't work.

Public Sub AssignDifferentMaterialsToDifferentFaces()
    Dim materials As New Dictionary(Of String, ObjectId)
    Dim materialName As String = "materialName"
    Dim oDocLock As _AcAp.DocumentLock = _AcAp.Application.DocumentManager.MdiActiveDocument.LockDocument()
    Dim oTransaction As Transaction = HostApplicationServices.WorkingDatabase.TransactionManager.StartTransaction()
    Dim solid As Solid3d = CType(oTransaction.GetObject(_ObjectId, OpenMode.ForWrite), Solid3d)
    Dim ids() As ObjectId = {solid.ObjectId}
    Dim path As FullSubentityPath = New FullSubentityPath(ids, New SubentityId(SubentityType.Null, IntPtr.Zero))
    Using brep As New Brep(path)
        For Each curFace As Face In brep.Faces
            SetFaceMaterial(solid, curFace, materialName, materials)
        Next
    End Using
End Sub

Private Sub SetFaceMaterial(Solid As Solid3d, CurFace As Face, MaterialName As String, Materials As Dictionary(Of String, ObjectId))
    Dim materialObjectId As ObjectId
    If Materials.TryGetValue(MaterialName, materialObjectId) Then
        Solid.SetSubentityMaterial(CurFace.SubentityPath.SubentId, materialObjectId)
    End If
End Sub

 

0 Likes
321 Views
2 Replies
Replies (2)
Message 2 of 3

david_sisson0278
Observer
Observer

Are you trying the code on solids that have had or have not had material already applied to the individual faces?  I have a very sloppily wrote program (that works great) that will swap the material on a solid's faces but it has to check if a face material has been applied first.  I can share the portion of it that may be of some use, but unfortunately I can't share the entire program as there is proprietary code...  Also note that this code is for ACAD 2024 and earlier running .netFramework 4.8 and it has not been converted to .NET for newer versions (yet).  I hope this helps you.

 

Dim DeletedFaceCnt_Solid As Integer = 0
Dim ObjId_Solid As ObjectId()
ReDim ObjId_Solid(0)
ObjId_Solid(0) = Cur.Ent.ObjectId
Dim SubEntPath_Solid As New FullSubentityPath(ObjId_Solid, New SubentityId(SubentityType.Null, IntPtr.Zero))
'SubEntPath_Solid = New FullSubentityPath(ObjId_Solid, New SubentityId(SubentityType.Null, IntPtr.Zero))

If Not SubEntPath_Solid.IsNull Then

Dim objBrep_Solid As Brep = New Brep(SubEntPath_Solid)
Using objBrep_Solid

Dim FaceCnt As Integer = -1

For Each Face As BoundaryRepresentation.Face In objBrep_Solid.Faces

'' go through the individual faces and save the face information
Try

FaceCnt += 1
'ReDim Preserve CurSubent_Solid(FaceCnt)


ReDim Preserve Cur.Face.SubentPath(FaceCnt)
ReDim Preserve Cur.Face.SubentID(FaceCnt)
ReDim Preserve Cur.Face.SubentMatID(FaceCnt)

Cur.Face.SubentPath(FaceCnt) = Face.SubentityPath
Cur.Face.SubentID(FaceCnt) = Face.SubentityPath.SubentId
Cur.Face.SubentMatID(FaceCnt) = Cur.Ent.GetSubentityMaterial(Cur.Face.SubentID(FaceCnt))

'acEnt(x).Ent.Highlight(acEnt(x).Face(FaceCnt).SubentPath, True)

Catch ex As System.Exception
''==========================================================================================================
'' It is normal for this try-catch to hit. It'll hit whenever a face doesn't have a seperate material applied.
'' Uncomment the lines below this comment for debugging if desired.
'' However, it can be completly ignored as there is no real "error" that'll be a problem.
'' DO NOT REMOVE THIS TRY-CATCH, IT NEEDS TO BE HERE! If you remove it, you'll have problems.
''==========================================================================================================
Dim exCur As String = ex.Message
DeletedFaceCnt_Solid += 1
exCur = exCur
FaceCnt -= 1
'Debug.WriteLine(exCur)
End Try
Next Face

'Call CoreChange(acEnt, acTrans, FaceCnt, row, CurSubent)

Try
'Change the core
' acEnt(x).Ent.UpgradeOpen()

For cnt = 0 To Mats.ToName.Count - 1

If Cur.Ent.Material = Mats.FromName(cnt) Then
Cur.Ent.Material = Mats.ToName(cnt)
'Utils.RegenEntity(Cur.Ent.ObjectId)
'AcDoc.TransactionManager.FlushGraphics()
'Cur.Ent.Unhighlight()

End If

Next

Catch ex As Autodesk.AutoCAD.Runtime.Exception
ex = ex
'MsgBox("There has been an error while changing the Core materials." & vbCrLf & ex.Message & vbCrLf & ex.StackTrace)
Catch es As System.Exception
es = es
'MsgBox("There has been an error while changing the Core materials." & vbCrLf & es.Message & vbCrLf & es.StackTrace)
End Try

'Call FaceCount(FaceCnt, acEnt, acTrans, row, CurSubent)

'Dim ChangeToMatID As ObjectId = Nothing

For cnt = 0 To FaceCnt
Try

For i = 0 To Mats.ToID.Count - 1

'Cur.Ent.Highlight(Cur.Face.SubentPath(cnt), True)
If Cur.Face.SubentMatID(cnt) = Mats.FromID(i) Then
Cur.Ent.SetSubentityMaterial(Cur.Face.SubentID(cnt), Mats.ToID(i))
'Utils.RegenEntity(Cur.Ent.ObjectId)
'AcDoc.TransactionManager.FlushGraphics()
End If

Next

'acEnt(cur).Ent.SetSubentityMaterial(acEnt(cur).Face(FaceCnt).SubentID, )
'acEnt.SetSubentityMaterial(CurSubent_Solid(cur).CurSubentID, CurSubent_Solid(cur).CurSubentMatID)
Catch ex As Autodesk.AutoCAD.Runtime.Exception
ex = ex
'MsgBox("There has been an error while changing the Core's visual materials back to what they were." & vbCrLf & ex.Message & vbCrLf & ex.StackTrace)
Catch es As System.Exception
es = es
'MsgBox("There has been an error while changing the Core's visual materials back to what they were." & vbCrLf & es.Message & vbCrLf & es.StackTrace)
End Try
Next cnt

'Utils.RegenEntity(Cur.Ent.ObjectId)
'AcDoc.TransactionManager.FlushGraphics()

End Using

End If

0 Likes
Message 3 of 3

_gile
Consultant
Consultant

Hi, 

It looks like you do not dispose the locked document and the transaction and do not commit the transaction.

To be able to edait each face, it is needed to enumerate them all (i.e., create an array) before editing the first one.

Here's a C# example:

public static void SetMaterialToSolidFaces(ObjectId solidId, string materialName)
{
    // Check if the OjectId is a Solid3d
    if (solidId.ObjectClass != RXObject.GetClass(typeof(Solid3d)))
        return;

    var db = solidId.Database;
    using (var tr = db.TransactionManager.StartTransaction())
    {
        // Check if the materials dictionanary contains the material
        var materials = (DBDictionary)tr.GetObject(db.MaterialDictionaryId, OpenMode.ForRead);
        if (!materials.Contains(materialName))
            return;

        var materialId = materials.GetAt(materialName);
        var solid = (Solid3d)tr.GetObject(solidId, OpenMode.ForWrite);
        var fullSubentityPath = new FullSubentityPath(
            [solidId],
            new SubentityId(SubentityType.Null, IntPtr.Zero));
        using (var brep = new Brep(fullSubentityPath))
        {
            // Enumerate the faces SubentityIds before editing them
            var faces = brep.Faces.ToArray();

            // Edit the faces
            foreach (var face in faces)
            {
                solid.SetSubentityMaterial(face.SubentityPath.SubentId, materialId);
            }
        }
        tr.Commit();
    }
}

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes