# Sort metal tube

Hi,

in my work, i design some metal structure, and i have to list all tubes with quantity,lenght, with both cutting angle… it’s always square or rectangle tube, not round…
like:

• 1 x Lg 2000 35° & 15°
• 15 x Lg 1230 0° & 0°
• 3 x Lg 600 15° & -45°

i would like to make a script:
i done some function,

• named each tube,
• Sort tube by Lenght,
• i would like to put all data ( Name, lenght, Cutting angle) in the object data ( i think it’s possible with DataObject)

but for cutting angle, i search the method:
no angle is when the volume of bounding box, is equal to the volume of the Tube, so if not equal, there is one or two cutting angle. and I don’t know how can i find the value, do you have an idea?

1 Like

Well, just off the top of my head - except for very short tubes, the end surfaces should have the smallest area (there might be another type of check you could make to get the ends as well). So if you can isolate the ends as surfaces, you can probably easily calculate their angle to the beam’s main axis.

Hello and “Bonne Année!” or Happy New Year!

i don’t understand what is a “iteration over non-sequence of type NoneType”
it stop at the line 44 : “for Tube in ArrTube:”

``````# coding: utf-8
import rhinoscriptsyntax as rs

def TriTube(ArrTube):
ArrTube=ArrTube.sort(key = lambda Tube : rs.SurfaceVolume(Tube)[0])
return ArrTube

def TriSurf (ArrSurf):
ArrSurf=sorted([(rs.SurfaceArea(Surf)[0],Surf) for Surf in ArrSurf])
return ArrSurf

def TriEdge (ArrEdge):
ArrEdge=sorted([(rs.CurveLength(Edge)[0],Edge) for Edge in ArrEdge])
return ArrEdge

def Name_Tube(Tube,PrefixTube,n):
ArrSurf=rs.ExplodePolysurfaces(Tube)
ArrSurf=TriSurf(ArrSurf)
ArrEdge=rs.DuplicateEdgeCurves(ArrSurf[len(Arrsurf)], select=False)
ArrEdge=TriEdge(ArrEdge)
UDom=rs.SurfaceDomain(ArrSurf[len(ArrSurf)],0)
VDom=rs.SurfaceDomain(ArrSurf[len(ArrSurf)],1)
NormSurf=rs.SurfaceNormal(BigSurf,((UDom[1]-UDom[0])/2,(VDom[1]-VDom[0])/2))
Origine=rs.CurveStartPoint(ArrEdge[len(ArrEdge)])
XVect=rs.CurveEndPoint(ArrEdge[len(ArrEdge)])-rs.CurveStartPoint(ArrEdge[len(ArrEdge)])
Plane=rs.PlaneFromNormal(Origine,NormSurf,XVect)
rs.DeleteObjects(ArrEdge)
rs.DeleteObjects(ArrSurf)
BBox=rs.BoundingBox(Tube,Plane)
print 'longueur', int(Lg)
print 'Section', int(round(rs.Distance(BBox[3],BBox[0]))),'x',int(round(rs.Distance(BBox[4],BBox[0])))
rs.ObjectName(Tube,(PrefixTube,n))

ArrTube=rs.GetObjects("selectionne l'ensemble des tubes")
if ArrTube:
PrefixTube=rs.GetString("une lettre de nommage?")
if PrefixTube:
ArrTube=TriTube(ArrTube)
n=0
for Tube in ArrTube:
Name_Tube(Tube,PrefixTube,n)
n+=1
``````

i think my script, is wrong, because my functions TriTube don’t return anything… so i search where i’m wrong!!!

Hello and bonne année à vous également!

“iteration over non-sequence of type NoneType” means it was expecting an iterable (e.g. list, set or string) and got `None` instead.

This line should be
`ArrTube.sort(key = lambda Tube : rs.SurfaceVolume(Tube)[0])`

Without the first part `ArrTube = `

