HDK: How to use GA_BlobData?

   1546   1   0
User Avatar
Member
9 posts
Joined: July 2006
Offline
I would like to create a custom attribute with HDK. There is `GA_BlobData` for this purpose as explained in the documentation [www.sidefx.com]. I have basically copy&pasted the code in the linked part of documentation, but the code keeps crashing

The code is not complete as it is, GA_BlobData requires also implementation of `getMemoryUsage` and `countMemory`
virtual int64 	getMemoryUsage (bool inclusive) const =0
virtual void 	countMemory (UT_MemoryCounter &counter, bool inclusive) const =0
I not really sure what the implementation should be, I just return `sizeof(*this) + myString.getMemoryUsage()` for the first and do nothing for the second one i.e. `{}`.

How to use `GA_BlobData` properly? Is there a full working example?

Related question(not very usefull) [www.sidefx.com]

Crash report:
Crash report from tskrivan; Houdini FX Version 17.0.352 [linux-x86_64-gcc6.3]
Uptime 111 seconds
Wed Aug 21 19:47:02 2019
Caught signal 11

Traceback from 7572 ThreadId=0x7f4a2a356cc0
AP_Interface::coreDumpChaser(UTsignalHandlerArg) <libHoudiniUI.so>
AP_Interface::si_CrashHandler::chaser(UTsignalHandlerArg) <libHoudiniUI.so>
signalCallback(UTsignalHandlerArg) <libHoudiniUT.so>
UT_Signal::UT_ComboSignalHandler::operator()(int, siginfo_t*, void*) const <libHoudiniUT.so>
UT_Signal::processSignal(int, siginfo_t*, void*) <libHoudiniUT.so>
__funlockfile <libpthread.so.0>
GA_BlobContainer::getMemoryUsage(bool) const <libHoudiniGEO.so>
GA_ATIBlob::getMemoryUsage(bool) const <libHoudiniGEO.so>
GA_AttributeProxy::getMemoryUsage(bool) const <libHoudiniGEO.so>
GA_AttributeSet::getMemoryUsage(bool) const <libHoudiniGEO.so>
GA_Detail::getMemoryUsage(bool) const <libHoudiniGEO.so>
GEO_Detail::getMemoryUsage(bool) const <libHoudiniGEO.so>
GU_Detail::getMemoryUsage(bool) const <libHoudiniGEO.so>
SOP_Node::getLoadedMemoryUsage() const <libHoudiniOP2.so>
SOP_CacheManager::alertCookEndSOP(SOP_Node*) <libHoudiniOP2.so>
SOP_Node::cookMe(OP_Context&) <libHoudiniOP2.so>
OP_Node::cook(OP_Context&)::{lambda()#1}::operator()() const <libHoudiniPRM.so>
OP_Node::cook(OP_Context&) <libHoudiniPRM.so>
OP_Node::internalCookInput(OP_Context&, int, OP_Node&) <libHoudiniPRM.so>
OP_Node::lockInput(unsigned int, OP_Context&) <libHoudiniPRM.so>
SOP_Node::lockInput(unsigned int, OP_Context&) <libHoudiniOP2.so>
SOP_Output::cookMySop(OP_Context&) <libHoudiniOP2.so>
SOP_Node::cookMe(OP_Context&) <libHoudiniOP2.so>
OP_Node::cook(OP_Context&)::{lambda()#1}::operator()() const <libHoudiniPRM.so>
OP_Node::cook(OP_Context&) <libHoudiniPRM.so>
SOP_Node::getCookedGeoHandle(OP_Context&, int)::{lambda()#1}::operator()() const <libHoudiniOP2.so>
SOP_Node::getCookedGeoHandle(OP_Context&, int) <libHoudiniOP2.so>
SOP_SubNet::cookMySop(OP_Context&) <libHoudiniOP2.so>
SOP_Node::cookMe(OP_Context&) <libHoudiniOP2.so>
OP_Node::cook(OP_Context&)::{lambda()#1}::operator()() const <libHoudiniPRM.so>
OP_Node::cook(OP_Context&) <libHoudiniPRM.so>
SOP_Node::getCookedGeoHandle(OP_Context&, int)::{lambda()#1}::operator()() const <libHoudiniOP2.so>
SOP_Node::getCookedGeoHandle(OP_Context&, int) <libHoudiniOP2.so>
OBJ_Node::getDisplayGeometryHandle(OP_Context&, int, OP_Node**) <libHoudiniOP3.so>
JEDI_View::opuiUpdateNodeInfo(OH_NodeInfo*) <libHoudiniAPPS3.so>
OH_OpHandler::opuiUpdateData() <libHoudiniUI.so>
OP3D_View::doUpdate() <libHoudiniAPPS3.so>
tbb::interface7::internal::task_arena_base::internal_execute(tbb::interface7::internal::delegate_base&) const (arena.cpp:899)
TBBPROXY_TaskArenaExecute <libTBBPROXY.so>
JEDI_View::opuiUpdateData() <libHoudiniAPPS3.so>
OH_OpHandler::trigger() <libHoudiniUI.so>
UI_Queue::processNextEvent() <libHoudiniUI.so>
UI_Queue::drain() <libHoudiniUI.so>
UI_Queue::eventLoop() <libHoudiniUI.so>
main <libHoudiniUI.so>
__libc_start_main (libc-start.c:291)
main <houdini-bin>
User Avatar
Member
9 posts
Joined: July 2006
Offline
I do not know what I was doing wrong. It works for me now.

For the future lost souls I'm including a working node with a simple test scene. Download & extract the attached archive somewhere and run the following commands

export CC=gcc-6 
export CXX=g++-6
mkdir build
pushd build
cmake .. -DHOUDINI_ROOT=/opt/hfs17.5/
make -j
popd
./test.py
The result should be:
Stored a string to a blob: "Hello World!"
Finished cooking!
Retrieved a string form a blob: "Hello World!"
Finished cooking!

Not sure about the lifetime of attachments here, but I'm frustrated when they disappear. Thus the code in plain text

BlobDataTest.h
#pragma once

#include <SOP/SOP_Node.h>
#include <UT/UT_MemoryCounter.h>

///////////////// String Blob //////////////////////////////

class StringBlob : public GA_BlobData {
public:
  StringBlob();
  StringBlob(const char *str);
  virtual ~StringBlob();

  virtual uint hash() const;
  virtual bool isEqual(const GA_BlobData &blob) const;

  virtual int64 getMemoryUsage(bool inclusive) const;
  virtual void  countMemory(UT_MemoryCounter &counter, bool inclusive) const;

public:
  UT_String myString;
};

///////////////// String Blob Test Node //////////////////////////////

class SOP_BlobDataTest : public SOP_Node {
public:
  SOP_BlobDataTest(OP_Network *net, const char *name, OP_Operator *op);
  virtual ~SOP_BlobDataTest();

  static PRM_Template myTemplateList[];
  static OP_Node *    myConstructor(OP_Network *, const char *, OP_Operator *);

protected:
  /// Method to cook geometry for the SOP
  virtual OP_ERROR cookMySop(OP_Context &context);

private:
};

BlobDataTest.cpp
#include <iostream>
#include <string>

#include <GA/GA_AIFBlob.h>
#include <GU/GU_Detail.h>
#include <OP/OP_AutoLockInputs.h>
#include <OP/OP_Director.h>
#include <OP/OP_Operator.h>
#include <OP/OP_OperatorTable.h>
#include <PRM/PRM_Include.h>
#include <UT/UT_DSOVersion.h>

#include "BlobDataTest.h"

///////////////// String Blob //////////////////////////////

StringBlob::StringBlob() : myString() {}

StringBlob::StringBlob(const char *str)
    : myString(UT_String::ALWAYS_DEEP, str) {}

StringBlob::~StringBlob() {}

// Implementation of methods on GA_Blob
uint StringBlob::hash() const { return myString.hash(); }

bool StringBlob::isEqual(const GA_BlobData &blob) const {
  const StringBlob *s;
  s = dynamic_cast<const StringBlob *>(&blob);
  return s && (s->myString == myString);
}

int64 StringBlob::getMemoryUsage(bool inclusive) const {
  int64 mem = inclusive ? sizeof(*this) : 0;
  mem += myString.getMemoryUsage();
  return mem;
}

void StringBlob::countMemory(UT_MemoryCounter &counter, bool inclusive) const {
  if (counter.mustCountUnshared()) {
    counter.countUnshared(getMemoryUsage(inclusive));
  }
}

///////////////// String Blob Test Node ////////////////////////

void newSopOperator(OP_OperatorTable *table) {
  table->addOperator(new OP_Operator(
      "blob_data_test_node", "Blob Data Test Node",
      SOP_BlobDataTest::myConstructor, SOP_BlobDataTest::myTemplateList, 0));
}


PRM_Template SOP_BlobDataTest::myTemplateList[] = {
    PRM_Template(),
};

OP_Node *SOP_BlobDataTest::myConstructor(OP_Network *net, const char *name,
                                         OP_Operator *op) {
  return new SOP_BlobDataTest(net, name, op);
}

SOP_BlobDataTest::SOP_BlobDataTest(OP_Network *net, const char *name,
                                   OP_Operator *op)
    : SOP_Node(net, name, op) {
  // Right now I'm assuming that anything can change
  // In fugure, I might bump data IDs base on which attribute handles were
  // created with `getattrib` function

  // mySopFlags.setManagesDataIDs(true);
}

SOP_BlobDataTest::~SOP_BlobDataTest() {}

OP_ERROR
SOP_BlobDataTest::cookMySop(OP_Context &context) {
  // We must lock our inputs before we try to access their geometry.
  // OP_AutoLockInputs will automatically unlock our inputs when we return.
  // NOTE: Don't call unlockInputs yourself when using this!
  OP_AutoLockInputs input_lock_guard(this);
  if (input_lock_guard.lock(context) >= UT_ERROR_ABORT)
    return error();

  // Duplicate incoming geometry
  duplicateSource(0, context);

  flags().setTimeDep(true);

  auto owner          = GA_ATTRIB_DETAIL;
  auto scope          = GA_SCOPE_PUBLIC;
  auto attribute_name = "blob_test";

  GA_RWAttributeRef blob_gah = gdp->findAttribute(owner, scope, attribute_name);

  // Set the blob if does not exists
  // Should be executed in the `test.hipnc/obj/geo1/set_blob` node
  if (!blob_gah) {

    // create attribute
    GA_RWAttributeRef blob_gah = gdp->createAttribute(
        owner, scope, attribute_name, nullptr, nullptr, "blob");

    // create blob
    GA_Attribute *    attr     = blob_gah.getAttribute();
    const GA_AIFBlob *aif      = attr->getAIFBlob();
    StringBlob *      blob_ptr = new StringBlob{"Hello World!"};

    // save blob
    aif->setBlob(attr, blob_ptr, 0);

    std::cout << "Stored a string to a blob: " << blob_ptr->myString
              << std::endl;

  } else {
    // Get the blob if it exists
    // Should be executed in the `test.hipnc/obj/geo1/get_blob` node

    // Retrieve the blob
    GA_Attribute *    attr = blob_gah.getAttribute();
    const GA_AIFBlob *aif  = attr->getAIFBlob();

    GA_BlobRef blob    = aif->getBlob(attr, 0);
    auto       strblob = dynamic_cast<const StringBlob *>(blob.get());

    std::cout << "Retrieved a string form a blob: " << strblob->myString
              << std::endl;
  }

  if (error() >= UT_ERROR_ABORT)
    return error();

  std::cout << "Finished cooking!" << std::endl;

  return error();
}

CMakeLists.txt
# Specify the minimum required version of CMake to build the project.
cmake_minimum_required( VERSION 3.6 )

set(CMAKE_PREFIX_PATH "${HOUDINI_ROOT}/toolkit")

### LIBRARIES ###
find_package(Houdini REQUIRED)
find_package(Eigen3 REQUIRED)

### EXECUTOR ###

# SOP
add_library(BlobDataTest SHARED BlobDataTest.cpp)
target_link_libraries(BlobDataTest Houdini)
houdini_configure_target( BlobDataTest )

Attachments:
blob_data_test.zip (12.3 KB)

  • Quick Links