PDA

View Full Version : Implement 'Set Driven Keys' in C++?


Kuroyume0161
03-30-2006, 09:12 AM
Remember that I said that one important part of interPoser Pro would be implementing Master/Slave dials. Well, after some basic tests, "Set Driven Keys" is exactly the best approach - allows user editing, uses existing Cinema 4D functionality, easily loaded and saved.

The problem is that this feature is what I call a 'composite' function. Cinema 4D seems to be filled with 'composite' functions - commands that group a set of individual tasks into a single or couple of actions. Set Driven Keys does just that - it gets the 'driver' object and 'driven' object and then adds the XPresso tag and nodes and configures so that the driver parameter drives the driven parameter. The problem with many composite functions is that they are inaccessible from the C++ SDK - especially if it's a right-context menu item in the Attribute Manager - oye.

I know nothing about GraphView - and boy is it a complex interface. I swear that half of the SDK Docs are just about this. Nothing of use at PluginCafe (mainly about implementing custom GvNodes). No examples. No code snippets in the docs (that I've located).

Anyone have any information about how one would go about 'reinventing the Set Driven Keys' programmatically? So far, I have gotten to creating the nodes and attempting to add ports and connections (IsConnectionValid() is complete gibberish without more information).

It is expected to be pulling-teeth time. My pliers are handy... :)

Robert

Per-Anders
03-30-2006, 10:29 AM
doesn't seem that complex to me looking at the docs, though i've not tried it.

but i would allocate an xpresso tag
get it's gvnodemaster (see xpresso tag)
create the nodes required using the createnode call
make sure their data is set how i wanted
connect the nodes using the gvnode::addconnection routine

and that should in theory be it.

other than that calling other commands can be done either via messages or using the callcommand function (same as coffee there).

Kuroyume0161
03-30-2006, 10:54 AM
Oh, you haven't tried it. Hmmm... ;)

Yes:

* Allocated an XPressoTag.
* Got the GvNodeMaster and Root GvNode.
* Created the Source/RangeMapper/Dest GvNodes
* Set the Object Links and Absolute/Relative respectively.

Now the part where 'not tried it' gets you (and me) in trouble. How do you add ports for dynamic description sliders that exist on these objects?

I tried this (entire section as outlined above):

// Create an XPresso tag on the dial tag's ippBase
xtag = static_cast<XPressoTag*>(ippBase->GetTag(Texpresso));
if (!(xtag && (xtag->GetName() == "IPP Slaved")))
{
xtag = XPressoTag::Alloc();
if (!xtag) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.xtag");
ippBase->InsertTag(xtag);
xtag->SetName("IPP Slaved");
}
// Create, Link, and Configure nodes
nodeMaster = xtag->GetNodeMaster();
if (!nodeMaster) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.nodeMaster");
root = nodeMaster->GetRoot();
if (!root) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.root");
snode = nodeMaster->CreateNode(root, ID_OPERATOR_OBJECT, NULL, x, y);
if (!snode) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.snode");
rnode = nodeMaster->CreateNode(root, ID_OPERATOR_RANGEMAPPER, NULL, x+144, y);
if (!rnode) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.rnode");
dnode = nodeMaster->CreateNode(root, ID_OPERATOR_OBJECT, NULL, x+288, y);
if (!dnode) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.dnode");
nbc = snode->GetOpContainerInstance();
nbc->SetLink(GV_OBJECT_OBJECT_ID, driver);
nbc->SetLong(GV_OBJECT_PATH_TYPE, GV_OBJECT_PATH_TYPE_ABSOLUTE);
nbc = dnode->GetOpContainerInstance();
nbc->SetLink(GV_OBJECT_OBJECT_ID, ippBase);
nbc->SetLong(GV_OBJECT_PATH_TYPE, GV_OBJECT_PATH_TYPE_RELATIVE);
// - Add Ports
if (snode->AddPortIsOK(GV_PORT_OUTPUT, sindex)) sport = snode->AddPort(GV_PORT_OUTPUT, sindex);
else return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.sport");
if (dnode->AddPortIsOK(GV_PORT_INPUT, dindex)) dport = dnode->AddPort(GV_PORT_INPUT, dindex);
else return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.dport");
roport = rnode->GetOutPort(0);
if (!roport) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.roport");
riport = rnode->GetInPort(0);
if (!riport) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.riport");


