Karamba scripting: failed conversion from model to model

Hi

I’m running into issues when joining “Karamba scripting” and “Karamba components”.
Error balloon: “1. Data conversion failed from Model to Model

The thing is that the whole script is running fine on other systems but on my laptop I’m getting errors.
I’ve tried purging my plug-in’s and reinstalling, Reinstalling Rhino.

Hope someone can help.
Thanks!

I’m running rhino 8 and Karamba 3.1.50414.


The Karamba script:

#region Usings
using System;
#r "C:\Users\name\AppData\Roaming\McNeel\Rhinoceros\packages\8.0\Karamba3D\3.1.50414\net8.0-windows\Karamba.gha"
#r "C:\Users\name\AppData\Roaming\McNeel\Rhinoceros\packages\8.0\Karamba3D\3.1.50414\net8.0-windows\KarambaCommon.dll"

using System;
using System.Linq;
using System.Collections.Generic;
using Rhino;
using Rhino.Geometry;
using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Karamba.Models;
using Karamba.CrossSections;
using Karamba.Elements;
using Karamba.Results;
using Karamba.Geometry;
#endregion

public class Script_Instance : GH_ScriptInstance
{
  /*
    ─────────────────────────────────────────────────────────────────────────────────
    NOTES:
      - This script performs an iterative cross-section selection for timber beams
        following an EC5-style check (axial + bending).
      - Custom material strength values (charFm, charFc, charFt) are provided as input.
      - The model is analyzed repeatedly until either the cross-sections do not change
        or the user-specified iteration limit (niter) is reached.
      - When niter = 0, the script performs a single analysis with the initial cross-sections.
      - Force redistribution is accounted for by re-analyzing the model after each
        batch of cross-section changes.
      - OPTIMIZATION: Multiple performance improvements to reduce calculation time.
    ─────────────────────────────────────────────────────────────────────────────────
  */

  // Partial factor (EC5, typically ~1.3)..
  private const double GAMMA_M = 1.3;
  
  // Material property lists (populated from input)
  private List<double> charFmValues = new List<double>();
  private List<double> charFcValues = new List<double>();
  private List<double> charFtValues = new List<double>();
  
  // OPTIMIZATION: Pre-calculate design strengths for all sections
  private List<double> designFmValues = new List<double>();
  private List<double> designFcValues = new List<double>();
  private List<double> designFtValues = new List<double>();
  
  // OPTIMIZATION: Cache cross-section properties for faster access
  private List<double> sectionAreas = new List<double>();
  private List<double> sectionWelY = new List<double>();

  // =================== HELPER METHODS ===================
  
  // OPTIMIZATION: Calculate all element forces at once to reduce calls
  private Dictionary<int, Tuple<double, double>> CalculateAllElementForces(Model workModel, string lcName, List<int> elementIndices)
  {
    var result = new Dictionary<int, Tuple<double, double>>();
    
    // Convert element indices to strings for Karamba API
    var elemIndicesStr = elementIndices.Select(idx => idx.ToString()).ToList();
    
    // Get forces for all elements at once
    List<List<double>> N, V, M;
    BeamResultantForces.solve(workModel, elemIndicesStr, lcName, 10, 5, out N, out V, out M);
    
    // Store results in dictionary
    // The first list is for the load case (always index 0 as we're only using one load case)
    // The second list contains values for each element
    for (int i = 0; i < elementIndices.Count; i++)
    {
      double axial = N[0][i];         // kN (keeping sign)
      double moment = Math.Abs(M[0][i]);  // kN*m (absolute value)
      result[elementIndices[i]] = new Tuple<double, double>(axial, moment);
    }
    
    return result;
  }
  
