Excel Reading Limitation

I’m using Bumblebee to read data into Rhino. My Excel files have about 500 000 rows each but I cannot get in more than ± 65 000 rows into Grasshopper at a time. In the old days, Excel had a limit of 65 000 rows but since 2007 one file can contain up to 1 048 576 rows.

Does anybody know if it’s possible (and how) to go beyond the 65K lines?

The Bumblebee gh userobject is as follows:


Option Strict Off
Option Explicit On

Imports Rhino
Imports Rhino.Geometry
Imports Rhino.DocObjects
Imports Rhino.Collections

Imports GH_IO
Imports GH_IO.Serialization
Imports Grasshopper
Imports Grasshopper.Kernel
Imports Grasshopper.Kernel.Data
Imports Grasshopper.Kernel.Types

Imports System
Imports System.IO
Imports System.Xml
Imports System.Xml.Linq
Imports System.Linq
Imports System.Data
Imports System.Drawing
Imports System.Reflection
Imports System.Collections
Imports System.Windows.Forms
Imports Microsoft.VisualBasic
Imports System.Collections.Generic
Imports System.Runtime.InteropServices



''' <summary>
''' This class will be instantiated on demand by the Script component.
''' </summary>
Public Class Script_Instance
  Inherits GH_ScriptInstance

  #Region "Utility functions"
  ''' <summary>Print a String to the [Out] Parameter of the Script component.</summary>
  ''' <param name="text">String to print.</param>
  Private Sub Print(ByVal text As String)
    __out.Add(text)
  End Sub
  ''' <summary>Print a formatted String to the [Out] Parameter of the Script component.</summary>
  ''' <param name="format">String format.</param>
  ''' <param name="args">Formatting parameters.</param>
  Private Sub Print(ByVal format As String, ByVal ParamArray args As Object())
    __out.Add(String.Format(format, args))
  End Sub
  ''' <summary>Print useful information about an object instance to the [Out] Parameter of the Script component. </summary>
  ''' <param name="obj">Object instance to parse.</param>
  Private Sub Reflect(ByVal obj As Object)
    __out.Add(GH_ScriptComponentUtilities.ReflectType_VB(obj))
  End Sub
  ''' <summary>Print the signatures of all the overloads of a specific method to the [Out] Parameter of the Script component. </summary>
  ''' <param name="obj">Object instance to parse.</param>
  Private Sub Reflect(ByVal obj As Object, ByVal method_name As String)
    __out.Add(GH_ScriptComponentUtilities.ReflectType_VB(obj, method_name))
  End Sub
#End Region
  
