ZRESULT initGlobalPointer(){ ZPtr<core::IRepository> pRepos; ZPtr<services::IService> pService; if (ZRESULT_OK == g_pZModeler->getRepository(&pRepos) && ZRESULT_OK == pRepos->getService(_T("services::CRoadSplineNodes"), &pService)) { // service instantated; it's "init" was called, so this instance is alreay // subscribed to event handling; all you need to do, is to add this instance // to open/save service, so it's used during save operation:

core::io::registerSerializeable(pService);

// and let the global variable save the pointer to an instance. // it's safe to keep a pointer, since an instance of your service will not // get released until ZModeler closes (it's subscribed to event handling, so // at least event dispatcher guarantees an instance exists). Your code is safe // to access service via "g_pSplineNodes", just ensure it's not NULL and go on. ZPtr<services::IRoadSplineNodes> pMyService; if (ZRESULT_OK == pService->QueryInterface(IID_(services::IRoadSplineNodes), (void**)&pMyService)) { services::g_pSplineNodes = pMyService; return ZRESULT_OK; } } return ZRESULT_FALSE;}

each of your code routine, that requires the service, would start with a row like this:

// // do what you need with service pointer services::g_pSplineNodes, it's safe now}

What is the benefit? The instance of your service will be created when some of PSDL-specific tools is used. On that moment the service is created and subscribed for events. So, if user does not use your tools in ZModeler, an instance is not created and no related stuff is saved into .z3d file (just like no extra plugin is available at all). As soon as user applies your tool or does anything that causes a "getService(....)" to be called, your service is created and ready-to-go-and-to-save

I'm not really sure why you made a seperate service for the splines storage class. Can't I just make CRoadSplineNodes into a service?

Why is g_pSplineNodes declared as CRoadSplineNodes and initialized as IRoadSplineNodes in the startup function? Is that an error?

What I was thinking was to make CRoadSplineNodes into a service which handles scene events, and declares its own virtual functions for events, which can be implemented in a class that inherits from this service. For example, CPSDLRoadTool would derive from CRoadSplineNodes and implement these functions to update its dialog controls when needed, so that CPSDLRoadTool does not inherit from ISceneEventHandler and IZMEventHandler, but just from IRoadSplineNodes. I don't know if something like this is possible..?

I've made a separate service out of the splines storage, since this storage should reflect scene changes, and should be able to open/save spline pointers. So, it looks like preferable to put splines management into the "service" and extract user-interation methods into a tool. The tool could request your service from ZModeler and call methods you need.

g_pSplineNodes should be IRoadSplineNodes, it's my mistake. Thus, if your tool needs any specific logic of spline storage to be accessible, respective methods should be available in an interface part.

A tool will not handle any events, the "storage service" will do it.

However, this solution will not work, if certain scene changes require some UI appearance like enabling/disabling controls or selecting/deselecting items in a list control, especially when these UI control are a part of the "Tool" (the case when your tool is a floater window with some controls).

Currently, this is usually resolved by moving all-and-everything into the "service", so the "tool" is just a button in toolbar that requests "service" to show a dialog box. (Materials service and textures service are an example).

The problem is with serialization. When reading .z3d file, the reading-service facing a declaration inside a .z3d file, it has to find (or create) an instance that will deal with respective saved data. It can handle "services" easily, as they are available directly in repository, but it can't handle tools, since tools instances are spread all around the ZModeler (some are in menu, some are in context menu, some are in commands bar and so on). Thus, most of tools are not serializeable.

If the "UI controls are in a Tool" is your case, and the event handling inside a "service" can not change UI state of controls (it can't reach them to enable/disable or whatsoever), I would recommend

the context tool as- user-interaction for adding under control / removing from control of a given spline (interacts with service)

If your UI elements are not a floater window, a regular dialog box could be a choice. In such a case, an implementation of "everything inside a service" (including dialog itself) can be preferable.

P.S. if you chose a "dialog-and-service-and-handler" implementation, the SDK\services\MaterialsService\MaterialsService.* is your helper. It's important to derive your class from CDialog (CWnd) first, and then the rest of interfaces can follow:

class CMyService : public CDialog, public services::IRoadSplineNode, public core::events::IZMEventHandler

and a very specific row inside interfaces map should be present: it should point to the very-very first ZModeler interface of your class:INTERFACE_BEGINMAP(CMyService) INTERFACE_CONTROLLING_UNKNOWN(services::IService) ...

where "services::IService" is the first, since it's the first in derivation of "services::IRoadSplineNode".Specifying wrong "controlling unknown" interface in a macro will cause app crash on attempt to instantiate a class (crash during CWnd::Create or at the attempt to invoke any method of zmodeler interface on a given instance).

I don't have zmod2 source to compile and/or test your plugin, but hope I see the overal logic behind.

Your tool is a floater that shows a width and/or other road-specific options for a spline you select in scene, right? I guess you don't actually need to collect a set of splines to store that much of information in your global CPSDLNodes. Well, you can actually use such a thing, but you can create it dynamically using a scene content when this info is needed. The key feature I would like to suggest is to move your per-spline options into user-defined options of the spline. You can give them specific names like

PSDL.RoadPlugin.Convert ('1' = convert, any other value = do not convert

PSDL.RoadPlugin.WidthRoad(if value successfully converts to float, and float is in correct range (0.0f to 10.0f), consider it to be correct width specified;

PSDL.RoadPlugin.WidthSidewalk...

and so on.

User will not need to create these options (yes, user could see them in regular "properties" window of zmodeler, can change or delete them), but the node itself will maintain them across through ZModeler logic. The spline will save and load these properties into .z3d, so you don't bother with this stuff.

Your tool can give a quick spot on scene nodes, collect splines that match criteria into array, check their user-defined options where applies (read given width values). You can move this into a routine like that:

It should not be a member of any class, so it can be accessed from any point of your code.The routine will collect either selected only splines (e.g., when some scene node is selected or deselected, it will collect correct array of "still selected" scene splines, so your floater window can show properties for selected splines), and/or it can also check availability of non-zero "PSDL.RoadPlugin.Convert" user-defined property on a spline, so it will find only those splines that user wants to convert (e.g. before an export).

Concerning your tool, there are some notes to mention:1. event handler should always return ZRESULT_FALSE. Returning ZRESULT_OK will stop processing and other event handlers will not get notified.2. "scene" event handing will need onNodeSelection only, all other can simply return ZRESULT_FALSE;3. handling "zm" events becomes not necessary.

Oleg wrote:Your tool is a floater that shows a width and/or other road-specific options for a spline you select in scene, right?

Yes. I will clarify with a screenshot:

The collectSceneSplineNodes function looks like a good idea (as long as it doesn't have performance 'drawbacks'). I will implement this.

However, the user properties, like I said, don't work for me in ZModeler. I changed the apply and queryLayout as you suggested, but the properties won't appear in the "Selection Properties" window after they were added. Am I missing something?

Thanks for your notes about my code also; will take them in account.

I also have a small remark about the while loop in apply(). It's only a detail, but I was just wondering:

ohh... my bad, the ISplineNode has no user-defined options in ZModeler2...

Since I don't have an environment to compile zmod2 stuff, I hope you can do it for your project. Here's an original source code attached, it has all required changes to support user-defined options on spline nodes. Just compile it and use instead of original Splines.zmp plugin.

Let me know if it does not compile or produce any errors.

Concerning your question about it.queryNode(...), it's a must, since ZModeler does not guarantee you that "apply" will be called only after "queryLayout". This is the default behavior of context menu service, but ITool::apply method could be accessed externally by plugins. For example, hotkeys service can call "apply" on any tool if user assigns hot-key for such a tool.

There is a problem though. IUserDefinedOptions::addOption() doesn't seem to overwrite the current value if it already exists. To fix this, it would be useful to have a function called "setOptionValueByName" just like there is "getOptionValueByName". Another way would be to find the index of the user option by iterating over getOptionName(). Then I can use the iterator value on setOptionValue() which takes an index. What do you think?

Midas wrote:There is a problem though. IUserDefinedOptions::addOption() doesn't seem to overwrite the current value if it already exists.

are you sure? the IUserDefinedOptions::addOption implementation starts with:

tOptionInfo& info = getOptionByNameOrCreateNew(strName);

(see API\Helpers\userDefined.h)

and changes value of the given info slot. So it have to alter existing property value when name matches. There is a bug that returned property index is always the index of last property (even if it was not added and existing was found), may be this has confused you?

Do you actually get duplicate option names in properties of your spline then?

Yes, I'm sure. I also tested the value of the string in the output window. I'm not getting duplicates either. After the property is added for the first time, it won't change after the following addOption() calls. I'm also not doing anything with the return value (index). (I didn't change anything to your code.)

Looks like a bug. There was something like that in ZMod2, I remember this kind of a problem. I guess you can reside on a "slower" method of querying option names one by one and getting the value of the matching: you can write a helper function that will read and provide a value out of IUserDefinedProperites given an option name.

The plugin is working, it can generate roads to PSDL, based on the settings in the user properties. This is the result:

The only problem, as you can see, is that the roads are all placed on the origin. This is because I used getControlPosition() to get the position for every point on a spline, and for splines this is relative to the spline's "coordinate space". Is it possible to get the absolute coordinates instead?

Also, since I would like to use bézier curves, I will have to calculate a point on the curve for each cross section of the road by means of a precission value. Would getSectionPoint() be the way to go?