The part below the comment "// - Add Ports", it returns with the error on the first check. 'sindex' and 'dindex' are the ID's of the dynamic sliders. You thought the same, right? It's wrong.

Calling other commands requires a command to be called. For, do a search of the entire 'resource' folder, "SETDRIVER", there is only an IDS string setting. I could not find a corresponding command to go with it. If you find a way to call "Set Driver" and "Set Driven (Relative)" with the appropriate A.M. parameter, let me know! :)

Per-Anders
03-30-2006, 10:06 PM
You may need to get the list of the ports on your node first then iterate through the list and simply change the visibility of the ports rather than trying to allocate a port that probably already exists.

Kuroyume0161
03-30-2006, 10:51 PM
What I have just done (literally - that's why I'm here) is to find that GvNode::GetPortList() returns the 'available' ports on the node. You then must go through the list and call GvNode::GetPortDescription() so that you can correlate the portlist.id with the portdescription.name. This id is not the description id in the AM. It is something altogether different. Here's the code thus far (untidied):

// IPPDial.ResolveMasterDials
// - Convert Channel MasterDialEntries into Active MasterDials (XPresso tags)
//*---------------------------------------------------------------------------*
Bool IPPDial::ResolveMasterDials(BaseDocument* doc, AtomArray* atomArray, BaseObject* ippBase, BaseTag* tag, BOOL doUndo)
//*---------------------------------------------------------------------------*
{
AutoAlloc<GvPortList> portList;
if (!portList) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.portList");
XPressoTag* xtag;
GvNodeMaster* nodeMaster;
GvNode* root;
// Driver
GvNode* snode;
GvPort* sport;
// RangeMapper
GvNode* rnode;
GvPort* riport;
GvPort* roport;
// Driven
GvNode* dnode;
GvPort* dport;
GvPortListEntry* portLE;
GvPortDescription portDesc;
BaseObject* driver;
BaseTag* dial;
BaseContainer* bc = tag->GetDataInstance();
BaseContainer* nbc;
LONG i;
LONG portCount;
LONG sindex;
LONG dindex = bc->GetLong(IPPDIAL_INDEX);
ULONG flags;
LONG x = 8, y = 8;
String dportName = bc->GetString(IPPDIAL_DIALNAME);
String nodeName = ippBase->GetDataInstance()->GetString(IPP_INTERNALNAME)+"."+dportName;
for (MasterDialEntry* md = firstMaster; md != lastMaster; md++)
{
// Find Driver slider
driver = md->bodypart.GetObject(atomArray);
if (!(driver && driver->IsInstanceOf(ID_IPPBASE))) continue;
// - Get Dial and index
for (dial = driver->GetFirstTag(); dial; dial = dial->GetNext())
{
if (!(dial->IsInstanceOf(ID_IPPDIALTAG) || dial->IsInstanceOf(ID_IPPMORPHTAG))) continue;
bc = dial->GetDataInstance();
if (bc->GetString(IPPDIAL_DIALNAME) == md->dialName) break;
}
if (!dial) continue;
sindex = bc->GetLong(IPPDIAL_INDEX);

// Create an XPresso tag on the dial tag's ippBase
xtag = static_cast<XPressoTag*>(ippBase->GetTag(Texpresso));
if (!(xtag && (xtag->GetName() == "IPP Slaved")))
{
xtag = XPressoTag::Alloc();
if (!xtag) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.xtag");
ippBase->InsertTag(xtag);
xtag->SetName("IPP Slaved");
if (doUndo) doc->AddUndo(UNDO_NEW, xtag);
}
// Create, Link, and Configure nodes
nodeMaster = xtag->GetNodeMaster();
if (!nodeMaster) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.nodeMaster");
root = nodeMaster->GetRoot();
if (!root) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.root");
snode = nodeMaster->CreateNode(root, ID_OPERATOR_OBJECT, NULL, x, y);
if (!snode) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.snode");
rnode = nodeMaster->CreateNode(root, ID_OPERATOR_RANGEMAPPER, NULL, x+144, y);
if (!rnode) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.rnode");
dnode = nodeMaster->CreateNode(root, ID_OPERATOR_OBJECT, NULL, x+288, y);
if (!dnode) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.dnode");
nbc = snode->GetOpContainerInstance();
nbc->SetLink(GV_OBJECT_OBJECT_ID, driver);
nbc->SetLong(GV_OBJECT_PATH_TYPE, GV_OBJECT_PATH_TYPE_ABSOLUTE);
nbc = dnode->GetOpContainerInstance();
nbc->SetLink(GV_OBJECT_OBJECT_ID, ippBase);
nbc->SetLong(GV_OBJECT_PATH_TYPE, GV_OBJECT_PATH_TYPE_RELATIVE);
// - Add Ports
roport = rnode->GetOutPort(0);
if (!roport) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.roport");
riport = rnode->GetInPort(0);
if (!riport) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.riport");

portList->FlushAll();
snode->GetPortList(GV_PORT_OUTPUT, *portList);
portCount = portList->GetCount();
for (i = 0; i < portCount; i++)
{
portLE = portList->GetIndex(i);
snode->GetPortDescription(GV_PORT_OUTPUT, portLE->id, &portDesc);
if (portDesc.name == md->dialName) break;
}
if (i == portCount) return ErrorException::Throw("Master Node Port not found!");
if (!snode->AddPortIsOK(GV_PORT_OUTPUT, portLE->id)) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.sport.NotOK");
sport = snode->AddPort(GV_PORT_OUTPUT, portLE->id);
if (!sport) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.sport");
portList->FlushAll();
dnode->GetPortList(GV_PORT_INPUT, *portList);
portCount = portList->GetCount();
for (i = 0; i < portCount; i++)
{
portLE = portList->GetIndex(i);
dnode->GetPortDescription(GV_PORT_INPUT, portLE->id, &portDesc);
if (portDesc.name == dportName) break;
}
if (i == portCount) return ErrorException::Throw("Slave Node Port not found!");
if (!dnode->AddPortIsOK(GV_PORT_INPUT, portLE->id)) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.dport.NotOK");
dport = dnode->AddPort(GV_PORT_INPUT, portLE->id);
if (!dport) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.dport");
/*
// - Connect Driver to RangeMapper
if (!nodeMaster->IsConnectionValid(snode, sport, rnode, riport, snode, sport, rnode, riport)) { GePrint("InvalidConnection 1"); return FALSE; }
if (!snode->AddConnection(snode, sport, rnode, riport)) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.AddConnection");
// - Connect RangeMapper to Driven
if (!nodeMaster->IsConnectionValid(rnode, roport, dnode, dport, rnode, roport, dnode, dport)) { GePrint("InvalidConnection 2"); return FALSE; }
if (!rnode->AddConnection(rnode, roport, dnode, dport)) return ErrorException::Throw(GeLoadString(ERROR_MEMORY_TEXT), "IPPDial.ResolveMasterDials.AddConnection");
*/

// Mark Driver as having Slaves
flags = (ULONG)bc->GetLong(IPPDIAL_TYPESUBFLAGS);
bc->SetLong(IPPDIAL_TYPESUBFLAGS, (LONG)(flags & DIAL_FLAGS_SLAVES));
y += 80;
}
EventAdd();
GeFree(firstMaster);
firstMaster = NULL;
return TRUE;
}