The sort method on a list sorts the list in-place and returns `None` so you are currently sorting the list and then assigning None to ArrTube

I need to attach all data i keep with my script to each tube, is the “rs.SetObjectData” is possible with python in Rhino?

Hello,

I have never tried that. There is also `rs.SetUserText()`, but I haven’t tried that either! So far I only have a single value that I need to set per object so I use `rs.ObjectName()`

Graham

Ok my script run!!!
but now, i’ve got all my tubes with a name and some usertext for each…

there is some tubes how are the same, all usertext are the same, so now, i search to compare them, if it’s the same, it change the name and compare to the tube after…etc etc…
but this part doesn’t run

``````for Tube in ArrTube:
c=1
while c ==len(ArrTube):
if rs.GetUserText(ArrTube[c],'Lg')==rs.GetUserText(ArrTube[c+1],'Lg'):
if rs.GetUserText(ArrTube[c],'Section')==rs.GetUserText(ArrTube[c+1],'Section'):
if rs.GetUserText(ArrTube[c],'Angle1')==rs.GetUserText(ArrTube[c+1],'Angle1'):
if rs.GetUserText(ArrTube[c],'Angle1')==rs.GetUserText(ArrTube[c+1],'Angle2'):
rs.ObjectName(ArrTube[c+1],rs.ObjectName(ArrTube[c]))
del ArrTube[c+1]
``````

Hmm, couple of things -

Don’t you want somethng like

``````while c < len(ArrTube):
``````

Plus, you don’t increment c in the loop…

There’s also this:

``````if rs.GetUserText(ArrTube[c],'Angle1')==rs.GetUserText(ArrTube[c+1],'Angle2'):
``````

I think the first `Angle1` is supposed to be `Angle2`

I guess you are trying to iterate over your list of tubes. Remember that the index of the first tube is 0, not 1.

This can be done like this:

``````for c in range(len(ArrTube)-1): # Arreter à l'avant dernière indice
if ...
``````

However you will only select duplicates if they are in successive slots in your list, not if, say, tube 0 has the same user text as tube 23…

Another way to do your comparison would be as follows:

``````for tube1, tube2 in zip(ArrTube, ArrTube[1:]):
if rs.GetUserText(tube1,'Lg') == rs.GetUserText(tube2,'Lg'):
...
``````

Also I recommend renaming `ArrTube` as `tubes` so it is simply `for tube in tubes:`, which seems more readable to me. Arr is shorthand for array but in python it is called a `list`, not an array.

Graham

Well, one thing I suggest is to back up a bit and avoid creating the exact duplicates in the first place, rather than searching for them to change afterward…

So, lets say that you have a collection of objects with a certain number of criteria, and that in this collection, there could be some that have exactly the same set of criteria (which is what I think you have). You first need to decide how you are going to distinguish between objects that have the same characteristics - in this case, perhaps by having a different object name.

Now, the brute force method of doing this is to make an empty list, and start putting objects in it one by one. Before any object is added to the list, check each individual criterion on object to be added against each corresponding criterion on all the objects in the existing list. If a duplicate is found, use some algorithm to change the object name on the object to be added until it is unique, then add the object. You can imagine that this procedure will get longer and longer as more objects are added to the list. If you only have a few objects to check, time won’t matter that much, but if you have thousands, it could take awhile.

One way to speed this up would be to sort the original list by one of the criteria (say like length), that way you can move through the original list and first check the value that the list is sorted by. If the item being checked item has the same value as the previous, store that value temporarily, and then check the rest of the criteria for that item. If it is indeed an exact duplicate, change the name of the item being checked (unique as above) and move to the next item. As there could be more than two duplicates, you need to continue this until the value that the list was sorted by does change, then you know that the item cannot be a duplicate of the previous; you can then clear the stored value and start over again. That way you only go through each item in the list once, no matter how long it is.

yes I saw but too late… i launched an infiny loop…
do you know how I can stop it?