  // OPTIMIZATION: Streamlined interaction ratio calculation with pre-calculated values
  private double CalculateInteractionRatio(double axial, double moment, int sectionIndex)
  {
    // Cross-section properties (already pre-calculated)
    double A = sectionAreas[sectionIndex];
    double Wel_y = sectionWelY[sectionIndex];

    // Pre-calculated design strengths
    double designFm = designFmValues[sectionIndex];
    double designFc = designFcValues[sectionIndex];
    double designFt = designFtValues[sectionIndex];
    
    // Calculate utilization based on axial force sign
    double utilAxial;
    if (axial >= 0)  // Tension
    {
        utilAxial = ((axial * 1000) / A) / designFt;  // Use tension strength
    }
    else  // Compression
    {
        utilAxial = ((Math.Abs(axial) * 1000) / A) / designFc;  // Use compression strength
    }
    
    double utilBending = ((moment * 1000000) / Wel_y) / designFm;  // Use bending strength
    return utilAxial + utilBending;
  }
  
  // Find appropriate cross-section from the list
  private int FindAppropriateSection(double axial, double moment, List<CroSec_Beam> croSecsAll)
  {
    // OPTIMIZATION: Search through all cross-sections from smallest to largest
    for (int i = 0; i < croSecsAll.Count; i++)
    {
      double interaction = CalculateInteractionRatio(axial, moment, i);
      
      // If interaction ≤ 1.0, this section is adequate
      if (interaction <= 1.0)
        return i;
    }
    
    // If no adequate section found, return the largest one
    return croSecsAll.Count - 1;
  }