At this rate, I'm going to be Lord of the Nodes (well, at least Loopy by the Nodes). ;)

Robert

Kuroyume0161
03-30-2006, 11:52 PM
Update: no luck with AddConnection().

I think that IsConnectionValid() sorta makes sense (why the distrust that one knows which ports are inputs and which are outputs is beyond me). So you send your source node and port and dest node and port. But you also send a set of pointers that takes them back, rearranged if the order is incorrect (?).

There are no errors with IsConnectionValid() or AddConnection(), but there are no connections (doh!). And if I manually connect the source output to the RangeMapper input and the RangeMapper output to the dest input, the RangeMapper turns yellow (I take it that this means something is askew with my source and dest ports).

Again, no idea. I've compiled every bit of GraphView code in the SDK docs - its a whopping 95 lines and is completely irrelevant to what I'm doing (except for the IsConnectionValid() code comprising 4 lines). A Google search on any number of related terms only points back to PluginCafe SDK online docs. And I did a thorough search of my entire Cinema 4D install for "SETDRIVER" and "Set Driven" case insensitive - nothing but that single IDS string identifier. I've looked at every GraphView entry in the documentation and every GraphView class/header/res in the Resource folder. Even Darf has thrown in the towel on using GraphView due to the overwhelming complexity of this enigmatic mess.

Going to look over every related thread at PluginCafe, but don't expect much. And I'll be bumping my thread up daily until there is a response or they come to take me away (oho, aha, ehe)! ;0)

Robert

CGTalk Moderation
03-30-2006, 11:52 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.