Surpac Tip - TCL/SCL macro series (10 of 10)

Surpac Tip - TCL/SCL macro series (10 of 10)

Part 10 – Retrieving data from arrays and drawing to screen

SCL provides many excellent functions to create, add, import and manipulate layers and data.  There is also great reference material in the Surpac->Help menu.  My aim in this series is to introduce some of these features in more layman’s terms.  Some knowledge of how macro’s work with variables and commands will certainly be helpful but is not essential.

In the following tip;

  • My comments in the code blocks will have a # in front of them. They are also in bold and italicized
  • If you would like to test this code in Surpac, the entire code block (Including previous parts 1 to 9) has been added to the end of the tip.  Copy it into any text editor and save it with a .tcl extension to use it in Surpac.  
  • The final result will be output to the graphics window and should look similar to below.

 

Use the puts command to write to the message window.

puts ""
puts "Part 10 - Retrieving information from an array"
# This part of the series flows on from "Part 9 - Using Arrays to store information"

We will use a procedure to draw the points in Surpac.  See part two for more information about that.

# See part 2 for how a procedure works.
proc newPoint {segHandle x y z} {
  \$segHandle SclCreatePoint pntHandle [\$segHandle SclCountItems]
  \$pntHandle SclSetValueByName X \$x
  \$pntHandle SclSetValueByName Y \$y
  \$pntHandle SclSetValueByName Z \$z
  return \$pntHandle
}

Use SCL commands to create a layer, add it to the viewport so we can see it and then create a string and segment structure that can be populated with points from an array.

# Get a reference to the active viewport (You can have more than one) store it in reference variable "vh"
SclGetActiveViewport vh
# Create a layer called array that can be manipulated/inquired with the "arraySwa" reference variable
SclCreateSwa arraySwa "array"
# Add the array layer into the layers pane so it is visible
sclGraphicsLayers SclAdd \$arraySwa \$vh
# Set the "array" layer to be active
\$vh SclSetActiveLayer \$arraySwa
# Using the reference to "arraySwa", create a reference variable "arrayStr" and make the string number 1
\$arraySwa SclCreateString arrayStr 1
# See details from part 1 about how this works.
\$arrayStr SclCreateSegment arraySeg [\$arrayStr SclCountItems]

We used a nested for loop in part 9 to create 100 points of data in a grid.  I'll show how that is done again by adding the code below.

#This is the nested for loop from part 9 as a reminder of how we put data into the array.
#Lets use nested for loops to put 100 points (x,y) into an array.  The string number and z values will be constants that we will add later
# This is the point count that we'll use as array key.
set i 1
# This for loop will increment the x coord
for {set x 1} {\$x <= 10} {incr x} {
  # This for loop will increment the y coord
  for {set y 1} {\$y <= 10} {incr y} {
    set pointX(\$i) \$x
    set pointY(\$i) \$y
    # Setting z value to a constant 10
    set pointZ(\$i) 10
    puts "Point \$i coords are -> \(\$x,\$y\)"
    # Incrementing the key number
    incr i
  }
}

#Check to see how many points have been added into the array
set arraySizeCheck [array size pointX]
puts "There are \$arraySizeCheck points in the array"

Now the information has been added into an array, let's retrieve it and draw the points to the screen.

# We will now use a for loop to retrieve information from an array.
for {set j 1} {\$j <= \$arraySizeCheck} {incr j} {
  set newX \$pointX(\$j)
  set newY \$pointY(\$j)
  set newZ \$pointZ(\$j)
  # Run the newPoint procedure to create points from the array
  newPoint \$arraySeg \$newX \$newY \$newZ
}

\$arraySeg SclDraw
SclFunction "ZOOM ALL" {}

puts ""
puts "This ends our 10 part series on using some TCL/SCL commands in Surpac.  See you in 2025 for some creative coding!"

We will be starting a new series in 2025 called Creative Coding in Surpac.  It won't teach you much about mining, but as with everything in life, the skills that you learn in the series can be put to good use in any macro you want to write.  Have a great Christmas and new year and I'll see you in 2025!

 

