Hi everybody,
This discussion is a continuation of this one from yesterday.
I’m currently trying to translate/port @dave_stasiuk’s script, recommended yesterday by @HS_Kim here, from Visual Basic to Python, because I need it as part of a more elaborate script.
However, I don’t know much about VB. I’ve done a rough translation, but something is askew. The Python component doesn’t throw an error per se, however the output isn’t correct. In other words, it doesn’t correspond to what the VB component outputs.
The script is basically about converting grid lines to cells, like so:
My Python script
import Rhino.Geometry as rg
import Grasshopper as gh
from Grasshopper.Kernel.Data import GH_Path
import math
tol = 0.01 # model tolerance
# Shatter all the input curves by intersecting them with each other
Crvs = [] # new list of curves
for CStart in range(len(C)):
T = [] # new list of floats
for CCut in range(len(C)):
if CStart != CCut:
CI = rg.Intersect.Intersection.CurveCurve(C[CStart], C[CCut], tol, tol)
for IE in CI:
T.append(IE.ParameterA)
CShattered = C[CStart].Split(T)
Crvs.extend(CShattered)
# Set your "half-curce" lists
Vtc = [] # new list of point3d; all unique vertices
HC = [] # new list of curves; half-curves
HCI = [] # new list of ints; half-curve indices
HCO = [] # new list of ints; half-curve opposites
HCN = [] # new list of ints; next index for each half-curve
HCV = [] # new list of ints; half-curve vertex
HCF = [] # new list of ints; half-curve face
HCPln = [] # new list of planes; half-curve planes
HCK = [] # new list of booleans; flag if half-curve needs to be killed (if either starts or ends hanging)
F = gh.DataTree[object]() # datatree of curves; for faces
VOut = gh.DataTree[object]() # datatree of ints; for outgoing half_curves from each vertex
for Crv in Crvs: # cycle through each curve
for CRun in range(0, 3, 2): # create two half-curves: first in one direction, and then the other...
HC.append(Crv)
HCI.append(len(HCI))
HCO.append(len(HCI)-CRun) # a little index trick
HCN.append(-1)
HCF.append(-1)
HCK.append(False)
VtcSet = -1
for VtxCheck in range(len(Vtc)):
if Vtc[VtxCheck].DistanceTo(Crv.PointAtStart) < tol:
VtcSet = VtxCheck # get the vertex index, if it already exists
break
if VtcSet > -1:
HCV.append(VtcSet) # if the vertex already exists, set the half-curve vertex
VOut.Add(HCI[-1], GH_Path(VtcSet)) # add the new half-curve index to the list of outgoing half-curves associates with the vertex
else:
HCV.append(len(Vtc)) # if the vertex doesn't already exists, add a new vertex index
VOut.Add(HCI[-1], GH_Path(len(Vtc))) # add the new half-curve index to the list of outgoing half-curves associates with the vertex
Vtc.append(Crv.PointAtStart) # add the new vertex to the vertex list
# Create, align and add the plane to be used for sequencing half-curves
AddPlane = rg.Plane(Vtc[HCV[-1]], P.ZAxis)
AddPlane.Rotate(rg.Vector3d.VectorAngle(AddPlane.XAxis, Crv.TangentAtStart, AddPlane), AddPlane.ZAxis)
HCPln.append(AddPlane)
Crv.Reverse # reverse the cure for creating the opposite half-curve in the second part of the loop
# For each vertex that has only one outgoing half-curve, kill the half-curve and its opposite
for Pth in VOut.Paths:
if VOut.Branch(Pth).Count == 1:
HCK[VOut.Branch(Pth)(0)] = True
HCK[HCO[VOut.Branch(Pth)(0)]] = True
# Find the "next" half-curve for each starting half-curve by identifiying the outgoing half-curve from the end vertex
# that presents the smallest angle by calculating its plane's x-axis angle from the starting half_curves's opposite plane
for HCIdx in HCI:
PlaneUse = HCPln[HCO[HCIdx]]
MinIdx = -1
MinAngle = 2 * math.pi
for HCOut in VOut.Branch(HCV[HCO[HCIdx]]):
if HCOut != HCO[HCIdx] and HCK[HCIdx] == False and HCK[HCOut] == False:
AngleTest = rg.Vector3d.VectorAngle(PlaneUse.XAxis, HCPln[HCOut].XAxis, PlaneUse)
if AngleTest < MinAngle:
MinIdx = HCOut
MinAngle = AngleTest
HCN[HCIdx] = MinIdx
# Sequencing half-curves into faces by running along "next" half-curves in order until the starting half-curve is returned to
FaceEdges = [] # list of ints
DeleteEdges = [] # list of ints
# Cycle through each half-curve
for HCIdx in HCI:
EmExit = 0
if HCF[HCIdx] == -1: # if it hasn't yet been assigned to a
EdgeCounter = 1
FaceIdx = F.Paths.Count
CurrentIdx = HCIdx
F.Add(HC[CurrentIdx], GH_Path(FaceIdx))
HCF[CurrentIdx] = FaceIdx
while True:
if HCN[CurrentIdx] == -1: # if sequence half-curve has no next curve assigned, then the face is invalid
DeleteEdges.append(FaceIdx) # and will be added to the delete list
break
CurrentIdx = HCN[CurrentIdx]
F.Add(HC[CurrentIdx], GH_Path(FaceIdx))
EdgeCounter += 1
HCF[CurrentIdx] = FaceIdx
if HCN[CurrentIdx] == HCIdx: # exit once the starting hflcruve is reached again
break
EmExit += 1
if EmExit == len(Crvs)-1: # energency exit prevents infinite loops
break
FaceEdges.append(EdgeCounter)
# Find the perimeter by counting edges... it's possible that an interior face might have the most edges
# so this could easily be approved upon
Perim = -1
PerimCount = -1
for FE in range(len(FaceEdges)):
if FaceEdges[FE] > PerimCount:
Perim = FE
PerimCount = FaceEdges[FE]
DeleteEdges.append(Perim)
NewPath = 0
OutputFaces = gh.DataTree[object]()
# Only output the faces that haven't been identified as either the perimeter of open
for Pth in F.Paths:
if not Pth.Indices[0] in DeleteEdges:
OutputFaces.AddRange(F.Branch(Pth), GH_Path(NewPath))
NewPath += 1
a = OutputFaces
Dave’s VB script:
'Start by Shattering all of your input curves by intersecting them with each other
Dim Crvs As New List(Of Curve)
For CStart As Int32 = 0 To C.Count - 1
Dim T As New List(Of Double)
For CCut As Int32 = 0 To C.Count - 1
If CStart <> CCut Then
Dim CI As Intersect.CurveIntersections = Intersect.Intersection.CurveCurve(C(CStart), C(CCut), RhinoDoc.ActiveDoc.ModelAbsoluteTolerance, RhinoDoc.ActiveDoc.ModelAbsoluteTolerance)
For Each IE As Intersect.IntersectionEvent In CI
T.Add(IE.ParameterA)
Next
End If
Next
Dim CShattered() As Curve = C(CStart).Split(T)
Crvs.AddRange(CShattered)
Next
'Set your "half-curve" lists
Dim Vtc As New List(Of Point3d) 'all unique vertices
Dim HC As New List(Of Curve) 'list of half-curves
Dim HCI As New List(Of Int32) 'half curve indices
Dim HCO As New List(Of Int32) 'half curve opposites
Dim HCN As New List(Of Int32) 'next index for each half-curve
Dim HCV As New List(Of Int32) 'half-curve vertex
Dim HCF As New List(Of Int32) 'half-curve face
Dim HCPln As New List(Of Plane) 'half-curve plane
Dim HCK As New List(Of Boolean) 'flag if a half-curve needs to be killed (if it either starts or ends hanging)
Dim F As New DataTree(Of Curve) 'data tree for faces
Dim VOut As New DataTree(Of Int32) 'data tree of outgoing half-curves from each vertex
For Each Crv As Curve In Crvs 'cycle through each curve
For CRun As Int32 = 0 To 2 Step 2 'create two half-curves: first in one direction, and then the other...
HC.Add(Crv)
HCI.Add(HCI.Count)
HCO.Add(HCI.Count - Crun) 'a little index trick
HCN.Add(-1)
HCF.Add(-1)
HCK.Add(False)
Dim VtcSet As Int32 = -1
For VtxCheck As Int32 = 0 To Vtc.Count - 1
If Vtc(VtxCheck).DistanceTo(Crv.PointAtStart) < RhinoDoc.ActiveDoc.ModelAbsoluteTolerance Then
VtcSet = VtxCheck 'get the vertex index, if it already exists
Exit For
End If
Next
If VtcSet > -1 Then
HCV.Add(VtcSet) 'If the vertex already exists, set the half-curve vertex
VOut.Add(HCI.Last, New GH_Path(VtcSet)) 'add the new half-curve index to the list of outgoing half-curves associated with the vertex
Else
HCV.Add(Vtc.Count) 'if the vertex doesn't already exist, add a new vertex index
VOut.Add(HCI.Last, New GH_Path(Vtc.Count)) 'add the new half-curve index to the list of outgoing half-curves associated with the vertex
Vtc.Add(Crv.PointAtStart) 'add the new vertex to the vertex list
End If
'create, align and add the plane to be used for sequencing half-curves
Dim AddPlane As New Plane(Vtc(HCV.Last), P.ZAxis)
AddPlane.Rotate(Vector3d.VectorAngle(AddPlane.XAxis, Crv.TangentAtStart, AddPlane), AddPlane.ZAxis)
HCPln.Add(AddPlane)
Crv.Reverse 'reverse the curve for creating the opposite half-curve in the second part of the loop
Next
Next
'For each Vertex that has only one outgoing half-curve, kill the half-curve and its opposite
For Each Pth As GH_Path In VOut.Paths
If VOut.Branch(Pth).Count = 1 Then
HCK(VOut.Branch(Pth)(0)) = True
HCK(HCO(VOut.Branch(Pth)(0))) = True
End If
Next
'Find the "next" half-curve for each starting half curve by identifying the outgoing half-curve from the end vertex
'that presents the smallest angle by calculating its plane's x-axis angle from x-axis of the starting half-curve's opposite plane
For Each HCIdx As Int32 In HCI
Dim PlaneUse As Plane = HCPln(HCO(HCIdx))
Dim MinIdx As int32 = -1
Dim MinAngle As Double = 2 * Math.PI
For Each HCOut As Int32 In VOut.Branch(HCV(HCO(HCIdx)))
If HCOut <> HCO(HCIdx) And HCK(HCIdx) = False And HCK(HCOut) = False
Dim AngleTest As Double = Vector3d.VectorAngle(PlaneUse.XAxis, HCPln(HCOut).XAxis, PlaneUse)
If AngleTest < MinAngle Then
MinIdx = HCOut
MinAngle = AngleTest
End If
End If
Next
HCN(HCIdx) = MinIdx
Next
'Sequence half-curves into faces by running along "next" half-curves in order until the starting half-curve is returned to
Dim FaceEdges As New List(Of Int32)
Dim DeleteEdges As New List(Of Int32)
'cycle through each half-curve
For Each HCIdx As Int32 In HCI
Dim EmExit As Int32 = 0
If HCF(HCIdx) = -1 Then 'if it hasn't yet been assigned to a
Dim EdgeCounter As Int32 = 1
Dim FaceIdx as Int32 = F.Paths.Count
Dim CurrentIdx As Int32 = HCIdx
F.Add(HC(CurrentIdx), New GH_Path(FaceIdx))
HCF(CurrentIdx) = FaceIdx
Do
If HCN(CurrentIdx) = -1 Then 'if sequence half-curve has no next curve assigned, then the face is invalid
DeleteEdges.Add(FaceIdx) 'and will be added to the delete list
Exit Do
End If
CurrentIdx = HCN(CurrentIdx)
F.Add(HC(CurrentIdx), New GH_Path(FaceIdx))
EdgeCounter += 1
HCF(CurrentIdx) = FaceIdx
If HCN(CurrentIdx) = HCIdx Then Exit Do 'exit once the starting half-curve is reached again
EmExit += 1
If EmExit = Crvs.Count - 1 Then Exit Do 'emergency exit prevents infinite loops
Loop
FaceEdges.Add(EdgeCounter)
End If
Next
'Find the perimeter by counting edges...it's possible that an interior face might have the most edges
'so this could easily be improved upon
Dim Perim As Int32 = -1
Dim PerimCount As Int32 = -1
For FE As Int32 = 0 To FaceEdges.Count - 1
If FaceEdges(FE) > PerimCount Then
Perim = FE
PerimCount = FaceEdges(FE)
End If
Next
DeleteEdges.Add(Perim)
Dim NewPath As Int32 = 0
Dim OutputFaces As New DataTree(Of Curve)
'only output those faces that haven't been identified as either the perimeter or open
For Each Pth As GH_Path In F.Paths
If DeleteEdges.Contains(Pth.Indices(0)) = False Then
OutputFaces.AddRange(F.Branch(Pth), New GH_Path(NewPath))
NewPath += 1
End If
Next
CN = OutputFaces
If you visit this thread by any chance, @dave_stasiuk, could you please briefly explain the logic behind the whole “half-curve” concept? I don’t get it! Thanks.
Here’s the file containing both scripts:
lines_to_grid_cells_2.gh (19.8 KB)
Thanks!