  // Main execution script
  private void RunScript(
		object Model_in,
		List<object> CroSecs_in,
		DataTree<double> materialProps_in,
		int niter,
		string lcName,
		double kmod,
		ref object Model_out,
		ref object Disp_out,
		ref object force_out,
		ref object utilization_out)
  {
    // 1) VALIDATE INPUTS
    var modelInitial = Model_in as Model;
    if (modelInitial == null)
      throw new ArgumentException("Input 'Model_in' is not a Karamba Model!");

    if (string.IsNullOrEmpty(lcName))
      throw new ArgumentException("Load case name cannot be empty!");

    var croSecsAll = new List<CroSec_Beam>();
    foreach(var obj in CroSecs_in)
    {
      if (obj is CroSec_Beam cs)
        croSecsAll.Add(cs);
      else
        throw new ArgumentException("One or more inputs in 'CroSecs_in' are not CroSec_Beam objects!");
    }
    
    if (croSecsAll.Count == 0)
      throw new ArgumentException("No cross sections provided!");
      
    // Process material properties data tree
    if (materialProps_in == null || materialProps_in.BranchCount < 3)
      throw new ArgumentException("Material properties input must contain three branches: charFm, charFc, and charFt!");
      
    // Extract values from the data tree
    charFmValues = materialProps_in.Branch(0); // First branch = charFm
    charFcValues = materialProps_in.Branch(1); // Second branch = charFc 
    charFtValues = materialProps_in.Branch(2); // Third branch = charFt
    
    // Verify that we have the correct number of material property values
    if (charFmValues.Count != croSecsAll.Count || charFcValues.Count != croSecsAll.Count || charFtValues.Count != croSecsAll.Count)
      throw new ArgumentException("Number of material property values must match number of cross sections!");
      
    // 2) PREPARE ANALYSIS
    var k3d = new KarambaCommon.Toolkit();
    // Clone the model so we don't modify the original input
    var workModel = modelInitial.Clone();
    workModel.cloneElements();
    
    // Store final utilization values
    var finalUtilization = new List<double>();
    // Initialize with zeros for all elements
    for (int i = 0; i < workModel.elems.Count; i++)
    {
      finalUtilization.Add(0.0);
    }
    
    // Create a map of cross-section names to indices in the croSecsAll list
    Dictionary<string, int> crossSectionMap = new Dictionary<string, int>();
    for (int i = 0; i < croSecsAll.Count; i++)
    {
      crossSectionMap[croSecsAll[i].name] = i;
    }
    
    // OPTIMIZATION: Create a list of beam elements for faster processing
    var beamElements = new List<int>();
    for (int e = 0; e < workModel.elems.Count; e++)
    {
      if (workModel.elems[e] is ModelBeam)
        beamElements.Add(e);
    }
    
    DateTime startTime = DateTime.Now;
    int iteration = 0;
    
    if (niter == 0)
    {
      // Handle the case where niter = 0
      // We perform a single analysis of the initial model without optimization
      Print("Running single analysis with initial cross-sections (niter=0)");
      
      // OPTIMIZATION: Pre-calculate design strengths and section properties
      PreCalculateProperties(croSecsAll, kmod);
      
      // Analyze the model
      IReadOnlyList<double> maxDisp;
      IReadOnlyList<Vector3> outForce;
      IReadOnlyList<double> outEnergy;
      string warning;
      
      workModel = k3d.Algorithms.Analyze(
        workModel, new List<string> { lcName },
        out maxDisp, out outForce, out outEnergy, out warning
      );
      
      if (!string.IsNullOrEmpty(warning))
        Print("Analysis warning: " + warning);
      
      // Calculate all element forces
      var allForces = CalculateAllElementForces(workModel, lcName, beamElements);
      
      // Calculate utilization for each beam element based on its current cross-section
      foreach (int e in beamElements)
      {
        var beam = workModel.elems[e] as ModelBeam;
        if (beam == null) continue;
        
        // Get current cross-section
        var currentCroSec = beam.crosec as CroSec_Beam;
        if (currentCroSec == null) continue;
        
        // Find the index of this cross-section in our list
        if (!crossSectionMap.TryGetValue(currentCroSec.name, out int sectionIndex))
        {
          Print($"Warning: Cross-section '{currentCroSec.name}' not found in input list. Using first cross-section for calculations.");
          sectionIndex = 0;
        }
        
        // Get element forces
        var forces = allForces[e];
        double axial = forces.Item1;
        double moment = forces.Item2;
        
        // Calculate interaction ratio
        double interaction = CalculateInteractionRatio(axial, moment, sectionIndex);
        
        // Store utilization
        finalUtilization[e] = interaction;
      }
      
      // Final displacement
      Disp_out = maxDisp.Count > 0 ? maxDisp[0] : 0.0;
    }
    else
    {
      // Original optimization code for niter > 0
      // OPTIMIZATION: Pre-calculate design strengths and section properties
      PreCalculateProperties(croSecsAll, kmod);
      
      // Dictionary to keep track of the current section index for each element
      var currentSectionIndices = new Dictionary<int, int>();
      
      // 3) ITERATION LOOP
      bool anySectionChanged = false;

      for (iteration = 0; iteration < niter; iteration++)
      {
        // Analyze the model for the given load case
        IReadOnlyList<double> maxDisp;
        IReadOnlyList<Vector3> outForce;
        IReadOnlyList<double> outEnergy;
        string warning;
        workModel = k3d.Algorithms.Analyze(
          workModel, new List<string> { lcName }, out maxDisp, out outForce, out outEnergy, out warning
        );
        
        if (!string.IsNullOrEmpty(warning))
          Print("Analysis warning: " + warning);

        // Reset change flag for this iteration
        anySectionChanged = false;
        
        // List to collect all changes to apply at once
        var changedElements = new List<Tuple<ModelBeam, CroSec_Beam>>();
        
        // OPTIMIZATION: Calculate all element forces once per iteration
        var allForces = CalculateAllElementForces(workModel, lcName, beamElements);

        // 3a) Evaluate each beam
        foreach (int e in beamElements)
        {
          var beam = workModel.elems[e] as ModelBeam;
          if (beam == null) continue;

          // Get current cross-section
          var oldCroSec = beam.crosec as CroSec_Beam;
          if (oldCroSec == null) continue;
          
          // Get element forces from pre-calculated dictionary
          var forces = allForces[e];
          double axial = forces.Item1;
          double moment = forces.Item2;
          
          // Find appropriate cross-section
          int newIdx = FindAppropriateSection(axial, moment, croSecsAll);
          
          // Store the current section index
          int oldIdx = -1;
          if (currentSectionIndices.ContainsKey(e))
              oldIdx = currentSectionIndices[e];
          
          currentSectionIndices[e] = newIdx;
          
          // Calculate and store the interaction ratio with the proposed cross-section
          double interaction = CalculateInteractionRatio(axial, moment, newIdx);
          
          // Always store the current utilization value
          finalUtilization[e] = interaction;
          
          // Check if we need to change the cross-section
          if (croSecsAll[newIdx] != oldCroSec)
          {
              changedElements.Add(new Tuple<ModelBeam, CroSec_Beam>(beam, croSecsAll[newIdx]));
              anySectionChanged = true;
          }
        }

        // Apply all changes at once and rebuild model if needed
        if (anySectionChanged)
        {
          foreach (var change in changedElements)
          {
            change.Item1.crosec = change.Item2;
          }
          
          // Rebuild model once per iteration
          workModel.initMaterialCroSecLists();
          workModel.buildFEModel();
          
          // Log number of changes in this iteration
          Print($"Iteration {iteration+1}: Changed {changedElements.Count} element(s)");
        }
        else
        {
          // No changes in this iteration => converged early
          Print($"Converged after {iteration+1} iterations");
          break;
        }
      } // end iteration loop
      
      // 4) FINAL ANALYSIS FOR OUTPUT
      // We need a final analysis to get updated forces after all changes
      IReadOnlyList<double> finalMaxDisp;
      IReadOnlyList<Vector3> outF;
      IReadOnlyList<double> outE;
      string finalWarning;
      workModel = k3d.Algorithms.Analyze(
        workModel, new List<string> { lcName },
        out finalMaxDisp, out outF, out outE, out finalWarning
      );

      // Final displacement
      Disp_out = finalMaxDisp.Count > 0 ? finalMaxDisp[0] : 0.0;
    }
    
    // Log performance information
    TimeSpan processingTime = DateTime.Now - startTime;
    Print($"Total processing time: {processingTime.TotalSeconds:F2} seconds");
    
    // 6) OUTPUTS
    // Model
    Model_out = new Karamba.GHopper.Models.GH_Model(workModel);
    
    // Summary text for force output
    if (niter == 0)
    {
      force_out = $"Single analysis completed (niter=0) in {processingTime.TotalSeconds:F2} seconds.";
    }
    else
    {
      force_out = $"Optimization completed after {Math.Min(niter, iteration + 1)} of {niter} iterations in {processingTime.TotalSeconds:F2} seconds.";
    }
    
    // Utilization - already populated during analysis
    utilization_out = finalUtilization;
  }
  