Entire code block - Parts 1 to 10 - Copy into text editor and run in Surpac with .tcl extension 

####### PART 1 #######
# Get a reference to the active viewport (You can have more than one) store it in reference variable "vh"
SclGetActiveViewport vh
# Using the "vh" reference, get the active layer and store the reference in the "initialSwa" variable
\$vh SclGetActiveLayer initialSwa
# Extract the layer name into the layerName variable using the SCL command "SclGetId"
set layerName [\$initialSwa SclGetId]
# Print the layer name to the message window
puts "Layer name is \$layerName"

# Create a layer called design that can be manipulated/inquired with the "designSwa" reference variable
SclCreateSwa designSwa "design"

# Use the SclAdd function to add the layer into the layer panel
sclGraphicsLayers SclAdd \$designSwa \$vh

# Using the reference to "designSwa", create a reference variable "designStr" and make the string number 8
\$designSwa SclCreateString designStr 8


# Using the reference to the "designStr" create a reference variable "designSeg" and count how many items (i.e. segments) already
# exist for string 8. If there are 5 existing segments in string number 8, [\$designStr SclCountItems] will return a value of 5.
# 5 will become the new segment number.  Now, why doesn't this clash with the existing segments if there are already 5 of them?
# This is because the segments are stored as an index position which starts at zero. i.e. segment 1 = index 0, segment 2 = index 1
# ... segment 5 = index 4 and so on.
# All of this to say that when we count how many items are in string 8, the number returned will work as the next index position
# which is what the "SclCreateSegment" function requires. So if I write it manually it becomes
# \$designStr SclCreateSegment designSeg 5 
\$designStr SclCreateSegment designSeg [\$designStr SclCountItems]

# Using the reference variable "designSeg" count how many points there are and add this one to the end
\$designSeg SclCreatePoint designPnt [\$designSeg SclCountItems]
\$designPnt SclSetValueByName X 10
\$designPnt SclSetValueByName Y 10
\$designPnt SclSetValueByName Z 10
\$designPnt SclSetValueByName d1 "Testing SCL layer commands"

# Using the reference variable "designSeg" count how many points there are and add this one to the end
\$designSeg SclCreatePoint designPnt [\$designSeg SclCountItems]
\$designPnt SclSetValueByName X 10
\$designPnt SclSetValueByName Y 20
\$designPnt SclSetValueByName Z 10

# Using the reference variable "designSeg" count how many points there are and add this one to the end
\$designSeg SclCreatePoint designPnt [\$designSeg SclCountItems]
\$designPnt SclSetValueByName X 20
\$designPnt SclSetValueByName Y 20
\$designPnt SclSetValueByName Z 10

# Using the reference variable "designSeg" count how many points there are and add this one to the end
\$designSeg SclCreatePoint designPnt [\$designSeg SclCountItems]
\$designPnt SclSetValueByName X 20
\$designPnt SclSetValueByName Y 10
\$designPnt SclSetValueByName Z 10

# To close the shape, make the x,y and z the same as the first point.
\$designSeg SclCreatePoint designPnt [\$designSeg SclCountItems]
\$designPnt SclSetValueByName X 10
\$designPnt SclSetValueByName Y 10
\$designPnt SclSetValueByName Z 10

# Using the reference to the design layer (designSwa), use the command "SclDraw" to draw everything in the design layer.
\$designSwa SclDraw
SclFunction "ZOOM ALL" {}

# Now, the active layer is still the initial layer that was active when the macro was first run.  Let's make it the design layer
\$vh SclSetActiveLayer \$designSwa

####### PART 2 #######

#Create a layer called square_2 that can be manipulated/inquired with the "square2Swa" reference variable
SclCreateSwa square2Swa "square_2" 
sclGraphicsLayers SclAdd \$square2Swa \$vh
# Using the reference to "square2Swa", create a reference variable "square2Str" and make the string number 6
\$square2Swa SclCreateString square2Str 6
# Create a new segment.  See details from part 1 about how this works.
\$square2Str SclCreateSegment square2Seg [\$square2Str SclCountItems]

