nanoCAD 5.0 VB.NET サンプル 2024/12/04, 10

やっていることは、LISP と同じで LISP より少し(1.5倍くらい)速いです。
電気回路図用のシンボルでは、体感的な差は感じられないので、.NET 化する意味はあまり無いように思います。

LISP からコマンドを使う場合は、図形選択時に、"H" オプションを使って、ハンドル (cdr (assoc 5 ent)) を送ると、そのまま使えます。
nanoCAD 5.0 では、.NET で作成した LISP 関数は認識されないっぽいです。


■選択したブロックの見えている図形の矩形範囲を取得
 ・2024/12/10 ReDim の間違いを修正
■選択したブロックの見えている図形に交差する LINE をカット
※ .NET での GetKeywords の使い方がよく分かっていません・・・。
※ GetPointで、直交モードがオンのとき、X または Y が 0 になる現象があります。

Imports Teigha.Runtime
Imports HostMgd.ApplicationServices
Imports Teigha.DatabaseServices
Imports Teigha.Geometry
Imports HostMgd.EditorInput

' alias
Imports _AcRx = Teigha.Runtime
Imports _AcAp = HostMgd.ApplicationServices
Imports _AcDb = Teigha.DatabaseServices
Imports _AcGe = Teigha.Geometry
Imports _AcEd = HostMgd.EditorInput
Imports _AcGi = Teigha.GraphicsInterface
Imports _AcClr = Teigha.Colors
Imports _AcWnd = HostMgd.Windows

