Adding a block from another file

Hi everyone!

I have a rhino file with hundreds of possible facade parts each of them modelled as a Block. Let’s call it a facade library. My goal is to import only certain blocks from the facade library to my current file according to the facade type defined by the user. Thus I can make the file size manageble and keep the library up-to-date without the need of manually importing blocks every time something is changed.

To this point I managed to collect all the InstanceDefinitions from the library file using Rhino.FileIO.File3dm.Read() and AllInstanceDefinitions property. But I can’t find a way to import an InstanceDefinition from a libary into my active RhinoDoc. Is there anything i’m missing?

I found an old thread with a similar question How to import selected blocks from a File3dm? (Or were blocks saved incorrectly in this file?) with an example from @dale but the file is not available anymore.

Max

i dont know if its doable via grasshopper but see elefront plugin for GH. there are some nodes for importing linked blocks etc

you can do this via elefront plugin if thats an option for you.

1 Like

Thanks for suggestion but it’s a little trade-off since all the blocks have to be kept in separate files. It’s interesting though how these components are done. I suppose it uses -insert command internally, I’ll give it a try anyway.

Ok. I made it finally
Here is a code sample in case anyone needs it.

private void RunScript(string filePath, string blockName)
{

    int index = -1; //instance definition index

    //check if block exists in current file;
    var localDef = RhinoDocument.InstanceDefinitions.Find(blockName);

    if (localDef != null)
    {
      index = localDef.Index;
    }
    else //get block from the outer file
    {
      var refDoc = Rhino.FileIO.File3dm.Read(filePath);

      var instDefs = refDoc.AllInstanceDefinitions;

      //find the blocks needed
      var instDef = instDefs.Where(d => d.Name == blockName).FirstOrDefault();

      if (instDef != null)
      {
        //add layers to current document otherwise imported blocks will loose layers/color
        var refLayers = refDoc.AllLayers;
        foreach (var layer in refLayers)
        {
          RhinoDocument.Layers.Add(layer);
        }

        //find block Objects
        var refObjects = refDoc.Objects;
        var ids = instDef.GetObjectIds();
        var objs = ids.Select(id => refObjects.FindId(id));

        //Add Instance Definition to the current file
        index = RhinoDocument.InstanceDefinitions.Add(blockName, string.Empty, Point3d.Origin, objs.Select(o => o.Geometry), objs.Select(o => o.Attributes));
      }
    }

    //Place new block to the document
    var xform = Transform.Identity;
    RhinoDocument.Objects.AddInstanceObject(index, xform);

}

4 Likes

Can anyone please tell me where in GH interface i should put this (above post) code to make it work?

If you just need to place an external block you can use Elefront plugin

If you need some more control over the process you need to place the code in c# component and refine it there. See the attached example.

place block from external file.gh (3.5 KB)

i was little bit dissapointed that elefront handles linked blocks in fashion that the whole linked document is a “block” and does not actually insert blocks from that document. I suppose your script works in that way. I am going to try thanks :slight_smile:

It seems to work except there is an issue with layers.
Source file with block “1” is originally put on layer “block”


Insides of the block is places on layer “lala”

After placement in other document using the script layers are somehow messed. Block is placed on “default” layer instead on block layer and insides are placed on mysterious “layer 01” instead “lala” .layer 01 had not existed in the source file nor current file.

There is probably some small error in the script. I took a look at it but i cant help myself :smiley:

I believe if you keep the same layer structure in block file and in your main file everything should work fine. It might not be always possible though.

I’ll try to import needed layers from the block file later today

Thank you for the favor. I believe it is just small adjusment to the script.

The point is i am creating larger script which strongly depends on the ability to load blocks from other files retaining all the attributes.

I need same behaviour via GH just like if i copy paste a block from other file (which brings its layers etc with it).

Actually i dont even need to place the component in the model space, only block defined (visible only in blockmanager) is preffered.

Moment I get my blocks from other files loaded (defined in blockmanager) i will then use elefront to insert the block by name from my current file.

