Creating and Loading Models Using Live C Extension (Ruby C Extension)

So I have been scratching my head for a LONG time on this problem. I am developing a Live Ruby C Extension, which in a nutshell deals with 3 models:

  1. reads some geometry from the active model that is open
  2. loads (and reads) some geometry from a “reference model”
  3. mashes the two above to create an “output model” that is saved to disk, then loaded in the live model by use of the DefinitionList.load Ruby method.

Now I have been struggling with a EXC_BAD_ACCESS error (segmentation fault to most people I think), when I load the said “output_model” using the Ruby API. I think I have finally found why.

I have been following @thomthom 's advice on Live C API Extension Examples . I have been wary NOT to SUModelRelease the model that was created with SUApplicationGetActiveModel - that way a whole lot of sadness is said to ensue.

But, what does one do with model that is NOT the model that is open… say the “output model”, which is created from scratch using SUModelCreate? Well, instinct told me that this model is completely separate from the model that is open in the application, so I should call SUModelRelease on it once I saved it to disk. Which is what I did.

My Ruby C Extension method on the C-side would save the output model, save it to disk, release it, then pass the file path of the output model back to Ruby to deal with. As soon as, or sometime soon after I call Sketchup.active_model.definitions.load(file_path) in Sketchup Ruby, segmentation error occurs. Horror.

Then I tried (finally) NOT calling SUModelRelease on the output model. NO CRASH! Hurray! But still, weird…

I have some minimal reproducible code below, which is a method to be added to example_extension.cpp in the GitHub - SketchUp/sketchup-live-c-api: Examples demonstrating how to use the SketchUp Live C API project

// Creates a model, saves it then returns the file_path.
VALUE load_definition(VALUE self) {
  // Create a Model and fill it with a simple face:
  SUModelRef output_model = SU_INVALID;
  SU(SUModelCreate(&output_model));

  SUEntitiesRef entities = SU_INVALID;
  SU(SUModelGetEntities(output_model, &entities));

  SUGeometryInputRef input = SU_INVALID;
  SU(SUGeometryInputCreate(&input));

  // Define a simple square in the XY plane
  std::vector<SUPoint3D> points{
    { 0.0, 0.0, 0.0 },
    { 9.0, 0.0, 0.0 },
    { 9.0, 9.0, 0.0 },
    { 0.0, 9.0, 0.0 }
  };
  for (size_t i = 0; i < points.size(); ++i) {
    SU(SUGeometryInputAddVertex(input, &points[i]));
  }

  // Outer loop
  SULoopInputRef outer_loop = SU_INVALID;
  SULoopInputCreate(&outer_loop);
  for (size_t i = 0; i < points.size(); ++i) {
    SU(SULoopInputAddVertexIndex(outer_loop, i));
  }
  size_t face_index = 0;
  SU(SUGeometryInputAddFace(input, &outer_loop, &face_index));

  // Fill entities using the geometry input
  SU(SUEntitiesFill(entities, input, true));

  SU(SUGeometryInputRelease(&input));

  // Put the file in the same folder as the open file
  SUModelRef open_model = SU_INVALID;
  SU(SUApplicationGetActiveModel(&open_model));
  if (SUIsInvalid(open_model)) {
    rb_raise(rb_eTypeError, "invalid model");
  }
  SUStringRef open_model_path = SU_INVALID;
  SUStringCreate(&open_model_path);
  SUModelGetPath(open_model, &open_model_path);
  std::string open_model_path_str = GetString(open_model_path);
  SUStringRelease(&open_model_path);

  size_t last_slash = open_model_path_str.find_last_of("/\\");
  if (last_slash != std::string::npos) {
    open_model_path_str = open_model_path_str.substr(0, last_slash);
  }

  // Save to a temporary path and return it
  std::string file_path = open_model_path_str + "/load_definition.skp";
  SU(SUModelSaveToFile(output_model, file_path.c_str()));

  //SU(SUModelRelease(&output_model)); // <== uncommenting this will cause the Ruby API method model.definitions.load(ruby_path) to crash with a memory error.

  VALUE ruby_path = rb_str_new_cstr(file_path.c_str());
  return ruby_path;
}

// Don't forget to register the function in Init_example() method:
  rb_define_module_function(mLiveCAPI, "load_definition", VALUEFUNC(load_definition), 0);

…one would also need to add these to the ruby file, (main.rb)

      sub_menu.add_item('Load Definition') {
        file_path = self.load_definition
        definition = Sketchup.active_model.definitions.load(file_path)
        Sketchup.active_model.entities.add_instance(definition, Geom::Transformation.new)
      }

Now, why do I post all of this when I seem to have resolved it? Well, because I don’t understand it. It seems fragile, and I’ve already had issues in the past with definitions.load method, and feel like I may be stung again if I make too many assumptions. What is to say that my fix won’t break tomorrow? I’d like to know what is guaranteed behaviour of the Rubby definitions.load method…?

How are models loaded in the back end of Sketchup? What is their lifetime?

This is what any informed reader of the Memory Management section at the SketchUp C API Index page would think.


I would encourage you to open a tracker issue if one for this weirdness does not already exist.

Thanks, Dan. I have now filed the issue on the tracker here:

1 Like