# Procedure is called "newPoint" and takes 4 parameters.  
# 1. The segment reference 2. The X coord, 3. The Y coord and 4. the Z coord
proc newPoint {segHandle x y z} {
  # The rest of the code is exactly the same as what we did in part one when creating a new point
  # The only difference being that we are using variables i.e. \$x that are input when the procedure is called.
  \$segHandle SclCreatePoint pntHandle [\$segHandle SclCountItems]
  \$pntHandle SclSetValueByName X \$x
  \$pntHandle SclSetValueByName Y \$y
  \$pntHandle SclSetValueByName Z \$z
  return \$pntHandle
}

newPoint \$square2Seg 11 11 10;# First point, note that we are using square2seg as the segment reference
newPoint \$square2Seg 11 19 10
newPoint \$square2Seg 19 19 10
newPoint \$square2Seg 19 11 10
newPoint \$square2Seg 11 11 10;# Closing point is same as first point

# Create a layer called square_3 that can be manipulated/inquired with the "square3Swa" reference variable
SclCreateSwa square3Swa "square_3"
sclGraphicsLayers SclAdd \$square3Swa \$vh
# Using the reference to "square3Swa", create a reference variable "square3Str" and make the string number 9
\$square3Swa SclCreateString square3Str 9
# See details from part 1 about how SclCreateSegment works.
\$square3Str SclCreateSegment square3Seg [\$square3Str SclCountItems]

newPoint \$square3Seg 13 13 10;# First point, note that we are using square3seg as the segment reference
newPoint \$square3Seg 13 17 10
newPoint \$square3Seg 17 17 10
newPoint \$square3Seg 17 13 10
newPoint \$square3Seg 13 13 10;# Closing point is same as first point

\$square2Str SclDraw
\$square3Str SclDraw "style=ssi_method=line,HGS_color=line=red,HGS_line_weight=2"

####### PART 3 #######

puts ""
puts "Part 3 - layer iteration and finding the layer you want. Plus, creating a small GUIDO form."

# Get a handle to the active viewport and store it in reference variable "vh"
SclGetActiveViewport vh
# Use sclGraphicsLayers command with SclIterateFirst command to setup a layer iterator
sclGraphicsLayers SclIterateFirst Iterator
# Using a TCL while loop, keep iterating over the layers until there are no more to iterate over. 
while {[\$Iterator SclIterateNext SwaHandle] == \$SCL_TRUE} {
  # Write the layer name to the message window by inquiring the SwaHandle reference variable using the SclGetId command.
  puts "Layer name is [\$SwaHandle SclGetId]"
  # store the layername into a variable called "layerName"
  set layerName [\$SwaHandle SclGetId]
  # Don't store the main graphics layer into the list of selectable layers as it may be empty.
  if {\$layerName != "main graphics layer"} {
    # Append the layerName into a list called layerList.  TCL lists are simple.  Just imagine a shopping list.
    lappend layerList \$layerName
  }
}

puts "Layers added to a list... Click in Graphics to continue"
SclPause

# Form definition start here.  Details of the form are stored in the "layerSelection" variable.
set layerSelection {
#GuidoForm container has two switches, the label and default_buttons switch.   
  GuidoForm form {
    -label "Select Layer"
    -default_buttons
    
    GuidoComboBox layer {
      -label "Layer"
      -width 15
      -exclusive true
      -default "design"
    }
  }
};# Form definition ends here.

# Use the SclCreateGuidoForm command to compile the form into a variable called "formH"
SclCreateGuidoForm formH \$layerSelection {
  # This populates the combobox with all the layers in the layerList variable
  set layer.setValues \$layerList
}
\$formH SclRun {}

if {"\$_status" != "apply"} {
  puts "Macro cancelled"
  # Normally you would stop the macro if someone pressed cancel but in our example we press on.  Just unhash return for normal function.  "return" is another TCL command that in this instance will stop the macro.
  #return
}

puts "You have selected the \"\$layer\" layer"