I found an old code I used for importing facade blocks from the ‘library’ file. It doesn’t import block attributes but it does import all the layers from the file with blocks.

Try it out

import blocks from external file.gh (5.3 KB)

Moment I get my blocks from other files loaded (defined in blockmanager) i will then use elefront to insert the block by name from my current file.

This can be tricky. It might require to “recompute” solution in order to work properly.

Best,
Max

1 Like

This one seems to work but it omits lot of data regarding blocks. It does not bring material used in the block etc. It works reliably just for geometry.

I see only viable way how to achieve what i need is to manually import files containing blocks from Rhino interface. Then delete everything in the model space and rest can be done with elefront insert block component.

I find it bit disappointing though it is not possible to control Rhino from GH straight forwardly with Rhino commands. All would be much easier. This particular operation (insert block from other file) can be done inside rhino with ctrl c ctrl v just copying and pasting block from other file but to achieve the same thing you would need hundred line code in GH.

Nevermind. Thank you for this anyways.

Actually i found a post of your regarding automated printing from GH i tried it with PDF creator it seems to work somewhat but when generating pdfs for more branches all pdfs seem to be the same no matter separation in data provided into script. Do you still use it?

I’ve been playing with your code and the problem seems to be that bringing the object in, it brings along the layer index it used to have in the old file rather than use the layer name to find its index in the new file.

This is great, since I can see a possible solution. Temporarily pull the names of the layers into an array. Pull the names of layers in the new documents too. Now we have a map of which layers should be changed according to the numbering of the new document.

When writing the layers, pass the new layer index instead of the old one. This is where my coding skills fall short. I cannot break it down any further than seeing you’ve assigned everything using methods like

objs.Select(o => o.Attributes)

I have the exact same issue with pulling blocks from a single library file. Did you end up getting anything more done on it?

As far as I remember I eventually made a method for importing all nessesary layers along with the blocks.
Try this one

It adds blocks to the BlockDefinition table, so you can place them with RhinoDocument.Objects.AddInstanceObject(index, xform);

1 Like

After lots of messing around, got it working. Mixed results though.

The layers are getting applied in the correct locations, but not all the geometry is getting done. Only one piece of geometry per layer is getting assigned. Everything else is default.

The strangest part is that once the layer gets used in any block, no other blocks with the same layers ever get it applied anywhere.

Also, layer hierarchy is lost. Parent layers aren’t imported.

image


Also, for anybody who comes after and wants to know what I did to get it working:

image
filePaths needs list access
the top two inputs are strings, the bottom are booleans

