Sunday, November 21, 2021

Use COM API/VBA to Open Viewport on Layout to Show Given Content in ModelSpace

Since I moved to AutoCAD .NET API more than 15 years ago, I dealt with AutoCAD VBA less and less, yet, still not completely left it behind. There was a recent discussion in the VBA discussion forum on the topic of opening viewport on layout to show ModelSpace content within a given area/scope. While there are a few useful replies, the OP still expects/prefers VBA code sample. According to the OP's request, the VBA code should not be too complicated, so I gave it a try. Once again, instead of posting the code sample as reply in the discussion thread, it would be better to post here with better readability.

First, let me assume the conditions of running the code:

A rectangle block is used as content boundary in the ModelSpace to be shown in a layout's viewport with given scale.

This picture shows the ModelSpace contents to be shown:


The coding process would be:

1. Determine the rectangle's size (width/height), rotation angle, and the coordinates of its corners;

2. Open a Viewport on a layout with the same size as the rectangle (scaled, of course);

3. Activate the Viewport in MSpace mode and zoom the view to the rectangle.

Note, ideally, based on the rectangle's size and required scale, the code could do calculation to decide what paper size is needed for the layout. But I omitted this calculation and simply manually chose a paper size for the layout ("Layout1") and choose the open the AcadPViewport at point (200, 150) as its center, which is based on the boundary block's size and a scale of 1/20 (0.05).

Here is the class BorderBlock, which holds all geometric information required to create the Viewport (e.g. in VBA Editor, create a class, name it as BorderBlock):

Option Explicit

Private mBlkName As String
Private mHeight As Double
Private mWidth As Double
Private mRotation As Double
Private mScale As Double
Private mLLCorner(0 To 2) As Double
Private mLRCorner(0 To 2) As Double
Private mULCorner(0 To 2) As Double
Private mURCorner(0 To 2) As Double

Private Sub Class_Initialize()
    mBlkName = "VPORT_BOUNDARY"
    mScale = 0.05
End Sub

Public Property Get VPortTweestAngle() As Double
    VPortTweestAngle = mRotation
End Property
Public Property Get Height() As Double
    Height = mHeight
End Property
Public Property Get Width() As Double
    Width = mWidth
End Property
Public Property Get VPortWidth() As Double
    VPortWidth = mWidth * mScale
End Property
Public Property Get VPortHeigth() As Double
    VPortHeigth = mHeight * mScale
End Property
Public Property Get VPortScale() As Double
    VPortScale = mScale
End Property

Public Property Get LLCorner() As Variant
    LLCorner = mLLCorner
End Property

Public Property Get URCorner() As Variant
    URCorner = mURCorner
End Property

Public Sub GetViewportData(blk As AcadBlockReference)

    '' Get border block's width and height
    Dim oldRotation As Double
    Dim minPt As Variant
    Dim maxPt As Variant
    If blk.Rotation <> 0 Then
        oldRotation = blk.Rotation
        blk.Rotation = 0#
    End If
    blk.GetBoundingBox minPt, maxPt
    mWidth = maxPt(0) - minPt(0)
    mHeight = maxPt(1) - minPt(1)
    blk.Rotation = oldRotation

    '' View port tweest angle
    mRotation = 2 * 3.1415926 - blk.Rotation
    
    '' Get 4 corners of the border block
    mLLCorner(0) = blk.InsertionPoint(0)
    mLLCorner(1) = blk.InsertionPoint(1)
    mLLCorner(2) = blk.InsertionPoint(2)
    
    Dim angle As Double
    Dim nextCorner As Variant
    
    angle = blk.Rotation
    nextCorner = CalculateNextCorner(mLLCorner, angle, mWidth)
    mLRCorner(0) = nextCorner(0)
    mLRCorner(1) = nextCorner(1)
    mLRCorner(2) = nextCorner(2)
    
    angle = angle + 3.1415926 / 2#
    nextCorner = CalculateNextCorner(mLRCorner, angle, mHeight)
    mURCorner(0) = nextCorner(0)
    mURCorner(1) = nextCorner(1)
    mURCorner(2) = nextCorner(2)
    
    angle = angle + 3.1415926 / 2#
    nextCorner = CalculateNextCorner(mURCorner, angle, mWidth)
    mULCorner(0) = nextCorner(0)
    mULCorner(1) = nextCorner(1)
    mULCorner(2) = nextCorner(2)
    
