Issues translating VB script to Python!

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:

04

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!

Hi PB,

Is the half curve thing putting a duplicate of each bit of curve into the list, with the order start --> end reversed so that each vertex is associated with the adjoining curves in +ve and -ve X and Y directions?

-ggg

1 Like

Hi Dancer,

Yes, inside the for loop (42) that iterates over each shattered curve segment (Crvs), a second for loop (44) iterates exactly two times per curve. The first time, the original curve is added to the list HC, and the second time the same, reversed curve.

Here, I’m not exactly sure. I don’t under the entire strategy with the “half-curves”. Originally the script was meant to be applied to a hexagonal grid, so I don’t think that it only applies to 90° angles. What do you mean by -/+ve?

Yes, that’s exactly what the half-curves are…duplicates of each shattered bit in reversed direction. Then each half-curve becomes assigned to a face, based on winding order of “outgoing” half-curves from each shared vertex.

Ugh, this code is old and ugly. It works, but I would do a lot of it differently if starting over to optimize/stabilize it. I explain a bit what I was doing here on the old grasshopper3d site.

2 Likes

Thanks for your reply. Could you please expand a little upon this?