private void RunScript(List filePaths, string blockName, bool attach, bool all, ref object A)
** {**

    A = ImportBlocks(filePaths, blockName, all);

    /// <summary>
    /// Finds and imports blocks from library files.
    /// If block with the same name exists in the current file then nothing is imported.
    /// </summary>
    /// <param name="filePaths">File paths to library 3dm files with blocks. If there are several blocks with the same name the first one will be imported.</param>
    /// <param name="blockName">Block name. Masks(* or ?) can be used.</param>
    /// <param name="all">If true, all blocks matching this mask <paramref name="blockName"/> will be imported.
    /// If false, only first one will be imported.
    /// Set true only if you use masks (such as blockName_*).</param>
    /// <returns>Existing or newly created BlockDefenition index.</returns>
  }

  internal static List <int> ImportBlocks(List <string> filePaths, string blockName, bool all)
  {
    var iDefIndexes = new List<int>();

    if (string.IsNullOrEmpty(blockName)) return iDefIndexes;
    string blockNamePattern = WildcardToRegex(blockName);

    var rhinodoc = Rhino.RhinoDoc.ActiveDoc;
    var allDefsNames = rhinodoc.InstanceDefinitions.Select(d => d.Name).ToList();
    List<string> defNames = allDefsNames
      .Where(n => n != null)
      .Where(n => Regex.IsMatch(n, blockNamePattern))
      .ToList();

    foreach (var defName in defNames)
    {
      var instanceDefinition = rhinodoc.InstanceDefinitions.Find(defName);
      iDefIndexes.Add(instanceDefinition.Index);
      if (!all) return iDefIndexes; // выходим, если нужен только один блок.
    }

    if (filePaths != null && filePaths.Any()) // если есть пути то нужно посмотреть в файлах-библиотеках.
    {
      foreach (var filePath in filePaths)
      {
        using (var libraryDoc = Rhino.RhinoDoc.CreateHeadless(null))
        {
          if (!System.IO.File.Exists(filePath))
          {
            // AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Path {filePath} doesn't exist");
            continue;
          }

          if (!libraryDoc.Import(filePath)) continue;

          var allLibDefsNames = libraryDoc.InstanceDefinitions.Select(d => d.Name).ToList();
          var libDefNames = allLibDefsNames
            .Where(n => Regex.IsMatch(n, blockNamePattern))
            .Where(n => !defNames.Contains(n)) // лишний раз не перезаписывать существующие с таким же именем!
            .ToList();

          foreach (var libDefName in libDefNames)
          {
            var libraryInstanceDefinition = libraryDoc.InstanceDefinitions.Find(libDefName);

            if (libraryInstanceDefinition.IsDeleted)
            {
              libraryInstanceDefinition = null;
              continue;
            }

            var geom = libraryInstanceDefinition.GetObjects().Select(o => o.Geometry);
            var attr = libraryInstanceDefinition.GetObjects().Select(o => o.Attributes);
            foreach (var att in attr)
            {
              var layerIndex = att.LayerIndex;
              var outerLayer = libraryDoc.Layers[layerIndex];
              var layerFullName = outerLayer.FullPath;

              var localLayerIndex = rhinodoc.Layers.FindByFullPath(layerFullName, -1);
              if (localLayerIndex == -1)
              {
                var materialIndex = AddLayerMaterial(outerLayer, libraryDoc, rhinodoc);
                var newIndex = rhinodoc.Layers.Add(outerLayer);
                att.LayerIndex = newIndex;

                var addedLayer = rhinodoc.Layers[newIndex];
                addedLayer.RenderMaterialIndex = materialIndex; //???
              }
              else
              {
                att.LayerIndex = localLayerIndex;
              }
            }

            var iDefIndex = rhinodoc.InstanceDefinitions.Add(libraryInstanceDefinition.Name, libraryInstanceDefinition.Description, libraryInstanceDefinition.Url, libraryInstanceDefinition.UrlDescription, Rhino.Geometry.Point3d.Origin, geom, attr);
            if (iDefIndex >= 0)
            {
              iDefIndexes.Add(iDefIndex);
              if (!all)
              {
                return iDefIndexes;
              }
            }
          }
        }
      }
    }

    return iDefIndexes;
  }

  private static int AddLayerMaterial(Rhino.DocObjects.Layer outerLayer, Rhino.RhinoDoc libraryDoc, Rhino.RhinoDoc rhinodoc)
  {
    var material = outerLayer.RenderMaterial;
    var materialIndex = outerLayer.RenderMaterialIndex;
    if (material == null)
    {
      material = libraryDoc.Materials[materialIndex].RenderMaterial;
    }

    var localMaterialIndex = rhinodoc.Materials.Find(material.Name, true);
    if (localMaterialIndex == -1)
    {
      if (materialIndex != -1)
      {
        var outerMaterial = libraryDoc.Materials[materialIndex];
        localMaterialIndex = rhinodoc.Materials.Add(outerMaterial);
      }
    }

    return localMaterialIndex;
    //rhinodoc.RenderMaterials.
  }

  public static string WildcardToRegex(string pattern)
  {
    return "^" + System.Text.RegularExpressions.Regex.Escape(pattern)
      .Replace(@"\*", ".*")
      .Replace(@"\?", ".")
      + "$";

}

You will also need to make sure these declarations are at the top:
using System.Linq;
using System.Text.RegularExpressions;