# CSV to Table

**URL:** <https://discourse.mcneel.com/t/csv-to-table/181685>\
**Category:** Grasshopper\
**Tags:** windows, python, grasshopperrhino\
**Created:** [May 8, 2024, 12:25pm UTC](https://discourse.mcneel.com/t/csv-to-table/181685 "2024-05-08T12:25:36Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Keelver\_De\_Asis](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/keelver_de_asis/32/654666_2.png) [@Keelver\_De\_Asis](https://discourse.mcneel.com/u/Keelver_De_Asis)\
**Post date:** [May 8, 2024, 12:25pm UTC](https://discourse.mcneel.com/t/csv-to-table/181685/1 "2024-05-08T12:25:36Z")

</div>

Hi All,

Is there someone who can edit the first python script so that you can preview the result before baking? similar to the 2nd python script. Thanks!

1st: [Quick Bom Generation from CSV.gh](https://discourse.mcneel.com/uploads/short-url/zgM3q2aSsIYbzBHRS2sts0ug8f6.gh) (8.7 KB)  
2nd:  
[DataGrid\_test.gh](https://discourse.mcneel.com/uploads/short-url/ficpkvcKViRRSHY92RZwlPWLk74.gh) (12.2 KB)

---

<div class="post-metadata">

**Author:** ![James\_Parrott](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/james_parrott/32/713265_2.png) [@James\_Parrott](https://discourse.mcneel.com/u/James_Parrott)\
**Post date:** [May 8, 2024, 2:07pm UTC](https://discourse.mcneel.com/t/csv-to-table/181685/2 "2024-05-08T14:07:17Z")

</div>

Have you asked JWalton or Florian, the respective authors of those scripts?

Quick Bom Generation from CSV.gh

```auto
"""Provides a scripting component.
    Inputs:
        x: The x script variable
        y: The y script variable
    Output:
        a: The a output variable"""

__author__ = "JWalton"
__version__ = "2019.11.28"

import rhinoscriptsyntax as rs
import Rhino
import Rhino as rc
import scriptcontext as sc
import Rhino.Geometry as rg
from operator import itemgetter

# Set the script context to the current Rhino doc
sc.doc = rc.RhinoDoc.ActiveDoc

def CSVBom():

    listA = _csv
    #a = listA
    plane=Rhino.Geometry.Plane.WorldXY
    ListTotals = []
    table = _pointFeed
    rs.EnableRedraw(False)
    ListLarge = []
    ListLen = []
    a=0

    for row in listA:
                row = row.split(";")
                rowA = []
                for str in row:
                    str = str.replace("\n","")
                    rowA.append(str)
                rowB = []
                for aa in rowA:
                    rowB=aa.split(',')
                if len(rowB)<= len(rowA):rowB=rowA
                list=[]
                A=len(rowB)
                ListLen.append(A)
                for txt in rowB:
                    if txt :
                       test = rs.AddText(txt, plane, height=_textSize, font='Arial', font_style=0, justification=None)
                       bb = rs.BoundingBox(test, plane, in_world_coords=True)
                       if bb :
                           large =round(( bb[1].DistanceTo(bb[0])),1)
                           if large < 0: largeur=large*(-1)
                           list.append(large)
                           rs.DeleteObject(test)
                       else : 
                           large=0.0
                           list.append(large)
                    else : 
                           large=0.0
                           list.append(large)
                ListLarge.append(list)

    N = max(ListLen)
    d_max = {i: max(map(itemgetter(i), ListLarge)) for i in range(N)}
    #Largest 
    ListMax = d_max.values()
    #Layers
    rs.AddLayer("Name")
    rs.AddLayer("Detail Lines",parent="Name")
    rs.AddLayer("Texts",parent="Name")

    
    listB = _csv

    Group=rs.AddGroup()
    ListGroup = []
    H = _textSize
    font='Arial'
    count=1

    for row in listB:
        V=0
        Y=0
        row = row.split(";")
        rowA = []
        for str in row:
            str = str.replace("\n","")
            rowA.append(str)
        rowB = []
        for aa in rowA:
            rowB=aa.split(',')
        if len(rowB)<= len(rowA):rowB=rowA
        for value in rowB:
            #text placement
            placeA= ((-H*2) - (float(count)*(H*2)))
            placeB= ((-H) - (float(count)*(H*2)))
            #rectangle
            LX = (ListMax[Y])+(H*.25+H)
            recA=rs.AddRectangle(plane,LX,H*2)
            VV=V+(LX/2)
            recA=rs.MoveObject(recA,[V,placeA,0.0])
            rs.ObjectLayer(recA,"Detail Lines")
            ListGroup.append(recA)
            #text
            txt=value
            if txt :
                txtA=rs.AddText (txt,plane,H,font,0,2 + 131072)
                txtA=rs.MoveObject(txtA,[VV,placeB,0.0])
                rs.ObjectLayer(txtA,"Texts")
                ListGroup.append(txtA)
            #new text placement
            Y=Y+1
            V=V+LX
            #New Line
        count=count+1
    ListGroup=rs.MoveObjects(ListGroup,table)
# rs.EnableRedraw(True)
    rs.AddObjectsToGroup(ListGroup,Group)

if x:
    CSVBom()

# Then set the script context back to the Grasshopper doc
sc.doc = ghdoc

```

DataGrid\_test.gh

```auto
"""Provides a scripting component.
    Inputs:
        x: The x script variable
        y: The y script variable
    Output:
        a: The a output variable"""

__author__ = "Florian"
__version__ = "2020.02.06"

import Rhino as rh
import scriptcontext as sc
import ghpythonlib as gh

vec = rh.Geometry.Vector3d(0,0,1)
Pl = rh.Geometry.Plane(P,vec)

# get dimstyle and change settings
ds = rh.DocObjects.DimensionStyle()

# change text properties ( a lot of options )
ds.Font = rh.DocObjects.Font(F)
ds.TextHeight = H

LL = [] #TextEntity_Width_Collumn
LLS = [] #TextEntity_Collumn
BB = [] #TextEntity_BoundingBox_Column
BHeight = [] #TextEntity_Height_All
for i in range(0,T.BranchCount):
    B = [] # TextEntity_BoundingBox
    BB.append(B) 
    L = [] # TextEntity_Width
    LL.append(L)
    LS = [] # TextEntity 
    LLS.append(LS)
    for j in T.Branch(i):
        Te = rh.Geometry.TextEntity
        te = Te.Create(j,Pl,ds,False,0,0)
        
        LS.append(te)
        L.append(te.TextModelWidth)
        Bounding_Box = te.GetBoundingBox(True)
        B.append(Bounding_Box)
        BHeight.append(abs(Bounding_Box.Min.Y-Bounding_Box.Max.Y))

# Get TextEntity_BoundingBox_with max width and inflate them with max height
LB = [] # TextEntity_BoundingBox with max width and height inflated
for e,i in enumerate(LL):
    ind = i.index(max(i)) #index with max Textentity_BoundingBox_Width
    BB_Max_W_H = BB[e][ind]
    Max_Width_Height = abs(abs(BB_Max_W_H.Min.Y-BB_Max_W_H.Max.Y)-max(BHeight))/2
    Width = abs(BB_Max_W_H.Min.X-BB_Max_W_H.Max.X)
    BB_Max_W_H.Inflate(0,Max_Width_Height,0)
    LB.append(BB_Max_W_H)

# inflate TransB
for i in LB:
    widthB = abs(i.Min.X-i.Max.X)
    i.Inflate(widthB*0.05,0,0)
    tr = rh.Geometry.Transform.Translation(widthB*0.05,0,0)
    i.Transform(tr)

FirstP = LB[0].GetCorners()[1]
counter = 0
TransB = []
Transf = []
TransB.append(LB[0])
def TransBox(FirstP,counter):
    if counter < len(LB)-1:
        counter = counter + 1
        Vec = rh.Geometry.Vector3d(0,0,1)
        PlaneTo = rh.Geometry.Plane(FirstP,Vec)
        B = LB[counter]
        PlaneFrom = rh.Geometry.Plane(B.Min,Vec)
        Trans = rh.Geometry.Transform.PlaneToPlane(PlaneFrom,PlaneTo)
        bbtr = Trans.TransformBoundingBox(LB[counter])
        NextP = bbtr.GetCorners()[1]
        TransB.append(bbtr)
        Transf.append(Trans)
        return TransBox(NextP,counter)
    else:
        return False

TransBox(FirstP,counter)

# get longest BranchList
ListLenght = []
for i in range(0,T.BranchCount):
     ListLenght.append(len(T.Branch(i)))

# Create Transform for Bounding_Box_Grid
BBox_Height = rh.Geometry.Transform.Translation(0,TransB[0].Min.Y-TransB[0].Max.Y,0)
count = 0
TransBB = []
TransBB.append(BBox_Height)
def TransB_to_grid(BBox_Height,TransBB,count):
    if count < max(ListLenght)-2:
        count = count + 1
        TransBB.append(TransBB[-1]*BBox_Height)
        return TransB_to_grid(BBox_Height,TransBB,count)
    else:
        return False

TransB_to_grid(BBox_Height,TransBB,count)

# Create Bounding_Box_Grid
B_Grid = []
for i in TransB:
    Grid = []
    Grid.append(i)
    B_Grid.append(Grid)
    for j in TransBB:
        Grid.append(j.TransformBoundingBox(i))

# Create Union BoundingBox from BoundingBoxGrid

UB_Grid = rh.Geometry.BoundingBox.Unset
for i in B_Grid:
    for j in i:
        UB_Grid.Union(j)

# Move TextEntity to BoundingBoxGrid
Vec = rh.Geometry.Vector3d(0,0,1)
for e,i in enumerate(B_Grid):
    for ee,ii in enumerate(i):
        # Get transform TextEntityBoundingBox to gridBox
        Planefrom = rh.Geometry.Plane(BB[e][ee].Min,Vec)
        Planeto = rh.Geometry.Plane(ii.Min,Vec)
        trans = rh.Geometry.Transform.PlaneToPlane(Planefrom,Planeto)
        LLS[e][ee].Transform(trans,ds)

# left top corner
CLT = UB_Grid.GetCorners()[3]
LT = rh.Geometry.Transform.PlaneToPlane(rh.Geometry.Plane(CLT,Vec),Pl)

# left bottom corner
CLB = UB_Grid.GetCorners()[0]
LB = rh.Geometry.Transform.PlaneToPlane(rh.Geometry.Plane(CLB,Vec),Pl)

# right top corner
CRT = UB_Grid.GetCorners()[2]
RT = rh.Geometry.Transform.PlaneToPlane(rh.Geometry.Plane(CRT,Vec),Pl)

# right bottom corner
CRB = UB_Grid.GetCorners()[1]
RB = rh.Geometry.Transform.PlaneToPlane(rh.Geometry.Plane(CRB,Vec),Pl)

for e,i in enumerate(B_Grid):
    for ee,ii in enumerate(i):
        if C == 'LeftTop':
            LLS[e][ee].Transform(LT,ds)
            ii.Transform(LT)
        elif C == C == 'LeftBottom':
            LLS[e][ee].Transform(LB,ds)
            ii.Transform(LB)
        elif C == C == 'RightTop':
            LLS[e][ee].Transform(RT,ds)
            ii.Transform(RT)
        elif C == C == 'RightBottom':
            LLS[e][ee].Transform(RB,ds)
            ii.Transform(RB)

# create TextEntity Surface 
TextS = []
for e,i in enumerate(LLS):
    TextSS = []
    TextS.append(TextSS)
    for j in i:
        TextSS.append(j.CreateSurfaces(ds,0,0))

# BoundingBox_Grid to Rectangle Grid
Rect_Grid = []
for i in B_Grid:
    Grid = []
    Rect_Grid.append(Grid)
    for j in i:
        Grid.append(rh.Geometry.Rectangle3d(Pl,j.Min,j.Max))

L = []
L.append(LLS)
L.append(B_Grid)

# TextSurface and Rectangles into DataTrees
S = gh.treehelpers.list_to_tree(TextS)
G = gh.treehelpers.list_to_tree(Rect_Grid)

# textentity rectangle
#box = rh.Geometry.Rectangle3d(P,bb.Min,bb.Max)

# bake text with same settings like textentity to get rhinotext object
if Bake:
    sc.doc = rh.RhinoDoc.ActiveDoc
    for e,i in enumerate(LLS):
        for ee,j in enumerate(i):
            rh.RhinoDoc.ActiveDoc.Objects.AddText(j.Text,j.Plane,j.TextHeight,F,False,False)
            rh.RhinoDoc.ActiveDoc.Objects.AddRectangle(Rect_Grid[e][ee])
    sc.doc = ghdoc

#rh.Display.DisplayPipeline.DrawBox()

```

---

<div class="post-metadata">

**Author:** ![Keelver\_De\_Asis](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/keelver_de_asis/32/654666_2.png) [@Keelver\_De\_Asis](https://discourse.mcneel.com/u/Keelver_De_Asis)\
**Post date:** [May 8, 2024, 9:12pm UTC](https://discourse.mcneel.com/t/csv-to-table/181685/3 "2024-05-08T21:12:49Z")

</div>

Hi @James_Parrott ,

Thank you for posting the codes itself. I already asked the author from this forum

> [@Wish For Rhino 6: BOM Generator](https://discourse.mcneel.com/t/wish-for-rhino-6-bom-generator/3355):
>
> I would really like to see a ‘Bill of Materials Generator’ in Rhino 6. The Generator would basically take your work and generate a cost estimate for you based on your assigned materials and/or various pre-made objects you have input into your work (i.e. number of buttons for a shirt you want to create, or feet of steel piping and bolts, or number of light bulbs etc…). so you would take a pipe shape you have created, assign to it the material ‘steel’ - and from this information the cost of steel…

but no reply from him yet.

---

<div class="post-metadata">

**Author:** ![James\_Parrott](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/james_parrott/32/713265_2.png) [@James\_Parrott](https://discourse.mcneel.com/u/James_Parrott)\
**Post date:** [May 9, 2024, 9:22am UTC](https://discourse.mcneel.com/t/csv-to-table/181685/4 "2024-05-09T09:22:31Z")

</div>

Great. It’s good to check. Who owns the first file by the way?

To get a “preview” in Grasshopper, the things to be previewed just need to be sent to a global variable, and an output param of the same name added to the Python component. The component in the second file has output params called `S` and `G`, that’s all that’s needed for a “preview” (outputting objects into Grasshopper from GhPython).

The complicated part is the first script does everything to the Rhino document on the fly, with rhinoscriptsyntax targetting Rhino:

> [@](#):
>
> `sc.doc = rc.RhinoDoc.ActiveDoc`

But the second uses Rhino Common and targets Grasshopper (`ghdoc`) for most of it, only switching the context to `Rhino.RhinoDoc.ActiveDoc` at the end `if Bake`.

So it might not take long at all to make the basic refactor above, but more is needed to guarantee it will all work as wished, i.e. testing it. The results of each individual modifications to the Rhino document (every call to `rs.Add`something and `rs.Move`something) need to be checked that they both work and produce the same change in the different Grasshopper context, and then baked afterwards.

I am definitely able to do this. However from experience, adapting code to run in a different context to the only context it was originally written and tested for, can quickly get fiddly. It can be quicker to rewrite it entirely. So this might only take an hour at best, but at worst it could require two days.

---

<div class="post-metadata">

**Author:** ![Keelver\_De\_Asis](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/keelver_de_asis/32/654666_2.png) [@Keelver\_De\_Asis](https://discourse.mcneel.com/u/Keelver_De_Asis)\
**Post date:** [May 9, 2024, 10:13am UTC](https://discourse.mcneel.com/t/csv-to-table/181685/5 "2024-05-09T10:13:34Z")

</div>

Hi @James_Parrott ,

Sorry for bothering you with this one. I have little to no knowledege about python or other coding. It will be great and very helpful not only for me but to others also if you will be able to do it.

Regarding the first file, it is from this link from @Rickson if im not mistaken:

> [@Excel to table in layout](https://discourse.mcneel.com/t/excel-to-table-in-layout/96140/12):
>
> def CSVBom(): listA = \_csv #a = listA plane=Rhino.Geometry.Plane.WorldXY ListTotals = [] table = \_pointFeed rs.EnableRedraw(False) ListLarge = [] ListLen = [] a=0 for row in listA: row = row.split(";") rowA = [] for str in row: str = str.replace("\n","") rowA.append(str) rowB = [] for aa in rowA: rowB=aa.split(',…

---

<div class="post-metadata">

**Author:** ![Cumberland](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/cumberland/32/755683_2.png) [@Cumberland](https://discourse.mcneel.com/u/Cumberland)\
**Post date:** [May 14, 2024, 3:41pm UTC](https://discourse.mcneel.com/t/csv-to-table/181685/6 "2024-05-14T15:41:29Z")

</div>

I had some time to check this scripts and I added `Text Size` and `Text Alignment` options to the [DataGrid\_test.gh](https://discourse.mcneel.com/uploads/short-url/ficpkvcKViRRSHY92RZwlPWLk74.gh) definition.

 ![Data_Grid](https://global.discourse-cdn.com/mcneel/uploads/default/original/4X/f/3/4/f346782ea5fc76b5b275a5cd15a4d4906a99e5ce.png)

[DataGrid.gh](https://discourse.mcneel.com/uploads/short-url/3FHK90NjOJxC6gUw6wnOidbBy20.gh) (14.5 KB)

Will be good if the Python script have an option to output Text entities to be able to edit the text in Rhino.

---

<div class="post-metadata">

**Author:** ![Cumberland](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/cumberland/32/755683_2.png) [@Cumberland](https://discourse.mcneel.com/u/Cumberland)\
**Post date:** [May 15, 2024, 7:59am UTC](https://discourse.mcneel.com/t/csv-to-table/181685/7 "2024-05-15T07:59:49Z")

</div>

Made some additional progress. Found a way to generate and bake Text entities using the Elefront components, however looks like there is a bug into the `Define Text Object` component because the justification and font size do not override the default style at bake time. I can see on the viewport the justification and font size changes but at bake time will reset everything to the values inherited from the default `Dimension Style` used.

 ![Data_Grid_Elefront](https://global.discourse-cdn.com/mcneel/uploads/default/original/4X/f/7/d/f7ddbd94c348cab526a808b5f5e565afeaf539eb.png)

[DataGrid\_Elefront.gh](https://discourse.mcneel.com/uploads/short-url/vEF41nyQxdC0it7EU5kb48mUc8s.gh) (24.6 KB)

---

<div class="post-metadata">

**Author:** ![krahimzadeh](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/krahimzadeh/32/766963_2.png) [@krahimzadeh](https://discourse.mcneel.com/u/krahimzadeh)\
**Post date:** [May 22, 2024, 4:56pm UTC](https://discourse.mcneel.com/t/csv-to-table/181685/8 "2024-05-22T16:56:27Z")

</div>

Interesting. Seems to work in Rhino 8. I’ve got it fixed in Rhino 7, will push an update in the next few days when I wrap some other fixes as well.

---

<div class="post-metadata">

**Author:** ![krahimzadeh](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/krahimzadeh/32/766963_2.png) [@krahimzadeh](https://discourse.mcneel.com/u/krahimzadeh)\
**Post date:** [July 18, 2024, 6:17pm UTC](https://discourse.mcneel.com/t/csv-to-table/181685/9 "2024-07-18T18:17:01Z")

</div>

This should be fixed in 5.1.9:

> [@eleFront Releases, Updates, Fixes](https://discourse.mcneel.com/t/elefront-releases-updates-fixes/188242/2):
>
> eleFront 5.1.9-beta This update includes a number of fixes. I’m designating it as a ‘beta’ to get feedback on some of the changes. Bugs resolved in this version: Previous version did not maintain overrides for Text objects, such as Justification, Size, etc. Objects on locked Layers not deleted when baking In some instances, Baking to an external file did not maintain the objects’ attributes Updates / Changes The eleFront Layer component has been changed. You can now also pass the componen…
