3DDOTNETGuide
3DDOTNETGuide
NET
Programmer’s Guide
Table of Contents
Preface .................................................................................................................................6
Documentation Comments ...........................................................................................6
Introduction ........................................................................................................................7
Introduction ....................................................................................................................... 66
Available Tools ................................................................................................................. 66
Writing the Stand-Alone Application ............................................................................... 67
SAPBase Example Projects .............................................................................................. 70
Runtime and Build Considerations ................................................................................... 70
Localization ......................................................................................................................73
Introduction ....................................................................................................................... 73
Adding a Resource File..................................................................................................... 73
Creating Batch File to Build Resource DLL..................................................................... 75
Adding Resource Identifier Class ..................................................................................... 77
Adding Localizer Class ..................................................................................................... 77
Index ................................................................................................................................160
Preface
This document is a user's guide for programming and customization using the Smart 3D .NET
programming layer and provides information about creating custom commands, naming rules,
and stand-alone applications with this new technology.
Documentation Comments
Send documentation comments or suggestions to PPMdoc@intergraph.com.
Introduction
The Intergraph Smart™ 3D .NET Programmer's Guide provides introductory information to
developers who want to create custom commands or custom programs using special tools and the
software. Before creating commands or programs, you must be very familiar with the software
interactively and understand its basic concepts of projects, engineering, architecture,
concurrency, and datastores.
For more specific information about creating commands using the software application
programming interface (API), please contact Intergraph Services.
This programming guide includes:
• An overview of customization with the software using Microsoft Visual Studio™.
• Some of the user-definable methods of implementation, such as creating commands
and naming rules.
• Examples and workflows.
The 3D .NET object model provides an easy-to-use .NET programming layer for external
customers and internal-use.
• Provides developers a comprehensible way to filter and locate objects and commit
changes to the database.
• Communicates with the existing middle tier 3D business objects and semantics so that all
modifications made to the plant and marine model remain consistent and valid.
• Consists of a set of common objects working with database connections, transactions,
filters, and so forth, including a set of objects and constructs in the application layer.
The 3D .NET object model is built as a set of .NET objects which wrap COM objects. A primary
issue in using .NET to manipulate the 3D business objects is communicating between .NET and
COM. The 3D COM business objects only come into existence as transactions are made against
the underlying database. When a transaction is committed or aborted, business objects essentially
disappear. Only their monikers remain.
The 3D .NET object model provides a base class called BusinessObject, which generically
wraps 3D business objects. BusinessObject provides a way to access and modify properties
using a dictionary approach. Applications extend the object model by subclassing from this base
class.
Getting Started
Before you begin using the 3D .NET programming layer, you should be familiar with the
following:
• Microsoft Visual Studio .NET technology
• Understanding of object-oriented programming principles
• Basic understanding of the 3D data model and functionality
• Understanding traversal of the 3D Schema Browser
• Basic understanding of relational databases
Intended Audience
• Those who want to customize within the framework of S3DHost – writing
commands, name rules, and creating symbols.
• Those who want to write applications to access the middle tier – to execute as a stand-
alone application independent of S3DHost.
• Those who want to develop new functionality using .NET at the client-level with
commands, GUI, and services.
4. Click Open. This places your specified helpcontentsetup.msha file path in the installation
source.
5. Click Add in the Action column, which changes the Action command to Cancel.
6. On the window’s lower right, click Update, which opens the Security Alert for the
digitally signed CAB file.
7. Click Yes to proceed and update the local library. Now the CAB file package is installed
and merged with other Intergraph and Microsoft indexes and installed content.
8. See the Contents section automatically update to display your set of documentation for
review.
3D Three-Tier Architecture
The software has an architecture that is component-oriented and built on three tiers.
The three-tier architecture of the 3D software includes the client, middle, and server tiers. These
tiers separate the graphical user interface, or GUI, stored in the client tier, from the business
logic in the middle tier. The business logic is further separated from the physical database in the
server tier.
The architecture emphasizes programming ease in producing functionality for the client tier. The
client tier mainly consists of commands, plus some components, ActiveX controls, and services.
Client Tier
The client tier's functionality is loaded on user demand in the form of tasks, services, and
commands.
The client tier contains the menus, toolbars, views, dialog boxes, and frames. In the client tier,
you can manipulate the GUI with services, such as the SelectSet and GraphicViewMgr, and
with software-supplied components, such as locators, highlighters, and view manipulations.
A set of commands make up each task in the client tier. Eighty to ninety percent of the code in a
task is written in the form of a command or to support a command.
Each task has its own COMponent called the EnvironmentMgr and an associated XML file.
This XML file defines the look and feel for the task and specifies the services that it uses.
Services and other components support the commands and tasks by providing a set of
commonly-used functionalities. Services should respond to task switches. When a task starts or
ends, services are notified of the event to initialize or to clean up.
A service implements one or more service-specific interfaces. Generally, a service wraps a
specific bit of re-usable functionality. A service exists as a singleton, which is the one and only
instance of its class.
Server Tier
The server tier is made up of one or more physical datastores.
The server tier also provides storage for the metadata, which contained in the Repository.
Metadata, is the total description of all the classes, interfaces, relationships, and semantics in the
system.
The RevisionMgr service in the middle tier accesses this metadata.
Polymorphism
The .NET wrappers for the Smart 3D business objects use class inheritance from
BusinessObject to realize individual business objects. BusinessObject provides access to
properties and relationships, including ownership and states of the COM interface IJDObject.
Like the underlying 3D COM model, the .NET layer achieves polymorphism by implanting
interfaces. .NET provides helper implementations to which .NET wrappers delegate
implementations of the polymorphic interfaces.
Namespaces
A namespace is simply a collection of different classes. All Visual Studio applications are
developed using classes from the .NET System namespace. The namespace with all the built-in
Visual Studio functionality is the System namespace. All other namespaces are based on this
System namespace and is the basic namespace used by every set of .NET code.
Namespaces are a way of grouping type names and reducing the chance of name collisions. A
namespace can contain both “other” namespaces and types. The full name of a type includes the
combination of namespaces that contain that type.
You're probably already familiar with namespaces in the .NET Framework Class Library. For
example, the Button type is contained in the System.Windows.Forms namespace. This means
that the Button class is contained in the Forms namespace, which is contained in the Windows
namespace, which is contained in the root System namespace.
The fully qualified name of a class is constructed by concatenating the names of all the
namespaces that contain the type. For example, the fully qualified name of the Microsoft Button
class is System.Windows.Forms.Button. The namespace hierarchy helps distinguish types with
the same name from one another. For example, you might define your own class named Button,
but it might be contained in the ControlPanel namespace within the PowerPlant namespace,
making its fully qualified name PowerPlant.ControlPanel.Button.
There's no need to use fully qualified names in your code unless you need to resolve an
ambiguity between two types with the same type name used in the same project.
Assemblies
An assembly is a collection of types and resources that form a logical unit of functionality. All
types in the .NET framework must exist in assemblies.
An assembly is the building block of a .NET application. It is a self-describing collection of
code, resources, and metadata. Assemblies take the form of a dynamic link library (.dll) or an
executable program (.exe), but they differ as they contain the information found in a type library
and the information about everything else needed to use an application or component.
Assemblies are made of two parts - manifest and modules. The manifest contains information
about what is contained within the assembly. The modules are internal files of intermediate
language (IL) code which are ready to run. When programming, it is not required to directly deal
with assemblies as the common language runtime (CLR) and the .NET framework do the job
behind the scenes. The assembly file is visible in the Solution Explorer window of the project.
An assembly includes:
• Information for each public class or type used in the assembly including class or type
names, the classes from which an individual class is derived, etc.
• Information on all public methods in each class, such as the method name and return
values (if any).
• Information on every public parameter for each method, such as the parameter's name
and type.
• Information on public enumerations including names and values.
• Information on the assembly version (each has a specific version number).
• Intermediate language code to execute.
• A list of types exposed by the assembly and a list of other assemblies required by the
assembly
Smart 3D .NET Assemblies
Examples of 3D .NET assemblies corresponding with their proper namespaces are:
• CommonMiddle.dll
• SystemMiddle.dll
• EquipmentMiddle.dll
Creating Commands
To create .NET-based commands that can execute in the 3D software, you must create a class
that subclasses from one of the base command classes provided in the 3D object model. This
approach to writing commands is significantly different from the way commands were written in
the 3D software COM world.
Commands historically have been written in Visual Basic (VB). Since VB 6 had no provision for
subclassing, it was necessary to always write a command “from scratch”, creating a VB class
that implemented the IJCommand2 interface. There was no way to take advantage of commonly
used functionality by using inheritance. Instead, a fairly hefty Command Wizard was provided
that created all the boilerplate code that might be applicable to the command being written:
GraphicView event handlers, SmartStep construction, converting world coordinates to screen
coordinates, etc.
With the advent of .NET, and especially its Common Runtime, it is possible to provide reusable
command functionality in the form of base classes. These base classes, though written in C#, are,
through the Common Runtime, seamlessly available to command writers in any supported .NET
language (specifically, to command writers who choose to use Visual Basic .NET).
Types of Commands
There are four types of commands used in the 3D software:
• Menu Handler Commands
• Modal Commands
• Graphic Commands
• Step Commands
Object-Based Commands
Most commands in the 3D software, however, are implemented as COM objects, and exist
(execute) as separate entities. They are ultimately started as the result of a menu handler, but in
most cases, the menu handler is the same function: NormalCmd, which takes as an argument
(passed in by the MenuHandler service) the ProgID of a class (HighCommand is another menu
handler that operates exactly as NormalCmd, except it starts the command with high priority).
This ProgID is passed in turn to the CommandManager service, which creates an instance of
the class, and transfers control to the object, which in turn executes the command.
These COM objects all support the IJCommand2 interface, by which the CommandManager
controls the commands execution. It invokes Start, Stop, Suspend and Resume methods on the
command object.
It is these COM object commands that form the real basis of commands in the 3D software.
There is no support, per se, in the .NET base classes for menu-handler commands. Modal,
graphic, and step commands are all separate objects, and are described in the following sections.
COM command, and the wrapper, when invoked by CommandManager at its various entry
points, in turn calls the corresponding .NET class method.
The following table shows the correspondence between IJCommand2 methods and properties
and their counterparts in the .NET command class. Note that the .NET method names are, in
accordance with .NET principles, given names like OnStart (rather than Start), signifying their
reactive nature.
Hereafter, reference is made only to the .NET methods, since that is the primary focus of this
section.
Start OnStart
Stop OnStop
Suspend OnSuspend
Resume OnResume
OnIdle OnIdle
EnableUIFlags EnableUIFlags
Modal Modal
Suspendable Suspendable
Running Running
ClientServiceProvider
In addition to inherited functionality, much of the activity performed by commands involves
client level services. You can access these services from the static class, ClientServiceProvider.
ClientServiceProvider provides direct access to the following client services:
Service Notes
Modal Commands
Modal commands are distinguished by the fact that after the Start method is called, the
command is immediately terminated. The modality is usually exemplified by the fact that the
command puts up a modal dialog with which to interact with the user (i.e., a dialog is not always
present; the Delete command is a modal command that runs without the need for a dialog.).
When the CommandManager runs a modal command (as determined by the value of the Modal
property), it first calls the OnStart method, and upon return from that, it immediately calls the
OnStop method, and then deletes the command object. Thus, all the work of the command is
done in the OnStart method. This usually consists of displaying a modal (!) dialog. After the
user presses OK or Cancel, the command may do some cleanup, and then return control to the
CommandManager. The essential parts of a modal command are therefore usually embedded in
the logic of the dialog.
Graphic Commands
Graphic commands (also called event-driven commands) differ from modal commands in that
the CommandManager does not terminate the command after the OnStart method is invoked.
Rather, the CommandManager relinquishes control back to the 3D software. Subsequently,
windows events arrive at the graphic command’s event handlers (generally these are
MouseDown, MouseMove, and MouseUp, but others may be handled). The command uses
these events to drive its internal logic. This usually consists of locating and hiliting graphics in
the graphic display, dynamically moving objects around, perhaps constructing new 3D business
objects, etc.
Eventually, the logic of the command is satisfied, and it terminates itself by calling the
StopCommand method (available to 3D .NET-based commands). Alternatively, it may be
stopped if another command of the same priority is started (e.g., the user clicking on a menu item
or toolbar button).
Optionally, graphic commands can be suspended:
• If their Suspendable property is true (the command determines whether it is suspendable
or not).
• Only if a command of higher priority is started. Examples of high priority commands are
View manipulation commands. When a higher priority command is started, and if the
command is Suspendable, CommandManager calls the OnSuspend method. As a
response to this call, the command takes steps to become inactive (i.e., not respond to
events). Eventually, the higher priority command terminates, upon which the original
command is invoked on its OnResume method. At that time, it restores itself and again
responds to events.
Step Commands
Step commands are a special case of graphic commands. They respond to events, but in addition
can define a number of steps or states. The command logically progresses from step to step as
part of its execution, perhaps going backwards, but generally arriving at some final step.
Step commands are quite common in the 3D software. They are often used in construction
workflows. For example, an initial step might locate an existing object, such as a pump. The next
step could locate another pump, and then the final step could route a pipe from the first pump to
the second. The command might then terminate, or perhaps return to the first step and begin the
process again. The steps and the logic can get quite complex, but organizing into specific steps
makes the command easier to deal with, both from the end user’s and programmer’s perspective.
Command Assistants
Command assistants are a concept in the 3D software whereby a command-like object is started,
and then runs in the background. Normally, only a single command is allowed to run, but there
are situations where it is useful for something to be running all the time. For instance, a
command assistant might follow mouse moves while preforming locates, and then continuously
report feedback to the user.
Command assistants are, for all practical purposes, exactly like commands. That is, they are
manipulated by the same interface. Thus, a 3D .NET-based command could act as a command
assistant. However, there is currently no way to start a .NET-based command assistant.
Depending on evolving requirements, this may change in the future.
Committing Transactions
A fundamental rule in the 3D software is this - changes to the database can only be made in
commands running at normal priority.
More specifically, commands not running at normal priority (and all command assistants) cannot
issue a TransactionManager command of Commit or Abort. Since modifying an object
implicitly involves a transaction in the software, only commands running at normal priority can
alter any business object.
Important: Failure to abide by this rule can result in database corruption.
Terminating Commands
A command may choose to stop itself. This should always be done by invoking the base class
StopCommand method. This is the only fully supported way for a command to terminate itself.
Invoking this method ensures that the command will be terminated in an orderly manner, and
that all entities, which base classes have brought into play on behalf of the command, are
properly terminated as well.
In order to use any 3D .NET base class, you need to add references to the assembly that contains
those base classes. After creating the project, right mouse click on the References node for the
project in the Solutions Explorer pane. Click Add Reference.
Note: The location of this folder varies depending on your software installation location. The
Add Reference dialog pictured shows the location for software delivered for Intergraph
developers. Generally, you can find these assemblies in {Product
Path}\Core\Container\Bin\SxS\Intergraph folder, where {Product Path} refers to the delivery
location of the software.
Select the Intergraph.Common.Toolkit.Client.dll and click Add.
Note in this example that both CommonClient and CommonMiddle are selected. The base
classes are located in CommonClient, but any command that does much more than “Hello,
World” usually requires CommonMiddle as well. Furthermore, you most likely need to
reference other assemblies. For example, if your command manipulates Equipment entities, you
need EquipmentMiddle (where 3D .NET wrappers for the Equipment business object are
located). You can add or remove references at any time.
Results of your selections will be listed and checked in the Recent pane of the Reference
Manager.
[VB example]
Imports Ingr.SP3D.Common.Client
Imports Ingr.SP3D.Common.Client.Services
Imports Ingr.SP3D.Common.Middle
Note that the default namespace for the newly created project is “MyNewCommand” (i.e., the
name of the project). The Create Project dialog also created a new class, called “Class1”.
Change the class name to something appropriate, such as, “MyModalCommand”:
[C# example]
public class MyModalCommand
{
}
[VB example]
Public Class MyModalCommand
End Class
Note that you can also simply delete the default new class (Class1.cs or Class.vb), and then
create a new class with the appropriate name. Right mouse click on the project in the Solutions
Explorer pane, and click Add… and select Class. In the Templates: pane, select Class, then
provide the name, for example, “MyModalCommand”. For C#, the dialog looks like this:
Now we are prepared to properly declare the class inheritance for a modal command. Edit the
class statement to provide the proper base class; in this case BaseModalCommand.
[C# example]
public class MyModalCommand : BaseModalCommand
{
}
[VB example]
Public Class MyModalCommand
Inherits BaseModalCommand
End Class
At this point, you actually have a functioning command, although one that does very little. But
this code can be compiled, and it can run properly and without error. This is because
BaseModalCommand provides stubs for all the required command methods, and can properly
start and shut down the command without further coding.
The key to writing commands in .NET is to provide overrides for only those methods that are
relevant for your work. For a modal command, that is likely to involve only the OnStart
method.
Method Notes
[VB Example]
Public Overrides Sub OnStart(ByVal commandID As Integer, _
ByVal argument As Object)
MyBase.OnStart(commandID, argument)
End Sub
Now use the inherited method WriteStatusBarMsg to do the work:
[C# Example]
base.WriteStatusBarMsg("Hello World");
[VB Example]
MyBase.WriteStatusBarMsg "Hello World"
Create a new command (either C# or VB) using Project > Add Class…. Name it FilterSieve.
Complete all the class preparation as described above (adding using or Imports statements for
CommonClient and CommonMiddle and properly subclassing from BaseModalCommand).
Provide an override for the OnStart method. Now, create a new Filter, and set the definition to
filter all Equipment objects:
[C# Example]
Filter filter = new Filter();
filter.Definition.AddObjectType("Equipment&Furnishing");
[VB Example]
Dim filter As New Filter()
filter.Definition.AddObjectType "Equipment&Furnishing"
In order to apply the filter (which returns a read-only collection of BusinessObjects), you’ll need
to add a using/Import statement:
[C# Example]
using System.Collections.ObjectModel; // To access
ReadOnlyCollection<T>
[VB Example]
Imports System.Collections.ObjectModel ' To access
ReadOnlyCollection<T>
Now add a statement to apply the filter and obtain the resulting objects. By default, Filter.Apply
operates on the current model database connection:
[C# Example]
ReadOnlyCollection<BusinessObject> results = filter.Apply();
[VB Example]
Dim results As ReadOnlyCollection(Of BusinessObject)
results = filter.Apply
Now iterate through the results collection. For the purposes of this exercise, we use generic
property access and look at the name of the object with the IJNamedItem COM interface. If it’s
not named, or if the name begins with “BAD”, we delete the object:
[C# Example]
foreach (BusinessObject businessObject in results)
{
var namePropertyValue =
(PropertyValueString)businessObject.GetPropertyValue("IJNamedItem",
"Name");
if (namePropertyValue == null ||
namePropertyValue.PropValue.StartsWith("BAD"))
{
businessObject.Delete();
}
}
[VB Example]
For Each businessObject As BusinessObject In results
Dim namePropertyValue As PropertyValueString = _
DirectCast(businessObject.GetPropertyValue("IJNamedItem",
"Name") "), PropertyValueString)
If namePropertyValue Is Nothing Then
businessObject.Delete()
ElseIf namePropertyValue.PropValue.StartsWith("BAD") Then
businessObject.Delete()
End If
Next
Finally, use the TransactionManager to commit the changes to the database:
[C# Example]
ClientServiceProvider.TransactionMgr.Commit("FilterSieve");
[VB Example]
ClientServiceProvider.TransactionMgr.Commit("FilterSieve")
The whole source looks like this:
[C# Example]
public override void OnStart(int commandID, object argument)
{
base.OnStart(commandID, argument);
[VB Example]
Public Overrides Sub OnStart(ByVal commandID As Integer, _
ByVal argument As Object)
MyBase.OnStart(commandID, argument)
results = filter.Apply
For Each businessObject As BusinessObject In results
Dim namePropertyValue As PropertyValueString = _
DirectCast(businessObject.GetPropertyValue("IJNamedItem", _
"Name"), PropertyValueString)
If namePropertyValue Is Nothing Then
businessObject.Delete()
ElseIf namePropertyValue.PropValue.StartsWith("BAD") Then
businessObject.Delete()
End If
Next
ClientServiceProvider.TransactionMgr.Commit("FilterSieve")
End Sub
Method Notes
Property Notes
The BaseGraphicCommand class has a series of virtual (and hence, overridable) methods that
are called in response to a small set of graphics events. The decision was made in designing this
class that 95% of all 3D software event-driven commands deal only with MouseDown,
MouseMove and MouseUp. A handful of others deal with keyboard events. Thus, the number of
available events in BaseGraphicCommand is quite a bit smaller than in the corresponding
COM world.
The event handler methods include:
• OnMouseDown
• OnMouseMove
• OnMouseUp
• OnKeyDown
• OnKeyUp
A graphic command merely overrides the event handling methods that it cares about, and ignores
the others.
Behind the scene, BaseGraphicCommand does both pre- and post-processing for all of these
events. For example, it keeps up with the active view (the view that generated the event and sets
the value of CurrentView based on whether the view changed since the last event was handled).
[VB example]
Public Class GraphicLocate
Inherits BaseGraphicCommand
End Class
Declare a class variable for a Hiliter and in the OnStart method, create a new Hiliter from the
GraphicViewManager, and set its color. Note the use of the .NET ColorConstants construct to
provide pre-defined color constants:
[C# example]
public class GraphicLocate : BaseGraphicCommand
{
private GraphicViewHiliter hiliter = null;
public override void OnStart(int commandID, object argument)
{
base.OnStart(commandID, argument);
hiliter = new GraphicViewHiliter();
hiliter.Color = ColorConstants.RGBGreen;
}
}
[VB example]
Public Class GraphicLocate
Inherits BaseGraphicCommand
Private m_hiliter As GraphicViewHiliter
Public Overrides Sub OnStart(ByVal commandID As Integer, _
ByVal argument As Object)
MyBase.OnStart(commandID, argument)
m_hiliter = New GraphicViewHiliter()
m_hiliter.Color = ColorConstants.RGBGreen
End Sub
End Class
Now provide overrides for OnMouseMove and OnMouseUp. In OnMouseMove, use the
CurrentView to locate, and then hilite anything located. In OnMouseUp, locate again, and add
to the select set anything located. Note that, since the boreline locate can locate things on top of
one another, you could potentially add multiple things to the select set.
[C# example]
protected override void OnMouseMove(GraphicView view,
GraphicViewManager.GraphicViewEventArgs e, Position position)
{
base.OnMouseMove(view, e, position);
Locator locator = CurrentView.CreateLocator();
locator.Locate(e.X, e.Y, 1.0);
if (locator.LocatedObjects.Count > 0)
{
_hiliter.HilitedObjects.Clear();
_hiliter.HilitedObjects.Add(oLocator.LocatedObjects);
}
}
protected override void OnMouseUp(GraphicView view,
GraphicViewManager.GraphicViewEventArgs e, Position position)
{
base.OnMouseUp(view, e, position);
Locator locator = CurrentView.CreateLocator();
locator.Locate(e.X, e.Y, 1.0);
if (locator.LocatedObjects.Count > 0)
{
ClientServiceProvider.SelectSet.SelectedObjects.Add
(locator.LocatedObjects);
}
}
[VB example]
Protected Overrides Sub OnMouseMove( _
ByVal view As GraphicView, _
ByVal e As GraphicViewManager.GraphicViewEventArgs, _
Position As Position)
MyBase.OnMouseMove(view, e, position)
Dim locator As Locator
locator = MyBase.CurrentView.CreateLocator()
locator.Locate(e.X, e.Y, 1.0)
If (locator.LocatedObjects.Count > 0) Then
m_hiliter.HilitedObjects.Clear()
m_hiliter.HilitedObjects.Add(locator.LocatedObjects)
End If
End Sub
Protected Overrides Sub OnMouseUp( _
ByVal view As GraphicView, _
ByVal e AsGraphicViewManager.GraphicViewEventArgs, _
Position As Position)
MyBase.OnMouseUp(view, e, position)
Dim locator As Locator
locator = MyBase.CurrentView.CreateLocator()
locator.Locate(e.X, e.Y, 1.0)
If (locator.LocatedObjects.Count > 0) Then
ClientServiceProvider.SelectSet.SelectedObjects.Add _
(locator.LocatedObjects)
End If
End Sub
Property Notes
Method Notes
GetSolvedConstraints Returns the collection of constraints used by
SmartSketch3D.
UpdateStep Reprocesses current step data using the step
settings.
Behind the scene, BaseStepCommand does both pre- and post-processing for all graphic events.
The developer is able to concentrate on the results of each step, and not be concerned with
specifics of locating, selecting, etc.
BaseStepCommand also adds a set of event handler methods similar to those supplied by
BaseGraphicCommand. A step command overrides the event handler methods that it cares
about, and ignores the others. The event handler methods are described as follows:
OnObjectsLocated
This method is called when business objects are located during MouseMove. This provides a
way for subclasses to filter the objects located by SmartSketch3D. This method is invoked
before OnMouseMove. Two arguments are passed in.
BOCollection Collection of objects that have been located.
oLocatedBusinessObjects
OnSmartSketchConstraintConflict
This method is called when a SmartSketch3D constraint is added that conflicts with an existing
constraint. The one argument is:
Constraint conflictingConstraint ‘The constraint that was added.
The overriding method might react, for example, by removing the offending constraint or
possibly some other constraint.
OnSmartSketchPositionChange
This method is called when SmartSketch3D has snapped the mouse position based on the
current set of constraints. There are two arguments:
Position oPosition Modified position (read-only).
MouseEvent eventMouse The mouse event that caused the
PositionChange (MouseMove or MouseUp).
OnStepSelectionChanged
This method is called when the step's selection is changed. There are four arguments:
Bool isSelected When true, BusinessObject and Position pair are selected.
BusinessObject The business object that was selected or deselected.
businessObj
OnStepPostSelectionChanged
This method is called after OnStepSelectionChanged has occurred. The one argument is the
step in which the SelectionInfos collection is modified.
[VB example]
Public Class NameChecker
Inherits BaseStepCommand
End Class
In the OnStart override, establish two steps.
[C# example]
public override void OnStart(int commandID, object argument)
{
base.OnStart(commandID, argument);
StepCount = 2;
Steps[0].Prompt = "Select one Equipment";
Steps[0].MaximumSelectable = 1;
Steps[0].LocateBehavior =
StepDefinition.LocateBehaviors.QuickPick;
Steps[0].StepFilter.AddInterface("IJEquipment");
Steps[1].Prompt = "Select as many things as you want.";
Enabled = true; // This starts the command.
}
[VB example]
Public Overrides Sub OnStart(ByVal commandID As Integer, _
ByVal argument As Object)
MyBase.OnStart(commandID, argument)
StepCount = 2
Steps(0).Prompt = "Select one Equipment"
Steps(0).MaximumSelectable = 1
Steps(0).LocateBehavior = _
StepDefinition.LocateBehaviors.QuickPick
Steps(0).StepFilter.AddInterface("IJEquipment")
Steps(1).Prompt = "Select as many things as you want."
Enabled = True
End Sub
Override OnMouseUp and check whether the current step is satisfied. If so, advance the step.
When the last step is completed, perform the name check.
[C# example]
protected override void OnMouseUp(GraphicView view,
GraphicViewManager.GraphicViewEventArgs e, Position position)
{
base.OnMouseUp(view, e);
// Anything selected?
if (Steps[CurrentStepIndex].SelectedBusinessObjects.Count > 0)
{
if (CurrentStepIndex == 0)
{
CurrentStepIndex++; // Bump to next step.
}
else
{
// Time to check; get Step 1 name.
int numberOfMatchingNames = 0;
BusinessObject oneBusinessObject =
Steps[0].SelectedBusinessObjects[0];
PropertyValueString stepOneName =
(PropertyValueString)
oneBusinessObject.GetPropertyValue
("IJNamedItem", "Name");
// Now look for matches from Step 2.
foreach (BusinessObject businessObject in
Steps[1].SelectedBusinessObjects)
{
If (businessObject.SupportsInterface("IJNamedItem"))
{
PropertyValueString namePropertyValue =
(PropertyValueString)
businessObject.GetPropertyValue
("IJNamedItem", "Name");
if (namePropertyValue.PropValue[0] ==
stepOneName.PropValue[0])
{
numberOfMatchingNames++;
}
}
}
WriteStatusBarMsg(numberOfMatchingNames.ToString() +
"objects match criteria");
StopCommand(); // Kill the command.
}
}
}
[VB example]
Protected Overrides Sub OnMouseUp(ByVal view As _
GraphicView, ByVal e As _
GraphicViewManager.GraphicViewEventArgs, _
position As Position
MyBase.OnMouseUp(view, e, position)
If CurrentStepIndex = 0 Then
CurrentStepIndex = CurrentStepIndex + 1
Else
' Time to check; get Step 1 name.
Dim numberOfMatchingNames As Integer = 0
Dim oneBusinessObject As BusinessObject
oneBusinessObject = Steps(0).SelectedBusinessObjects(0)
Dim stepOneName As PropertyValueString
stepOneName = DirectCast( _
oneBusinessObject.GetPropertyValue("IJNamedItem", _
"Name"), PropertyValueString)
If namePropertyValue.PropValue(0) =
stepOneName.PropValue(0) Then
numberOfMatchingNames = numberOfMatchingNames +1
End If
End If
Next
WriteStatusBarMsg(numberOfMatchingNames.ToString() + " objects
match criteria")
StopCommand() ' Kill the command.
End If
End Sub
Ribbon Bars
A ribbon bar is a toolbar on the S3DHost main window that is specific to the active command.
The ribbon bar lies just below the main S3DHost toolbar. Since it is specific to the active
command, the ribbon bar will generally change as the active command changes. If a command
does not need a ribbon bar, the ribbon bar area remains blank. The following illustration displays
the ribbon bar for the PointAlong command.
.NET graphic and step commands can employ a ribbon bar as follows:
1. Create a ribbon bar control. The control must be subclassed from the
BaseRibbonBarControl class.
2. Create an instance of the ribbon bar control.
3. Set the RibbonBar property (defined on the BaseGraphicCommand class) to the newly
created control.
BaseRibbonBarControl
The BaseRibbonBarControl class exists primarily to ensure that the RibbonBar property has a
specific signature. In the future, utility methods may be added to the class to aid in implementing
a ribbon bar, but for now, it mainly guarantees that the RibbonBar property has a well-defined
type. It also guarantees that the ribbon bar is properly disposed of when an instance is destroyed.
To create your own command ribbon bar, create a new class and subclass it from
BaseRibbonBarControl:
public partial class MyRibbonBar : BaseRibbonBarControl
The use of the partial keyword is typical in .NET when developing GUI elements. Part of the
GUI is kept in a “partial” segment, while operational code is written in another.
Add whatever controls are deemed necessary for the ribbon bar. For instance, the following
example adds four text box controls and their associated labels:
In general, the ribbon bar also contains public methods and properties, which allow the command
to communicate. In the previous example, we might expect four properties allowing the
command to set and retrieve Distance, East, North, and Elevation values. For a step command,
the ribbon bar might offer a method (or property) that the command uses to change the step,
presenting a different set of prompts or controls. Finally, the ribbon bar can offer events that are
raised when the user interacts with the command. For example, if the user presses a button in
order to change step, the ribbon bar should raise an event back to the command.
In any case, after the ribbon bar has been constructed, the next effort is to set the ribbon bar for
the command. This is generally done in the command’s Start method:
[C# example]
// Set ribbon bar.
myRibbonBar = new MyRibbonBar();
base.RibbonBar = myRibbonBar;
[VB example]
‘ Set ribbon bar.
m_yRibbonBar = New MyRibbonBar()
Base.RibbonBar = m_yRibbonBar
The lifetime management of the ribbon bar is handled by the base class.
Architecture
All .NET name rules must be subclassed from the NameRuleBase class, which is provided in
Ingr.SP3D.Common.Middle namespace of the CommonMiddle assembly.
NameRuleBase
PermissionGroup,
Volume_NamedSpace
}
The hierarchy type PermissionGroup is not supported and will throw
System.NotSupportedException.
7. protected BusinessObject GetPart(BusinessObject oOccurrence)
Returns a part for the occurrence passed in if it is a PartOccurrence or SmartOccurrence.
8. protected string GetPartNumber(BusinessObject oPart)
Returns a part number for the part passed in.
9. protected string GetTypeString(BusinessObject oNamedItem)
Returns type string for model and catalog items.
[VB example]
Public Class CommonNameRule
End Class
3. .NET name rules must derive from NameRuleBase. The base class, NameRuleBase, is
defined with the Ingr.SP3D.Common.Middle namespace in the CommonMiddle.dll
assembly. Add a reference to the CommonMiddle assembly by using Project > Add
Reference… in Visual Studio. CommonMiddle.dll is located in {Product
Path}\Container\Bin\Assemblies\Release folder.
Note: Select the CommonMiddle reference in the Solution Explorer. In Reference
Properties set the Copy Local property value to “False” to avoid copying the reference
assembly to the project’s output folder.
4. Add the following :
[C# example]
using System.Collections.ObjectModel // To access
ReadOnlyCollection<T>.
using Ingr.SP3D.Common.Middle // To access NameRuleBase.
[VB example]
Imports System.Collections.ObjectModel ’ To access
ReadOnlyCollection<T>.
Imports Ingr.SP3D.Common.Middle ’ To access NameRuleBase.
5. Derive the name rule class from NameRuleBase and implement stubs for the abstract
method defined in base class.
[C# example]
public class CommonNameRule : NameRuleBase
{
public override Collection<BusinessObject> GetNamingParents(
BusinessObject
oEntity)
{
// To be implemented
[VB example]
Public Class CommonNameRule
Inherits NameRuleBase
End Function
End Sub
End Class
6. Replace code in the previous override function and method with code for the customized
naming scheme. For details see Writing a Name Rule in the following section.
7. Build your project to produce the name rule assembly, but this name rule assembly must be
made available on the symbol server. For details about assembly location and placement, see
Deployment, which follows Writing a Name Rule.
[C# Example]
public override Collection<BusinessObject> GetNamingParents(
BusinessObject
oEntity)
{
Collection<BusinessObject> oParents= new
Collection<BusinessObject>();
return oParents;
}
[VB Example]
Public Overrides Function GetNamingParents(
ByVal oEntity As BusinessObject) As Collection(Of
BusinessObject)
End Function
name has changed, compare the formulated name with the name basis from active entity. Use
the GetNamingParentsString helper function to get the name basis.
[C# Example]
public override void ComputeName(
BusinessObject oEntity,
ReadOnlyCollection<BusinessObject>
oParents,
BusinessObject oActiveEntity)
{
string sPartName = "";
if (sPartName == "")
{
// Get class name.
sPartName = base.GetTypeString(oEntity);
}
}
}
[VB Example]
Public Overrides Sub ComputeName(
ByVal oObject As BusinessObject, _
ByVal oParents As ReadOnlyCollection(Of
BusinessObject),
ByVal oActiveEntity As BusinessObject)
If sPartName.Length() = 0 Then
' Get class name.
sPartName = MyBase.GetTypeString(oEntity)
End If
MyBase.SetName(oEntity, sPartName)
End If
End Sub
• Naming scheme: If the object to be named is a part occurrence, the name must be
SystemParentName-PartName -UniqueCounter. If not, it must be SystemParentName-
ClassName-UniqueCounter.
This naming scheme uses the name of the system parent; hence implementation of
GetNamingParents must return the system parent of the object being named. Use the
GetParent helper function to get the parent in a particular hierarchy. This function takes an
enumeration value of HierarchyTypes and the child for which the parent must be returned.
[C# Example]
public override Collection<BusinessObject> GetNamingParents(
BusinessObject
oEntity)
{
Collection<BusinessObject> oParents = new
Collection<BusinessObject>();
[VB Example]
Public Overrides Function GetNamingParents(
ByVal oEntity As BusinessObject) As Collection(Of
BusinessObject)
End Function
ComputeName implementation is very similar to the above example, with the extension that
the naming scheme uses the name of the system parent. Use the GetName function to get the
name of the parent system.
Deployment
All customer name rule assemblies must be located in the \NameRules folder beneath \Custom
Symbols, which is a sub-folder of Symbol Share - specified by the “Symbol and custom
program file locations” setting for the Catalog database in Project Management.
{Symbol Share}\Custom Symbols\NameRules\
1. Copy the name rule assembly to the NameRules folder.
2. Update the custom symbol configuration file (CustomSymbolConfig.xml) by running the
Update Custom Symbol Configuration command in the Tools menu of Project
Management.
Configuration
Name rules must be associated with object types before they can be used. This is done by editing
the NamingRules sheet in the GenericNamingRules.xls and updating the database using
bulkload. GenericNamingRules.xls can be found in the {Product
Path}\CatalogData\BulkLoad\DataFiles folder.
Each entry in the NamingRules sheet of the GenericNamingRules.xls file contains four fields:
• Head – the action that needs to be performed when data is bulkloaded. It can be “A” - Add,
“M” - Modify, or “D” – Delete.
• TypeName – Class name to which this name rule applies.
• Name – Display name of the name rule.
• SolverProgID – ProgID of the name rule.
SolverProgID for the name rules authored in .NET must be in the following format:
[AssemblyName],[Namespace].[ClassName]
For example:
Assembly name is CommonNameRuleLib.
Namespace is NameRules.
Class name is CommonNameRule.
The corresponding SolverProgID is:
CommonNameRuleLib,NameRules.CommonNameRule.
An entry in the GenericNamingRules.xls, to add the above to the database looks like this:
Head TypeName Name SolverProgID
A CPDesignEquipment NETNameRule CommonNameRuleLib,NameRules.CommonNameRule
After a successful bulkload operation, the changes to the database can be verified using the
Catalog environment of S3DHost. Expand NamingRules nodes in the Catalog Browser, and
select the object type that was added or modified using bulkload. Verify that SolverProgID has
changed.
Note: Only new rules should be added to an existing catalog. Using the Delete and Replace
mode or modifying the ProgID of an existing name rule by using the AMD mode is not
supported in this release.
Testing
Start S3DHost.exe and define a workspace to return the object type for which the name rule must
be tested. Select the object in t workspace and display its properties dialog. Change the Name
Rule property value on the object’s properties dialog and apply the change to see the name
modified.
Available Tools
When building a stand-alone application, the first thing to keep in mind is that the number of
available services and development components is not as large as that available to the custom
command writer. In particular, none of the client tier level services may be used in a stand-alone
application. These programs are essentially middle tier applications, and as such, do not have
access to the client services. Furthermore, the proper execution of client services depends on
their initialization within the S3DHost executable.
• Site, Plant, Model and Catalog - components that allow you to interrogate, and to some
degree, manipulate these 3D software entities. The caller may open a plant, and then have
access to its model and catalog.
• UOMManager - to convert database units to and from more readable units of measure.
• MetadataManager - provide access to a 3D class, interface, and property definitions.
• BusinessObject – serves as a generic 3D software business object wrapper class that
allows generic access to the properties and relationships for objects that do not yet have
specific 3D .NET wrappers.
• Filters - to filter objects from the database and obtain a collection of business objects,
conforming to a set of filter criteria.
• ErrorLog - a component used to write information to the error log.
Many of the steps are similar to those for creating a .NET command project. To continue
creating your stand-alone application, see the previous sections Creating a Command Solution
and Project and Writing Commands: Modal Command in the Creating Commands section.
Important: Remember that CommonClient-related services are not valid for stand-alone
applications. You can only add CommonMiddle-related references.
Now, add using statements in the MainForm source. For example, these three statements
provide essentially everything you require in CommonMiddle:
[C# example]
using Ingr.SP3D.Common.Middle.Services;
using Ingr.SP3D.Common.Middle;
using Ingr.SP3D.Common.Exceptions;
[VB example]
Imports Ingr.SP3D.Common.Middle.Services
Imports Ingr.SP3D.Common.Middle
Imports Ingr.SP3D.Common.Middle.Exceptions
Now add code to open a Smart 3D software Model database. There are two approaches to
consider.
1. The simplest mechanism uses a straightforward .NET API to open the most recent Site
that was opened in the 3D software. This should be on the same machine where the
stand-alone application executes. The 3D software leaves information in the Registry that
identifies the last Site that was opened.
2. The second approach allows the code (or the user) to open a specific Site. After the Site
is opened, the code should open a specific Plant and Model or allow the user to specify
what to open. User-interaction is required depending on whether you are writing in
interactive mode or a batch application. The latter may decide what plant or model is
required by some other mechanism (i.e., reading an input file).
The following example takes the simpler approach. To open the most recently accessed site, add
a button to the form and define its Text property “Open Most Recent”. To employ good
programming practices, name the button btnOpenMostRecent.
Double-click the button in the Form Designer window. This displays the code editor for the
button’s Click event handler. Add a class variable for the form to hold the reference to the Site:
[C# example]
public partial class MainForm : Form
{
Site m_oSite = null;
…
[VB example]
Public Class MainForm
Private m_oSite As Site
…
In the btnOpenMostRecent Click handler, add code to connect to the most recent site and to
(arbitrarily, for purposes of this example) open the first plant in the list:
[C# example]
private void btnOpenMostRecent_Click(object sender, EventArgs e)
{
// Connect to the Site database found in the Registry.
m_oSite = MiddleServiceProvider.SiteMgr.ConnectSite();
if (m_oSite.Plants.Count > 0)
{
m_oSite.OpenPlant(m_oSite.Plants[0]); // Choose the first.
}
else
{
MessageBox.Show("No plants defined in default Site!");
}
}
[VB example]
Private Sub btnOpenMostRecent_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnOpenMostRecent.Click
' Connect to the Site database found in the Registry.
m_oSite = MiddleServiceProvider.SiteMgr.ConnectSite()
If (m_oSite.Plants.Count > 0) Then
m_oSite.OpenPlant(m_oSite.Plants(0)) 'Choose the first.
Else
MessageBox.Show("No plants defined in default Site!")
End If
End Sub
The application is now ready to begin processing data in the model. The simple act of connecting
a site performs all the necessary 3D .NET initialization.
When shutting down the stand-alone application, it is necessary to do some clean-up in order to
release resources. Generally, this consists of aborting any potentially ongoing database
transactions, and invoking the MiddleServiceProvider Cleanup method. The best place to do
this is in the main form’s FormClosing event handler:
[C# example]
private void MainForm_FormClosing(object sender, FormClosingEventArgs
e)
{
MiddleServiceProvider.TransactionMgr.Abort();
MiddleServiceProvider.Cleanup();
}
[VB example]
Private void SAMain_FormClosing(object sender, FormClosingEventArgs e)
{
MiddleServiceProvider.TransactionMgr.Abort();
MiddleServiceProvider.Cleanup();
}
Runtime Considerations
In order for the stand-alone application to execute properly, the system PATH variable must be
set to contain paths to {Product Path}\Smart3D\Core\Runtime and to {Product
Path}\Smart3D\GeometryTopology\Runtime.
To make any changes, select Start > Control Panel > System > Advanced > Environment
Variables.
Localization
Introduction
When creating a user interface or conveying a message to the user we need to be aware of our
audience. Localization in this sense is preparing the project to allow for easy modification of
messages and user interfaces to be translated into different languages and/or locales.
For example:
throw new InvalidParentSystemException("The parent system is not in
working status.");
The assumption with this example is that English is the audience language and therefore no
localization is needed. However, if the project is later to be used in other parts of the world with
different languages and/or locales; how do we translate these types of messages? The answer is,
Recompilation. In that case we would need to translate all messages to each particular world
market and recompile. This clearly is not a feasible solution.
To make localization simpler and more robust, we use separate resource files. These files are
used by the message authors to translate the messages to their specific locale. After the
translation is complete, just generate the resource .dll file that contains the current locale. This
way there is no need to change and recompile the project, and only the language and/or locale are
affected.
To correctly achieve the separation of project and locale resources, just follow a series of steps,
to guide you through the recommended process of project localization.
8. Now find your project location and add a locale folder; for example,
D:\Users\MyAppMiddle.
9. Add an en-US folder for the United States English locale. Customize this step to set the
appropriate locale.
10. Move the resource file (MyAppResourceMiddle.Designer.cs) to the en-US folder and
delete the original Designer.cs file.
SETLOCAL
"%resgpath%resgen.exe" <MyAppDirectory>\en-US\MyAppResource.Resx
<MyAppDirectory>\en-US\MyAppResource.resources
"%alpath%al.exe" /embed:<MyAppDirectory>\en-
US\MyAppResource.resources,MyAppResource.resources /out:<MyAppDirectory>\en-
US\MyAppResource.resources.dll /c:en-US
ENDLOCAL
6. Modify the project location <MyAppDirectory> to the location of your project. After
correcting the project location, modify the resource file names.
7. Save and run the batch file. This creates the necessary files for .NET to use the resources
for your specific locale. At this point the localized .dll file can be used. The
CommonMiddle API provides a static class, CmnLocalizer, which resolves and returns
the localized string based on the resource .dll and the executing assembly.
8. Add the CommonMiddle reference to your project - {Product
Path}\Container\Bin\Assemblies\Debug\CommonMiddle.dll. Now, the localizer class can
be called on your code.
string localizedString = CmnLocalizer.GetString(resourceId,
defaultMessage,"MyAppResourceMiddle",
"MyAppMiddle");
Notice the hard-coded strings. It could be a bit tedious and unproductive to hard-code the
resource name and assembly name every time when localizing strings.
9. Add two more classes - a resource identifier and a localizer class, which saves time for
future work.
The resource identifier class contains all of the constants that identify each particular resource
string, and the localizer class provides a convenient API in which to get a localized string from
the resource .dll.
2. Modify the resource identifier class to be public and static. Also, change the
namespace to an appropriate namespace, and add a DefaultResource and
DefaultAssembly string constants. The DefaultResource constant is the name of
your resource file, MyAppResourceMiddle. The DefaultAssembly constant is the
name of the project output assembly, MyAppMiddle.
Now, you can use the localizer to get the localized string by simply specifying the resource
identifier and default string.
string localizedMessage = MyAppLocalizer.GetString(
MyAppResourceIdentifier.ErrInvalidApprovalStatusP
arentSystem,
"The parent system is not in working status.");
The software needs to know the location of these assemblies in order to resolve and load them
during runtime. To define the location of custom assemblies, you can:
1. Locate the S3DCustomAssemblyPath.xml in the XML folder in your SymbolShare
folder. If it does not exist, create a new XML file with this name.
2. The XML file can have two elements: CustomMiddleServicesPath and
CustomClientServicesPath. You can edit and add your own path to each element, but
each element can consist of only one path.
Sample XML file:
<CustomMiddleServicesPath>C:\Temp\Debug</CustomMiddleServicesPath
>
<CustomClientServicesPath />
</S3DCustomAssemblyPath>
3. If the XML file is found and there is a valid directory entry in one or both of the expected
elements in the XML, then the assembly resolver tries to load the assembly based on the
retrieved paths.
</configuration>
Smart 3D executables, according to the software version installed on your computer, already
contain these required entries in the config file to permit trusting and loading Assemblies from
remote locations. When you use Smart 3D’s .NET API to write your own custom executables,
ensure that you add the configuration file for setting your custom executable’s
<appname>.exe.config file to allow it to trust assemblies loaded from remote sources.
Dimensions: As a symmetrical valve, the face to face dimension is required in order to draw the
valve. The dimensions required to draw the flange are obtained from the standard geometric data
bulkloaded in the Smart 3D project’s catalog database. This data is available in the
AllCommon.xls for the various end preparations (Bolted, Male, or Female). The radius of the
sphere is assumed to be a factor of the known dimensions. Hence the input parameters required
those for representing the Physical Aspect (Face to Face dimension).
Orientation: The symbol is drawn with the origin (0, 0, 0) at the center of the valve. The left-
hand side port is drawn along –X direction and the right-hand side port is drawn along +X
direction.
Assembly Creation
First, decide which assembly will contain the new symbol. To avoid performance issues when
loading multiple assemblies, group .NET symbols according to BusinessObject type and usage
type.
Build the assembly to %OLE_SERVER%\Custom Symbols. You can build the assembly in any
subfolder under the Custom Symbols folder.
A ToDoMessageWarning type should be used when the custom definition code did not return
expected results, which might need action by the user, but is still able to complete computation.
Also, a content writer has the option to save resource identifier information by providing the
module name that contains the resource table and the ID of the resource string itself.
'Following code creates an error ToDoListMessage with a localizable
message.
Dim myToDoListMessage As ToDoListMessage = New
ToDoListMessage(ToDoMessageTypes.ToDoMessageError, “MyResourceModuleName”,
int messageID, "Something wrong in my symbol."))
Default software behavior is to show the symbol business object as the “object to update” on the
ToDoListMessage. A content writer has the option to add another business object as the “object
to update”, by specifying it as an argument.
When a To Do List message is created, it must be set on the base class to allow the software to
be able to use it. If an error ToDoListMessage is created, computation can be stopped.
Dynamic Outputs
Often the symbol outputs are not known before-hand and are determined dynamically during the
computation of the symbol. For example, concerning a symbol to create the geometry of a stair,
the number of outputs is dependent on the span of the stair and pitch.
The 3D API framework allows addition of these outputs dynamically. To add dynamic outputs to
the symbol:
1. Inform symbol machinery that you will be creating variable outputs.
If you derive your symbol definition class from a Business Object-specific base class
(e.g., StairSymbolDefinition), this step is not required, and you can jump directly to step
2.
Add VariableOutputs attribute to your class.
<VariableOutputs()>
2. Construct the output object.Symbol output must be persistent; i.e., it must be created with
a valid database connection.
3. Add an output object to the symbol outputs with a unique name.
'Add an output object myOutputObject named “MyOutputName”
'to the aspect m_oSimplePhysicalAspect.
1. Get the net volume and COG for the geometric outputs of the symbol for each material.
2. Construct a VolumeCOG named output object for each material and add it as output to
the symbol.
m_oSimplePhysicalAspect.Outputs[“VolumeCOG”] = oHandRailVolCOG
3. Symbols which handle parts with single material only need to complete previous steps 1
and 2. Weight COG will be calculated by the business object from this named output.
4. Symbols which handle parts with multiple materials need to realize ICustomWeightCG.
This interface provides methods to evaluate weight COG or the part by the symbol. In
this case, the symbol gets the outputs from the output collection and the materials from
the part to calculate the net weight COG.
'Set the net weight and COG on the Business Object using
'helper method provided on StructureSymbolDefinition.
SymbolHelper.SetWeightAndCOG(oBO, dWeight, dCOGX, dCOGY, dCOGZ)
End If
…
'Initialize the matrix to identity.
oMatrix = New Matrix4X4()
'Construct a double array and set the actual double values for the local
'x, y, z vectors in the transformation matrix.
'Also set the translation component.
Dim dArrMatrix As Double() = New Double(15) {}
…
Custom Mirror
Some business objects delegate the mirror implementation for the symbol code, which allows the
symbol writer to override the mirror behavior of a specific part; e.g., a stair may need to be
flipped around the top support on mirror.
The 3D API framework provides an interface ICustomMirror, which should be realized by the
symbol to support custom mirror behavior for mirroring parts.
Supporting custom behavior for mirror on a symbol involves the following:
1. Realize ICustomMirror on the symbol.
2. Set properties that effect mirror behavior inside the Mirror method.
The following code shows how to implement mirror for a ladder:
Following code demonstrates how to set the display status of a property to read-only:
Next
…
Following code shows how to validate the value of a property on change in the property value.
sInterfaceName = oPropToChange.[Property].PropertyInfo.InterfaceInfo.Name
sPropertyName = oPropToChange.[Property].PropertyInfo.Name
…
'Check the property value.
If sErrorMessage.Length > 0 Then
bOnPreLoad = False
Exit For
End If
…
{Product Path}\Core\Container\Bin\Assemblies\Release\SymbolWizard.exe
For more information see its accompanying context sensitive (F1) help.
The wizard implementation is language neutral. It uses style sheet templates and XML
transformation to generate a .NET symbol definition for any programming language desired by
the symbol author. The style sheet templates are delivered in separate folders, \CS and \VB:
{Product Path}\CommonApp\SOM\Client\Services\SymbolWizard\Templates
One style sheet for each VB or C# solution, project, assembly information, XML aspects, and the
Symbol class is delivered, but these can be replaced with other programming language templates.
Workflow
The workflow for using the wizard to create a new symbol consists of the following steps:
1. Identify the .NET symbol project and location.
2. Specify either a new or existing project in which to add the .NET symbol.
3. Provide a mamespace and the symbol class name. Define inputs to the symbol in the
inputs grid. For naming guidelines, see the previous section Naming of the Symbol
Definition in Writing Code for the .NET Symbol.
4. Select aspects defined by the symbol.
5. Define outputs for each aspect.
Ending the wizard with finish, the new .NET Symbol class is created in the target project.
• A new symbol definition format of the ConstructOutputs() method stub is provided for
logic to be added to create the necessary outputs for each aspect.
The field variable must be declared Public and have a defining attribute providing it with a unique index
and name. Omitting the defining attribute from the output simply ignores the declared output.
The code always checks whether the output already exists and only constructs the output when it
is Nothing. Failure to make this check results in an exception indicating that the output already
exists.
<AssemblyOutput(2, SPSSymbolConstants.Pier)> _
Public m_oPierAssemblyOutput As AssemblyOutput
.
.
Direct casting of the occurrence to the specific business object provides access to the object-
specific properties that include the inputs (supported members and supporting members in the
example above).
Dynamic Outputs
Similar to a symbol, Custom Assembly provides for dynamic outputs (i.e., outputs with a count
which may vary dynamically at runtime). An example of this might be the structural member
legs of an equipment foundation. Declaring a dynamic output can appear similar to the
following:
' Declaring a dynamic structural member leg output.
<AssemblyOutput(1, "FoundationMemberlegs”)> _
Public m_objFoundationLegs As AssemblyOutputs
The definition of a field variable of type AssemblyOutputs with same AssemblyOutput
defining attribute as present for singularly declared assembly outputs. AssemblyOutputs inherits
from List<BusinessObject>; hence, AssemblyOutputs is a collection (i.e., list) of
BusinessObjects. The code within the EvaluateAssembly method can add and subtract business
objects from this list based on the desired count:
<InputString(2, "FoundationShape", "Foundation Shape", "Rectangle")> _
Public m_sFoundationShape As InputString
<AssemblyOutput(1, "FoundationLegs")> _
Public m_objFoundationLegs As AssemblyOutputs
The Custom Assembly ProgID (in this case, assembly .dll name with custom assembly class
name and its namespace), defined in the Definition field of a bulkloaded spreadsheet, indicates a
Custom Assembly.
When this field exists in the spreadsheet for items such as equipment, footings, equipment
foundations, etc., it is expected that the Definition field should be completed with the name of a
Custom Assembly. The SymbolDefinition is left blank (i.e., this field is ignored).
Job Submission
Batch jobs can be scheduled for submission by using the scheduler form or programmatically.
3. Create a button named “SubmitJob” on the batch command form. This name can be
changed at any time by the user.
The Framework provides a method for determining whether or not IBS available. This
button can be enabled or disabled based on the IBS availability.
if (JobScheduler.IsBatchServicesAvailable)
{
// Enable submit button
}
4. Create the object of JobScheduler class to display the scheduler user form, which allows
the user to submit the batch job to the Intergraph Batch Server.
When a user selects required inputs and clicks the Submit Job button, the scheduler form
will be displayed. Call the Show method on JobScheduler to display the scheduler form.
The Show method returns an enumerator value (DialogResult_OK or
DialogResult_CANCEL) based on the user’s selection with the scheduler form. The
batch command should contain the code for whether or not to submit the job.
/// <param name="caption">Caption of the scheduler form.</param>
DialogResult dr = jobScheduler.Show(caption);
3. Create the Schedule class object in which to submit the job at any time.
JobInfo class has a Schedule property that gets or sets the timing schedule instance as the
Schedule object. This allows you to specify date, time, and interval details if the enumerated
type is not “RunNow”.
Schedule dailySchedule = new Schedule();
dailySchedule.Type = TaskScheduler.TriggerType.RunDaily;
dailySchedule.Begin = new DateTime(DateTime.Now.Year,
DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour,
DateTime.Now.Minute, DateTime.Now.Second);
dailySchedule.End = new DateTime(DateTime.Now.Year + 1,
DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour,
DateTime.Now.Minute, DateTime.Now.Second);
dailySchedule.Intervals.DaysInterval = 2;
4. Create the JobInfo class object to provide all job-related information as input for
scheduling properties that must be set before submitting the job.
JobInfo jobinfo = new JobInfo();
jobinfo.JobName = JobName;
jobinfo.QueueName = QueueName;
jobinfo.CommandProgId = CmdProgID;
jobinfo.CommandArgsInXMLFormat = cmdArgs;
jobinfo.MailAddressList = "user@intergraph.com";
jobinfo.MailNotificationFlags = JobNotificationFlagsVal;
jobinfo.TriggerType = TaskScheduler.TriggerType.RunNow;
jobinfo.CanRunAs64BitJob = false;
jobinfo.Schedule = dailySchedule;
Job Execution
Implement the IBatchMiddleCommand with Initialize and Execute methods.
• Initialize method – stores input arguments information to use for the Execute method.
For example:
public override bool Initialize(JobInfo jobInfo)
{
try
{
bool functionReturnValue = false;
}
else
{
functionReturnValue = true;
}
return functionReturnValue;
}
catch (Exception e)
{
LogBatchMessage(e.Message);
return false;
}
}
Following overridden methods are provided depending on the type of reinforcement, such as
weld or pad.
public override BranchReinforcementData
GetBranchReinforcementPadData(PipeSpec pipeSpec, PipeComponent
pipePart, PipeStock headerPipeStock, PipeStock branchPipeStock,
HeaderAndBranchInfo headerAndBranchInfo)
{
}
2. Add an entry to the ScheduleThickness codelist sheet and bulkload into the database.
4. Add data to the MaterialsData sheet and bulkload into the database.
double calculatedWallThickness = 0;
CatalogOutfittingHelper pipingServices = new
CatalogOutfittingHelper();
double minimumThickness = 0;
//retirement thickness from the thickness data rule
double retirementThickness = 0;
//uncorroded thickness
double uncorrodedThickness = 0;
//minimum calculated thickness
double minimumCalculatedThickness = 0;
//required thickness
double requiredThickness = 0;
//corrosion allowance from the Corrosion Allowance rule
double corrosionAllowence = 0;
//nominal pipe thickness
double nominalPipeThickness = 0;
//minimum wall thickness permissible under purchase specification
double minimumWallThickness = 0;
//required minimum wall thickness
double requiredMinimumWallThickness = 0;
//mill tolerance
double millToleranceVal = 0;
//pressure design thickness
double pressureDesignThickness = 0;
minCalculatedThickness = 0.0;
schedule = 0;
long ii = 0;
if (pipeSpec == null)
{
throw new CmnArgumentNullException("pipeSpec");
}
if (pipeInfo == null)
{
throw new CmnArgumentNullException("pipeInfo");
}
try
{
outsideDiameter = pipeInfo.PipeOutsideDiameter;
try
{
jointQualityFactor = pipeSpec.GetJointQualityFactor(pipeInfo.NPD,
pipeInfo.PipeStockCommodityOption);
}
catch (Exception )
{
this.ErrorStatus = new
RefDataRuleErrorStatus(RefDataRuleErrorType.E_FAIL , "Error while
calculating joint quality factor");
}
try
{
dataSet = pipingServices.GetMaterialsData(pipeSpec,
pipeInfo.MaterialCategory, pipeInfo.Temperature);
}
catch (Exception )
{
this.ErrorStatus = new
RefDataRuleErrorStatus(RefDataRuleErrorType.E_FAIL, "Error while
getting Materials data");
}
if (Utilities.GetInterpolatedSYFromMaterialsDataRule(dataSet,
pipeInfo.Temperature, ref allowableStress, ref materialCoefficient))
{
this.ErrorStatus = new
RefDataRuleErrorStatus(RefDataRuleErrorType.REFDATAMATERIALNOTFOUNDEXC
EPTION , "RefData material not found ");
}
Utilities.ConvertPressureOrStressToGageUnits(pipeInfo.Pressure, ref
gagePressure);
int count;
do
{
count = 0;
wallThicknessRangeLowEnd = Utilities.GetRecordsFromDataset(dataSet,
"WallThicknessFrom");
wallThicknessRangeHighEnd = Utilities.GetRecordsFromDataset(dataSet,
"WallThicknessTo");
if (Math.Abs(millTolerence - Utilities.DOUBLEZERO) ==
Math3d.DistanceTolerance)
{
millTolerence = Utilities.GetRecordsFromDataset(dataSet,
"MillTolerancePercentage");
millTolerence = millTolerence / 100;
if (Math.Abs(millTolerence - Utilities.DOUBLEZERO) ==
Math3d.DistanceTolerance)
{
this.ErrorStatus = new
RefDataRuleErrorStatus(RefDataRuleErrorType.E_FAIL, "Error while
calculating mill tolerance");
}
isMillTolerancePercent = true;
}
else
{
isMillTolerancePercent = false;
}
try
{
corrosionAllowence =
pipeSpec.CorrosionAllowance(pipeInfo.MaterialCategory,
pipeInfo.FluidCode);
}
catch (Exception )
{
this.ErrorStatus = new
RefDataRuleErrorStatus(RefDataRuleErrorType.E_FAIL, "Error while
calculating corrosion allowance");
if (isMillTolerancePercent)
{
minimumCalculatedThickness = (uncorrodedThickness + corrosionAllowence
+ threadThickness) / (1 - millTolerence);
}
else
{
minimumCalculatedThickness = uncorrodedThickness + corrosionAllowence
+ threadThickness + millTolerence;
}
minCalculatedThickness = minimumCalculatedThickness;
pipeportProperties.EndPreparation = pipeInfo.EndPreparation;
pipeportProperties.EndStandard = pipeInfo.EndStandard;
pipeportProperties.Npd = pipeInfo.NPD.Size;
pipeportProperties.NpdUnitType = pipeInfo.NPD.Units;
pipeportProperties.PressureRating = pipeInfo.PressureRating;
pipeportProperties.Schedule = Convert.ToInt32(schedule);
pipeportProperties.PipingPointBasis = pipeInfo.PipingPointBasis;
pipeportProperties.GeometricIndustryStd =
pipeInfo.GeometricIndustryStandard;
pipeportProperties.LiningMaterial = pipeInfo.LiningMaterial;
schedule = lPreferredSchedule[ii];
try
{
nominalPipeThickness =
pipeSpec.GetNominalPipeThickness(pipeportProperties);
}
catch (Exception )
{
this.ErrorStatus = new
RefDataRuleErrorStatus(RefDataRuleErrorType.E_FAIL, "Error while
calculating Nominal Pipe Thickness ");
}
if (isMillTolerancePercent)
{
millToleranceVal = nominalPipeThickness * millTolerence;
}
else
{
millToleranceVal = millTolerence;
}
}
catch (Exception)
{
this.ErrorStatus = new
RefDataRuleErrorStatus(RefDataRuleErrorType.E_FAIL, "Error while
calculating wall thickness");
}
finally
{
calculatedWallThickness = minimumCalculatedThickness;
return calculatedWallThickness;
}
}
GetBranchReinforcementPadData Method
/// <summary>
/// Returns the BranchReinforcementData object, which contains values
for
/// ReinforcingPadThickness and ReinforcingPadWidth.</summary>
/// <param name="pipeSpec">Pipe specification class as
PipeSpec.</param>
/// <param name="pipePart">Pipe part as PipeComponent class.</param>
/// <param name="headerPipeStock">Header pipe stock as
PipeStock.</param>
/// <param name="branchPipeStock">Branch pipe stock as PipeStock
</param>
/// <param name="headerAndBranchInfo">Contains all the properties on
header
/// and branch pipe stock.</param>
public override BranchReinforcementData
GetBranchReinforcementPadData(PipeSpec pipeSpec, PipeComponent
pipePart, PipeStock headerPipeStock, PipeStock branchPipeStock,
HeaderAndBranchInfo headerAndBranchInfo)
{
if (pipeSpec == null)
{
throw new CmnArgumentNullException("pipeSpec");
}
if (pipePart == null)
{
throw new CmnArgumentNullException("pipePart");
}
if (headerPipeStock == null)
{
throw new CmnArgumentNullException("headerPipeStock");
}
if (branchPipeStock == null)
{
throw new CmnArgumentNullException("branchPipeStock");
if (headerAndBranchInfo == null)
{
throw new CmnArgumentNullException("headerAndBranchInfo");
}
//Default message for the error if anything else happened apart from
all the mentioned error codes
string message = "Error while calculating branch reinforcement";
CatalogOutfittingHelper pipingServices = new
CatalogOutfittingHelper();
BusinessObject BO;
BO = pipeSpec;
PreferredSchedule perferredSchedule = new PreferredSchedule();
BranchReinforcementData branchreinforcementData = new
BranchReinforcementData();
double branchOutsideDiameter = 0;
int branchMaterialsGrade = 0;
int branchMaterialsCategory = 0;
int branchSchedule = 0;
int branchSchedulePractice = 0;
int branchEndPrep = 0;
int branchEndStd = 0;
int branchPressureRating = 0;
int branchTerminationClass = 0;
int branchTerminationSubClass = 0;
int branchPipingPointBasis = 0;
int branchGeoIndStd = 0;
#endregion
double t_b = 0;
#endregion
double allowableStressRatio = 0;
//pad reinforcement area
double reinfocementPadArea = 0;
//calculated reinforcing pad width
double reinforcingPadCalculatedWidth = 0;
//minimum reinforcing pad width from reinforcing pad data rule
double reinforcingPadMinimumWidth = 0;
//calculated half width of reinforcing pad
double reinforcingPadhalfWidth = 0;
//reinforcing pad thickness
double reinforcingPadThickness = 0;
//reinforcing pad width
double reinforcingPadWidth = 0;
//allowable stress in tension for the reinforcing pad
double reinforcingPadAllowableStressinTension = 0;
double Y_pad = 0;
#endregion
try
{
IPipeComponent pipeComponent;
pipeComponent = headerPipeStock;
Part part = (Part)pipeComponent;
headerOutsideDiameter = headerPipePortDef.PipingOutsideDiameter;
headerEndPrep = headerPipePortDef.EndPreparation;
headerEndStd = headerPipePortDef.EndStandard;
headerPressureRating = headerPipePortDef.PressureRating;
headerSchedule = headerPipePortDef.ScheduleThickness;
headerSchedulePractice = headerPipePortDef.SchedulePractice;
headerTerminationClass = headerPipePortDef.TerminationClass;
headerTerminationSubClass = headerPipePortDef.TerminationSubClass; ;
headerPipingPointBasis = headerPipePortDef.PipingPointBasis;
pipeComponent = branchPipeStock;
Part branchpart = (Part)pipeComponent;
ReadOnlyCollection<BusinessObject> branchports =
branchpart.PortDefinitions;
branchNPD = pipeComponent.PrimaryNPD;
branchMaterialsGrade = pipeComponent.MaterialGrade;
branchMaterialsCategory = pipeComponent.MaterialCategory;
branchGeoIndStd = pipeComponent.GeometricIndustryStandard;
branchOutsideDiameter = branchPipePortDef.PipingOutsideDiameter;
branchEndPrep = branchPipePortDef.EndPreparation;
branchEndStd = branchPipePortDef.EndStandard;
branchPressureRating = branchPipePortDef.PressureRating;
branchSchedule = branchPipePortDef.ScheduleThickness;
branchSchedulePractice = branchPipePortDef.SchedulePractice;
branchTerminationClass = branchPipePortDef.TerminationClass;
branchTerminationSubClass = branchPipePortDef.TerminationSubClass; ;
branchPipingPointBasis = branchPipePortDef.PipingPointBasis;
headerOutDiameter = headerOutsideDiameter;
//Get the header Joint Quality factor
try
{
headerJointQualityFactor = pipeSpec.GetJointQualityFactor(headerNPD,
headerAndBranchInfo.HeaderCommodityOption);
}
catch (Exception Ce)
{
throw Ce;
}
try
{
dataset = pipingServices.GetMaterialsData(pipeSpec,
headerMaterialsGrade, headerAndBranchInfo.HeaderTemperature);
}
catch (Exception)
{
throw new Utilities.RefDataInsufficientDataException(message);
}
if(dataset != null)
isInterpolatedSY =
Utilities.GetInterpolatedSYFromMaterialsDataRule(dataset,
if (isInterpolatedSY == false)
{
message = "MaterialsDataRule does NOT address the calculated header
thickness.";
throw new Utilities.RefDataInsufficientDataException(message);
}
Utilities.ConvertPressureOrStressToGageUnits(headerAndBranchInfo.Heade
rPressure, ref headerPressureGauge);
int count = 1;
do
{
headerlowThicknessRange = Utilities.GetRecordsFromDataset(dataset,
"WallThicknessFrom");
headerHighThicknessRange = Utilities.GetRecordsFromDataset(dataset,
"WallThicknessTo");
break;
}
count++;
} while (count <= dataset.Tables[0].Rows.Count);
try
{
pipeSpec.GetThicknessData(headerNPD, out headerMinThickness, out
headerRetirementThickness, out headerThreadThickness, ref
perferredSchedule);
}
catch (Exception Ce)
{
throw Ce;
}
try
{
headerCorrosionAllownace =
pipeSpec.CorrosionAllowance(headerMaterialsCategory,
headerAndBranchInfo.HeaderFluidCode);
}
catch (Exception Ce)
{
throw Ce;
}
if (headerMillTolerancePercent)
{
headerMinCalThickness = (headerUncorrodedThickness +
headerCorrosionAllownace + headerThreadThickness) / (1 -
headerMillTolerance);
}
else
{
headerMinCalThickness = headerUncorrodedThickness +
headerCorrosionAllownace + headerThreadThickness +
headerMillTolerance;
}
pipePortProperties.Schedule = headerSchedule;
pipePortProperties.EndPreparation = headerEndPrep;
pipePortProperties.EndStandard = headerEndStd;
pipePortProperties.Npd = headerNPD.Size;
pipePortProperties.NpdUnitType = headerNPD.Units;
pipePortProperties.PressureRating = headerPressureRating;
pipePortProperties.PipingPointBasis = headerPipingPointBasis;
pipePortProperties.GeometricIndustryStd = headerGeoIndStd;
pipePortProperties.LiningMaterial = 1;
try
{
headernominalPipeThickness =
pipeSpec.GetNominalPipeThickness(pipePortProperties);
}
catch (Exception Ce)
{
throw Ce;
}
if (headerMillTolerancePercent)
{
headerMillToleranceVal = headernominalPipeThickness *
headerMillTolerance;
}
else
{
headerMillToleranceVal = headerMillTolerance;
}
headerAdditionalThickness = headerMillToleranceVal +
headerCorrosionAllownace;
branchOutDiameter = branchOutsideDiameter;
try
{
branchJointQualityFactor = pipeSpec.GetJointQualityFactor(branchNPD,
headerAndBranchInfo.BranchCommodityOption);
try
{
dataset = pipingServices.GetMaterialsData(pipeSpec,
branchMaterialsGrade, headerAndBranchInfo.BranchTemperature);
}
catch (Exception)
{
throw new Utilities.RefDataInsufficientDataException(message);
}
isInterpolatedSY = false;
if (dataset != null)
isInterpolatedSY =
Utilities.GetInterpolatedSYFromMaterialsDataRule(dataset,
headerAndBranchInfo.BranchTemperature, ref branchAllowableStress, ref
branchMaterialCoefficient);
if (isInterpolatedSY == false)
{
//insufficient
message = "MaterialsDataRule does NOT address the calculated branch
thickness.";
throw new Utilities.RefDataInsufficientDataException(message);
}
Utilities.ConvertPressureOrStressToGageUnits(headerAndBranchInfo.Branc
hPressure, ref branchPressureGauge);
//////////////////////////////////////////////////////////////////////
//
int counter = 1;
do
{
branchLowThicknessRange = Utilities.GetRecordsFromDataset(dataset,
"WallThicknessFrom");
branchHighThicknessRange = Utilities.GetRecordsFromDataset(dataset,
"WallThicknessTo");
counter++;
} while (counter <= dataset.Tables[0].Rows.Count);
try
{
pipeSpec.GetThicknessData(branchNPD, out branchMinThickness, out
branchRetirementThickness, out branchThreadThickness, ref
perferredSchedule);
}
catch (Exception Ce)
{
throw Ce;
}
pipePortProperties.EndPreparation = branchEndPrep;
pipePortProperties.EndStandard = branchEndStd;
pipePortProperties.Npd = branchNPD.Size;
pipePortProperties.NpdUnitType = branchNPD.Units;
pipePortProperties.PressureRating = branchPressureRating;
pipePortProperties.Schedule = branchSchedule;
pipePortProperties.PipingPointBasis = branchPipingPointBasis;
pipePortProperties.GeometricIndustryStd = branchGeoIndStd;
pipePortProperties.LiningMaterial = 1;
try
{
branchNominalPipeThickness =
pipeSpec.GetNominalPipeThickness(pipePortProperties);
}
catch (Exception Ce)
{
throw Ce;
}
if (branchMilltolerancePercentage)
{
branchMillToleranceVal = branchNominalPipeThickness *
branchMillTolerance;
}
else
{
branchMillToleranceVal = branchMillTolerance;
}
try
{
branchCorrosionAllowance =
pipeSpec.CorrosionAllowance(branchMaterialsCategory,
headerAndBranchInfo.BranchFluidCode);
if (branchMilltolerancePercentage)
{
branchMinCalThickness = (branchUncorrodedThickness +
branchCorrosionAllowance + branchThreadThickness) / (1 -
branchMillTolerance);
}
else
{
branchMinCalThickness = branchUncorrodedThickness +
branchCorrosionAllowance + branchThreadThickness +
branchMillTolerance;
}
branchAdditionalThickness = branchMillToleranceVal +
branchCorrosionAllowance;
int reinforcingPadMaterialsGrade = 0;
int reinforcingPadMaterialsCategory = 0;
reinforcingPadMaterialsGrade = pipeComponent.MaterialGrade;
reinforcingPadMaterialsCategory = pipeComponent.MaterialCategory;
try
{
dataset = pipingServices.GetMaterialsData(pipeSpec,
reinforcingPadMaterialsGrade, headerAndBranchInfo.HeaderTemperature);
}
catch (Exception Ce)
{
throw Ce;
}
isInterpolatedSY = false;
if (dataset != null)
isInterpolatedSY =
Utilities.GetInterpolatedSYFromMaterialsDataRule(dataset,
headerAndBranchInfo.HeaderTemperature, ref
reinforcingPadAllowableStressinTension, ref Y_pad);
if (isInterpolatedSY == false)
{
//insufficient
message = "MaterialsDataRule does NOT address the reinforcing Pad";
throw new Utilities.RefDataInsufficientDataException(message);
}
if (headerAndBranchInfo.IsBranchReinforcementByUser)
{
reinforcingPadThickness =
branchreinforcementData.ReinforcingPadThickness;
}
else
{
reinforcingPadThickness = headernominalPipeThickness;
}
if (headerMillTolerancePercent)
{
minimumReinforcementThickness = reinforcingPadThickness * (1.0 -
headerMillTolerance);
}
else
{
minimumReinforcementThickness = reinforcingPadThickness -
headerMillTolerance;
}
reinforcmentAreaExcessBranch = (2.0 *
reinforcementHeightOutsidePipeRun * branchExcessThickness) /
Math.Sin(headerAndBranchInfo.BranchAngle);
branchFilletWeldDimension = coverFillerWeldThroatThickness /
Math.Sin(Math.PI / 4);
reinforcmentHeight = reinforcingPadThickness +
branchFilletWeldDimension;
halfThicknessReinforcementPad = reinforcingPadThickness;
}
else if (reinforcingPadThickness < reinforcementHeightOutsidePipeRun)
{
branchPadSizeAvaialble = reinforcementHeightOutsidePipeRun -
reinforcingPadThickness;
halfThicknessReinforcementPad = reinforcingPadThickness;
}
else
{
branchWeldReinforcementArea = 0.0;
halfThicknessReinforcementPad = reinforcementHeightOutsidePipeRun;
}
headerPadMinDimension = headerReinforcementPadFillet /
Math.Sin(Math.PI / 4);
reinforcingAreaBranchWeld = branchWeldReinforcementArea +
headerWeldReinforcmentArea;
{
headerAllowableStress = headerallowableStress;
}
allowableStressRatio = reinforcingPadAllowableStress /
headerAllowableStress;
reinforcingAreaBranchWeld = 0.0;
}
else
{
allowableStressRatio = 1.0;
}
reinfocementPadArea = (requiredReinforcementArea -
reinforcmentAreaExcessHeader - reinforcmentAreaExcessBranch -
reinforcingAreaBranchWeld) * allowableStressRatio;
if (headerMillTolerancePercent)
{
reinforcingPadCalculatedWidth = 0.5 * reinfocementPadArea /
(halfThicknessReinforcementPad * (1 - headerMillTolerance));
}
else
{
try
{
reinforcingPadMinimumWidth =
pipeSpec.GetMinimumReinforcingPadWidth(headerNPD, branchNPD,
headerAndBranchInfo.BranchAngle);
}
catch (Exception Ce)
{
throw Ce;
}
if (!headerAndBranchInfo.IsBranchReinforcementByUser)
{
if (reinforcingPadCalculatedWidth <= reinforcingPadMinimumWidth)
{
reinforcingPadhalfWidth = reinforcingPadCalculatedWidth + 0.5 *
branchOutDiameter + headerPadMinDimension;
}
}
else if (reinforcingPadCalculatedWidth > reinforcingPadMinimumWidth)
{
reinforcingPadhalfWidth = reinforcingPadCalculatedWidth + 0.5 *
branchOutDiameter + headerPadMinDimension;
else
{
//insufficient
message = "(reinforcingPadhalfWidth <= reinforcmentZoneHalfWidth) is
FALSE.";
throw new Utilities.RefDataInsufficientDataException(message);
}
}
}
else
{
reinforcingPadWidth = branchreinforcementData.ReinforcingPadWidth;
branchreinforcementData.ReinforcingPadThickness =
reinforcingPadThickness;
branchreinforcementData.ReinforcingPadWidth = reinforcingPadWidth;
}
catch (Exception Ce)
{
if (Ce is Utilities.RefDataInsufficientDataException)
{
//do nothing
}
else { throw Ce; }
}
return branchreinforcementData;
GetBranchReinforcementWeldData Method
/// <summary>
/// Returns the BranchReinforcementData object, which contains
/// ReinforcingWeldSize type data.</summary>
/// <param name="pipeSpec">Pipe specification class as
PipeSpec.</param>
/// <param name="headerPipeStock">Header pipe stock as
PipeStock.</param>
/// <param name="branchPipeStock">Branch pipe stock as
PipeStock.</param>
/// <param name="headerAndBranchInfo">Contains all the properties on
header
/// and branch pipe stock.</param>
public override BranchReinforcementData
GetBranchReinforcementWeldData(PipeSpec pipeSpec, PipeStock
headerPipeStock, PipeStock branchPipeStock, HeaderAndBranchInfo
headerAndBranchInfo)
{
if (pipeSpec == null)
{
throw new CmnArgumentNullException("pipeSpec");
}
if (headerPipeStock == null)
{
throw new CmnArgumentNullException("headerPipeStock");
}
if (branchPipeStock == null)
{
throw new CmnArgumentNullException("branchPipeStock");
}
//Default message for the error if anything else happened apart from
all the mentioned error codes
string message = "Error while calculating branch reinforcement";
CatalogOutfittingHelper pipingServices = new
CatalogOutfittingHelper();
BranchReinforcementData branchreinforcementData = new
BranchReinforcementData();
PreferredSchedule perferredSchedule = new PreferredSchedule();
double headerOutsideDiameter = 0;
int headerMaterialsGrade = 0;
int headerMaterialsCategory = 0;
int headerSchedule = 0;
int headerSchedulePractice = 0;
int headerEndPrep = 0;
int headerEndStd = 0;
int headerPressureRating = 0;
int headerTerminationClass = 0;
int headerTerminationSubClass = 0;
int headerPipingPointBasis = 0;
int headerGeoIndStd = 0;
double branchOutsideDiameter = 0;
int branchMaterialsGrade = 0;
int branchMaterialsCategory = 0;
int branchSchedule = 0;
int branchSchedulePractice = 0;
int branchEndPrep = 0;
int branchEndStd = 0;
int branchPressureRating = 0;
int branchTerminationClass = 0;
int branchTerminationSubClass = 0;
int branchPipingPointBasis = 0;
int branchGeoIndStd = 0;
#endregion
#endregion
//reinforcement calculation
//*********************************************
//branch reinforcement for reinforcing welds
#endregion
try
{
IPipeComponent pipeComponent;
pipeComponent = headerPipeStock;
headerOutsideDiameter = headerPipePortDef.PipingOutsideDiameter;
headerEndPrep = headerPipePortDef.EndPreparation;
headerEndStd = headerPipePortDef.EndStandard;
headerPressureRating = headerPipePortDef.PressureRating;
headerSchedule = headerPipePortDef.ScheduleThickness;
headerSchedulePractice = headerPipePortDef.SchedulePractice;
headerTerminationClass = headerPipePortDef.TerminationClass;
headerTerminationSubClass = headerPipePortDef.TerminationSubClass; ;
headerPipingPointBasis = headerPipePortDef.PipingPointBasis;
pipeComponent = branchPipeStock;
Part branchpart = (Part)pipeComponent;
ReadOnlyCollection<BusinessObject> branchports =
branchpart.PortDefinitions;
branchOutsideDiameter = branchPipePortDef.PipingOutsideDiameter;
branchEndPrep = branchPipePortDef.EndPreparation;
branchEndStd = branchPipePortDef.EndStandard;
branchPressureRating = branchPipePortDef.PressureRating;
branchSchedule = branchPipePortDef.ScheduleThickness;
branchSchedulePractice = branchPipePortDef.SchedulePractice;
branchTerminationClass = branchPipePortDef.TerminationClass;
branchTerminationSubClass = branchPipePortDef.TerminationSubClass; ;
branchPipingPointBasis = branchPipePortDef.PipingPointBasis;
headerOutDiameter = headerOutsideDiameter;
try
{
//Get the header Joint Quality factor
headerJointQualityFactor = pipeSpec.GetJointQualityFactor(headerNPD,
headerAndBranchInfo.HeaderCommodityOption);
}
catch (Exception Ce)
{
throw Ce;
}
try
{
dataset = pipingServices.GetMaterialsData(pipeSpec,
headerMaterialsGrade, headerAndBranchInfo.HeaderTemperature);
}
catch (Exception)
{
throw new Utilities.RefDataInsufficientDataException(message);
}
if (dataset != null)
isInterpolatedSY =
Utilities.GetInterpolatedSYFromMaterialsDataRule(dataset,
headerAndBranchInfo.HeaderTemperature, ref headerallowableStress, ref
headerMaterialCoefficient);
if (isInterpolatedSY == false)
{
//insufficient
message = "MaterialsDataRule does NOT address the calculated header
thickness.";
throw new Utilities.RefDataInsufficientDataException(message);
}
Utilities.ConvertPressureOrStressToGageUnits(headerAndBranchInfo.Heade
rPressure, ref headerPressureGauge);
int counter1 = 1;
//Add here data set calculation
do
{
headerlowThicknessRange = Utilities.GetRecordsFromDataset(dataset,
"WallThicknessFrom");
headerHighThicknessRange = Utilities.GetRecordsFromDataset(dataset,
"WallThicknessTo");
}
headerMillTolerancePercent = true;
}
else
{
headerMillTolerancePercent = false;
}
break;
}
counter1++;
} while (counter1 <= dataset.Tables[0].Rows.Count);
try
{
pipeSpec.GetThicknessData(headerNPD, out headerMinThickness, out
headerRetirementThickness, out headerThreadThickness, ref
perferredSchedule);
}
catch (Exception Ce)
{
throw Ce;
}
}
if (headerMillTolerancePercent)
{
headerMinCalThickness = (headerUncorrodedThickness +
headerCorrosionAllownace + headerThreadThickness) / (1 -
headerMillTolerance);
}
else
{
headerMinCalThickness = headerUncorrodedThickness +
headerCorrosionAllownace + headerThreadThickness +
headerMillTolerance;
}
pipePortProperties.Schedule = headerSchedule;
pipePortProperties.EndPreparation = headerEndPrep;
pipePortProperties.EndStandard = headerEndStd;
pipePortProperties.Npd = headerNPD.Size;
pipePortProperties.NpdUnitType = headerNPD.Units;
pipePortProperties.PressureRating = headerPressureRating;
pipePortProperties.PipingPointBasis = headerPipingPointBasis;
pipePortProperties.GeometricIndustryStd = headerGeoIndStd;
pipePortProperties.LiningMaterial = 1;
try
{
headernominalPipeThickness =
pipeSpec.GetNominalPipeThickness(pipePortProperties);
}
catch (Exception Ce)
{
throw Ce;
}
if (headerRequiredThickness < headernominalPipeThickness)
{
//required header thickness is sufficient for branch reinforcement
calc
}
else
{
//insufficient
if (headerMillTolerancePercent)
{
headerMillToleranceVal = headernominalPipeThickness *
headerMillTolerance;
}
else
{
headerMillToleranceVal = headerMillTolerance;
}
headerAdditionalThickness = headerMillToleranceVal +
headerCorrosionAllownace;
branchOutDiameter = branchOutsideDiameter;
try
{
branchJointQualityFactor = pipeSpec.GetJointQualityFactor(branchNPD,
headerAndBranchInfo.BranchCommodityOption);
}
catch (Exception Ce)
{
throw Ce;
}
try
{
dataset = pipingServices.GetMaterialsData(pipeSpec,
branchMaterialsGrade, headerAndBranchInfo.BranchTemperature);
}
catch (Exception Ce)
{
throw Ce;
}
isInterpolatedSY = false;
if (dataset != null)
isInterpolatedSY =
Utilities.GetInterpolatedSYFromMaterialsDataRule(dataset,
headerAndBranchInfo.BranchTemperature, ref branchAllowableStress, ref
branchMaterialCoefficient);
if (isInterpolatedSY == false)
{
//insufficient
message = "MaterialsDataRule does NOT address the calculated branch
thickness.";
throw new Utilities.RefDataInsufficientDataException(message);
}
Utilities.ConvertPressureOrStressToGageUnits(headerAndBranchInfo.Branc
hPressure, ref branchPressureGauge);
//////////////////////////////////////////////////////////////////////
//
int count1 = 1;
//Add here data set calculation
do
{
branchLowThicknessRange = Utilities.GetRecordsFromDataset(dataset,
"WallThicknessFrom");
branchHighThicknessRange = Utilities.GetRecordsFromDataset(dataset,
"WallThicknessTo");
count1++;
} while (count1 <= dataset.Tables[0].Rows.Count);
////////////do while
ends//////////////////////////////////////////////////
try
{
pipeSpec.GetThicknessData(branchNPD, out branchMinThickness, out
branchRetirementThickness, out branchThreadThickness, ref
perferredSchedule);
}
catch (Exception Ce)
{
throw Ce;
pipePortProperties.EndPreparation = branchEndPrep;
pipePortProperties.EndStandard = branchEndStd;
pipePortProperties.Npd = branchNPD.Size;
pipePortProperties.NpdUnitType = branchNPD.Units;
pipePortProperties.PressureRating = branchPressureRating;
pipePortProperties.Schedule = branchSchedule;
pipePortProperties.PipingPointBasis = branchPipingPointBasis;
pipePortProperties.GeometricIndustryStd = branchGeoIndStd;
pipePortProperties.LiningMaterial = 1;
try
{
branchNominalPipeThickness =
pipeSpec.GetNominalPipeThickness(pipePortProperties);
}
catch (Exception Ce)
{
throw Ce;
}
if (branchMilltolerancePercentage)
{
branchMillToleranceVal = branchNominalPipeThickness *
branchMillTolerance;
}
else
{
branchMillToleranceVal = branchMillTolerance;
}
try
{
branchCorrosionAllowance =
pipeSpec.CorrosionAllowance(branchMaterialsCategory,
headerAndBranchInfo.BranchFluidCode);
}
catch (Exception Ce)
{
throw Ce;
}
if (branchMilltolerancePercentage)
{
branchMinCalThickness = (branchUncorrodedThickness +
branchCorrosionAllowance + branchThreadThickness) / (1 -
branchMillTolerance);
}
else
{
branchMinCalThickness = branchUncorrodedThickness +
branchCorrosionAllowance + branchThreadThickness +
branchMillTolerance;
}
branchAdditionalThickness = branchMillToleranceVal +
branchCorrosionAllowance;
else
{
branchMinWallThickness = branchNominalPipeThickness -
branchMillToleranceVal;
branchRequiredWallThickness = branchUncorrodedThickness +
branchMillToleranceVal + branchCorrosionAllowance;
branchExcessThickness = branchNominalPipeThickness -
branchMillToleranceVal - branchCorrosionAllowance -
branchUncorrodedThickness;
branchPrseeureDesignThickness = branchUncorrodedThickness;
}
minReinforcementThickness = 0.0;
reinforcmentZoneHalfWidth = branchPrseeureDesignThickness -
branchAdditionalThickness + headerMinPermissibleWallThickness +
pipeLengthRemoved / 2.0;
if (headerAndBranchInfo.IsBranchReinforcementByUser)
{
minimumWeldSize = branchreinforcementData.ReinforcingWeldSize;
}
else
{
pipeSpec.GetReinforcingWeldSize(headerNPD, branchNPD,
headerAndBranchInfo.BranchAngle, out minimumWeldSize);
}
coverFillerWeldThroatThickness = minimumWeldSize;
branchFilletWeldDimension = coverFillerWeldThroatThickness /
Math.Sin(Math.PI / 4);
reinforcingAreaBranchWeld = branchFilletWeldDimension *
branchFilletWeldDimension;
totalReinforcementArea = reinforcmentAreaExcessHeader +
reinforcmentAreaExcessBranch + reinforcingAreaBranchWeld;
branchreinforcementData.ReinforcingWeldSize = minimumWeldSize;
}
catch (Exception Ce)
{
if (Ce is Utilities.RefDataInsufficientDataException)
{
//do nothing
}
else { throw Ce; }
}
return branchreinforcementData;
Index
.NET architecture, 14 types, 21
appendix writing graphic commands, 34
usage caveats, 93 writing modal commands, 28
creating .NET symbols, 75 writing step commands, 38
bulkloading, 79 creating naming rules, 45
bulkloading piping symbols, 79 configuration, 55
defining ports, 77 deployment, 55
definition, 78 GenericNamingRules, 55
deploying symbols, 78 name rule project, 48
placing a piping symbol, 80 NameRuleBase, 46
understanding the geometry, 76 writing a name rule, 50
creating advanced .NET symbols, 81 examples
custom foul check, 83 PartSubImpact, 63
custom mirror, 84 SAPBase, 62
custom weight and COG, 81 namespaces and assemblies, 17
dynamic outputs, 81 3D assemblies, 19
origin and orientation, 82 3D namespaces, 18
property management, 85 assemblies, 19
creating commands namespaces, 18
.NET, 20 preface, 6
.NET methods and properties, 21 references, 9
.NET project, 26 resources, 9
BaseGraphicCommand, 34 Smart 3D architecture
BaseModalCommand, 29 business objects, 10
BaseRibbonBarControl, 43 client, 10
BaseStepCommand, 38 metadata, 10
client level services, 22 middle, 10
ClientServiceProvider, 22 server, 10
command assistants, 25 Smart 3D architecture
committing transactions, 25 three-tier, 10
graphic command example, 35 stand-alone applications, 57
graphic commands, 23 available services, 57
IJCommand2 COM interface, 21 PartSubImpact example, 63
menu handler, 21 runtime and building, 63
modal commands, 23 SAPBase example, 62
modal example, 30 services not available, 58
object-based, 21 setting system PATH, 65
preventing database corruption, 25 settings for C#, 64
ribbon bars, 43 settings for VB, 65
step command example, 40 writing, 58
step commands, 24 writing .NET symbols, 78
terminating commands, 25