sclGraphicsLayers SclIterateFirst Iterator
# ourSwa is the reference variable that we can use when setting the active layer
while {[\$Iterator SclIterateNext ourSwa] == \$SCL_TRUE} {
  # store the layername into a variable called "layerName"
  set layerName [\$ourSwa SclGetId]
  # Check to see if the current layer stored in \$layerName is the one selected which is stored in \$layer variable.
  if {\$layerName == \$layer} {
    puts "Active layer is now \$layer.  Check the \"layers\" pane to see if this is true."
    # If we find the required layer, use the SclSetActiveLayer command to change the active layer referenced by \$SwaHandle
    \$vh SclSetActiveLayer \$ourSwa
    puts "Click in graphics once layer is confirmed."
    SclPause
    # no need to search once we have found the layer we are after.  Break will exit the while loop.
    break
  }
}

puts "End of part 3"

####### PART 4 #######

puts ""
puts "Part 4 - Saving a layer then recalling data into a created layer before modifying."
#Saving a layer can be done with a normal recorded "Save Layer" or "Save File" function however SCL provides a way to do it as well.

#Depending on which layer you chose in part 3 will depend on which layer will now be saved.

# This will get the date into a format that the string file header can use
set today [clock format [clock seconds] -format "%d %b %Y"]

#Options variable is a set of parameters separated by the | symbol. Some of the options are styles, binary or text and string range.
set options "header=\$layer, \$today, Test of SclSwaSaveStringFile, ssi_styles:styles.ssi|axis=0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0|binary=off"

# This is the line of code that will save a string file with your included options.
\$ourSwa SclSwaSaveStringFile \$layer.str "\$options"

#Let's assume we have saved the data we want and need to exit graphics to start another project.
SclFunction "EXIT GRAPHICS" {}

#Now, let's recall your saved string file into a new layer we will call new_design
# Get a reference to the active viewport (You can have more than one) store it in reference variable "vh"
SclGetActiveViewport vh
# Create a layer called new_design that can be manipulated/inquired with the "newDesignSwa" reference variable
SclCreateSwa newDesignSwa "new_design"
# Add the new_design layer into the layers pane so it is visible
sclGraphicsLayers SclAdd \$newDesignSwa \$vh
# Everything in "" is an option.  The option to "load draw styles" will ensure the styles file is used.
\$newDesignSwa SclSwaOpenFile \$layer.str "load draw styles"

# Make the "new_design" layer active.
\$vh SclSetActiveLayer \$newDesignSwa

# Get a handle to all the strings in the new_design layer
\$newDesignSwa SclGetStrings newDesignStrs
# Draw all the strings in graphics
\$newDesignStrs SclDraw

# Whenever you bring data into a layer, you'll generally need to zoom all to see it.
SclFunction "ZOOM ALL" {}

# Now, lets say we wanted to edit a point in the layer to a known coordinate. We first need to get a "handle" to the point.
# We will use "SclSelectPoint" to achieve this.  This is a very versatile function that I'll cover in a later QA doco.
# There are many options for editing this point however for this QA I will just move the point 1m East for every click on the point.
# To do this we need to put the command in a while loop.
while {1} {

  # Note pointMod below.  This is the reference variable that gives me complete access to this point and all data attached to it.  
  # Note also that the coordinates of the point are stored in the x, y and z variables below as well.
  set status [SclSelectPoint pointMod "Click a point to move by 1m East. Press Esc to continue." layerName strNum segNum pntNum x y z desc]
  
  # Using the handle we have to the point in reference variable "pointMod" get a handle to the segment it resides in.
  \$pointMod SclGetParent segMod 
  
  if {\$status != "\$SCL_OK"} {
     puts "Finished moving points"
     break
  }
  # Add 1m to the current x value
  \$pointMod SclSetValueByName x [expr \$x+1]

  \$segMod SclErase
  # Using the handle to the segment, draw the updated coords on the screen.
  \$segMod SclDraw
}

puts "End of part 4"

# At this point you could exit or save the modifications. We will leave the modified layer in graphics ready for Part 5 of this series.

# Part 5 will cover how to use SCL to loop over all points in a layer.

####### PART 5 #######

puts ""
puts "Part 5 - Using Scl to loop over all points in a layer"


