question

Hao Zhou avatar image
1 Like"
Hao Zhou asked alan.zhang commented

How to create FlexScript classes that incorporate dot syntax in module SDK

In 17.1 beta, I noticed that FlexSim creates FlexScript classes in AGV and Conveyor modules. Is it possible to provide documents or tutorials to show how to create custom FlexScript classes incorporating dot syntax for module developers?

Thanks, Hao

FlexSim 17.0.3
module sdk
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.

1 Answer

Jordan Johnson avatar image
9 Likes"
Jordan Johnson answered alan.zhang commented

That is absolutely the plan. We'll make much better documentation in the future. For now, allow me to get you started with some basics.

FlexScript Interfaces

Each FlexScript interface is defined by a special object in the engine. That object stores the list of methods, operators, and properties for the interface. To add a new interface, all we have to do is create a new interface object, and make sure it goes in the list that the compiler and code editor read.

In order to create a new interface from a module, all you have to do is override a method called bindInterface(), and then let the engine know that it needs to call bindInterface() for that object. These two steps are slightly different for ObjectDataType objects and SimpleDataType objects, so I'll cover both. There is also a way to make an interface that wraps a pointer (like the Table interface). I'll explain a little about that as well. In all three cases, you will need to implement/overload a bindInterface() method. I'll discuss how to do that last.

ObjectDataType

In the C++ class definition, be sure to add the following method:

virtual void bindInterface() override;

You will also need to implement that method, and that will be discussed later. To make sure the engine calls your object's bindInterface() method, you need to add a node to the object's tree definition. This node should be a subnode of behavior/eventfunctions. Its name must be flexScriptInterface, and it must have text data. The text data contains the FlexScript name of the class. The engine searches the library for classes with this node, and calls their bindInterface() method.

SimpleDataType

Like the ObjectDataType, add the following method to your class's C++ definition:

virtual void bindInterface() override;

We'll talk about implementation later. Now we need to tell the engine that it should make an interface object for this class. The engine looks at the node MAIN:/project/exec/globals/sdtinterfaces for interfaces to create. Your module just needs to add a node to this list with the following structure:

  • The name of the node is the getClassFactory() name of your class.
  • The text of the node is the interface name.
  • There is one subnode:
    • The name of the subnode is isCoupling.
    • The number value of the subnode reflects whether your class inherits SimpleDataType (0) or CouplingDataType (1)

Pointer-Based Helper Classes

There are some classes which are not based on an SDT or an ODT, like the Conveyor.SlugBuilder class. This interface is defined by the following object:

  1. class SlugBuilderProperty
  2. {
  3. protected:
  4. TreeNode* conveyorNode = nullptr;
  5. const SlugBuilder& getSlugBuilder();
  6. public:
  7. // methods and properties
  8. // ...
  9. static void bindInterface();
  10. }

The important thing to remember is that this class has a static void method called bindInterface(), which takes no parameters. If you class has that, you can make a FlexScript interface for that class.

To get the engine to create the FlexScript interface object, you have to call the bindClass() method within another object's bindInterface() method. The Conveyor's bindInterface() method contains this code:

bindClassByName<SlugBuilderProperty>("Conveyor.SlugBuilder");

This method tells the compiler to create a FlexScript class called Conveyor.SlugBuilder, and to give it the interface defined by the C++ SlugBuilderProperty class. But more on that in the next section.

bindInterface()

Here's the bindInterface() code from the Conveyor class:

  1. void Conveyor::bindInterface()
  2. {
  3. bindParentClass("Object");
  4. bindDocumentationXMLPath("modules\\Conveyor\\help\\FlexScriptAPI\\Conveyor.xml");
  5. bindTypedPropertyByName<Motor>("motor", "Object", force_cast<void*>(&Conveyor::__getMotor), nullptr);
  6. bindMethod(estimateConveyTime, Conveyor, "double estimateConveyTime(Object origin, double n_a, Object dest, double n_a, double itemLength, int flags = 0)", BIND_METHOD_STATIC);
  7. bindMethod(sendItem, Conveyor, "void sendItem(Object item, Object dest)", BIND_METHOD_STATIC);
  8. bindTypedProperty(targetSpeed, double, &Conveyor::__getTargetSpeed, &Conveyor::__setTargetSpeed);
  9. bindTypedProperty(currentSpeed, double, &Conveyor::__getCurrentSpeed, nullptr);
  10. bindTypedProperty(defaultSpeed, double, &Conveyor::__getDefaultSpeed, nullptr);
  11. bindTypedProperty(acceleration, double, &Conveyor::__getAcceleration, nullptr);
  12. bindTypedProperty(deceleration, double, &Conveyor::__getDeceleration, nullptr);
  13. bindTypedProperty(width, double, &Conveyor::__getWidth, nullptr);
  14. bindTypedProperty(length, double, &Conveyor::__getSimLength, nullptr);
  15. bindNodeListArrayClassByName<ConveyorItems>("Conveyor.Items", "Conveyor.Item", true);
  16. bindTypedPropertyByName<ConveyorItems>("itemData", "Conveyor.Items", force_cast<void*>(&Conveyor::__getItemData), nullptr);
  17. bindClassByName<SlugBuilderProperty>("Conveyor.SlugBuilder");
  18. bindTypedPropertyByName<SlugBuilderProperty>("slugBuilder", "Conveyor.SlugBuilder", force_cast<void*>(&Conveyor::__getSlugBuilder), nullptr);
  19. }