i’ll try it!!!
thank you!

Kill Rhino…

I was curious so I tried it myself:

``````import rhinoscriptsyntax as rs
import Rhino, random

def RCh(lst):
return random.choice(lst)

def RPt():
#generate a random point with integer values
x=random.randrange(0,100.0,1.0)
y=random.randrange(0,100.0,1.0)
z=random.randrange(0,100.0,1.0)
return Rhino.Geometry.Point3d(x,y,z)

def RLine(length):
"""generates a line in Y from random start point with length "length"
with one of the lengths from input list at random"""
spt=RPt()
ept=Rhino.Geometry.Point3d(spt.X,spt.Y+length,spt.Z)

def UTxtMatch(obj_a,obj_b,tkey):
#checks if a user text matches another
a_txt=rs.GetUserText(obj_a,tkey)
b_txt=rs.GetUserText(obj_b,tkey)
if a_txt and b_txt and a_txt==b_txt: return True
return False

#variables to generate objects semi-randomly
lengths=[150,200,250]
sections=["square","round","rect"]
angle_1s=["0","30","45"]
angle_2s=["0","30","45"]

#now we have a situation (hopefully) similar to your tubes
#at least a few of them should have duplicate names and values
rs.EnableRedraw(False)
master_list=[]
n=500
for i in range(n):
length=RCh(lengths)
line_id=RLine(length)
rs.ObjectName(line_id,str(length))
rs.SetUserText(line_id,"obj_len",str(length),False)
rs.SetUserText(line_id,"obj_sect",RCh(sections),False)
rs.SetUserText(line_id,"obj_ang1",RCh(angle_1s),False)
rs.SetUserText(line_id,"obj_ang2",RCh(angle_2s),False)
master_list.append(line_id)

#now master_list has n different entries, some might be duplicates
#first sort the list by length
master_list.sort(key=lambda item: rs.GetUserText(item,"obj_len"))

#set first name in list as reference
current_name=rs.ObjectName(master_list[0])

#now, try to find entries with duplicate names
count=1
for i in range(1,len(master_list)):
chk_name=rs.ObjectName(master_list[i])
if chk_name==current_name:
#duplicate name found, check all other criteria
if UTxtMatch(master_list[i-1],master_list[i],"obj_sect"):
if UTxtMatch(master_list[i-1],master_list[i],"obj_ang1"):
if UTxtMatch(master_list[i-1],master_list[i],"obj_ang2"):
#match found on all criteria, change name by adding suffix
rename=chk_name+"_{}".format(count)
rs.ObjectName(master_list[i],rename)
#increment suffix so it will not be duplicated again
count+=1
#print count
#if no match on all criteria, surrent name stays current, go to next
else:
#no duplicate, set next name current and continue
current_name=rs.ObjectName(master_list[i])
count=1

#that's it, try running it and check the object names and other user text.
``````

ok thank you but i saw on your script that the name is the lenght, in my case, the name is just a letter with a counter( like C1, C2, C3…) sort by volume…

in fact at finish, i would like to have all duplicates with the same name, and when i would like to generate a bom, i will take all tubes and sort by name, count all duplicate by name and print it:
example:

Qty/lenght/Angle1/Angle2
2/C1/1830/25°/35°
5/C2/2500/30°/0°

OK, I think there are enough tools in some of the above examples that you can get where you want - if not yell and someone will jump in…

One other interesting way to deal with your object data perhaps:
Instead of storing each individual value as a separate user text, you could “serialize” all your data into one long string using a defined separator. The advantage of having all the data in one string is that strings can be compared to each other very easily with `==` and also respond to things like

``````if my_string in my_list: #do something
``````

Of course you will have to write a function to serialize your data to store it as a string on your object, and another to de-serialize your string back into the original list of data when you need it. But it could help considerably in cases like yours where you have a collection of different data on each object and you want to find the cases where all the values match - only exactly equal strings will be the matching objects.