# Get a handle to the viewport
SclGetActiveViewport vh
# Using the viewport handle, get a handle to the SWA (layer)
\$vh SclGetActiveLayer SwaH
# Using the handle to the SWA, get a handle to all the strings in the layer
\$SwaH SclGetStrings StrsH
# Count how many strings are in the active layer
set strQuantity [\$StrsH SclCountItems]

# If there aren't any strings then write a message to the user and stop the macro using the "return" command.
if {\$strQuantity <= 0} {
  puts "No strings in active layer."
  return
}

# Setup a string iterator based on the handle to the strings in "\$StrsH"
\$StrsH SclIterateFirst StringsIterator
#While there are strings to iterate, keep going
while {[\$StringsIterator SclIterateNext StringHandle] == \$SCL_TRUE} {
#Use SclGetId to work out the string number and print it to the message window
  puts "String number = [\$StringHandle SclGetId]"
  # Setup a segment iterator based on the handle to the string in "\$StringHandle"
  \$StringHandle SclIterateFirst StringIterator
  # While there are segments to iterate over in the current string, keep going
  while {[\$StringIterator SclIterateNext SegmentHandle] ==  \$SCL_TRUE} {
  #Remember, we add one to the segment number because segments work from an index position which starts at zero.
    puts "Segment number = [expr [\$SegmentHandle SclGetId]+1]"
    # Setup a point iterator based on the handle to the segment in "\$SegmentHandle"
    \$SegmentHandle SclIterateFirst SegmentIterator
    set PointCount 1
    # While there are points in the segment to iterate over, keep going.
    while {[\$SegmentIterator SclIterateNext PointHandle] == \$SCL_TRUE} {
      set x [\$PointHandle SclGetValueByName x]
      set y [\$PointHandle SclGetValueByName y]
      set z [\$PointHandle SclGetValueByName z]
      set d1 [\$PointHandle SclGetValueByName d1]
      puts "Point \$PointCount X = \$x, Y = \$y, Z = \$z, D1 = \$d1"
      incr PointCount
    }
  }
}

puts "You can also iterate backwards by changing SclIterateFirst for SclIterateLast"
puts "AND SclIterateNext for SclIteratePrev.  There are several reasons for needing to do this especially when deleting strings"

\$StrsH SclIterateLast StringsIterator
while {[\$StringsIterator SclIteratePrev StringHandle] == \$SCL_TRUE} {
  puts "String number = [\$StringHandle SclGetId]"
  \$StringHandle SclIterateLast StringIterator
  while {[\$StringIterator SclIteratePrev SegmentHandle] ==  \$SCL_TRUE} {
    puts "Segment number = [expr [\$SegmentHandle SclGetId]+1]"
    \$SegmentHandle SclIterateLast SegmentIterator
    set PointCount 1
    while {[\$SegmentIterator SclIteratePrev PointHandle] == \$SCL_TRUE} {
      set x [\$PointHandle SclGetValueByName x]
      set y [\$PointHandle SclGetValueByName y]
      set z [\$PointHandle SclGetValueByName z]
      puts "Point \$PointCount X = \$x, Y = \$y, Z = \$z"
      incr PointCount
    }
  }
}

#There are many things that can be done with this simple looping code.
#1. Write points to a csv file
#2. Modify the z value of points in a segment
#3. Write points inside a boundary to a new layer
#4. Identify points by inquiring there dfields.

puts ""
puts "End of part 5"
puts "Part 6 will cover how to use Surpac ranges.  While no data is actually needed, the code will check for it so keep it in graphics for now."

####### PART 6 #######

#Part 6
puts ""
puts "Part 6 - How to code Surpac Ranges using TCL/SCL."

# Get a handle to the viewport
SclGetActiveViewport vh
# Using the viewport handle, get a handle to the SWA (layer)
\$vh SclGetActiveLayer SwaH
# Using the handle to the SWA, get a handle to all the strings in the layer
\$SwaH SclGetStrings StrsH
# Count how many strings are in the active layer
set strQuantity [\$StrsH SclCountItems]
# If there aren't any strings then write a message to the user and stop the macro using the "return" command.
if {\$strQuantity <= 0} { 
  puts "No strings in active layer."
  return
}