#Region "Members"
  ''' <summary>Gets the current Rhino document.</summary>
  Private RhinoDocument As RhinoDoc
  ''' <summary>Gets the Grasshopper document that owns this script.</summary>
  Private GrasshopperDocument as GH_Document
  ''' <summary>Gets the Grasshopper script component that owns this script.</summary>
  Private Component As IGH_Component
  ''' <summary>
  ''' Gets the current iteration count. The first call to RunScript() is associated with Iteration=0.
  ''' Any subsequent call within the same solution will increment the Iteration count.
  ''' </summary>
  Private Iteration As Integer
#End Region

  ''' <summary>
  ''' This procedure contains the user code. Input parameters are provided as ByVal arguments, 
  ''' Output parameter are ByRef arguments. You don't have to assign output parameters, 
  ''' they will have default values.
  ''' </summary>
  Private Sub RunScript(ByVal XL_Path As List(Of Object), ByVal Origin As Point3d, ByVal Bound As Point3d, ByVal ByColumn As Boolean, ByVal Type As Integer, ByVal _Stream As Boolean, ByRef A As Object, ByRef B As Object, ByRef Data As Object) 
    Component.Params.Input(0).Name = "Path"
    Component.Params.Input(0).Description = "Optional input for the XL Path component which specifies the Target Workbook and Sheet. If blank the active workbook and worksheet is used, or the worksheet index supplied by an origin component"
    Component.Params.Input(1).Name = "Data Origin"
    Component.Params.Input(1).Description = "Optional point input which specifies the lower bounds from which data will be read where X = Row, Y = Column, and Z = Sheet Index. If Z = 0 the active sheet will be used. If input is empty first used cell on active sheet will be used"
    Component.Params.Input(2).Name = "Data Bound"
    Component.Params.Input(2).Description = "Optional point input which specifies the upper bounds from which data will be read where X = Row, Y = Column, and Z = Sheet Index. If Z = 0 the active sheet will be used, If input is empty last used cell on active sheet will be used"
    Component.Params.Input(3).Name = "By Column"
    Component.Params.Input(3).Description = "Sets if data will be transposed"
    Component.Params.Input(4).Name = "Type"
    Component.Params.Input(4).Description = "Optional input for the XL Data Type component or integer value specifying the type of data being read [Value = 0, Value2 = 1, Formula = 2, FormulaLocal = 3, FormulaR1C1 = 4, FormulaR1C1Local = 5]"
    Component.Params.Input(5).Name = "Stream"
    Component.Params.Input(5).Description = "If true, data will be read"

    Component.Params.Output(1).Name = "Origin"
    Component.Params.Output(1).Description = "List output of points which specify the lower bounds of read data"
    Component.Params.Output(2).Name = "Bound"
    Component.Params.Output(2).Description = "List output of points which specify the upper bounds of read data"
    Component.Params.Output(3).Name = "Data"
    Component.Params.Output(3).Description = "Datatree of generic data read from Excel"
    If _Stream = True Then
      'If _LIVE = True Then owner.ExpireSolution(True)

      Dim objExcel, excelWorkbook, excelSheet As Object

      If XL_Path.Count() > 1 Then
        objExcel = XL_Path.item(0)
        excelWorkbook = XL_Path.item(1)
        excelSheet = XL_Path.item(2)
      Else
        Dim oldCI As System.Globalization.CultureInfo = system.Threading.Thread.CurrentThread.CurrentCulture
        System.Threading.Thread.CurrentThread.CurrentCulture = New System.Globalization.CultureInfo("en-US")

        'Access Active Session of Excel if no Path is specified

        objExcel = System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application")
        excelWorkbook = objExcel.activeWorkbook
        excelSheet = objExcel.ActiveSheet
      End If

      objExcel.screenUpdating = False

      Dim arrStr As New datatree(Of Object)
      Dim i,j,k,m,n As Integer

      If XL_Path.count() <= 1 Then
        If CInt(origin.z) = 0 Then
          n = 0
          excelSheet = objExcel.ActiveSheet
        ElseIf CInt(bound.z) = 0 Then
          n = 0
          excelSheet = excelWorkbook.Sheets.item(CInt(origin.Z))
        Else
          n = (bound.z - origin.z)
          excelSheet = excelWorkbook.Sheets.item(CInt(origin.Z))
        End If
      End If

      'specify a range in excel and stream data
      For m = 0 To n Step 1
        If m <> 0 Then
          excelSheet = excelWorkbook.Sheets.item(CInt(origin.Z + n))
        End If

        If Origin = Nothing Then
          Origin.Y = excelSheet.usedrange.row
          Origin.X = excelSheet.usedrange.column
        End If
        If Bound = Nothing Then
          Bound.Y = excelSheet.usedrange.rows(excelSheet.usedrange.rows.count).row
          Bound.X = excelSheet.usedrange.columns(excelSheet.usedrange.columns.count).column
        End If
        Dim rng As Object
        If (origin.x = bound.x) And (origin.y = bound.y) Then
          arrStr.add(excelSheet.Cells(Origin.Y, Origin.X).value, New GH_Path(0))
        Else

          If type = 1 Then
            rng = excelSheet.Range(excelSheet.Cells(Origin.Y, Origin.X), excelSheet.Cells(Bound.Y, Bound.X)).value2
          ElseIf type = 2 Then
            rng = excelSheet.Range(excelSheet.Cells(Origin.Y, Origin.X), excelSheet.Cells(Bound.Y, Bound.X)).formula
          ElseIf type = 3 Then
            rng = excelSheet.Range(excelSheet.Cells(Origin.Y, Origin.X), excelSheet.Cells(Bound.Y, Bound.X)).formulalocal
          ElseIf type = 4 Then
            rng = excelSheet.Range(excelSheet.Cells(Origin.Y, Origin.X), excelSheet.Cells(Bound.Y, Bound.X)).FormulaR1C1
          ElseIf type = 5 Then
            rng = excelSheet.Range(excelSheet.Cells(Origin.Y, Origin.X), excelSheet.Cells(Bound.Y, Bound.X)).FormulaR1C1Local
          Else
            rng = excelSheet.Range(excelSheet.Cells(Origin.Y, Origin.X), excelSheet.Cells(Bound.Y, Bound.X)).value
          End If

          If ByColumn = True Then
            rng = objExcel.transpose(rng)
          End If
          k = 0
          For i = LBound(rng, 1) To UBound(rng, 1) Step 1
            For j = LBound(rng, 2) To UBound(rng, 2) Step 1
              If n = 0 Then
                arrStr.add(rng(i, j), New GH_Path(k))
              Else
                arrStr.add(rng(i, j), New GH_Path({m,k}))
              End If
            Next
          k = k + 1
          Next
        End If
      Next
      excelSheet.activate
      objExcel.screenUpdating = True

      A = New point3d(Origin.x, origin.Y, excelSheet.Index)
      B = New point3d(Bound.x, Bound.Y, excelSheet.Index)
      Data = arrStr

    End If
  End Sub 

  '<Custom additional code> 

  '</Custom additional code> 

  Private __err As New List(Of String)
  Private __out As New List(Of String)
  Private doc As RhinoDoc = RhinoDoc.ActiveDoc            'Legacy field.
  Private owner As Grasshopper.Kernel.IGH_ActiveObject    'Legacy field.
  Private runCount As Int32                               'Legacy field.
  
  Public Overrides Sub InvokeRunScript(ByVal owner As IGH_Component, _
                                       ByVal rhinoDocument As Object, _
                                       ByVal iteration As Int32, _
                                       ByVal inputs As List(Of Object), _
                                       ByVal DA As IGH_DataAccess) 
    'Prepare for a new run...
    '1. Reset lists
    Me.__out.Clear()
    Me.__err.Clear()

    'Current field assignments.
    Me.Component = owner
    Me.Iteration = iteration
    Me.GrasshopperDocument = owner.OnPingDocument()
    Me.RhinoDocument = TryCast(rhinoDocument, Rhino.RhinoDoc)

    'Legacy field assignments
    Me.owner = Me.Component
    Me.runCount = Me.Iteration
    Me.doc = Me.RhinoDocument

    '2. Assign input parameters
    Dim XL_Path As List(Of System.Object) = Nothing
    If (inputs(0) IsNot Nothing) Then
      XL_Path = GH_DirtyCaster.CastToList(Of System.Object)(inputs(0))
    End If

    Dim Origin As Point3d = Nothing
    If (inputs(1) IsNot Nothing) Then
      Origin = DirectCast(inputs(1), Point3d)
    End If

    Dim Bound As Point3d = Nothing
    If (inputs(2) IsNot Nothing) Then
      Bound = DirectCast(inputs(2), Point3d)
    End If

    Dim ByColumn As Boolean = Nothing
    If (inputs(3) IsNot Nothing) Then
      ByColumn = DirectCast(inputs(3), Boolean)
    End If

    Dim Type As Integer = Nothing
    If (inputs(4) IsNot Nothing) Then
      Type = DirectCast(inputs(4), Integer)
    End If

    Dim _Stream As Boolean = Nothing
    If (inputs(5) IsNot Nothing) Then
      _Stream = DirectCast(inputs(5), Boolean)
    End If



    '3. Declare output parameters
  Dim A As System.Object = Nothing
  Dim B As System.Object = Nothing
  Dim Data As System.Object = Nothing


    '4. Invoke RunScript
    Call RunScript(XL_Path, Origin, Bound, ByColumn, Type, _Stream, A, B, Data)

    Try
      '5. Assign output parameters to component...
      If (A IsNot Nothing) Then
        If (GH_Format.TreatAsCollection(A)) Then
          Dim __enum_A As IEnumerable = DirectCast(A, IEnumerable)
          DA.SetDataList(1, __enum_A)
        Else
          If (TypeOf A Is Grasshopper.Kernel.Data.IGH_DataTree) Then
            'merge tree
            DA.SetDataTree(1, DirectCast(A, Grasshopper.Kernel.Data.IGH_DataTree))
          Else
            'assign direct
            DA.SetData(1, A)
          End If
        End If
      Else
        DA.SetData(1, Nothing)
      End If
      If (B IsNot Nothing) Then
        If (GH_Format.TreatAsCollection(B)) Then
          Dim __enum_B As IEnumerable = DirectCast(B, IEnumerable)
          DA.SetDataList(2, __enum_B)
        Else
          If (TypeOf B Is Grasshopper.Kernel.Data.IGH_DataTree) Then
            'merge tree
            DA.SetDataTree(2, DirectCast(B, Grasshopper.Kernel.Data.IGH_DataTree))
          Else
            'assign direct
            DA.SetData(2, B)
          End If
        End If
      Else
        DA.SetData(2, Nothing)
      End If
      If (Data IsNot Nothing) Then
        If (GH_Format.TreatAsCollection(Data)) Then
          Dim __enum_Data As IEnumerable = DirectCast(Data, IEnumerable)
          DA.SetDataList(3, __enum_Data)
        Else
          If (TypeOf Data Is Grasshopper.Kernel.Data.IGH_DataTree) Then
            'merge tree
            DA.SetDataTree(3, DirectCast(Data, Grasshopper.Kernel.Data.IGH_DataTree))
          Else
            'assign direct
            DA.SetData(3, Data)
          End If
        End If
      Else
        DA.SetData(3, Nothing)
      End If

    Catch ex As Exception
      __err.Add(String.Format("Script exception: {0}", ex.Message))
    Finally
      'Add errors and messages...
      If (owner.Params.Output.Count > 0) Then
        If (TypeOf owner.Params.Output(0) Is Grasshopper.Kernel.Parameters.Param_String) Then
          Dim __errors_plus_messages As New List(Of String)
          If (Me.__err IsNot Nothing) Then __errors_plus_messages.AddRange(Me.__err)
          If (Me.__out IsNot Nothing) Then __errors_plus_messages.AddRange(Me.__out)
          If (__errors_plus_messages.Count > 0) Then
            DA.SetDataList(0, __errors_plus_messages)
          End If
        End If
      End If
    End Try
  End Sub 
End Class

Can you read your excel sheet just using RhinoScript?

Perhaps this is just a limitation with BumbleBee?

Hi Wim,

did you try asking in the Bumblebee page/forum, so that David Mans could answer?

If you did and there is no solution, maybe in Grasshopper there are other Excel add-ins,
for example GhExcel, ykTools, ghowl, etc: http://www.food4rhino.com/browse?searchterm=excel

Giulio

Giulio Piacentino
for Robert McNeel & Associates
giulio@mcneel.com

@dale, @piac, Thanks for your reactions.
I modified the RhinoScript so that it could read the *.xlsx file and let it run for a while and now just got back in and see that all 428405 lines in one of the files were dumped to the command line.

I did ask David Mans and he answered that he believed that it either was the vb array limit, or the limit in excel. The test above seems to indicate that it’s a BumbleBee limitation. I had tried GhExcel and at least one other before I went with BumbleBee but didn’t have success with those. I guess I’ll have another look at the alternatives. I haven’t touched code for about 7 years now so I’ll refrain from diving into the BumbleBee code :sunglasses: :blush:.

cheers,
wim