Public Class Class1

    '' ブロックの見えている矩形範囲と内部図形を返す
    Public Function getBlockRect(blkObjId As ObjectId, ByRef minx As Double, ByRef miny As Double, ByRef maxx As Double, ByRef maxy As Double) As DBObjectCollection
        Dim doc As _AcAp.Document = _AcAp.Application.DocumentManager.MdiActiveDocument
        Dim db As Database = doc.Database
        Dim ed As Editor = doc.Editor

        Dim result As DBObjectCollection = Nothing

        Using tr As Transaction = db.TransactionManager.StartTransaction
            Try
                '' フリーズ、非表示でない画層名を取得しておく
                Dim layNames() As String
                ReDim layNames(0) '' 配列数を 0 に
                '' 画層テーブルを取得
                Using lt As LayerTable = tr.GetObject(db.LayerTableId, OpenMode.ForRead)
                    Dim ltr As LayerTableRecord
                    Dim idx As Integer = 0
                    For Each objid In lt
                        ltr = tr.GetObject(objid, OpenMode.ForRead)
                        If Not (ltr.IsHidden Or ltr.IsFrozen) Then ''ltr.IsLocked Or ''ロック画層は含めない時
                            ReDim Preserve layNames(idx) '' 2024/12/10 間違いを修正(添字の上限に変更)
                            layNames(idx) = ltr.Name
                            idx = idx + 1
                        End If
                    Next
                End Using
                ed.WriteMessage(vbLf + blkObjId.ToString)
                Dim ent As Entity = tr.GetObject(blkObjId, OpenMode.ForRead)
                If TypeOf ent Is BlockReference Then
                    Dim blkref = TryCast(ent, BlockReference)
                    ''ed.WriteMessage(vbLf + blkref.Name) ''ブロック名
                    ''Dim blkPos As Point3d = blkref.Position '' ブロックの挿入基点(未使用)

                    Dim objs = New DBObjectCollection
                    blkref.Explode(objs) '' 分解
                    ''ed.WriteMessage(vbLf + "Explode count=" + objs.Count.ToString)

                    Dim bound As Extents3d
                    Dim count As Integer = 0
                    result = New DBObjectCollection
                    For Each obj In objs
                        Dim et As Entity = TryCast(obj, Entity)
                        ''Dim etName As String = et.GetType().Name
                        ''ed.WriteMessage(vbLf + etName) '' 図形の名前

                        If Array.IndexOf(layNames, et.Layer) >= 0 Then ''見えている図形が対象
                            If etName = "Circle" Or etName = "Line" Or etName = "Arc" Or etName = "Polyline" Then
                                result.Add(obj)
                                bound = et.Bounds
                                 If count = 0 Then
                                    minx = bound.MinPoint.X
                                    miny = bound.MinPoint.Y
                                    maxx = bound.MaxPoint.X
                                    maxy = bound.MaxPoint.Y
                                Else '' 2024/12/10 Math.Min, Math.Max に変更
                                    minx = Math.Min(minx, bound.MinPoint.X)
                                    miny = Math.Min(miny, bound.MinPoint.Y)
                                    maxx = Math.Max(maxx, bound.MaxPoint.X)
                                    maxy = Math.Max(maxy, bound.MaxPoint.Y)
                                End If
                                count = count + 1
                            End If
                        End If
                    Next
                End If
            Catch ex As System.Exception
                _AcAp.Application.ShowAlertDialog(ex.Message)
            End Try
        End Using
        Return result
    End Function

    '' 点列のうち、相互距離が最大な2点を得る
    Sub maxDistPts(pts As Point3dCollection, ByRef p1 As Point3d, ByRef p2 As Point3d)
        Dim maxDist = 0.0
        Dim dist As Double
        Dim cnt As Integer = pts.Count
        If cnt = 2 Then
            p1 = pts(0)
            p2 = pts(1)
        Else
            For i = 0 To cnt - 2
                For j = i + 1 To cnt - 1
                    dist = pts(i).DistanceTo(pts(j))
                    If dist > maxDist Then
                        maxDist = dist
                        p1 = pts(i)
                        p2 = pts(j)
                    End If
                Next
            Next
        End If
    End Sub

    '' 点列のうち、相互距離が最大な2点を得る(未使用)
    Public Shared Sub maxDist4p(p1 As Point3d, p2 As Point3d, p3 As Point3d, p4 As Point3d, ByRef resp1 As Point3d, ByRef resp2 As Point3d)
        Dim pts As Point3dCollection = New Point3dCollection
        pts.Add(p1)
        pts.Add(p2)
        pts.Add(p3)
        pts.Add(p4)
        maxDistPts(pts, resp1, resp2)
    End Sub

    '' 点が線分上にあるか
   Public Shared Function pointOnLine(pt As Point3d, p1 As Point3d, p2 As Point3d) As Boolean
        Dim lineSeg As LineSegment3d = New LineSegment3d(p1, p2)
        Return lineSeg.IsOn(pt)
    End Function

    '' ブロックに交差する LINE をカット
   <CommandMethod("netbtrim")>
    Public Sub netbtrim()
        Dim doc As _AcAp.Document = _AcAp.Application.DocumentManager.MdiActiveDocument
        Dim db As Database = doc.Database
        Dim ed As Editor = doc.Editor
        Dim fuzz As Double = 0.1 '' 矩形範囲の少し外側にかかる線分を選択する
        Using tr As Transaction = db.TransactionManager.StartTransaction
            Try
                Dim res As PromptEntityResult = ed.GetEntity(vbLf + "交差線分をカットするブロックを指示 : ")
                If (res.Status = PromptStatus.OK) Then
                    Dim ent As Entity = tr.GetObject(res.ObjectId, OpenMode.ForRead)
                    If TypeOf ent Is BlockReference Then
                        Dim blkref As BlockReference = TryCast(ent, BlockReference)
                        ''Dim blkPos As Point3d = blkref.Position '' ブロックの挿入基点(未使用)
                        Dim minx, miny, maxx, maxy As Double
                        '' 見えているブロック内部の図形と矩形範囲
                        Dim blkObjs As DBObjectCollection = getBlockRect(res.ObjectId, minx, miny, maxx, maxy)
                        If blkObjs.Count > 0 Then
                            Dim pt1 As Point3d = New Point3d(minx - fuzz, miny - fuzz, 0.0)
                            Dim pt2 As Point3d = New Point3d(maxx + fuzz, maxy + fuzz, 0.0)
                            Dim filter As TypedValue() = {New TypedValue(DxfCode.Start, "LINE")}
                            '' 交差線分をセレクト
                            Dim pres As PromptSelectionResult = ed.SelectCrossingWindow(pt1, pt2, New SelectionFilter(filter))
                            Dim count As Integer = 0
                            Dim objIdArray() As ObjectId = pres.Value.GetObjectIds()
                            ''ed.WriteMessage(vbLf + "lines=" + objIdArray.Count.ToString)
                            Dim acBlkTbl As BlockTable = tr.GetObject(db.BlockTableId, OpenMode.ForRead)
                            Dim model As BlockTableRecord = tr.GetObject(acBlkTbl(BlockTableRecord.ModelSpace), OpenMode.ForWrite)
                            For Each objId As ObjectId In objIdArray ''線分ごとに
                                Dim intpts As Point3dCollection = New Point3dCollection '' 線分ごとの交点
                                Dim lnobj As Line = TryCast(tr.GetObject(objId, OpenMode.ForWrite), Line)
                                For Each blkobj In blkObjs
                                    Dim pts As Point3dCollection = New Point3dCollection
                                    '' ブロック内部図形との交点
                                    lnobj.IntersectWith(blkobj, Intersect.OnBothOperands, pts, IntPtr.Zero, IntPtr.Zero)
                                    '' 同一直線上の点を含める
                                    If TypeOf blkobj Line Then
                                        Dim blkln As Line = TryCast(blkobj, Line)
                                        If pointOnLine(blkln.StartPoint, lnobj.StartPoint, lnobj.EndPoint) Then
                                            pts.Add(blkln.StartPoint)
                                        End If
                                        If pointOnLine(blkln.EndPoint, lnobj.StartPoint, lnobj.EndPoint) Then
                                            pts.Add(blkln.EndPoint)
                                        End If
                                    End If
                                    If pts IsNot Nothing Then
                                        Dim i As Integer
                                        For i = 0 To pts.Count - 1
                                            intpts.Add(pts(i)) '' 線分ごとの交点リストに追加
                                        Next
                                    End If
                                Next
                                '' 線分ごとの交点を整理
                                '' 先に端点を intpts に含めておく
                                '' 始点、終点がどちらも矩形内にある場合を想定していない
                                Dim mode As Integer = 0
                                If (lnobj.StartPoint.X >= minx) And (lnobj.StartPoint.X <= maxx) And (lnobj.StartPoint.Y >= miny) And (lnobj.StartPoint.Y <= maxy) Then
                                    intpts.Add(lnobj.StartPoint)
                                    mode = 1
                                ElseIf (lnobj.EndPoint.X >= minx) And (lnobj.EndPoint.X <= maxx) And (lnobj.EndPoint.Y >= miny) And (lnobj.EndPoint.Y <= maxy) Then
                                    intpts.Add(lnobj.EndPoint)
                                    mode = 2
                                End If

                                If intpts.Count >= 2 Then
                                    ''相互距離が最大の2点を得る
                                    Dim p1 As Point3d = New Point3d(0, 0, 0)
                                    Dim p2 As Point3d = New Point3d(0, 0, 0)
                                    maxDistPts(intpts, p1, p2)
                                    '' 確認の為に円を作成
                                    'Dim cir As Circle = New Circle
                                    'cir.Center = p1
                                    'cir.Radius = 0.1
                                    'model.AppendEntity(cir)

                                    'tr.AddNewlyCreatedDBObject(cir, True)
                                    'Dim cir2 As Circle = New Circle
                                    'cir2.Center = p2
                                    'cir2.Radius = 0.1
                                    'model.AppendEntity(cir2)
                                    'tr.AddNewlyCreatedDBObject(cir2, True)

                                    If mode = 1 Then ''始点が矩形内
                                        lnobj.StartPoint = p1
                                    ElseIf mode = 2 Then
                                        lnobj.EndPoint = p1
                                    Else
                                        Dim lnobj2 As Line = New Line
                                        lnobj2.CopyFrom(lnobj)
                                        If lnobj.StartPoint.DistanceTo(p1) < lnobj.StartPoint.DistanceTo(p2) Then
                                            lnobj.EndPoint = p1
                                            lnobj2.StartPoint = p2
                                        Else
                                            lnobj.EndPoint = p2
                                            lnobj2.StartPoint = p1
                                        End If
                                        model.AppendEntity(lnobj2)
                                    End If
                                End If
                            Next
                            '' Save the new object to the database
                            tr.Commit()
                        End If
                    End If
                End If
            Catch ex As System.Exception
                _AcAp.Application.ShowAlertDialog(ex.Message)
            End Try
        End Using
    End Sub

    '' ブロックを囲う矩形範囲を取得(見えている図形の範囲)
    <CommandMethod("netbrect")>
    Public Sub netbrect()
        Dim doc As _AcAp.Document = _AcAp.Application.DocumentManager.MdiActiveDocument
        Dim db As Database = doc.Database
        Dim ed As Editor = doc.Editor
        Using tr As Transaction = db.TransactionManager.StartTransaction
            Try
                Dim res = ed.GetEntity(vbLf + "矩形範囲を取得するブロックを指示 : ")
                If (res.Status = PromptStatus.OK) Then
                    Dim minx, miny, maxx, maxy As Double
                    If getBlockRect(res.ObjectId, minx, miny, maxx, maxy) IsNot Nothing Then
                       '' LISP から使うために、結果をシステム変数で渡す
                        _AcAp.Application.SetSystemVariable("userr1", minx)
                        _AcAp.Application.SetSystemVariable("userr2", miny)
                        _AcAp.Application.SetSystemVariable("userr3", maxx)
                        _AcAp.Application.SetSystemVariable("userr4", maxy)
                        '' 確認の為に描画
                        Dim pso As New PromptSelectionOptions
                        pso.Keywords.Add("Yes(Y)")
                        pso.Keywords.Add("No(N)")
                        pso.MessageForAdding = vbLf & " 矩形範囲を描画しますか? : " & vbLf & pso.Keywords.GetDisplayString(True)
                        pso.SelectEverythingInAperture = True
                        Dim psr As PromptSelectionResult = Nothing
                        psr = ed.GetSelection(pso)
                        If psr.StringResult = "Yes(Y)" Then
                            Dim pts(4) As Point2d
                            pts(0) = New Point2d(minx, miny)
                            pts(1) = New Point2d(maxx, miny)
                            pts(2) = New Point2d(maxx, maxy)
                            pts(3) = New Point2d(minx, maxy)
                            Dim pline As New Polyline()
                            Dim i As Integer
                            For i = 0 To 3
                                pline.AddVertexAt(i, pts(i), 0.0, 0.0, 0.0)
                            Next
                            pline.Closed = True
                            ' Open the Block table for read
                            Dim acBlkTbl As BlockTable = tr.GetObject(db.BlockTableId, OpenMode.ForRead)
                            ' Open the Block table record Model space for write
                            Dim model As BlockTableRecord = tr.GetObject(acBlkTbl(BlockTableRecord.ModelSpace), OpenMode.ForWrite)
                            ' Add the new object to the block table record and the transaction
                            model.AppendEntity(pline)
                            tr.AddNewlyCreatedDBObject(pline, True)
                            ' Save the new object to the database
                            tr.Commit()
                        End If
                    End If
                End If
            Catch ex As System.Exception
                _AcAp.Application.ShowAlertDialog(ex.Message)
            End Try
        End Using
    End Sub
End Class