# The range below would expand to 1,3,5,7,9 
puts "Example range below"
set myRange "1,10,2"
# Print the range to the message window
puts \$myRange

# Place all the expanded components into something equivalent to a TCL list
set status [SclRangeExpand handle \$myRange]
# Count how many items are in the range now it is a list
set count [SclRangeGetCount \$handle]
puts "The example range expands to the following:"
# Loop over each item in the range extracting the unique string number as you go.
for {set i 0} {\$i < \$count} {incr i} {
  # Store the item at index i into a variable called "value"
  set status [SclRangeGet \$handle \$i value];
  # The value always comes from the list in "double" format i.e. it has a decimal -> 1 is 1.0 NINT removes the decimal
  set value [SclExpr NINT(\$value)]
  puts "The range value at index \$i is \$value"
}
# Remove the range handle from memory
SclDestroy handle

# Now have a go at entering your own range in the GUIDO form.
set rangeForm {
  GuidoForm form {
    -label "Range Finder"
    -default_buttons
    
    GuidoField myRange {
      -label "Enter Range"
      -width 20
      -format range
      -null false
      -tip "Enter a valid Surpac range.  i.e 1,10,2 or 1;5;23"
    }
  }
}

SclCreateGuidoForm formH \$rangeForm {}
\$formH SclRun {}

if {"\$_status" != "apply"} {
  puts "Macro Cancelled"
  return
}

puts ""
puts "Your range is as below"
set status [SclRangeExpand handle \$myRange];# Place all the expanded components into something equivalent to a TCL list
set count [SclRangeGetCount \$handle];# Count how many items are in the range now it is a list
for {set i 0} {\$i < \$count} {incr i} {;# Loop over each item in the range extracting the unique string number as you go.
  set status [SclRangeGet \$handle \$i value];# Store the item at index i into a variable called "value"
  set value [SclExpr NINT(\$value)];# The value always comes from the list in "double" format i.e. it has a decimal -> 1 is 1.0 NINT removes the decimal
  puts "The range value at index \$i is \$value"
}
SclDestroy handle;# Remove the handle from memory

puts "End of Part 6.  The next part in this series will cover TCL lists. Click in Graphics to continue."
SclPause

###### PART 7 ####### 

#Let's assume we have saved the data we want and need to exit graphics to start another project.
SclFunction "EXIT GRAPHICS" {}

puts ""
puts "Part 7 - TCL lists for storing information"

set shoppingList {}

#Add items to our list with the lappend command
lappend shoppingList bacon
lappend shoppingList eggs
lappend shoppingList bread
lappend shoppingList meat
lappend shoppingList tomatoes
lappend shoppingList potatoes
lappend shoppingList onion
lappend shoppingList cheese

set item [lindex \$shoppingList 3]

#Write to the message window and then add a blank line for readability.
puts "Item at index position 3 is \$item"
puts ""

#foreach is a special TCL list command designed specifically to iterate over all items in a list, one by one.
set position 0
foreach item \$shoppingList {
 puts "Item \$position is \$item"
 incr position
}
puts ""

#Use the variable "shoppingListLength" to store the number of items in the list
set shoppingListLength [llength \$shoppingList]
puts "There are \$shoppingListLength items in your shopping list."
puts ""

puts "Now lets look at lists of lists"
#I have created a list (coordList) and stored four points in it with each point having an x and y value.
set coordList [list {100 100} {100 200} {200 200} {200 100}]

set point3 [lindex \$coordList 2]
#point3 is it's own list with the x coord in index 0 and the y coord in index 1
puts "Extract out a single value"
set point3X [lindex \$point3 0]
set point3Y [lindex \$point3 1]
puts "Point 3 coords -> X = \$point3X, Y = \$point3Y"
puts ""

#Now let's iterate over all points in the coordList
set pointNum 1
puts "Extract values for all points in the list"
foreach point \$coordList {
 set pointX [lindex \$point 0]
 set pointY [lindex \$point 1]
 puts "Point \$pointNum coords -> X = \$pointX, Y = \$pointY"
 incr pointNum
}

