Note: Portions of the code used in this article were originally contributed by Mark Stefanchuk and Graham Steele and are used by permission.
In this example, framework is set up for an application that places different doors. The doors then can be linked to Tag Data that can be later referenced for easy identification. The application is organized into modules (i.e. ModRun, FmDoorMgr, clsDoor, clsRollUpDoor, clsStallDoor, clsRegularDoor, and ManipulateCell) that match the application items, so you can build it as one project made of components that can be reused in other projects.
The first component to look at is the user interface, which is contained in the FmDoorMgr form class and allows for the selection of the type and size of door to place and the options for the door (e.g. double, reverse, etc.) This dialog opens when the command is started, so default settings must be specified to allow the user to be in placement mode if they do not want to change any setting.
clsDoor is the generic door object that handles the different doors and associated door information. The associated door information contains the fire rating and the door ID number and can be optionally placed as Tag Data. The application creates a door, then the door geometry, and finally the tag to add to the door geometry. By putting these steps in separate classes, changes can be made to any one of them without affecting the rest of the application workflow. An option is to add a database connection so that the information can also be stored in an external database, but that option will not be covered in this article. In clsDoor, the door geometry is created using the method CreateDoor, which accepts a door type and other parameters as input to determine the door to place. When the door is finally placed, the Tag Data is added using the TagDoor method. The CreateDoor method uses a select/case structure to create the proper door that has been requested.
Option Explicit
Public Enum DoorType
itypicaldoor = 1
iRollUpDoor = 2
iStallDoor = 3
End Enum
Private pDoor As Object 'this is the door.
Private pTag As clsDoorTags 'this is the tagging for the door information.
Private m_DoorType As DoorType
'Generic create method to allow for creating any door
Public Sub CreateDoor(iDoorType As Integer, dblDoorSize As Double, _
bReverse As Boolean, bDoubleDoor As Boolean, bTagInfo As Boolean)
'this will call the specific door creation method.
Select Case iDoorType
Case 3
Set pDoor = New clsStallDoor
pDoor.CreateDoor dblDoorSize
Case 2
Set pDoor = New clsRollUpDoor
pDoor.CreateDoor dblDoorSize
Case Else
Set pDoor = New clsRegularDoor
pDoor.CreateDoor dblDoorSize, bDoubleDoor
End Select
End Sub
Public Sub TagDoor(iFireRating As Integer, iDoorID As Integer)
Set pTag = New clsDoorTags
pTag.CreateDoorTags iFireRating, iDoorID, pDoor
End Sub
Public Property Get door() As Object
Set door = pDoor
End Property
Public Property Get TagInfo() As clsDoorTags
Set TagInfo = pTag
End Property
clsDoorTag isolates the Tag Data from the door. The door's Tag Data is added through the CreateDoorTags method. The next thing that is added is the ability to uniquely identify the doors. In this particular application, that is added in a utility function that scans the model refs and finds the highest used ID number. A utility module is used since the same method is needed for both the GUI and the placement code.
Option Explicit
Dim iFireRating As Integer
Dim iDoorID As Integer
Public Property Let FireRating(iRating As Integer)
iFireRating = iRating
End Property
Public Property Get FireRating() As Integer
FireRating = iFireRating
End Property
Public Property Let DoorID(id As Integer)
iDoorID = id
End Property
Public Property Get DoorID() As Integer
DoorID = iDoorID
End Property
Public Function CreateDoorTags(iFireRating As Integer, iDoorID As Integer, pDoor As Object) As Boolean
Dim strName As String
Dim oTagSets As TagSets
Dim oTagSet As TagSet
Dim oTagDefns As TagDefinitions
Dim oTagDefn As TagDefinition
Dim oTagElement As TagElement
strName = "Door Tags"
On Error Resume Next
'this will create the tag def if it is not in the file
CheckForTagDefn True
Set oTagSets = ActiveDesignFile.TagSets
Set oTagSet = oTagSets(strName)
'if the tag does not exist this will throw an error
Set oTagDefns = oTagSet.TagDefinitions
Set oTagDefn = oTagDefns("Fire Rating")
'this adds the tag to the element
Set oTagElement = pDoor.door.AddTag(oTagDefn)
oTagElement.Value = iFireRating
'since the addTag writes to file the otagelement needs to be rewritten.
oTagElement.Rewrite
oTagElement.Redraw
Set oTagDefn = oTagDefns("Door ID")
Set oTagElement = pDoor.door.AddTag(oTagDefn)
oTagElement.Value = iDoorID
oTagElement.Move Point3dFromXY(0, -ActiveSettings.TextStyle.Height * 2)
oTagElement.Rewrite
oTagElement.Redraw
CreateDoorTags = True
End Function
Private Sub CheckForTagDefn(bCreateIfMissing As Boolean)
Dim oTagSet As TagSet
Dim oTagDefn As TagDefinition
Dim strName As String
Dim oTagSets As TagSets
On Error Resume Next
strName = "Door Tags"
Set oTagSets = ActiveDesignFile.TagSets
Set oTagSet = oTagSets(strName)
If bCreateIfMissing And oTagSet Is Nothing Then
Dim oTagDef As TagDefinition
Set oTagSet = oTagSets.Add(strName)
Set oTagDef = oTagSet.TagDefinitions.Add("Fire Rating", msdTagTypeLongInteger)
oTagDef.IsHidden = False
oTagDef.DefaultValue = ""
Set oTagDef = oTagSet.TagDefinitions.Add("Door ID", msdTagTypeLongInteger)
oTagDef.IsHidden = False
oTagDef.DefaultValue = ""
End If
errorHandle:
End Sub
The next part to look at is the set of classes that make up the different doors. By defining the doors in classes, the code can create a specific door without referring to the specific door type. The benefit is that the application can be enhanced or extended by simply adding door types as new classes. The structure of the class is to use a cell for the graphics. The object can also be programmed to contain other information that can be added as the application evolves. Each class contains a CreateDoor method that generates the graphics for the door. The graphics are identified in a member variable m_DoorType, which is private to the class and exposed only as a get property. Using this approach, the graphics are readable but cannot be set or changed from outside the object and the user class cannot assign graphics to the object. The object also includes a flag to determine if door is to be placed reversed or as a double door.
Option Explicit
Private m_TypicalDoor As CellElement
Private m_reverse As Boolean
Private m_tagNumber As Integer
Private m_swingDoor As Boolean
Public Property Get Swing() As Boolean
Swing = m_swingDoor
End Property
Public Property Let Swing(s As Boolean)
m_swingDoor = s
End Property
Public Property Get reverse() As Boolean
reverse = m_reverse
End Property
Public Property Let reverse(rev As Boolean)
m_reverse = rev
End Property
Public Property Get door() As CellElement
Set door = m_TypicalDoor
End Property
Private Property Set door(pDoor As CellElement)
Set m_TypicalDoor = pDoor
End Property
Public Property Get tagNumber() As Integer
tagNumber = m_tagNumber
End Property
Public Property Let tagNumber(t As Integer)
m_tagNumber = t
End Property
Function CreateDoor(DoorSize As Double, Optional bDblDoor As Boolean) As CellElement
...Code for door creation here -- this will differ based on the door geometry you wish to create...
The manipulation class is an implementation of an IPrimitiveCommandEvents class that acts as the placement code. By implementing this interface, the class is called for Data Points and Resets, which are typical events for element placement in MicroStation. The placement code only needs to determine the door type being placed once -- after that all the code is common between the types. This makes the code clearer and simpler since there are not multiple cases that need to be implemented and the code is only written once. In this application, the dynamics allow the door to be flipped by pressing Reset. Two methods of interest in this class are the dynamics and the datapoint methods. In the dynamics method, a door is created on-the-fly and is presented on the cursor. If the user has already established the anchor point the door is then put in a rotation mode to allow the door to be placed at an angle determined by a second data point. In the datapoint method, the first action is to collect the anchor point. Once the door is anchored users can then rotate the door. If they press Reset, the door is mirrored, allowing for a more flexible placement process. Again, the focus of this class is on placing graphics and not on what is being placed.
Option Explicit
Implements IPrimitiveCommandEvents
Dim m_atPoints(0 To 1) As Point3d
Dim DataPoints As Integer
Dim Reset As Boolean
Private m_nPoints As Integer
Dim InsertPoint As Boolean
Dim reverse As String
Private Sub IPrimitiveCommandEvents_Cleanup()
CommandState.StopDynamics
Unload FmDoorMgr
'CommandState.StartDefaultCommand
End Sub
Private Sub IPrimitiveCommandEvents_DataPoint(Point As Point3d, ByVal View As View)
'Dim oCell As CellElement
Dim oDrCell As CellElement
Dim oDoor As clsDoor
Dim bDblDoor As Boolean
If FmDoorMgr.TxtDbleDoorSize > 0 Then
bDblDoor = True
Else
bDblDoor = False
End If
If DataPoints = 0 Then 'user has entered Startpoint of the line segment
Dim ZAngle As Double
m_atPoints(0) = Point
InsertPoint = True
DataPoints = 1
CommandState.StartDynamics
ShowPrompt "Rotate point"
Else
' CommandState.StopDynamics
Set oDoor = New clsDoor
m_atPoints(1) = Point
If FmDoorMgr.OptStallDoor.Value = True Then
oDoor.CreateDoor 3, FmDoorMgr.TxtDoorSize, FmDoorMgr.cbReverse, bDblDoor, False
ElseIf FmDoorMgr.OptRollUp.Value = True Then
oDoor.CreateDoor 2, FmDoorMgr.TxtDoorSize, FmDoorMgr.cbReverse, bDblDoor, False
Else
oDoor.CreateDoor 1, FmDoorMgr.TxtDoorSize, FmDoorMgr.cbReverse, bDblDoor, False
End If
oDoor.door.reverse = FmDoorMgr.cbReverse
'Transform the elemenet
oDoor.door.door.Transform Transform3dFromPoint3d(m_atPoints(0))
'Calculate the angle and rotate the cell
ZAngle = Atn2(m_atPoints(0).Y - m_atPoints(1).Y, m_atPoints(0).X - m_atPoints(1).X)
oDoor.door.door.Rotate m_atPoints(0), 0, 0, ZAngle
ActiveModelReference.AddElement oDoor.door.door
'make this part of the door object.
If FmDoorMgr.TxtDrNbr.Text <> "" Then
' CreateDoorNumber Point, ZAngle, reverse
oDoor.TagDoor 1, CInt(FmDoorMgr.TxtDrNbr)
'increment the door number here?
FmDoorMgr.TxtDrNbr = FmDoorMgr.TxtDrNbr + 1
End If
oDoor.door.door.Redraw msdDrawingModeNormal
'Set the flags to initial state
InsertPoint = False
Reset = False
DataPoints = 0
End If
Reset = False
End Sub
Private Function CreateCellForDynamics() As Object
Dim pDoor As Object
If FmDoorMgr.OptStallDoor.Value = True Then
If Reset = True Then
Set pDoor = New clsStallDoor
Else
Set pDoor = New clsStallDoor
End If
ElseIf FmDoorMgr.OptRollUp.Value = True Then
If Reset = True Then
Set pDoor = New clsRollUpDoor
Else
Set pDoor = New clsRollUpDoor
End If
Else
If Reset = True Then
Set pDoor = New clsRegularDoor
Else
Set pDoor = New clsRegularDoor
End If
End If
pDoor.reverse = FmDoorMgr.cbReverse
pDoor.CreateDoor CDbl(FmDoorMgr.TxtDoorSize.Text), CBool(FmDoorMgr.TxtDbleDoorSize.Text)
Set CreateCellForDynamics = pDoor
End Function
Private Sub IPrimitiveCommandEvents_Dynamics(Point As Point3d, ByVal View As View, ByVal DrawMode As MsdDrawingMode)
Dim oCell As CellElement
Dim ZAngle As Double
Dim pDoor As Object
Set pDoor = CreateCellForDynamics
If InsertPoint = True Then
Dim RotationMatrix As Matrix3d
m_atPoints(1) = Point
ZAngle = Atn2(m_atPoints(0).Y - m_atPoints(1).Y, m_atPoints(0).X - m_atPoints(1).X)
pDoor.door.Transform Transform3dFromPoint3d(m_atPoints(0))
pDoor.door.Rotate m_atPoints(0), 0, 0, ZAngle
Else
pDoor.door.Transform Transform3dFromPoint3d(Point)
End If
'this is for the mirroring
If Reset Then
pDoor.door.Mirror pDoor.door.Origin, Point
End If
pDoor.door.Redraw DrawMode
Set pDoor = Nothing
End Sub
Private Sub IPrimitiveCommandEvents_Keyin(ByVal KeyIn As String)
End Sub
Private Sub IPrimitiveCommandEvents_Reset()
If Reset = True Then
CommandState.StopDynamics
CommandState.StartDefaultCommand
RedrawAllViews
Else
Reset = True
CommandState.StartDynamics
End If
End Sub
Private Sub IPrimitiveCommandEvents_Start()
FmDoorMgr.Show
InsertPoint = False
'This starts accurdraw in the iprimative event.
CommandState.EnableAccuSnap
CommandState.StartDynamics
End Sub
One of the most powerful methods of application development is to divide and conquer. In this application, the problem is divided into multiple classes, each handling one specific task. The form is used only for collecting user information, the ManipulateCell class is used only to place the graphics, and the clsDoor object is used to isolate the door from the rest of the application.