Here's a brief description of the bind calls in this code:

  • bindParentClass - This is how FlexScript interfaces can inherit other FlexScript classes. This give the Conveyor class access to Object methods and properties.
  • bindDocumentationXMLPath - This connects a FlexScript interface to specific help documentation.
  • bindTypedPropertyByName - This method adds a property to the interface. You provide the property name, the FlexScript name of the type it returns, and pointers to a getter (and, optionally, a setter) method. For simple properties, you can use the bindTypedProperty macro.
  • bindMethodByName - This method adds a method to the interface. Unfortunately, intellisense almost always gives this a syntax error, but it still builds. If you pass the static flag, be sure that the method you pass is in fact a static method.
    • The method signature should use only type names available in FlexScript. If the engine can't parse this expression, FlexSim will crash on start up.
  • bindNodeListArrayClassByName - This method creates a new class interface, based on a node list array. If you have a custom NodeListArray class, you can use this method to create an interface for this class.
  • bindClassByName - This method creates a new class interface, based on an arbitrary class. That class must have the static void bindInterface() method.
    • By default, classes bound with this method (and bindNodeListArrayByName) cannot be declared. You can only create a local variable for these classes using the var type. You can pass a flag that makes the type user accessible, meaning you can delcare a local variable of that type directly.

Here's the code for the Conveyor.SlugBuilder's bindInterface:

  1. void Conveyor::SlugBuilderProperty::bindInterface()
  2. {
  3. XS;
  4. bindCopyConstructor(&SlugBuilderProperty::construct);
  5. bindCopyAssigner(&SlugBuilderProperty::operator=);
  6. bindTypedProperty(conveyor, Conveyor, &SlugBuilderProperty::__getConveyor, nullptr);
  7. bindTypedProperty(isEnabled, int, &SlugBuilderProperty::__getEnabled, &SlugBuilderProperty::__setEnabled);
  8. bindTypedProperty(isClear, int, &SlugBuilderProperty::__getIsClear, nullptr);
  9. bindTypedProperty(state, int, &SlugBuilderProperty::__getState, nullptr);
  10. bindMethodByName<decltype(&SlugBuilderProperty::makeReady)>("makeReady", &SlugBuilderProperty::makeReady, "void makeReady()");
  11. bindMethodByName<decltype(&SlugBuilderProperty::release)>("release", &SlugBuilderProperty::release, "void release()");
  12. // bindMethodByName<decltype(&SlugBuilderProperty::addItem)>("addItem", &SlugBuilderProperty::addItem, "void addItem(Object item)");
  13. bindDocumentationXMLPath("modules\\Conveyor\\help\\FlexScriptAPI\\Conveyor.SlugBuilder.xml");
  14. XE;
  15. }

Here's a description of the new bind calls in this code:

  • bindCopyConstructor - This is only needed for non-SDT/ODT classes. It allows you to copy the pointers you need from one instance of the class to the other (conveyorNode = other.conveyorNode).
  • bindCopyAssigner - This is only needed for non-SDT/ODT classes. It allows you to assign this variable to a var type in FlexSim.

There are a couple other bind___ methods that can be used in bindInterface:

  • bindBracketOperator - allows the FlexScript interface to use [] syntax.
  • bindCastOperator - allows this object to be cast to other types (usually TreeNode*)

As a final note, you should know that the engine binds classes in two passes. The first pass creates empty type shell for all the outer types (SDT/ODT). That makes the types available for use in method calls. If you are using bindClass or bindNodeListArrayClass, then you should bind those classes before binding properties and methods that deal with that class.

That should get you started. We'll make better documentation in the future. Good luck!

· 9
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.