puts "This is the end of part 7.  Click in graphics to continue."
SclPause

###### PART 8 ####### 

puts ""
puts "Part 8 - Further work with TCL lists"

SclFunction "EXIT GRAPHICS" {}

proc newPoint {segHandle x y z} {;# See part 2 for how a procedure works.
 \$segHandle SclCreatePoint pntHandle [\$segHandle SclCountItems]
 \$pntHandle SclSetValueByName X \$x
 \$pntHandle SclSetValueByName Y \$y
 \$pntHandle SclSetValueByName Z \$z
 return \$pntHandle
}

SclGetActiveViewport vh
#Create a layer called list_ex that can be manipulated/inquired with the "pointsSwa" reference variable
SclCreateSwa pointsSwa "list_ex"
#Add the list_ex layer into the layers pane so it is visible
sclGraphicsLayers SclAdd \$pointsSwa \$vh
#Set the "list_ex" layer to be active
\$vh SclSetActiveLayer \$pointsSwa
#Using the reference to "pointsSwa", create a reference variable "listStr" and make the string number 1
\$pointsSwa SclCreateString listStr 1
#See details from part 1 about how this works.
\$listStr SclCreateSegment listSeg [\$listStr SclCountItems]

#Let's create another list of lists. In this example, each record stored in PointsList will have 5 items which should be a diagonal line but is all jumbled up. We are using the TCL command "lappend" to achieve this.
#This is the structure of the list
#lappend PointsList [list stringNum x y z desc]
lappend PointsList [list 1 10 10 5 pnt1]
lappend PointsList [list 1 50 50 5 pnt2]
lappend PointsList [list 1 30 30 5 pnt3]
lappend PointsList [list 1 40 40 5 pnt4]
lappend PointsList [list 1 20 20 5 pnt5] 

puts "Current list headers are ->  Str, X, Y, Z, DESC"
foreach point \$PointsList {
  #String trim command eliminates any white space that may creep in.  Index 0 is where the string number is stored.
  set strNum [string trim [lindex \$point 0]]
  set x [string trim [lindex \$point 1]]
  set y [string trim [lindex \$point 2]]
  set z [string trim [lindex \$point 3]]
  set desc [string trim [lindex \$point 4]]
  #Run the newPoint procedure to create points from the list
  newPoint \$listSeg \$x \$y \$z
  puts "Current Point values are -> \$strNum, \$x, \$y, \$z, \$desc"
}
\$listSeg SclDraw
SclFunction "ZOOM ALL" {}

\$pointsSwa SclCreateString listStr 8
\$listStr SclCreateSegment listSeg [\$listStr SclCountItems]
#Sort the list by Easting
set sortedList [lsort -index 1 -increasing \$PointsList]
set i 1
foreach point \$sortedList {
  set strNum [string trim [lindex \$point 0]]
  set x [string trim [lindex \$point 1]]
  set y [string trim [lindex \$point 2]]
  set z [string trim [lindex \$point 3]]
  set desc pnt\$i
  newPoint \$listSeg \$x \$y \$z
  puts "Current Point values are -> \$strNum, \$x, \$y, \$z, \$desc"
  incr i
}
\$listSeg SclDraw
SclFunction "ZOOM ALL" {}

#The below code will show the original and subsequent point numbers.
set status [ SclFunction "DRAW DESC" {
  frm00089={
    {
      range1="1"
      range2=""
      range3=""
      ifld_num="segment-point-no"
      textalignment="v"
      position="All points"
      layer_name="list_ex"
      display_object_number=""
      display_trisolation_number=""
    }
  }
}]

set status [ SclFunction "DRAW DESC" {
  frm00089={
    {
      range1="8"
      range2=""
      range3=""
      ifld_num="segment-point-no"
      textalignment="^"
      position="All points"
      layer_name="list_ex"
      display_object_number=""
      display_trisolation_number=""
    }
  }
}]

puts "This is the end of part 8. Click in graphics to continue."
SclPause

###### Part 9 ######