  // OPTIMIZATION: Pre-calculate all properties for faster access during iterations
  private void PreCalculateProperties(List<CroSec_Beam> croSecsAll, double kmod)
  {
    designFmValues.Clear();
    designFcValues.Clear();
    designFtValues.Clear();
    sectionAreas.Clear();
    sectionWelY.Clear();
    
    for (int i = 0; i < croSecsAll.Count; i++)
    {
      // Design strengths
      double designFm = kmod * (Math.Abs(charFmValues[i]) * 10) / GAMMA_M;
      double designFc = kmod * (Math.Abs(charFcValues[i]) * 10) / GAMMA_M;
      double designFt = kmod * (Math.Abs(charFtValues[i]) * 10) / GAMMA_M;
      
      designFmValues.Add(designFm);
      designFcValues.Add(designFc);
      designFtValues.Add(designFt);
      
      // Cross-section properties
      double area = (1000000 * croSecsAll[i].A);                // in m²
      double welY = (1000000000 * croSecsAll[i].Welz_y_pos);    // in m³
      
      sectionAreas.Add(area);
      sectionWelY.Add(welY);
    }
  }
}

Hi @Oliver_Klejs,
I think the issue might be caused by the KarambaCommon library being loaded twice in slightly different versions.
Do you happen to have multiple versions of Karamba3D installed—perhaps different package versions via the Package Manager, or one in Rhino 7 and another in Rhino 8?
Have you tried toggling the “Memory load *.GHA assemblies using COFF byte arrays” option after running GrasshopperDeveloperSettings in Rhino?
You can also adjust this setting for individual plug-ins via the Grasshopper “Help/About…” banner: click the small icon in the top left, double-click the Karamba3D icon, and then select the jigsaw puzzle icon.
– Clemens

1 Like

Hi Clemens

Unchecking “Memory load *.GHA assemblies using COFF byte arrays” saved me!
Forgot all about this.

Thank you

  • Oliver