Below are my quickie serialize-deserialize functions:

``````def SerializeDataList(data_list,sep):
#serializes a list of data into one string with separators
output=""
for i,item in enumerate(data_list):
output+="{}".format(item)
if i<len(data_list)-1: output+=sep
return output

def DeserializeDataString(data_str,sep,numerize,boolize):
#deserializes a sting of data with separators into a list
#option to reconstitute integers, floats, and boolean values
data_list=data_str.split(sep)
if numerize:
for i,item in enumerate(data_list):
try:
integer_data=int(item)
if integer_data:
data_list[i]=integer_data
continue
except:
pass
try:
float_data=float(item)
if float_data:
data_list[i]=float_data
continue
except:
pass
if boolize:
if item=="False": data_list[i]=False
if item=="True": data_list[i]=True
return data_list

stuff=["green","eggs","ham",2,3.5,True,False]
sep="||"

test=SerializeDataList(stuff,sep)
print type(test),test
retest=DeserializeDataString(test,sep,True,True)
print type(retest),retest
for item in retest: print type(item),item
``````

Hello again,

You can use a `Counter` from the `collections` module for this. This should give you a starting point. Please provide a sample file if you would like some more help…

``````from collections import Counter

TubeCounter = Counter()

for tube in ArrTube:
Lg = rs.GetUserText(tube, 'Lg')
Section = rs.GetUserText(tube, 'Section')
Angle1 = rs.GetUserText(tube, 'Angle1')
Angle2 = rs.GetUserText(tube, 'Angle2')
data = (Lg, Section, Angle1, Angle2)
TubeCounter.update([data])

for item, count in TubeCounter.iteritems():
print count, item``````
1 Like

STR METAL Test.3dm (791.1 KB)

it’s a few part of the assembly,
and the script…
the end doesn’t run:

``````# coding: utf-8
import rhinoscriptsyntax as rs

def TriTube(Tubes):
Tubes.sort(key = lambda Tube : rs.SurfaceVolume(Tube)[0])
return Tubes

def TriSurf (Surfs):
Surfs=sorted([(rs.SurfaceArea(Surf)[0],Surf) for Surf in Surfs])
return [item[1] for item in Surfs]

def TriEdge (ArrEdge):
ArrEdge=sorted([(rs.CurveLength(Edge),Edge) for Edge in ArrEdge])
return [item[1] for item in ArrEdge]

def VectNormSurf(Surf):
UDom=rs.SurfaceDomain(Surf,0)
VDom=rs.SurfaceDomain(Surf,1)
NormSurf=rs.SurfaceNormal(Surf,((UDom[1]-UDom[0])/2,(VDom[1]-VDom[0])/2))
return NormSurf

def MUserTex(Tube):
Surfs=rs.ExplodePolysurfaces(Tube)
Surfs=TriSurf(Surfs)
ArrEdge=rs.DuplicateEdgeCurves(Surfs[len(Surfs)-1], select=False)
ArrEdge=TriEdge(ArrEdge)
NormSurf=VectNormSurf(Surfs[len(Surfs)-1])
Origine=rs.CurveStartPoint(ArrEdge[len(ArrEdge)-1])
XVect=rs.CurveEndPoint(ArrEdge[len(ArrEdge)-1])-rs.CurveStartPoint(ArrEdge[len(ArrEdge)-1])
Plane=rs.PlaneFromNormal(Origine,NormSurf,XVect)