puts ""
puts "Part 9 - Using Arrays to store information"


SclFunction "EXIT GRAPHICS" {}

#Unset some variables that already exist in this combined TCL script as non array variables.
unset pointX
unset pointY

#A simple example of this is using an array to store point information. The first point will have a key of (1)
set pointStrNum(1) 8
set pointX(1) 10
set pointY(1) 10
set pointZ(1) 10

#To retrieve the information we can simply use a puts command
puts "Point 1 details are -> \$pointStrNum(1), \$pointX(1), \$pointY(1), \$pointZ(1)"

#Lets use nested for loops to put 100 points (x,y) into an array.  The string number and z values will be constants that we will add later
# This is the point count that we'll use as array key.
set i 1
# This for loop will increment the x coord
for {set x 1} {\$x <= 10} {incr x} {
  # This for loop will increment the y coord
  for {set y 1} {\$y <= 10} {incr y} {
    set pointX(\$i) \$x
    set pointY(\$i) \$y
    # Setting z value to a constant 10
    set pointZ(\$i) 10
    puts "Point \$i coords are -> \(\$x,\$y\)"
    incr i
  }
}
# Check to see how many points have been added into the array
set arraySizeCheck [array size pointX]
puts "There are \$arraySizeCheck points in the array"

puts "This is the end of part 9. Click in graphics to continue."
SclPause


##### Part 10 ##### 

puts ""
puts "Part 10 - Retrieving information from an array"
# This part of the series flows on from "Part 9 - Using Arrays to store information"

# See part 2 for how a procedure works.
proc newPoint {segHandle x y z} {
  \$segHandle SclCreatePoint pntHandle [\$segHandle SclCountItems]
  \$pntHandle SclSetValueByName X \$x
  \$pntHandle SclSetValueByName Y \$y
  \$pntHandle SclSetValueByName Z \$z
  return \$pntHandle
}

# Get a reference to the active viewport (You can have more than one) store it in reference variable "vh"
SclGetActiveViewport vh
# Create a layer called array that can be manipulated/inquired with the "arraySwa" reference variable
SclCreateSwa arraySwa "array"
# Add the array layer into the layers pane so it is visible
sclGraphicsLayers SclAdd \$arraySwa \$vh
# Set the "array" layer to be active
\$vh SclSetActiveLayer \$arraySwa
# Using the reference to "arraySwa", create a reference variable "arrayStr" and make the string number 1
\$arraySwa SclCreateString arrayStr 1
# See details from part 1 about how this works.
\$arrayStr SclCreateSegment arraySeg [\$arrayStr SclCountItems]

#This is the nested for loop from part 9 as a reminder of how we put data into the array.
#Lets use nested for loops to put 100 points (x,y) into an array.  The string number and z values will be constants that we will add later
# This is the point count that we'll use as array key.
set i 1
# This for loop will increment the x coord
for {set x 1} {\$x <= 10} {incr x} {
  # This for loop will increment the y coord
  for {set y 1} {\$y <= 10} {incr y} {
    set pointX(\$i) \$x
    set pointY(\$i) \$y
    # Setting z value to a constant 10
    set pointZ(\$i) 10
    puts "Point \$i coords are -> \(\$x,\$y\)"
    # Incrementing the key number
    incr i
  }
}

#Check to see how many points have been added into the array
set arraySizeCheck [array size pointX]
puts "There are \$arraySizeCheck points in the array"

# We will now use a for loop to retrieve information from an array.
for {set j 1} {\$j <= \$arraySizeCheck} {incr j} {
  set newX \$pointX(\$j)
  set newY \$pointY(\$j)
  set newZ \$pointZ(\$j)
  # Run the newPoint procedure to create points from the array
  newPoint \$arraySeg \$newX \$newY \$newZ
}

\$arraySeg SclDraw "style=ssi_method=marker,HGS_color=marker=green"
SclFunction "ZOOM ALL" {}

puts ""
puts "This ends our 10 part series on using some TCL/SCL commands in Surpac.  See you in 2025 for some creative coding!"

 

More Tips: 

   

 

 

Surpac Product Tips Macros TCL/SCL