Brief code samples:
More...
Brief code samples:
How to define your own relationships
Also see Full Working Example
{
...
};
extern "C" int MdlMain (
int,
char**)
{
static MyHandler s_handler;
...
}
static void CreateMyRelationship ()
{
...
msElementDescr* relationshipElement = createRelationshipElement ();
Relation rel (relationshipElement, RELATIONID_MyRelationship, privateKeyPart, true);
rel.AddMoniker (NULL, PID_MyTarget, target);
}
Full Working Example
Shows how a simple Relation is defined, how its Relationship::Handler cooperates with IGraphEvaluation to build the dependency graph, and how its Handler reevaluates the relationship when called upon after the input is changed.
In this example, a dependent element is related to a datum element. The dependent is to be located at a fixed offset from the datum. When the datum element is changed, the relationship's handler is notified, and it moves the dependent element if necessary to maintain the fixed offset.
using namespace std;
#define PID_Datum (ParameterId)0
#define PID_Dependent (ParameterId)1
#define RELATIONID_TestRel 1997
#define RELATIONID_Align_MinorId 0
{
DPoint3d m_point;
PointParamData (DPoint3d const& p) : m_point(p) {;}
PointParamData () {;}
virtual void Store (Bentley::MstnPlatform::Relationship::DataOutput& sink) const override {sink.put (&m_point.x, sizeof(m_point)/sizeof(m_point.x));}
virtual StatusInt Load (Bentley::MstnPlatform::Relationship::DataInput& source)
override {source.get (&m_point.x,
sizeof(m_point)/
sizeof(m_point.x));
return SUCCESS;}
};
static DPoint3d computeOffset (Moniker&, Moniker&);
static void moveElement (Moniker&, Moniker&, DPoint3d const&);
{
virtual WString GetDescription (Relation const&) override {return L"Align Handler";}
virtual bool EnterGraph (IGraphEvaluation& g, Relation const& alignRel) override
{
ParameterQuery datum (alignRel, PID_Datum);
ParameterQuery dependent(alignRel, PID_Dependent);
if (!datum.IsTargetAvailable() || !dependent.IsTargetAvailable())
return false;
g.AddInput (alignRel, datum.GetMoniker().EvaluateElement().GetElementRef());
g.AddOutput (alignRel, dependent.GetMoniker().EvaluateElement().GetElementRef());
return false;
}
virtual void Reevaluate (IGraphEvaluation& graph, Relation& alignRel) override
{
ParameterQuery datum (alignRel, PID_Datum);
ParameterQuery dependent(alignRel, PID_Dependent);
PointParamData offsetData;
datum.GetData (offsetData);
moveElement (dependent.GetMoniker(), datum.GetMoniker(), offsetData.m_point);
}
};
static AlignHandler s_alignHandler;
{
Moniker dependentMon (DgnPlatform::PersistentSnapPath (
ACTIVEMODEL,dependentDP));
Moniker datumMoniker (DgnPlatform::PersistentSnapPath (
ACTIVEMODEL,datumDP));
Relation rel (createRelationshipElement(), RELATIONID_TestRel, RELATIONID_Align_MinorId, true);
rel.AddMoniker (NULL, PID_Datum, datumMoniker);
rel.AddMoniker (NULL, PID_Dependent, dependentMon);
DPoint3d offset = computeOffset (dependentMon, datumMoniker);
rel.AddParameterDataItem (NULL, PID_Datum, PointParamData(offset));
}
extern "C" int MdlMain (
int argc,
char **argv)
{
...
}
static DPoint3d computeOffset (Moniker& dependent, Moniker& datum)
{
DPoint3d locDependent;
dependent.EvaluatePoint (locDependent);
DPoint3d locDatum;
datum.EvaluatePoint (locDatum);
DPoint3d offset;
bsiDPoint3d_subtractDPoint3dDPoint3d (&offset, &locDependent, &locDatum);
return offset;
}
static void moveElement (Moniker& dependent, Moniker& datum, DPoint3d const& offset)
{
DPoint3d currOffset = computeOffset (dependent, datum);
DPoint3d adjust;
bsiDPoint3d_subtractDPoint3dDPoint3d (&adjust, &offset, &currOffset);
if (bsiDPoint3d_magnitude (&adjust) < 1.0e-15)
return;
Transform trans;
bsiTransform_initIdentity (&trans);
bsiTransform_setTranslation (&trans, &adjust);
DgnPlatform::EditElementHandle ehDependent = dependent.EvaluateElement ();
ehDependent.ReplaceInModel ();
}
Resolving Cycles
The first step in resolving cycles is to detect them by writing an event handler that:
- recognizes a cycle that it can handle
- removes the relations in that cycle from the graph and substitutes a temporary block relation
Note that resolving a cycle in this way involves making a temporary change to the run-time graph. It does not require the persistent relationship elements to be modified. The temporary block relation can be created on the fly. In the following example, we call CreateTemporaryRelationshipElement to create the block relation.
In the following example, the logic for recognizing the cycle has been left out. It's up to you to know how to recognize the cycles that you can handle.
struct MyEventhandler : IGraphManagerEventHandler
{
...
virtual void OnCyclesDetected (IGraphEvaluation& graph) override
{
for (RefRefPairVectorVector::const_iterator i = graph.GetCycles().begin(); i != graph.GetCycles().end(); ++i)
{
if (aCycle is one of mine)
{
ElementRefSet relsInCycle;
for (T_RefRefPairVector::const_iterator iCycleMember = aCycle.begin(); iCycleMember != aCycle.end(); ++iCycleMember)
relsInCycle.insert (iCycleMember->first);
DgnPlatform::EditElementHandle eh;
if (graph.CreateTemporaryRelationshipElement (eh, MYCYCLERESOLVERMAJORID, MYCYCLERESOLVERMINORID))
return;
Relation cycleResolver (eh.GetElementRef(), eh.GetModelRef());
graph.RemoveRelationshipsAndSubstituteBlock (cycleResolver, relsInCycle);
graph.TopologyChanged ();
return;
}
}
}
The next step is to write an IHandler for the relation that stands in for the cycle. This IHandler will probably use the relations that were replaced. Here is an example where the block relation just iterates over the original relations. That's just one way to resolve a cycle.
struct CycleResolver : public Bentley::Relationship::HandlerBase
{
virtual void Reevaluate (IGraphEvaluation& graph, Relation& rel) override
{
ElementRefSet rels = graph.GetRelationshipsRemovedByBlockSubstitute (rel);
int niters = 0;
do {
for (ElementRefSet::const_iterator i= rels.begin(); i != rels.end(); ++i)
{
Relation irel (*i, NULL);
g_rmanager->GetHandler(irel)->Reevaluate (graph, irel);
}
}
while (++niters < 10 && !IsSatisfied (&graph, rel));
}
...
};
Here is how to register the event handler and the block relationship handler in your main function:
void cycleResolver_main ()
{
static CycleResolver s_handler;
static CycleResolverEventhandler s_eventHandler;
}
Is element a member of your relation? Forward search.
{
Relation depRel (depRef, NULL);
if (!depRel.IsOpenOnElement ())
continue;
if (depRel.GetHandlerPublicIdPart() == RELATIONID_XYZ
&& depRel.GetHandlerPrivateIdPart() == YOUR_MINOR_ID)
{
}
}
Is element a member of your relation? Backward search.
Build the graph, starting from a given element:
GraphEvaluationPtr graph = mdlSystem_getMstnApp()->GetRelationshipManager().WhatIfChanged (anElement));
And then check to see what it's connected to:
graph->GetElementConnections (&inputTo, &outputFrom, anElement);
for (ElementRefVector::const_iterator i = inputTo.begin(); i != inputTo.end(); ++i)
{
Relation rel (*i);
if (rel.GetHandlerPublicIdPart() == RELATIONID_XYZ
&& rel.GetHandlerPrivateIdPart() == YOUR_MINOR_ID)
{
}
}
This is more expensive than the brute force method above and is pointless if you have a simple, explicit set of relationships. It is useful if you have a directed graph and/or if the structure of your graph is not known until your Relation's EnterGraph method is called. For directed graphs, WhatIfChanged does a topological sort.
Adding Monikers To Temporary Relation
Can I add monikers to my temporary relation?
Yes, but note that relationship manager does not require me to do this. In fact, a temporary relation does not have to have any monikers at all. If a temporary relation does have monikers, they will play no role in how it is linked into the graph. IGraphEvaluation::RemoveRelationshipsAndSubstituteBlock sets up the input and output edges of the temporary relation based solely on the inputs and outputs of the relations that it replaces. Nevertheless, I might want to add monikers to my temporary relationship for my own private purposes. For example, I might use monikers to tell my handler code something about how to evaluate the cycle. In any case, suppose relation R has a moniker M that points to element E. Suppose I want to copy M from R to another relation R'. Moniker M contains a path that must start in the model of R. If R' is not in the same model as R, then it would be incorrect to add M to R', since the starting point would be wrong for the path in M. Now, a temporary relation is in a special temporary model. When copy a moniker from a real relation to a temporary relation, then I run into this problem of moving the starting point for the moniker. I can work around this problem by manipulating the home model ref of the moniker as I copy and then access it.
Here is an example.
Relation rel1 (firstRelInCycle, NULL);
Moniker const& mon = rel1.BeginMonikers().GetMoniker ();
Moniker monWithFakeHomeModelRef (mon);
monWithFakeHomeModelRef.SetHomeModelRef (eh.GetModelRef());
tempRel.AddMoniker (NULL, (
ParameterId)0, monWithFakeHomeModelRef);
tempRel.GetElemHandle().ReplaceInModel ();
Later on ...
Moniker mon = monQuery.GetMoniker ();
Using Relationship::Expression to parse and evaluate C expressions
The Expression class can be used to parse and evaluate a C expression. The expression can contain references to GlobalVariables.
Lifecycle
The Expression class allows you to parse a C expression once and then reevaluate it many times after that. You create an Expression object, ask it to parse the C expression, and then you store it. When you want to evalute the expression, you load the Expression object and call its Evaluate method. You don't have to store and load an expression object in between parsing and evaluating.
Store/Load To store an Expression objectm, call its Store method. That will write out a stream of bytes. It is up to you to save that stream of bytes somewhere and to remember its length. To re-creat an Expression object, call its Load method, passing the stream of bytes that you saved. After calling Load, the expression ready to evaluate.
EnterGraph To add the expressions inputs to the graph, the EnterGraph method of your relationship class should call the Expression's EnterGraph method.
Monikers To add the expressions inputs as monikers to your relationship element, you should call the addMonikers function shown below.
Parsing
To parse an expression, call one of the Expression::Parse
methods. For simple expressions that contain no assignments, call ParseNoAssignmentsCExpression.
The Expression parsing methods return an error object if there is a syntax error or if the expression refers to a GV that is not found in the dictionary.
An expression follows C syntax. The usual arithmetic operators are supported. A number of built-in functions such as sin, cos, and tan are also supported. See RelationshipExpressionToken.h for the list of built-in functions. The keywords PI, true, and false are built in as well. There are also a number of extensions built into the Expression evaluator that go beyond C expressions, as described below.
Quantities have units. Distances are stored in UORs and angles are stored in radians. The user may enter these values in master units and degrees, as long as the units are specified. In many cases, the expression parser can recognize the units. For example, if the user inputs 1:10 the parser will recognize this as MU:SU and will convert to UORs. Or, if the user enters 90 deg, the parse will recognize this as degrees and will convert to radians. The following units keywords are recognized: deg, mu, musq (master units squared), mucu (master units cubed). When calling the Parse methods, you can specify the default units to apply when the user does not specify units.
The parser checks for units mismatches. For example, its illegal to cannot add a distance (mu) to an angle (deg).
An expression can contain references to GlobalVariables. When parsing, you must specify the GlobalVariableDictionary that you used to create the GVs.
Evaluating
Call the Expression::Evaluate method to get the value of an expression. Evaluate returns an OperandToken object. You can interrogate this object to find out what type it is. You can then extract its primitive (or extended) value.
using namespace Bentley::MstnPlatform::Relationship;
Expression expression (NULL, getTargetModel(), getTargetDictionary());
if (parseRvalueExpression (exprssion, inputString, defUnits,
true) !=
SUCCESS)
DataOutput sink;
expression.store (sink);
add sink.getBuf() to your relationship element
ParameterID nextParameterId = ??
addMonikerFromExpression (apModelingRelation, expression, getTargetModel(),getTargetDictionary(), nextParameterId);
------------
DataInput source (bytes, nBytes);
Expression expression (NULL);
if (expression.Load (source) !=
SUCCESS)
RefCountedPtr<OperandToken>* resultSmartPtr
if (evaluateRvalueExpression (resultSmartPtr, expression,
true) !=
SUCCESS)
RefCountedPtr<DoubleValue> dbl ( tokenOperand_cast<DoubleValue> (NULL, resultSmartPtr.get()), false );
if (dbl == NULL)
dbl->m_value is a double
(
Expression& expression,
bool popupOK
)
{
terr = expression.ParseNoAssignmentsCExpression (wunp,
VISIBILITY_CALCULATOR, g_mdlDesc, defUnits, NULL);
if (terr.IsError ())
{
}
}
(
RefCountedPtr<OperandToken>* resultSmartPtr,
Expression& expression,
bool popupOK
)
{
terr = expression.Evaluate (&result);
if (terr.IsError ())
{
}
RefCountedPtr<OperandToken> rvalue ( result->
GetValue (&terr), false );
if (rvalue == NULL)
{
}
*resultSmartPtr = rvalue;
}
{
if (<we are editing a cell>)
return s_targetCell.GetModelRef();
}
SemiPrivate DgnPlatform::ElementHandle getTargetDictionary ()
{
GlobalVariableDictionary gvd;
if ((<we are editing a cell>)
gvd.OpenCellDictionary (s_targetCell);
else
return gvd.GetDictionaryElement();
}
{
expr.GetAllElementRefs (&targets, NULL);
for (ElementRefSet::const_iterator iRef = targets.begin (); iRef != targets.end (); ++iRef)
{
Moniker mon (model, DgnPlatform::PersistentElementPath (model,ref));
rec.AddMoniker (NULL, nextMonikerId, mon);
++nextMonikerId;
}
Moniker mon (model, DgnPlatform::PersistentElementPath (model, dict->GetDictionaryElement().GetElementRef()));
++nextMonikerId;
}
Extended Types
An expression can evaluate simple C expressions involving arithmetic operators and built-in functions such as sin, cos, and tan. Expression syntax also supports a number of extended type, including points, vectors, rotation matrices, strings, and arrays. See RelationshipExpressionToken.h for the set of supported types. There are a number of builtin functions to create and manipulate these types. For example, Vector3d creates a vector value, and VectorDotProduct computes the dot product of two vectors.
A GV can hold a primitve C type, such as a double, or it can hold an extended type, such as Point, Vector, etc.
The main purpose of these extended types is to allow you to work with element properties.
Using Expression and GVs together, the user can do something like this:
GVs
a = Vector3d (1, 0, 0)
b = Point3d (0, 0, 0)
Note: Use Expression to parse and evaluate the above (constant) expressions when creating the GVs!
Expression
PointAddVector (b, VectorScale (a, 10.0))
The expression above scales the vector a by 10 and then adds it to the point b. The value of the expression is a new point.