BBox=rs.BoundingBox(Tube,Plane)
Lg=rs.Distance(BBox[0],BBox[1])
AngleE=[0,0]
#definition coupe ou pas
VolBox=rs.Distance(BBox[0],BBox[1])*rs.Distance(BBox[0],BBox[3])*rs.Distance(BBox[0],BBox[4])
VolTube=rs.SurfaceVolume(Tube)
if round(VolBox,2)<>round(VolTube[0],2):
SurfAppui=None
#vecteur Petite surface
for aa in range (0,2):
NormSurfE=VectNormSurf(Surfs[aa])
#vecteur grande surface
if SurfAppui!=None:
NormSurf=VectNormSurf(SurfAppui)
AngleETemp=rs.VectorAngle(NormSurf,NormSurfE)
AngleE[aa]=-(AngleETemp-90)
else:
for bb in range(2,6):
NormSurf=VectNormSurf(Surfs[bb])
AngleETemp=rs.VectorAngle(NormSurf,NormSurfE)
if int(round(AngleETemp))!=90:
SurfAppui=Surfs[bb]
AngleE[aa]=-(AngleETemp-90)
break

rs.DeleteObjects(ArrEdge)
rs.DeleteObjects(Surfs)

Lgretenu=int(round(Lg))
htTube=int(round(rs.Distance(BBox[3],BBox[0])))
largTube=int(round(rs.Distance(BBox[4],BBox[0])))
Sectionretenu=str(htTube)+'x'+str(largTube)
Angle1=int(round(AngleE[0]))
Angle2=int(round(AngleE[1]))
print 'longueur', Lgretenu
print 'Section', htTube,'x',largTube
print 'angle 1=',Angle1,'°'
print 'angle 2=',Angle2,'°'
rs.SetUserText(Tube,'Lg',str(Lgretenu))
rs.SetUserText(Tube,'Section',Sectionretenu)
rs.SetUserText(Tube,'Angle1',str(Angle1))
rs.SetUserText(Tube,'Angle2',str(Angle2))

Tubes=rs.GetObjects("selectionne l'ensemble des tubes",rs.filter.polysurface)
Tubes2=[]
for Tube in Tubes:
if rs.IsPolysurfaceClosed(Tube):
Tubes2.append(Tube)
else:
rs.ObjectColor( Tube, (250,0,0))
Tubes=Tubes2
if Tubes:
PrefixTube=rs.GetString("une lettre de nommage?")
if PrefixTube:
Tubes=TriTube(Tubes)
for Tube in Tubes:
MUserTex(Tube)
TubesNom=[]
n=1
TubesLenght=len(Tubes)
while len(TubesNom)<TubesLenght or n<200:
NewName=PrefixTube+str(n)
for Tube in Tubes:
if rs.ObjectName(Tube)==None:
rs.ObjectName(Tube,NewName)
Chk_Lg=rs.GetUserText(Tube,'Lg')
Chk_Sect=rs.GetUserText(Tube,'Section')
Chk_A1=rs.GetUserText(Tube,'Angle1')
Chk_A2=rs.GetUserText(Tube,'Angle2')
TubesNom.append(Tube)
Tubes.remove(Tube)
for i in range (0,len(Tubes)-1):
if rs.ObjectName(Tubes[i])==None:
if rs.GetUserText(Tubes[i],'Lg')==Chk_Lg:
if rs.GetUserText(Tubes[i],'Section')==Chk_Sect:
if rs.GetUserText(Tubes[i],'Angle1')==Chk_A1:
if rs.GetUserText(Tubes[i],'Angle2')==Chk_A2:
rs.ObjectName(Tubes[i],NewName)
TubesNom.append(Tubes[i])

n+=1
``````

Well to break out of the while loop after 200 tries you need `while len(TubesNom)<TubesLenght and n<200:` using and rather than or

In your first time through the while loop you name all unnamed tubes with the same name so the second and subsequent loops do nothing…

actually i was scared of the infinite loop so i tried to put a security… so it’s the lenght or the counter how breack the loop… that’s why I wrote or…

i don’t understand…
i take the first tube, i Named it, I Keep all Data to Compare, and i compare it to the other tube, if it’s the same i name it else i pass to the next tube

Oui mais c’est and car actuellement il ne vérifie pas le deuxième critère si le premier est `True`