End Sub

Private Function CalculateNextCorner(corner As Variant, angle As Double, distance As Double) As Variant
    
    Dim nextCorner(0 To 2) As Double
    Dim x As Double
    Dim y As Double
    
    x = distance * Cos(angle)
    x = x + corner(0)
    
    y = distance * Sin(angle)
    y = y + corner(1)
    
    nextCorner(0) = x: nextCorner(1) = y: nextCorner(2) = corner(2)
    
    CalculateNextCorner = nextCorner
    
End Function


There is the VBA module with a public method OpenPVport() as a macro:


Option Explicit

Public Sub OpenPVport()
    
    Dim border As BorderBlock
    Set border = SelectBorderBlock()
    If border Is Nothing Then Exit Sub
    
    ThisDrawing.ActiveSpace = acPaperSpace
    ThisDrawing.ActiveLayout = ThisDrawing.Layouts("Layout1")
    
    CreatePVort border
    
End Sub

Private Function SelectBorderBlock() As BorderBlock
    If ThisDrawing.ActiveSpace <> acModelSpace Then
        ThisDrawing.ActiveSpace = acModelSpace
    End If
    Dim blk As AcadBlockReference
    Dim ent As AcadEntity
    Dim pt As Variant
    On Error Resume Next
    ThisDrawing.Utility.GetEntity ent, pt, vbCr & "Select vport boundary block:"
    If ent Is Nothing Then Exit Function
    If TypeOf ent Is AcadBlockReference Then
        Set blk = ent
        If UCase(blk.Name) <> "VPORT_BOUNDARY" Then
            MsgBox "Selected wrong block: not VPORT_BOUNDARY block!"
            Exit Function
        End If
    Else
        MsgBox "Selected entity is not a block reference!"
        Exit Function
    End If
    
    Dim border As BorderBlock
    Set border = New BorderBlock
    border.GetViewportData blk
    Set SelectBorderBlock = border
End Function

Private Sub CreatePVort(border As BorderBlock)

    Dim vport As AcadPViewport
    Dim center(0 To 2) As Double
    center(0) = 200: center(1) = 150: center(2) = 0
    Set vport = ThisDrawing.PaperSpace.AddPViewport( _
        center, border.VPortWidth, border.VPortHeigth)
    vport.Display True
    vport.TwistAngle = border.VPortTweestAngle
    
    '' zoom properly
    ThisDrawing.MSpace = True
    ThisDrawing.ActivePViewport = vport
    ThisDrawing.MSpace = True
    ZoomWindow border.LLCorner, border.URCorner
    ThisDrawing.MSpace = False
    
    '' Lock the viewport's display
    vport.DisplayLocked = True
    vport.Update
      
End Sub

See following video clip for the action of the code:





2 comments:

ahmed saber said...

hi sir
can you covert this code to C#

Norman Yuan said...

Well, I usually only post code for AutoCAD .NET API. This article is about COM API and VBA code. If using C#/VB.NET, then, there is no reason to use COM API.

As matter of fact, if you search my previous articles, there were at least 3 of them on this topic, where the code was in C# and used AutoCAD .NET API. They are:

https://drive-cad-with-code.blogspot.com/2014/03/selecting-entities-in-modelspace.html
https://drive-cad-with-code.blogspot.com/2014/06/open-viewport-from-modelspace.html
https://drive-cad-with-code.blogspot.com/2014/07/open-viewport-from-modelspace-take-2.html

Hope these 3 articles have enough or more than you want to know.

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.