[ORD CONNECT C#] Looking for a managed API over the native Contraint3d

I am working on a C# application to be run inside OpenRoads Designer that creates a variety of 3D items (cells, cylinders, blocks, etc.) and would like to programatically create 3D constraints between these items and items that already exist in the 3D model.  The MicroStation SDK includes a good example for how to do this with 2D models using native C++.  Is there a managed API over Contstraint3d so that I can more readily code this using C#?  Also is it possible to query for existing constraints using an ECQuery?

Parents
  • Hi John,

    The MicroStation SDK includes a good example for how to do this with 2D models using native C++.  Is there a managed API over Contstraint3d

    It's a big jump in your formulation: Based on existing 2D C++ examples you ask about NET 3D examples ;-)

    so that I can more readily code this using C#?

    No. General recommendation for any 3D code is "use C++". As Jon wrote, 3D modeling, including parametric and constrains, is pretty new, and when opened for 3rd party developers, it happens firstly in C++ API (and NET follows, but not always, with unspecified delay).

    3D constraint API is available in C++, it's not available in NET. But you can write your own wrappers or to implement the logic in C++ and to call it from C# code.

    Also is it possible

    Please, respect this forum rules! Do not ask more different questions in one post!

    My assumption is: Yes, it would be possible. My question is: Why do you not try it yourself? The code receive relationship is not so complicated.

    With regards,

      Jan

  • I started looking into creating my own C# wrapper over the native Constraint3d.  However I see that the Constraint3d API is significantly different from the Constraint2d API, so the delivered example constraint2ddemo is not very helpful in this regard.  I didn't find any examples or documentation that describes how to use the Constraint3d API.  Can you point me to either?  I also see from the comments in Constraint3dCoreApi.h that this feature likely developed back in 2016 so the API should be fairly mature by now.

  • I also see from the comments in Constraint3dCoreApi.h that this feature likely developed back in 2016 so the API should be fairly mature by now

    Well, that's an optimistic view.

    From class declarations like this, I suspect that things continue to evolve...

    struct FixedElementId : GeometryId           // NO LONGER CREATED -- For beta compatibility only.

    There's also, by the way, Constraint3dElementApi.h. What does that do?

     
    Regards, Jon Summers
    LA Solutions

  • That header file defines Constraint3dElement which looks promising.  But without any documentation its hard to know what to do with it.  I see functions there for reporting on existing constraints but nothing that says to me "add a constraint of a given type to an element."  Perhaps the function SaveConstraints might be the one to use?  

  • Hi ,

    I have asked development to review this thread and see if we can provide some additional information (documentation and example) for your questions and to be added to a future SDK release. If enhancements are generated I will update this response with them accordingly.

    Thank you,
    Bob



  • Hey Bob,

    Thanks!  Much appreciated.

    --John M.

  • ConstraintsAPI_3d.pptx

    here is a presentation which introduce 3d constraint in details and with some example

  • here is a presentation which introduce 3d constraint in details and with some example

    Excellent! 

     
    Regards, Jon Summers
    LA Solutions

  • The PowerPoint defines a function SaveConstraintsToHost() that does not compile on the following line:

    hostEh.GetDisplayHandler()->ValidateElementRange(hostEh, true);

    ...because the function ValidateElementRange is not defined on the struct DisplayHandler.

  • you are correct. this function is internal but not a published SDK API. can you remove that line and build the example again to see if it still works?

  • Hello Dorlig,

    Using your example from the PowerPoint I created a static helper class Constraint3dUtility.h.  See code, below.

    I was attempting to create a 3d concentric constraint between two cylinders by simply changing Constraint3dType::DCM3_PERPENDICULAR to Constraint3dType::DCM3_CONCENTRIC.  Of course I should have realized that wouldn't work.  I did get the constraint added to both of my cylinders but it wasn't usable.  I'm guessing the code below does not correctly select the required element surface (as we observe in the MicroStation command?)

    -- John

    #pragma once
    
    #include <windows.h>
    
    #include <Mstn\MdlApi\mssystem.fdf>
    #include <PSolid\PSolidCoreAPI.h>
    #include <Mstn\Constraint3dElement\Constraint3dElementAPI.h>
    
    USING_NAMESPACE_BENTLEY;
    USING_NAMESPACE_BENTLEY_MSTNPLATFORM;
    USING_NAMESPACE_BENTLEY_MSTNPLATFORM_ELEMENT;
    USING_NAMESPACE_BENTLEY_DGNPLATFORM;
    USING_NAMESPACE_CONSTRAINT3D
    
    class Constraint3dUtility
    {
    public:
    	static void ApplyPerpendicularConstraint(ElementId elmId1, ElementId elmId2)
    	{
    		ApplyConstraint(elmId1, elmId2, Constraint3dType::DCM3_PERPENDICULAR);
    	}
    
    	static void ApplyConcentricConstraint(ElementId elmId1, ElementId elmId2)
    	{
    		ApplyConstraint(elmId1, elmId2, Constraint3dType::DCM3_CONCENTRIC);
    	}
    
    	static void ApplyConstraint(ElementId elmId1, ElementId elmId2, Constraint3dType constraintType)
    	{
    		// Specify geometries to constrain
    		bvector<GeometryIdPtr> geomsToConstrain;
    
    		ElementHandle eh1(elmId1, mdlModelRef_getActive());
    		ISolidKernelEntityPtr solid1;
    		SolidUtil::Convert::ElementToBody(solid1, eh1, true, false, false);
    		bvector<ISubEntityPtr> subFacesOfSolid1;
    		SolidUtil::GetBodyFaces(&subFacesOfSolid1, *solid1);
    		geomsToConstrain.push_back(FaceGeometryId::Create(eh1, *subFacesOfSolid1[0]));
    
    		ElementHandle eh2(elmId2, mdlModelRef_getActive());
    		ISolidKernelEntityPtr solid2;
    		SolidUtil::Convert::ElementToBody(solid2, eh2, true, false, false);
    		bvector<ISubEntityPtr> subFacesOfSolid2;
    		SolidUtil::GetBodyFaces(&subFacesOfSolid2, *solid2);
    		geomsToConstrain.push_back(FaceGeometryId::Create(eh2, *subFacesOfSolid2[0]));
    
    		// Create  constraint
    		Constraint3dPtr newConstraint = Constraint3dBase::Create(constraintType);
    		newConstraint->SetGeometryIds(geomsToConstrain);
    
    		// Get existing constraint and add new constraint
    		bset<ElementRefP> elementRefs;
    		for (auto const& geometryId : geomsToConstrain)
    			elementRefs.insert(geometryId->GetElementRef());
    
    		bvector <Constraint3dPtr> existingConstraints;
    		bset<ElementRefP> hosts;
    		Constraint3dElement::GetExistingConstraints(hosts, existingConstraints, elementRefs, mdlModelRef_getActive(), false);
    
    		bvector <Constraint3dPtr>  allConstraints = existingConstraints;
    		allConstraints.push_back(newConstraint);
    
    		// Evaluate the constraint and get result transform map
    		Constraint3dSolver constraintSolver;
    		constraintSolver.Initialize(allConstraints, *mdlModelRef_getActive());
    
    		bmap <ElementRefP, Transform> transformMap;
    		constraintSolver.Evaluate(transformMap);
    
    		// Update the positions of the constrained element and save the constraints
    		for (auto const &curr : transformMap)
    		{
    			EditElementHandle eeh(curr.first, mdlModelRef_getActive());
    			if (SUCCESS == eeh.GetHandler().ApplyTransform(eeh, TransformInfo(curr.second)))
    				eeh.ReplaceInModel(curr.first);
    		}
    
    		SaveConstraintsToHost(hosts, mdlModelRef_getActive(), allConstraints);
    	}
    
    	static BentleyStatus SaveConstraintsToHost(T_StdElementRefSet& hosts, DgnModelRefP modelRef, T_Constraints& constraints)
    	{
    		EditElementHandle hostEh;
    
    		if (hosts.empty())
    			Constraint3dElement::Create(hostEh, *modelRef);
    		else
    		{
    			for (auto const& curr : hosts)
    			{
    				if (NULL == hostEh.GetElementRef())
    					hostEh.SetElementRef(curr, modelRef);
    				else
    				{
    					EditElementHandle eeh(curr, modelRef);
    					eeh.DeleteFromModel();
    				}
    			}
    		}
    
    		ElementRefP hostElementRef = hostEh.GetElementRef();
    		Constraint3dElement::SaveConstraints(hostEh, constraints);
    
    		if (NULL == hostElementRef)
    			hostEh.AddToModel();
    		else
    			hostEh.ReplaceInModel(hostElementRef);
    
    		return SUCCESS;
    	}
    };

Reply
  • Hello Dorlig,

    Using your example from the PowerPoint I created a static helper class Constraint3dUtility.h.  See code, below.

    I was attempting to create a 3d concentric constraint between two cylinders by simply changing Constraint3dType::DCM3_PERPENDICULAR to Constraint3dType::DCM3_CONCENTRIC.  Of course I should have realized that wouldn't work.  I did get the constraint added to both of my cylinders but it wasn't usable.  I'm guessing the code below does not correctly select the required element surface (as we observe in the MicroStation command?)

    -- John

    #pragma once
    
    #include <windows.h>
    
    #include <Mstn\MdlApi\mssystem.fdf>
    #include <PSolid\PSolidCoreAPI.h>
    #include <Mstn\Constraint3dElement\Constraint3dElementAPI.h>
    
    USING_NAMESPACE_BENTLEY;
    USING_NAMESPACE_BENTLEY_MSTNPLATFORM;
    USING_NAMESPACE_BENTLEY_MSTNPLATFORM_ELEMENT;
    USING_NAMESPACE_BENTLEY_DGNPLATFORM;
    USING_NAMESPACE_CONSTRAINT3D
    
    class Constraint3dUtility
    {
    public:
    	static void ApplyPerpendicularConstraint(ElementId elmId1, ElementId elmId2)
    	{
    		ApplyConstraint(elmId1, elmId2, Constraint3dType::DCM3_PERPENDICULAR);
    	}
    
    	static void ApplyConcentricConstraint(ElementId elmId1, ElementId elmId2)
    	{
    		ApplyConstraint(elmId1, elmId2, Constraint3dType::DCM3_CONCENTRIC);
    	}
    
    	static void ApplyConstraint(ElementId elmId1, ElementId elmId2, Constraint3dType constraintType)
    	{
    		// Specify geometries to constrain
    		bvector<GeometryIdPtr> geomsToConstrain;
    
    		ElementHandle eh1(elmId1, mdlModelRef_getActive());
    		ISolidKernelEntityPtr solid1;
    		SolidUtil::Convert::ElementToBody(solid1, eh1, true, false, false);
    		bvector<ISubEntityPtr> subFacesOfSolid1;
    		SolidUtil::GetBodyFaces(&subFacesOfSolid1, *solid1);
    		geomsToConstrain.push_back(FaceGeometryId::Create(eh1, *subFacesOfSolid1[0]));
    
    		ElementHandle eh2(elmId2, mdlModelRef_getActive());
    		ISolidKernelEntityPtr solid2;
    		SolidUtil::Convert::ElementToBody(solid2, eh2, true, false, false);
    		bvector<ISubEntityPtr> subFacesOfSolid2;
    		SolidUtil::GetBodyFaces(&subFacesOfSolid2, *solid2);
    		geomsToConstrain.push_back(FaceGeometryId::Create(eh2, *subFacesOfSolid2[0]));
    
    		// Create  constraint
    		Constraint3dPtr newConstraint = Constraint3dBase::Create(constraintType);
    		newConstraint->SetGeometryIds(geomsToConstrain);
    
    		// Get existing constraint and add new constraint
    		bset<ElementRefP> elementRefs;
    		for (auto const& geometryId : geomsToConstrain)
    			elementRefs.insert(geometryId->GetElementRef());
    
    		bvector <Constraint3dPtr> existingConstraints;
    		bset<ElementRefP> hosts;
    		Constraint3dElement::GetExistingConstraints(hosts, existingConstraints, elementRefs, mdlModelRef_getActive(), false);
    
    		bvector <Constraint3dPtr>  allConstraints = existingConstraints;
    		allConstraints.push_back(newConstraint);
    
    		// Evaluate the constraint and get result transform map
    		Constraint3dSolver constraintSolver;
    		constraintSolver.Initialize(allConstraints, *mdlModelRef_getActive());
    
    		bmap <ElementRefP, Transform> transformMap;
    		constraintSolver.Evaluate(transformMap);
    
    		// Update the positions of the constrained element and save the constraints
    		for (auto const &curr : transformMap)
    		{
    			EditElementHandle eeh(curr.first, mdlModelRef_getActive());
    			if (SUCCESS == eeh.GetHandler().ApplyTransform(eeh, TransformInfo(curr.second)))
    				eeh.ReplaceInModel(curr.first);
    		}
    
    		SaveConstraintsToHost(hosts, mdlModelRef_getActive(), allConstraints);
    	}
    
    	static BentleyStatus SaveConstraintsToHost(T_StdElementRefSet& hosts, DgnModelRefP modelRef, T_Constraints& constraints)
    	{
    		EditElementHandle hostEh;
    
    		if (hosts.empty())
    			Constraint3dElement::Create(hostEh, *modelRef);
    		else
    		{
    			for (auto const& curr : hosts)
    			{
    				if (NULL == hostEh.GetElementRef())
    					hostEh.SetElementRef(curr, modelRef);
    				else
    				{
    					EditElementHandle eeh(curr, modelRef);
    					eeh.DeleteFromModel();
    				}
    			}
    		}
    
    		ElementRefP hostElementRef = hostEh.GetElementRef();
    		Constraint3dElement::SaveConstraints(hostEh, constraints);
    
    		if (NULL == hostElementRef)
    			hostEh.AddToModel();
    		else
    			hostEh.ReplaceInModel(hostElementRef);
    
    		return SUCCESS;
    	}
    };

Children
  • the concentric constraint must be added to circular sub entities, such as circular edge and circular face. the problem here is that the example which only want to illustrate constraint topic hard-code the face to be constrained as the first face of a solid. for a cylinder, the first face may not be the circular face, it may be the top or bottom face.  solid utility or solid API is another topic I am not very familiar. @Robert Hook, would you explain how to select a circular face of a cylinder? or add another developer who is expert of solid API?

  • By a process of trial-and-error I was able to guess that the correct sub entity to use to create a 3rd concentric constraint between two cylinders was the third face, that is: subFacesOfSolid1[2] and subFacesOfSolid2[2].

    It looks like programatically creating the other 3d constraints will be a bit challenging for now.  My application is based on DgnElementSetTool which works with items of type Bentley.DgnPlatformNET.Elements.Element.  So to create the constraints I need to correctly get at the appropriate 3d sub entities.   I'm sure this will be much easier for folks once the SDK starts delivering 3d constraint examples.

  • By a process of trial-and-error

    I'm impressed by your diligence!  In my experience, trial-and-error has long been part and parcel of developing for MicroStation.

    Posting intelligent questions along with pragmatic use of the API helps (a) Bentley Systems to understand that people want to understand their technology and (b) developers to acknowledge that examples are required to supplement terse help documentation.

     
    Regards, Jon Summers
    LA Solutions