A 3D Model File Loader-and-Examiner - for OBJ, PLY, OFF, STL, FEA files

uniquename - 2013jan17

On my 'bio' page at uniquename, in early 2012, I pointed out that I found the 3D model viewing programs of MBS = Mark Stucky (3dviewer : A canvas only viewer of 3D data) and GS = Gerard Soohaket (3D polyhedra with simple tk canvas) quite inspiring.

I stated my plan to devise a similar 3D viewer --- but with enhanced 3D model import options and some other enhancements. My plan was to support reading and examining models from ASCII file formats such as Wavefront OBJ, Stereolithography STL, Cyberware PLY, Geomview OFF, and at least one CAE(FEA) file format.

Thus, an unlimited number of 3D models could be viewed.

I took steps toward those ends by developing two somewhat less ambitious 3D surface viewers at the pages

In the first of these, I presented code for a Tk GUI that allows for examining surfaces generated over a rectangular grid in the x-y plane --- by generating the z-values of points on the surface, over a rectangular grid, from a function of x and y --- z=f(x,y).

In that script, I established a lot of procs that I could use for other 3D viewers. In particular, I provided a proc to rotate '3D point clouds'.

In the second of these 2 pages/scripts, I devised a proc for polygon-sorting according to a 'z-depth'.

So those two 3D viewer projects provided a lot of the code that I would need to make the 3D model-file viewer that was my ultimate goal.

In both of those projects, I was plotting only 4-sided polygons (quadrilaterals) on the Tk canvas.

Some of the new items that I would need were:

  • data-loader procs for ASCII 3D model files --- in particular, for OBJ, PLY, OFF, and STL files

and

  • enhanced 'sort' and 'draw' procs that would handle N-gons for N=2,3,4,5,6,7,8,9,10,... --- not just N=4, where N here indicates the number of vertices in an N-gon (so N=2 means a line-segment).

---

MY GOALS:

I decided to make a 3D model file reading-and-viewing utility that included the following features.

1) ROTATION METHOD:

I would allow the user to specify latitude and longitude angles to specify the view direction.

I would use Tk 'scale' widgets so that setting the 2 view angles can be done quickly and redraw is initiated immediately. I would use button1-release bindings on the scales to cause the redraw as soon as a scale change is complete.

(I may eventually add bindings to mouse events on the canvas, like <Motion>, so that the view rotation can be done even more quickly & conveniently. This would be similar to rotate/zoom/pan controls that Mark Stucky provided in a 3D model viewer that he published at wiki.tcl.tk/15032 - 3dviewer : A canvas only viewer of 3D data)

2) DATA ENTRY:

Instead of having one entry field for the function f(x,y) --- or 3 entry fields for functions f(u,v), g(u,v), h(u,v) --- I would have an entry field for a 3D model filename --- along with a 'Browse...' button by which to navigate to and select a file in the computer's directory structure.

'Data-loader' procs for the Wavefront OBJ, Cyberware PLY, Geomview OFF, and Stereolithography ASCII 3D file formats would be provided.

Thus we have the ability to choose from an unlimited variety of 3D models.

3) COLOR CHOICES:

I would (again) allow color choices for

  • polygon fill
  • polygon outline
  • canvas background

from among 16 million colors, each.

4) DISPLAY OPTIONS:

I would provide radiobuttons by which the type of model display could be chosen:

  • 'fill-only' of polygons
  • 'fill-and-outline' of polygons

and two 'outline-only' options:

  • 'wireframe-hidden'
  • 'wireframe-show-all'.

5) ZOOM OPTION:

I would (again) provide a 'zoom' Tk scale widget, by which the plot can easily be resized, down or up --- to make sure the entire plot can be seen on the canvas.

Like with the 2 scales for the longitude-latitude view angles, I would use a button1-release binding on the zoom scale to cause the redraw to be initiated as soon as a scale change is complete.

6) MATH APPROACH:

Whereas in the two surface plotting Tk scripts I used a z-axis in the 'up' direction, for this model-file viewer I would think of the 'fixed, viewing' coordinate axes oriented as follows:

  • positive y-axis is 'up' (parallel to the monitor screen)
  • positive x-axis is 'to the right' (parallel to the monitor screen)
  • positive z-axis is 'out of the screen' (perpendicular to the monitor screen).

Based on that, I would let the 'longitudinal' ('yaw') view angle specify a rotation around the y-viewing-axis and the 'latitudinal' ('pitch') view angle specify a rotation around the x-viewing-axis.

Then rotations of the models could be given by an Rx * Ry rotation matrix product.

(We are avoiding 'roll' --- rotation around the z axis. It is too disorienting. 'Roll' is for fighter jet simulations and for emulating a snowboarder doing flips off a half-pipe or a snow-park ramp. After all ...

When we examine an object, like a (stuffed) tiger, we walk around it and we may put our eyes somewhat above or below its middle --- but we generally do not stand on our head to examine it.)

As I did for the viewer of parametric surfaces given by 3 functions of u and v, I needed to implement a procedure for sorting the polygons before drawing them. I could take the sorting utility for that parametric surface viewer and enhance it to handle N-gons, where N = 2,3,4,... --- not just 4-gons (quadrilaterals).

Again, I would use the '-command' option of the Tcl 'lsort' command --- by providing a 'compare' proc to compare 2 given polygon IDs, according to a depth-measure.

---

In aiming to accomplish these goals, I ended up with the GUI seen in the following image.

3DmodelExaminerGUI_OBJ_gear_11264faces_grayWithWhiteOutlinesOnBlack_1024x715.jpg

In this image, you can see the three buttons for color-setting, across the top of the GUI --- 'Fill', 'Outline', and 'Background'.

In the frame below the color-buttons frame, you can see the entry field for a 3D model filename --- along with a 'Browse...' button.

Below that frame, you can see the frame for radiobuttons by which the user can choose the 'data-loader' to use --- OBJ, PLY, OFF, STL, or whatever.

Below that frame, you can see the frame for

  • fill/outline radiobuttons
  • fill-color-source radiobuttons

And below that frame is the frame containing some 'shading' options, for shading the polygon fill color(s) based on height measures along the 'local', 'model' coordinate axes (z or y or x) or a z-depth measure along the viewing direction (the z-axis of the fixed, 'viewing' coordinate system).

And the next frame contains the 2 scales for the longitude (yaw) and latitude (pitch) rotation angles --- around the fixed, 'viewing' axes --- the y-axis and the x-axis, respectively. Also in that frame is the scale for zooming the model, in or out.

---

In addition to these GUI features, the image above indicates that the polygon-sort routine does its job quite capably --- at least for models that are nicely meshed, with lots of polygons of approximately the same size (and no long 'slivers').

In fact, the polygons (triangles) are nicely hidden in the classic 'cow' and 'bunny' models that are available from large model sites at Stanford and Georgia Tech --- as seen in the following images.

3DmodelExaminerGUI_OBJ_cow_5804faces_brownWithYellowWireOnGreen_716x500.jpg

3DmodelExaminerGUI_PLY_bunny_69451faces_1024x715.jpg

The 'gear' model above contained 11,264 faces and was loaded-translated-rotated-sorted-and-drawn in less than 2 seconds. This 'bunny' model contained 69,451 faces --- what seems like a superfluous number of triangular faces --- and was drawn in about 14 seconds.

That is not as speedy as a viewer using OpenGL (and firmware in a graphics card to handle the 'graphics pipeline') would draw this bunny. But the draw speed is not too shabby for free software --- and considering the large number of polygons --- and if you simply want to examine the model, not make it spin like a top.

I was pleasantly surprised that I could view models with tens of thousands of polygons in a reasonable amount of time.

And I was able to find 'decimated' bunny models --- models with reduced numbers of polygons --- as seen in the following images.

3DmodelExaminerGUI_PLY_bunny_16301faces_552x627.jpg

16,301 faces and a draw-time of about 3.0 seconds.

3DmodelExaminerGUI_PLY_bunny_9580faces_548x626.jpg

9,580 faces and a draw-time of about 1.8 seconds.

3DmodelExaminerGUI_PLY_bunny_3851faces_B_562x623.jpg

3,851 faces and a draw-time of about 0.6 second.

---

To show that OBJ and PLY and OFF and STL files can be handled by this utility, note that the 'gear' and 'cow' models above were from OBJ files, the 'bunny' files were from PLY files, and here are a couple of models from OFF and STL files.

3DmodelExaminerGUI_OFF_pipes_768faces_blueShaded_716x500.jpg

This OFF model of intersecting pipes contained 768 faces.

3DmodelExaminerGUI_STL_turbineBlades_1818trias_redWithYelloWireOnGreen_714x500.jpg

This STL model of turbine blades contained 1,818 triangles.

In fact, during development and testing of the 'data-loader' procs of this utility, I read in about 80 different OBJ files, about 50 different PLY files, about 20 different OFF files, and about 5 different STL files.

I have confidence that I can read in most of the ASCII OBJ, PLY, OFF and STL files that one would run across --- and if I/you hit any glitches, the source code is available. You can enhance these 'data-loaders' as needed.

---

The 'Help' button on the GUI shows the following text. It describes the various ways in which a 'draw' is triggered.


HELP for this 3D-Model-Examiner Utility

LOADING A MODEL:

When the GUI comes up, you can use the 'Browse...' button to find a 3D model file to load. Then set a radiobutton for the appropriate 'data-loader' to use.

An MB1-click-release on any of the 'data-loader' radiobuttons causes 3D model data in the specified filename to be loaded into 'in-memory arrays' and then be displayed on the 'canvas'. (MB = Mouse Button)

The selected 'data-loader' will read the file's 3D model data --- data for points and polygons (and line-segments, if any).

Typically, the polygons in 3D model files are triangles and/or quadrilaterals (N-gons where N, the number of vertices, is 3 or 4), but some of these data-loaders will also load data for 2-gons (line-segments) and N-gons where N is greater than 4.

ROTATING THE MODEL:

Once the model data is loaded, you can use the two rotation angle 'SCALE' (slider) widgets, to quickly change either a 'longitude' (yaw) angle or a 'latitude' (pitch) angle. These two scales allow for 'semi-dynamically' rotating the 3D-model by moving the sliders of the 2 scale widgets.

Three types of slider moves and the results:

  - 'Grab' the slider button (by clicking on it with MB1 and
    holding) and then drag MB1.  RESULT: When MB1 is released, the
    angle at that point on the scale is used to rotate the model.

or

  - MB1-click-release repeatedly on the TROUGH of the scale
    (to the RIGHT or LEFT of the slider button). RESULT:
    The model is rotated 1 degree per click-release.

or

  - Click on the TROUGH of the scale (to the RIGHT or LEFT of the
    slider button) with MB1 and hold down while the slider button
    moves, then release MB1.  RESULT:  When MB1 is released, the
    angle at that point on the scale is used to rotate the model.

Note that the 'fixed, viewing' axes of this utility are assumed to be as follows.

- the positive y-axis is 'up' (parallel to the monitor screen)

- the positive x-axis is 'to the right' (parallel to the monitor screen)

- the positive z-axis is 'out of the screen) (perpendicular to the monitor screen)

The longitudinal (yaw) rotation is about the y-axis. The latitudinal (pitch) rotation is about the x-axis.

A model is initially loaded so that its 'local' xyz axes are aligned with these 'fixed, viewing' axes. Then rotations of the points in the model are performed relative to the 'viewing' axes.

---

MAPPING VERTEX-RECORD COLUMNS TO AXES:

There are 3 radiobuttons that allow for mapping the 3 coordinates of each vertex record to the xyz axes: 123-to-xyz OR 123-to-zxy OR 123-to-yzx

This effectively allows the user to change the axes along which the model was originally built. This can also help with rotating the model with 2 rotation angles (yaw and pitch) rather than with 3 rotation angles (yaw, pitch, and roll).

---

There are several types of radiobuttons to control the type of display of the 3D model:

FILL/OUTLINE of polygons:

1) fill-only

2) fill-and-outline,

3) wire-hidden (i.e. outline-only, with hidden outlines)

4) wire-show-all (i.e. outline-only, none hidden)

---

FILL-COLOR SOURCE of polygons:

1) Polygon-Fill color button (applied to all polygons)

2) Model file color data (can be different for each polygon or for groups of polygons)

3) random colors (for each polygon)

4) colors from a color-table (distributed over the polygons according to the loaded Z-coordinates of the model; intended mainly for use with terrain surfaces)

---

Type of SHADING of polygons:

0) none

1) by depth, along the 'original' z-axis of the model

2) by depth, along the 'original' y-axis of the model

3) by depth, along the 'original' x-axis of the model

4) by depth, along the current view direction

5) by angle of polygon normals relative to a lighting direction

---

'WIRE' DISPLAY OPTIONS:

Clicking on the 'wire-hidden' or 'wire-show-all' radiobutton causes immediate display of a 'wire-frame' image of the model --- using the setting of the 'Polygon Outline' color button as the color of the wire-frame lines.

When either of the 2 'wire' FILL/OUTLINE options are chosen, the radiobuttons for FILL-COLOR-SOURCE and SHADING are disabled, i.e. 'grayed-out'.

---

FILL DISPLAY OPTIONS:

When either of the 2 'fill' FILL/OUTLINE options are chosen, the FILL-COLOR-SOURCE options are activated --- and the SHADING options may be activated according to user-selection of a FILL-COLOR-SOURCE option.

---

POLYGON 'PAINTING' (SORTING) OPTIONS:

There are several radiobuttons that allow for calling on different sort procedures to try in case one procedure is not drawing some polygons in a proper order --- i.e. if some polygons that should be hidden (for the most part) are being drawn in front of polygons that should be in front.

---

Summary of 'EVENTS' that cause a 'REDRAW' of the plot:

MB1-clicking on a 'Data Loader' radiobutton after a filename is placed in the 3D-model-filename entry field.

MB1-clicking on the 'wire-hidden' or 'wire-nohide' radiobuttons.

MB1-clicking on one of the Shading radiobuttons --- if the 'fill-only' or 'fill-and-outline' radiobutton is set. (The current setting of the fill-source radiobuttons determines the color source for the fill of each polygon.)

Button1-release on the LONGITUDE or LATITUDE scale widget.

Button1-release on the ZOOM scale widget.

Changing color via the FILL-COLOR or OUTLINE-COLOR buttons.

ALSO: Resizing the window changes the size of the canvas, which triggers a redraw of the plot according to the new canvas size.

---

INPUT FILE FORMATS:

Some information on supported records of OBJ, PLY, OFF, and STL files follows.

(Snip. That record format info is not here but can be seen at the bottom of the script below.)


The code

I provide the code for this 3D model file reader-and-examiner Tk script below.

I follow my usual 'canonical' structure for Tk code for this Tk script:

  0) Set general window & widget parms (win-name, win-position,
     win-color-scheme, fonts, widget-geometry-parms, win-size-control,
     text-array-for-labels-etc).

  1a) Define ALL frames (and sub-frames, if any).
  1b) Pack   ALL frames and sub-frames.

  2) Define & pack all widgets in the frames, frame by frame.
     Within each frame, define ALL the widgets. Then pack the widgets.

  3) Define keyboard and mouse/touchpad/touch-sensitive-screen action
     BINDINGS, if needed.

  4) Define PROCS, if needed.

  5) Additional GUI initialization (typically with one or more of
     the procs), if needed.

This structure is discussed in more detail on the page A Canonical Structure for Tk Code --- and variations.

This structure makes it easy for me to find code sections --- while generating and testing a Tk script, and when looking for code snippets to include in other scripts (code re-use).

I call your attention to step-zero. One new thing that I have started doing recently is using a text-array for text in labels, buttons, and other widgets in the GUI. This can make it easier for people to internationalize my scripts. I will be using a text-array like this in most of my scripts in the future.


Experimenting with the GUI

As in all my scripts that use the 'pack' geometry manager (which is all of my 100-plus scripts, so far), I provide the four main pack parameters --- '-side', '-anchor', '-fill', '-expand' --- on all of the 'pack' commands for the frames and widgets.

That helps me when I am initially testing the behavior of a GUI (the various widgets within it) as I resize the main window.

I think that I have used a nice choice of the 'pack' parameters. The labels and buttons and scales stay fixed in size and relative-location as the window is re-sized --- while the 'canvas' expands/contracts as the window is re-sized.

You can experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various frames and widgets --- to get the widget behavior that you want.

___

In addition, you might want to change the fonts used for the various GUI widgets. For example, you could change '-weight' from 'bold' to 'normal' --- or '-slant' from 'roman' to 'italic'. Or change font families.

In fact, you may NEED to change the font families, because the families I used may not be available on your computer --- and the default font that the 'wish' interpreter chooses may not be very pleasing.

I use variables to set geometry parameters of widgets --- parameters such as border-widths and padding. And I have included the '-relief' parameter on the definitions of frames and widgets. Feel free to experiment with those 'appearance' parameters as well.

___

Note that the color buttons call on a color-selector-GUI script to set the colors. You can make that color-selector script by cutting-and-pasting the code from the page A non-obfuscated color selector GUI on this site.


Some features in the code

That said, here's the code --- with plenty of comments to describe what most of the code-sections are doing.

You can look at the top of the PROCS section of the code to see a list of the procs used in this script, along with brief descriptions of how they are called and what they do.

___

As in my f(x,y) surface-generator-and-examiner script and my f(u,v)-g(u,v)-h(u,v) surface-generator-and-examiner script, one interesting feature of this GUI is the way the procs involved in a redraw are broken up into a sequence. In this script:

  1) load_points_array
  2) translate_points_array
  3) rotate_points
  4) sort_polyIDs_list
  5) draw_2D_pixel_polys

Some 'events' --- such as click-release on any of the 'data-loader' radiobuttons --- trigger the execution of all 5 procs (in that order).

Other events (like longitude or latitude change) trigger the execution of only the last 3 procs.

Clicking on one of the sort-option radiobuttons triggers the execution of the last 2 procs.

And some 'simple' changes (like a color change or a switch to wireframe mode) trigger the execution of only the last proc.

Note that I do most of the calculations in 'world coordinates', NOT pixel coordinates. All the coordinate calculations in the first 3 procs are done in world coordinates.

It is in the 5th proc that I obtain a set of 2D points from a family of 3D points, and I map a 'bounding area' of the 2D points into the current canvas area, in units of pixels --- to finally get the plot, via 'create polygon' commands --- and 'create line' commands, if there are any line-segments in the model.

---

To implement the 'sort_polyIDs_list' proc, I needed to provide a 'compare' proc for the '-command' option of the 'lsort' command. You can see the guts of that 'compare' proc --- which is named 'compare_2polyIDs_by_zdepth' --- in the code.

Actually, I provide 3 alternate 'compare' scripts in this code:

  • compare_2polyIDs_by_AVEzdepth
  • compare_2polyIDs_by_biggerMINzdepth
  • compare_2polyIDs_by_MAXzdepth

This is because I found that in initial testing on a model with long, 'sliver-shaped' polygons (see the Dilbert's head model below), the second of these 3 procs worked better than the other two.

But these procs (especially the MAX and MIN procs) need some work --- especially in case of a 'tie' in the comparison of a pair of polygons.

This GUI has been equipped with several 'sort-opts' radiobuttons by which you can choose from these 3 sort-compare procs (or more).

___

The 'data-loader' procs may be of use to others who need such 3D data loaders in their Tcl-Tk applications. The techniques used to load the data can be seen in the procs named

  • 'load_3DfileData_cOBJ'
  • 'load_3DfileData_PLY'
  • 'load_3DfileData_OFF'
  • 'load_3DfileData_STL'

___

It is my hope that the copious comments in the code will help Tcl-Tk coding 'newbies' get started in making GUI's like this.

Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch videos of skateboarders kissing concrete.


 Code for Tk script '3DmodelExaminer_readModelFiles_RxRy.tk' :
#!/usr/bin/wish -f
##
## SCRIPT: 3DmodelExaminer_readModelFiles_RxRy.tk
##
## PURPOSE: This Tk GUI script serves to examine 3D model data. The script
##          projects 3D-points onto a 2D 'viewing plane'. Actually, it projects
##          the connective geometry of the points --- that is, polygons ---
##          and straight-line-segments, if any --- onto the viewing plane.
##
##          The model data is read in from a 3D model data file in one of
##          the following ASCII file formats: Wavefront OBJ, Cyberware PLY,
##          Geomview OFF, Stereolithograpy STL, or at least one
##          CAE(FEA)-like file format.
##
##          Many other file formats (binary and ASCII) such as '.3ds', '.blend',
##          VRML2, or VRML1 can be read into a 3D modeling program (such as
##          freeware Blender) or a converter program (such as freeware IVCON) and
##          converted to Wavefront OBJ format. ASCII OBJ is our high-priority
##          format to support. It is the most common ASCII format used at
##          sites that provide a wide variety of 3D models.
##
##          The '3D points' data are read from vertex (point) data records
##          of a user-specified 3D model file. Also polygon records such
##          as triangle and quadrilateral 'connectivity' records are read.
##          Some formats --- such as OBJ and the CAE(FEA)-like formats ---
##          may have line-segment records. Those 'line connectivity' records
##          are read by the OBJ and CAE(FEA) file reading procs.
##
##          File formats such as 'obj', 'ply', 'stl', 'off', and CAE/FEA-like
##          data formats are only *PARTIALLY SUPPORTED*. As an example
##          of 'partial support', 'vertex texture' ('vt') records in
##          OBJ files are not supported. The OBJ file-reader proc in this script
##          skips over those records. It may also skip over 'vertex normal'
##          ('vn') records. But the OBJ file-reader DOES read and use all
##          'v' (vertex) and 'f' (face) and 'l' (line) records.
##
##          The point, polygon, and line-segment data are stored in arrays
##          in memory.  A main way this script facilitates graphically examining
##          the data stored in those arrays is via rotation of the model data.
##          The model is rotated via user-specified 'longitude' and 'latitude' angles.
##
##          The 2D projection points, after being determined according to a
##          user-specified view direction (specified via longitude & latitude
##          angles), are 'mapped' onto a rectangular Tk canvas.
##
##          The '3D projection' of the model data is rendered by plotting
##          polygons (and lines, if any) that are defined by the
##          point-connectivity data.
##
##          The polygons in model file records are typically triangles or quadrangles,
##          but, in some files, higher order n-gons may occur. The data-loader
##          procs of this script support reading high order n-gons.
##
##          In addition to plotting polygons via Tk-canvas 'create polygon' commands,
##          plotting of straight lines is supported via Tk-canvas 'create line'
##          commands. The intent is to support point-connectivity data for
##          line-segments in the OBJ and CAE/FEA-like model files --- as well
##          as connectivity data for polygons.
##
##+###########
## INSPIRED BY:
##           This script is inspired by 3D model viewing/examining scripts by
##           MBS = Mark Stucky ('3dviewer : A canvas only viewer of 3D data')
##           and GS = Gerard Soohaket ('3D polyhedra with simple tk canvas')
##           at wiki.tcl.tk.
##
##           Their scripts demonstrated that one can rotate 2D projections of
##           3D models consisting of many hundreds of facets in 'real time'
##           using 'plain vanilla' Tcl-Tk (no OpenGL required) --- using
##           the 'create polygon' facility of the Tk canvas.
##
##           Their scripts dealt mainly with point and polygon data stored
##           in Tcl vertex and connectivity variables.
##
##           Their scripts did not have robust model file reading routines
##           for any of several popular 3D model file types. This script
##           is intended to support an essentially unlimited variety of
##           3D models read in from model files of several ASCII types.
##
##+################################################################################
## METHOD:   We use a 'viewing' coordinate system based on x and y axes in the plane
##           of the monitor viewing-screen and a z axis perpendicular to the screen.
##           We assume the positive x-axis is to the right, the positive y-axis
##           is up, and the positive z-axis is out of the screen toward the viewer
##           (user).
##
##           The rotation-and-projection of the 3D model is based on an Rx * Ry
##           rotation-matrix-product approach, where Ry is a longitudinal (yaw)
##           rotation about the fixed, 'viewing' vertical y-axis and  Rx is a
##           latitudinal (pitch) rotation about the fixed, 'viewing' horizontal x-axis.
##
##           To prepare for rotating the model, the point coordinates are translated
##           so that their new origin is a point in the middle of the 'point cloud'
##           of the model.
##
##           The 2D projections of 3D points are taken to be the transformed (translated
##           and rotated) x and y coordinates of the 3D points. The transformed z-coordinates
##           are used to sort the polygons (and lines) and thus determine the order
##           in which to draw the projected polygons (and lines) onto the 2D canvas.
##
##           For any given rotation of the 3D model, rendering of each 2D projection 
##           of a 3D polygon is done with Tk canvas-'create polygon' commands.
##
##           And, in the case when there are line-records, canvas-'create line'
##           commands are used to plot 2D projections of the 3D line-segments.
##
##           A 'Background Color' button is provided on the GUI so that the
##           user can change the background color of the canvas.
##
##           Some radiobuttons/checkbuttons are provided so that the user
##           can specify:
##             1) whether the polygons should be OUTLINED or FILLED or BOTH,
##             2) if polygons are filled, the SOURCE OF the FILL-COLOR for the polygons
##                --- where the choices include
##                    - polygon-fill-color specified via a button on the GUI, OR
##                    - color data (if any) from the model file, OR
##                    - random colors, OR
##                    - a table of colors (built-in to this script)
##             3) if the polygons are filled, whether to SHADE the fill color via
##                any one of several methods.
##
##           We load the MODEL DATA  --- POINTS DATA AND 'CONNECTIVITY' DATA
##           (polygons and lines) --- into array variables --- TWO ARRAYS ---
##           one for points, and one for connectivity data (polygons and lines).
##
##+################
## THE POINTS DATA:
##
##           We apply ROTATION-AND-PROJECTION operations (via a pair of
##           user-specified rotation angles) to TRANSLATED-POINTS model data.
##           The translated points-data is held in a 3rd array, and the rotated
##           points-data is held in a 4th array.  In other words ...
##
##           We do not replace the loaded model data in the ORIGINAL POINTS-ARRAY
##           with transformed coordinate data, after the translation and the first
##           rotation --- or any other rotation.
##
##           Instead, we keep the original model data in the points data array
##           and use other arrays for transformations (translation and rotations)
##           of the original points.
##
##           The 'ORIGINAL points' array consists of list elements of the form:
##            aRpoints($i)  = [list $pointID $RGBcolors-list/$propID $x $y $z]
##
##           Each element of the 'points' array is a list of 5 items:
##           - a point-ID, 1 color-spec, and 3 (floating-point/decimal/integer) coordinates
##
##           The 'point-ID' could be different from the integer '$i' that sequentially
##           indexes the array element. This is mainly to accomodate CAE(FEA)-type data
##           where the user may choose integer ID's, in various ranges, to identify
##           the points --- to help organize the data in some way. In other words,
##           the 'point-ID' integers do not have to be sequential; there may be gaps.
##
##           In the Tcl array 'aRpoints', the 'color-spec' may be either
##             -  a Tcl list of 3 RGB values --- 3 integers between 0 and 255 
##                (for example: 135 126 203)
##           OR
##             - a single integer value (which may be used as an index into
##               a color-table or a range of RGB colors).
##
##+#####################
## THE CONNECTIVITY DATA:
##
##           The elements of our 'connectivity' array are Tcl lists which
##           may be of variable length.
##
##           The connectivity array consists of list elements of the form:
##    aRconnect($i) = [list $elemID $RGBcolors-list/$mtlID $numVertices $vertID1 $vertID2 ...]
##
##           The CONNECTIVITY DATA ARRAY stays constant after it is loaded,
##           because we assume no changes in the connectivity once loaded.
##
##           The 'polygons' array will hold lines, triangles, quadrilaterals,
##           and on up. Since lines are typically not thought of as polygons,
##           our 'polygons' array is really a 'connectivity' array holding
##           elements that consist of connected points --- from
##           2 points on up.   
##
##           We will allow for storing an 'element-ID' and an 'RGB-color specification'
##           with each connectivity element, so each 'connectivity' array element may be:
##
##           For a line:
##               - an element ID, 1 color, the integer '2', and 2 integer IDs of points
##           For a triangle:
##               - an element ID, 1 color, the integer '3', and 3 integer IDs of points
##           For a quadrangle: 
##               - an element ID, 1 color, the integer '4', and 4 integer IDs of points        
##           And so on for other N-gons.
##
##           In the Tcl 'aRconnect' array, the 'element-ID' and 'color spec' for the
##           array elements may be of the same format and intent described above for
##           the 'point-id' and 'color-spec' items described for the 'aRpoints' array.
##
##           I.e. the element-IDs need not be sequential, and the color-spec for each
##           n-gon may be a 3 element list of RGB colors.
##
##+################
## DRAWING THE DATA:
##
##           For any given rotation, the polygons-and-lines are sorted according to
##           their distance from the view point (eye) --- i.e. according to their
##           transformed (translated-rotated) 'z-depth'. The script draws the
##           polygons-and-lines starting with the farthest first and progressing
##           to the nearest.
##
##           For CAE(FEA)-like model files, the line segments and polygons may include
##           a group/material/property ID (or color specification) for each such item.
##           The GUI is intended to provide the option of using those color specs.
##
##           The line-segments are drawn on a Tk viewing 'canvas' using Tk
##           canvas-'create line' commands --- with each line-segment possibly being
##           a different color. Similarly, each polygon, drawn with the
##           Tk canvas-'create polygon' command, could have a different color.
##
##           If we ever provide for a points-only display, the points could
##           also be plotted --- as small crosses or circles, say --- or even
##           as individual pixels --- with each point possibly having a different color.
##
##+##############
## THE GUI LAYOUT:
##           Of course,
##           the GUI contains a rectangular CANVAS widget onto which the
##           set of 3D points will be projected. The 3D points are shown as
##           a set of 2D points.
##
##           Actually, rather than points, polygons (usually triangles or
##           quadrilaterals) --- and, in some models, lines --- are drawn on the
##           canvas --- and the 2D points are the vertices (and end-points) of
##           colored polygons (and colored lines, if any) lying on a viewing plane
##           (the canvas).
##
##           In addition to the canvas widget (which is in a bottom frame of the
##           GUI window), there are at least 4 frames at the top of the GUI window:
##
##              - 1 frame for an 'Exit' BUTTON, a 'Help' BUTTON, 3 color BUTTONS
##                (for polygon-fill, polygon-outline, background), and
##                a LABEL widget to hold 'info' --- for example, the current
##                color settings of the buttons, in hex format. This label
##                may also show draw-times, in milliseconds.
##
##              - 1 frame for a LABEL widget, an ENTRY widget, and a
##                'Browse...' BUTTON --- to be used to select a 3D
##                model file for input
##
##              - 1 frame for some 'filetype' RADIOBUTTONS --- used to specify
##                3D model-file format --- i.e. to specify a 'data-loader'
##                to use with a selected model file. (We do not make any
##                assumptions based on file suffixes.)
##                This frame may also contain a LABEL widget, to display
##                number of points/lines/facets=polygons in the currently
##                loaded model.
##
##              - 1 frame for 'fill/outline' RADIOBUTTONS (for fill/outline/both) and 'src'
##                RADIOBUTTONS with which to choose a source of color(s) with which to
##                fill the polygons (from the polygon-fill color button on the GUI; OR
##                from model file color info, if any; OR random colors; OR colors from
##                a color table in this script).
##
##              - 1 frame for 'shade' RADIOBUTTONS (for fill-color shading) to specify
##                the method of shading (none, z-height, y-height, x-height, view-depth).
##
##              - 1 frame for 2 'SCALE' (slider) widgets, to quickly
##                specify a couple of rotation angles (longitude and
##                latitude), and thus allow for 'semi-dynamically' rotating
##                the view/3D-model by moving the sliders of the 2 scale widgets.
##                Also, a 'zoom' SCALE may be shown in this frame.     
##
##+###################
## DYNAMICS OF THE GUI:
##
##           Button1-release bindings on the scale widgets will be used to
##           call one or more procs to re-draw the 3D-model, projected
##           onto the canvas, according to the current settings of the
##           2 rotation-angles.
##
##           By using the button1-release bindings, we can get a 'semi-dynamic'
##           (almost immediate) GUI-update action from the rotation and zoom scales,
##           and the user may see rather smooth movement of the projected
##           3D model on the 'viewing canvas' by rapidly clicking button1 in the
##           trough of a rotation-angle scale --- if the model is not too large.
##
##           NOTE: The redraws may be fast enough (for not-too-large 3D models)
##           so that the projected points/polygons/lines move in a smooth fashion
##           as the scale sliders are moved. So we could use a '-command'
##           option on the sliders to call on the procs to render the model for
##           a changing angle. And we could set a threshold of a certain number
##           of points over which the '-command' would not be used, and
##           a button1-release binding on each scale widget would be used instead.
##           Perhaps that method may be used in a future enhancement of this script.
##
##+######################################################################
## 'CANONICAL' STRUCTURE OF THIS CODE:
##
##  0) Set general window parms (win-name,win-position,color-scheme,fonts,
##     widget-geometry-parms,win-size-control,text-array-for-labels-etc).
##  1a) Define ALL frames (and sub-frames, if any).
##  1b) Pack   ALL the frames and sub-frames.
##  2) Define widgets in the frames, frame by frame.
##     When ALL widgets are defined for a frame, pack them.
##
##  3) Define keyboard and mouse/touchpad/touch-sensitive-screen 'event'
##     BINDINGS, if needed.
##  4) Define PROCS, if needed.
##  5) Additional GUI INITIALIZATION (typically, with one or two procs),
##     if needed.
##
## The code-structure detail for this particular script:
##
##  1a) Define ALL frames:
## 
##      Top-level : '.fRbuttons'   '.fRfile'       '.fRfiletypes'
##                  '.fRfillopts'  '.fRshadeopts'  '.fRscales'     '.fRcanvas'
##
##      Sub-frames: none
##
##  1b) Pack ALL frames.
##
##  2) Define all widgets in the frames (and pack them):
##
##       - In '.fRbuttons': 5 button widgets ('Exit','Help','FillColor','OutlineColor',
##                          'BkgdColor'), 1 label widget
##
##       - In '.fRfile':      label,entry,button widgets for 3D model file selection
##
##       - In '.fRfiletypes': several radiobutton widgets for OBJ,PLY,STL,OFF,CAE(FEA)
##
##       - In 'fRsorts':      several radiobuttons for 'z-depth' sort types
##
##       - In '.fRfillopts': - radiobuttons to specify polygon fill-only/outline-only/both
##                             and, for when fill is on,
##                             radiobutton widgets with which to specify the source of
##                             polygon-fill-color (from the model data OR from the
##                             FillColor button OR ...)
##
##       - In '.fRshadeopts': - radiobuttons to specify how to do the shading, if fill is on
##
##       - In '.fRscales' frame:  3 scale widgets (with label widgets) for
##                                longitude and latitude angles and for zoom
##
##       - In '.fRcanvas': one 'canvas' widget 
##
##  3) Define BINDINGS: 
## 
##        - Mouse-button1-release bindings on the filetype (dataloader)
##          radiobuttons --- to trigger loading the data from the
##          selected file according to the file-type radiobutton setting. The
##          bindings call proc 'load_3DfileData', which selects the dataloader
##          according to the 'filetypes' radiobutton setting.
##
##        - button1-release bindings on the 2 view-angle scale widgets,
##          and on the zoom scale widget.
##
##        - and some other bindings, in particular, on the various
##          radiobuttons (See the BINDINGS section in the code below.)
##
##  4) Define PROCS:
##
##  'get_model_filename' - presents the user with a file-selector GUI
##                         and returns a selected filename in the
##                         filename entry widget (This proc is called by
##                         the 'Browse...' button on the GUI.)
##
##  'load_3DfileData' - called by bindings on the filetype radiobuttons.
##                      This proc calls one of the following 'load'
##                      procs depending on the current setting of the
##                      filetype radiobuttons.
##
##  'load_3DfileData_OBJ'  for ASCII Wavefront OBJ files
##  'load_3DfileData_PLY'  for ASCII Cyberware PLY files
##  'load_3DfileData_STL'  for ASCII Stereolithography files
##  'load_3DfileData_OFF'  for ASCII Geomview OFF files
## 
##  and a couple of other data loaders may be implemented, such as:
##
##  'load_3DfileData_NASshort' -
##                          This particular 'data-loader' proc loads
##                          NASTRAN 'short field' format records from the file 
##                          whose filename is in the filename entry field. The
##                          'GRID' record data is loaded into the points array,
##                          'aRpoints' and connectivity data from 'C' connectivity
##                          finite-element records is loaded into the connectivity
##                          array 'aRconnect'.
##
##
##   - procs to do translate-data, rotate-data, sort-polygons, pixel-draw-the-polygons.
##
##   - 3 procs for setting colors (fill, outline, background/canvas)
##
##   - 'popup_msgVarWithScroll' - to display a Help text var and error msg vars
##
##   - and some other procs (See the PROCS section in the code below.)
##
## Perhaps someday:
##
##  'cross_product' - to be called by the draw-polygons proc to support
##                    'shading' of the color of the polygons, according to
##                    the angle that the polygon normals make to a
##                    'lighting' vector --- such as the 'view vector',
##                    if we assume the lighting is attached to the
##                    viewer's forehead, say.
##                    This proc may also be used someday to support a
##                    'cull backfaces' checkbutton that may be added to the GUI.
##
##  5) Additional GUI initialization:
##         Radiobutton settings, color settings, rotation angles,
##         and zoom factor will be initialized here.
##
##+############################################################################
## DEVELOPED WITH:
##   Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october, 'Karmic Koala).
##
##   $ wish
##   % puts "$tcl_version $tk_version"
##                                  showed   8.5 8.5   on Ubuntu 9.10.
##+###########################################################################
## MAINTENANCE HISTORY:
## Started by: Blaise Montandon 2012nov09 Started laying out the GUI.
## Changed by: Blaise Montandon 2012nov12 Changed from 3 scales for a
##                                        'view vector' to 2 scales for
##                                        2 rotation angles.
## Changed by: Blaise Montandon 2012dec23 Added the radiobuttons related to
##                                        fill color-source and fill shading.
## Changed by: Blaise Montandon 2013jan03 Added the 'sort_polyIDs_list' and
##                                        'compare_2polyIDs_by_zdepth' procs.
## Changed by: Blaise Montandon 2013jan08 Added most of the guts of the
##                                        PLY and OFF 'data loader' procs.
##                                        Started a preliminary version of the
##                                        'NASshort' data loader.
## Changed by: Blaise Montandon 2013jan16 Updated the HELPtext to match the
##                                        current status of this script.
## Changed by: Blaise Montandon 2013feb07 Added 3 radiobuttons to map
##                                        vertex columns 123 to xyz axes.
##                                        Added sort radiobuttons to allow
##                                        for choice of several sort methods.
##                                        Implemented shade 'byLighting' 
##                                        radiobutton.
##                                        Implemented fill-source from
##                                        ColorTable radiobutton.
##+###########################################################################

##+#######################################################################
## Set window parms --- WIN-TITLE and WIN-POSITION.
##+#######################################################################

wm title    . "3D Model Reader-Examiner --- for various 3D model file formats"
wm iconname . "3Dexamine"

wm geometry . +15+30


##+#########################################################
## Set the COLOR SCHEME (palette) for the window ---
## and some colors for its widgets --- such as
## background colors of scale, entry, and listbox widgets.
##+#########################################################

tk_setPalette "#e0e0e0"

set scaleBKGD "#ffffff"
set entryBKGD "#ffffff"
set radbuttBKGD "#ffffff"

set labelFGND "#ff0000"
set labelBKGD "#cccccc"


## Initialize the polygons fill-color (COLOR1) and
## the outline-color (COLOR2) --- and background (canvas) color.
##
## Alternatively, these initial color settings could be moved
## to the additional-GUI-initialization' section at the bottom
## of this script.

# set COLOR1r 255
# set COLOR1g 255
# set COLOR1b 255
set COLOR1r 255
set COLOR1g 0
set COLOR1b 255
set COLOR1hex [format "#%02X%02X%02X" $COLOR1r $COLOR1g $COLOR1b]

# set COLOR2r 255
# set COLOR2g 255
# set COLOR2b 255
set COLOR2r 255
set COLOR2g 255
set COLOR2b 255
set COLOR2hex [format "#%02X%02X%02X" $COLOR2r $COLOR2g $COLOR2b]

# set COLORbkGNDr 100
# set COLORbkGNDg 100
# set COLORbkGNDb 100
set COLORbkGNDr 0
set COLORbkGNDg 0
set COLORbkGNDb 0
set COLORbkGNDhex \
    [format "#%02X%02X%02X" $COLORbkGNDr $COLORbkGNDg $COLORbkGNDb]


## Make a 'color table' of Ncolors, for use with the
## 'byColorTable' option of the Shade options.
##
## PRELIMINARY. This table may have entries added to be a table
## of at least 18 colors. And color values may be refined by
## some testing with terrain-like models.

## Lake/Ocean Blue
# set aRcolor(1) "#0099ff"
  set aRcolor(1) [list 0 153 255]

## Blue-Green
# set aRcolor(2) "#00ccaa"
  set aRcolor(2) [list 0 204 170]

## Delta/Riverside Green
# set aRcolor(3) "#00ff00"
  set aRcolor(3) [list 0 255 0]

## Green-to-Brown
# set aRcolor(4) "#66cc00"
  set aRcolor(4) [list 102 204 0]

## Dark Earth Brown
# set aRcolor(5) "#cc6622"
  set aRcolor(5) [list 204 102 16]

## Light Earth Brown
# set aRcolor(6) "#eeaa66"
  set aRcolor(6) [list 238 170 102]

## Dark Green Trees
# set aRcolor(7) "#009900"
  set aRcolor(7) [list 0 153 0]

## Green-to-Gray
# set aRcolor(8) "#66aa66"
  set aRcolor(8) [list 102 170 102]

## Gray Alps/Mountains Rock-Gravel
# set aRcolor(9) "#cccccc"
  set aRcolor(9) [list 204 204 204]

## Gray-to-White
# set aRcolor(10) "#dcdcdc"
  set aRcolor(10) [list 220 220 220]

## Snow White
# set aRcolor(11) "#ffffff"
  set aRcolor(11) [list 255 255 255]

set Ncolors 11



##+########################################################
## SET 'FONT-NAMES'.
##
## We use a VARIABLE-WIDTH FONT for labels and buttons
## and the numeric values shown by scale widgets.
##
## We use a FIXED-WIDTH FONT for the help text in a text
## widget (so that any columns in the text stay lined up)
## and for text in entry fields (so that letters like i,j,l
## are easily accessible when changing text entries).
##+########################################################

font create fontTEMP_varwidth \
   -family {comic sans ms} \
   -size -14 \
   -weight bold \
   -slant roman

font create fontTEMP_SMALL_varwidth \
   -family {comic sans ms} \
   -size -10 \
   -weight bold \
   -slant roman

## Some other possible (similar) variable width fonts:
##  Arial
##  Bitstream Vera Sans
##  DejaVu Sans
##  Droid Sans
##  FreeSans
##  Liberation Sans
##  Nimbus Sans L
##  Trebuchet MS
##  Verdana

font create fontTEMP_fixedwidth  \
   -family {liberation mono} \
   -size -14 \
   -weight bold \
   -slant roman

font create fontTEMP_SMALL_fixedwidth  \
   -family {liberation mono} \
   -size -10 \
   -weight bold \
   -slant roman

## Some other possible fixed width fonts (esp. on Linux):
##  Andale Mono
##  Bitstream Vera Sans Mono
##  Courier 10 Pitch
##  DejaVu Sans Mono
##  Droid Sans Mono
##  FreeMono
##  Nimbus Mono L
##  TlwgMono


##+###########################################################
## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS.
## (e.g. width and height of canvas, and padding for Buttons)
##+###########################################################

## CANVAS widget geom settings:

set initCanWidthPx 300
set initCanHeightPx 300
set minCanWidthPx 100
set minCanHeightPx 24
set BDwidthPx_canvas 0

## BUTTON widget geom settings:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## LABEL widget geom settings:

set PADYpx_label 0
set PADXpx_label 0
# set BDwidthPx_label 0
  set BDwidthPx_label 2

  set RELIEF_label "ridge"
# set RELIEF_label "groove"
# set RELIEF_label "solid"

## ENTRY widget geom settings:

set BDwidthPx_entry 2


## SCALE widget geom settings:

set BDwidthPx_scale 2


## RADIOBUTTON geom parameters:

set PADXpx_radbutt 0
set PADYpx_radbutt 0
set BDwidthPx_radbutt 2


## CHECKBUTTON geom parameters:

set PADXpx_chkbutt 0
set PADYpx_chkbutt 0
set BDwidthPx_chkbutt 2


## For TEXT widgets:

set BDwidthPx_text 2


##+##########################################################
## Set a MINSIZE of the window (roughly) -- according to the
## approx max WIDTH of the buttons in the 'fRbuttons' frame
## --- and according to the approx HEIGHT of the frames stack.
##+##########################################################

set minWinWidthPx [font measure fontTEMP_varwidth \
   "Exit  Help  Fill  Outline  Background"]

## Add some pixels to account for right-left-side window decoration
## (about 8 pixels), about 5 widgets x 3 pixels/widget for borders/padding
## for 5 widgets --- 5 buttons.

set minWinWidthPx [expr {23 + $minWinWidthPx}]


## MIN HEIGHT --- allow
##     2 chars   high for the 'fRbuttons'   frame
##     1 char    high for the 'fRfile'      frame
##     1 char    high for the 'fRfiletypes' frame
##     1 char    high for the 'fRfillopts'  frame
##     1 char    high for the 'fRshadeopts' frame
##     2 chars   high for the 'fRscales'    frame
##   240 pixels  high for the 'fRcanvas'    frame

set charHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr {240 + (8 * $charHeightPx)}]

## Add some pixels to account for top-bottom window decoration
## (about 28 pixels) and frame/widget padding vertically
## (about 4 pixels/frame x 6 frames).

set minWinHeightPx [expr {52 + $minWinHeightPx}]

## FOR TESTING:
#   puts "minWinWidthPx = $minWinWidthPx"
#   puts "minWinHeightPx = $minWinHeightPx"

wm minsize . $minWinWidthPx $minWinHeightPx

## We allow the window to be resizable and we pack the canvas
## (and any of its containing frames) with '-fill both', so
## that the canvas can be enlarged by enlarging the window.


## If you want to make the window un-resizable, 
## you can use the following statement.

# wm resizable . 0 0


##+####################################################################
## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI.
##     NOTE: This can aid INTERNATIONALIZATION. This array can
##           be set according to a nation/region parameter.
##+####################################################################

## if { "$VARlocale" == "en"}

## For the 'fRbuttons' frame:

set aRtext(buttonEXIT)      "Exit"
set aRtext(buttonHELP)      "Help"
set aRtext(buttonTOGOPTS)   "Toggle
Opts"
set aRtext(buttonBkgdCOLOR) "Background
Color"
set aRtext(buttonOUTLINE)   "Polygon
Outline Color"
set aRtext(buttonFILL)   "Polygon
Fill Color"


## For the 'fRfile' frame:

set aRtext(labelFILENAME)  "3D Model filename:"
set aRtext(buttonBROWSE)   "Browse..."


## For the 'fRfiletypes' frame:

set aRtext(labelFILETYPES) "Data Loader to use:"
set aRtext(radbuttOBJ)      "cOBJ"
set aRtext(radbuttPLY)      "PLY"
set aRtext(radbuttSTL)      "STL"
set aRtext(radbuttOFF)      "OFF"
set aRtext(radbuttNASshort) "NASshort
(someday?)"

set aRtext(label123toXYZ) "VertexCols2Axes:"
set aRtext(radbuttX1Y2Z3) "1x2y3z"
set aRtext(radbuttX2Y3Z1) "2x3y1z"
set aRtext(radbuttX3Y1Z2) "3x1y2z"
# set aRtext(radbuttX1Y2Z3) "x1y2z3"
# set aRtext(radbuttX2Y3Z1) "x2y3z1"
# set aRtext(radbuttX3Y1Z2) "x3y1z2"

## For the 'fRsorts' frame:

set aRtext(labelSORT)    "SortOpts:"
set aRtext(radbuttSORTaveDepth)  "AVEpolyDepth"
set aRtext(radbuttSORTmaxDepth)  "MAXpolyDepth"
set aRtext(radbuttSORTminDepth)  "MINpolyDepth"


## For the 'fRfillopts' frame:

set aRtext(labelFILLOUT)    "Fill or Outline polys
(wire = outline-only):"
set aRtext(radbuttFILLOUTfillonly) "fill-
only"
set aRtext(radbuttFILLOUTboth)     "fill-and-
outline"
set aRtext(radbuttFILLOUTwirehide) "wire-
hidden"
set aRtext(radbuttFILLOUTwirenohide) "wire-
showall"

set aRtext(labelFILLSRC) "Fill-color
source:"
set aRtext(radbuttFILLSRCfromFile)   "Model
File"
set aRtext(radbuttFILLSRCfromButton) "FillColor
Button"
set aRtext(radbuttFILLSRCrandom)     "Random
EachPoly"
set aRtext(radbuttFILLSRCfromTable)   "$Ncolors-Color
TableForZheight"

set aRtext(labelWIRE)    "Color for the 'wire' display options
is set from the Polygon-Outline color button."


## For the 'fRshadeopts' frame:

set aRtext(labelSHADE) "Do Fill shading
according to:"
set aRtext(radbuttSHADEnone)        "noShading"
set aRtext(radbuttSHADEorigZheight) "polyZheight"
set aRtext(radbuttSHADEorigYheight) "polyYheight"
set aRtext(radbuttSHADEorigXheight) "polyXheight"
set aRtext(radbuttSHADEzDepth)      "polyViewDepth"
set aRtext(radbuttSHADEbyLighting)  "polyLighting"

set aRtext(labelSHADEinfo) "Clicking on any of the Fill-Opts radiobuttons above (non-wire)
does NOT cause a re-draw, but clicking on any of these
Shade option radiobuttons DOES cause a re-draw."

# set aRtext(labelSHADEinfo) "Shading by 'Lighting' is not implemented yet.
# If fill-color-source is ColorTable, pick a height/depth method, which
# will be used to distribute (unshaded) table colors on the model."


## For the 'fRscales' frame:

set aRtext(labelVIEW)    "View via 2 angles
longitude,latitude:"
set aRtext(labelZOOM)    "Zoom
factor:"

## Some error messages:

   set modelNOTloadedMSG "\
It appears that a 3D model file has not been loaded yet.
 Try selecting or entering a filename for the filename entry field.
 Then press Enter/Return or click MouseButton3 on the entry field."

## END OF  if { "$VARlocale" == "en"}


##+################################################################
## DEFINE *ALL* THE FRAMES:
##
##   Top-level : 'fRbuttons' , 'fRfile'      , 'fRfiletypes' ,
##               'fRsorts'   , 'fRfillopts'  , 'fRshadeopts' ,
##               'fRscales'  , 'fRcanvas'
##
##   Sub-frames: none
##+################################################################

## FOR TESTING of expansion of frames (esp. during window expansion):
# set RELIEF_frame raised
# set BDwidth_frame 2

set RELIEF_frame flat
set BDwidth_frame 0

  frame .fRbuttons   -relief $RELIEF_frame  -borderwidth $BDwidth_frame

  frame .fRfile      -relief $RELIEF_frame  -borderwidth $BDwidth_frame

  frame .fRfiletypes -relief $RELIEF_frame  -borderwidth $BDwidth_frame
# frame .fRfiletypes -relief raised         -borderwidth 2

# frame .fRsorts  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
  frame .fRsorts -relief raised         -borderwidth 2

# frame .fRfillopts  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
  frame .fRfillopts  -relief raised         -borderwidth 2

# frame .fRshadeopts -relief $RELIEF_frame  -borderwidth $BDwidth_frame
  frame .fRshadeopts -relief raised         -borderwidth 2

  frame .fRscales    -relief $RELIEF_frame  -borderwidth $BDwidth_frame

  frame .fRcanvas    -relief $RELIEF_frame  -borderwidth $BDwidth_frame


##+##############################
## PACK the top-level FRAMES. 
##+##############################

pack .fRbuttons \
   -side top \
   -anchor nw \
   -fill none \
   -expand 0

pack .fRfile \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack .fRfiletypes \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack  .fRsorts \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack  .fRfillopts \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack  .fRshadeopts \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack  .fRscales \
   -side top \
   -anchor nw \
   -fill none \
   -expand 0


pack .fRcanvas \
   -side top \
   -anchor nw \
   -fill both \
   -expand 1

## OK. ALL frames are defined. Ready to define widgets.


##+################################################################
##+################################################################
## START DEFINING & PACKING WIDGETS WITHIN THEIR FRAMES,
## frame-by-frame. When all widgets for a frame are defined,
## pack them in the frame.
##+################################################################
##+################################################################

##+################################################################
## IN THE '.fRbuttons' frame -
## DEFINE several BUTTONS (Exit,Help,Colors) and a LABEL widget.
##+################################################################

button .fRbuttons.buttEXIT \
   -text "$aRtext(buttonEXIT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {exit}

button .fRbuttons.buttHELP \
   -text "$aRtext(buttonHELP)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {popup_msgVarWithScroll .fRhelp "$HELPtext"}

button .fRbuttons.buttTOGOPTS \
   -text "$aRtext(buttonTOGOPTS)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {toggle_opts}

button  .fRbuttons.buttFILL \
   -text "$aRtext(buttonFILL)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_polygon_color1"

button  .fRbuttons.buttOUTLINE \
   -text "$aRtext(buttonOUTLINE)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_polygon_color2"

button .fRbuttons.buttCOLORbkGND \
   -text "$aRtext(buttonBkgdCOLOR)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_background_color"


## The text for this label will be loaded by
## one or more procs, such as 'set_polygon_color2'
## and 'set_background_color'.

label .fRbuttons.labelCOLORS \
   -text "" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

label .fRbuttons.labelSTATUS \
   -text "\
Click on a data-loader radiobutton to cause a selected file to be loaded.
 Use the 'Help' button to get info on the file types (supported records)." \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label \
   -fg $labelFGND \
   -bg $labelBKGD


## Pack ALL the widgets in frame 'fRbuttons'.

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttHELP \
     .fRbuttons.buttTOGOPTS \
     .fRbuttons.buttFILL \
     .fRbuttons.buttOUTLINE \
     .fRbuttons.buttCOLORbkGND \
     .fRbuttons.labelCOLORS \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbuttons.labelSTATUS \
   -side left \
   -anchor w \
   -fill x \
   -expand 1 \
   -padx {50 0}


##+################################################################
## IN THE '.fRfile' frame -
## DEFINE several LABEL,ENTRY,BUTTON widgets.
##+################################################################

label .fRfile.labelFILENAME \
   -text "$aRtext(labelFILENAME)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label


## We initialize this widget var (and others)
## in the GUI initialization section at the
## bottom of this script.
##
# set ENTRYfilename ""

entry .fRfile.entFILENAME \
   -textvariable ENTRYfilename \
   -bg $entryBKGD \
   -font fontTEMP_fixedwidth \
   -width 30 \
   -relief sunken \
   -bd $BDwidthPx_entry

button .fRfile.buttBROWSE \
   -text "$aRtext(buttonBROWSE)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {get_model_filename}


## Pack ALL the widgets in frame 'fRfile'.

pack  .fRfile.labelFILENAME \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfile.entFILENAME \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack  .fRfile.buttBROWSE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+########################################################
## In the '.fRfiletypes' frame -
## DEFINE a LABEL widget and several RADIOBUTTON widgets.
##+########################################################

label .fRfiletypes.labFILETYPES \
   -text "$aRtext(labelFILETYPES)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label

## The radiobuttons variable, VARfiletype, is
## initialized in the additional-GUI-initialization
## section at the bottom of this script, or simply
## left in a 'to-be-determined-by-the-user' state.

# set VARfiletype "cOBJ"
# set VARfiletype "PLY"
# set VARfiletype "OFF"
# set VARfiletype "STL"
# set VARfiletype "NASshort"


radiobutton  .fRfiletypes.radbuttOBJ \
   -text "$aRtext(radbuttOBJ)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable VARfiletype \
   -value "cOBJ" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button

## We can apply this to any radiobutton,
## to disable it until a reader is ready-for-use/fixed.

#   -state disabled

radiobutton  .fRfiletypes.radbuttPLY \
   -text "$aRtext(radbuttPLY)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable VARfiletype \
   -value "PLY" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button

radiobutton  .fRfiletypes.radbuttOFF \
   -text "$aRtext(radbuttOFF)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable VARfiletype \
   -value "OFF" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button

radiobutton  .fRfiletypes.radbuttSTL \
   -text "$aRtext(radbuttSTL)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable VARfiletype \
   -value "STL" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button

radiobutton  .fRfiletypes.radbuttNASshort \
   -text "$aRtext(radbuttNASshort)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable VARfiletype \
   -value "NASshort" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button \
   -state disabled

## The 'NASshort' data-loader option will be enabled if
## its implementation is finished someday.


label .fRfiletypes.lab123toXYZ \
   -text "$aRtext(label123toXYZ)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label

## The radiobuttons variable, VAR123toxyz, is
## initialized in the additional-GUI-initialization
## section at the bottom of this script.

# set VAR123toxyz "x1y2z3"
# set VAR123toxyz "x2y3z1"
# set VAR123toxyz "x3y1z2"

radiobutton  .fRfiletypes.radbuttX1Y2Z3 \
   -text "$aRtext(radbuttX1Y2Z3)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable VAR123toxyz \
   -value "x1y2z3" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button

radiobutton  .fRfiletypes.radbuttX2Y3Z1 \
   -text "$aRtext(radbuttX2Y3Z1)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable VAR123toxyz \
   -value "x2y3z1" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button

radiobutton  .fRfiletypes.radbuttX3Y1Z2 \
   -text "$aRtext(radbuttX3Y1Z2)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable VAR123toxyz \
   -value "x3y1z2" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button


## Pack ALL the widgets in frame 'fRfiletypes'.

pack .fRfiletypes.labFILETYPES \
     .fRfiletypes.radbuttOBJ \
     .fRfiletypes.radbuttPLY \
     .fRfiletypes.radbuttOFF \
     .fRfiletypes.radbuttSTL \
     .fRfiletypes.radbuttNASshort \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfiletypes.lab123toXYZ \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 \
   -padx {50 0}

pack .fRfiletypes.radbuttX1Y2Z3 \
     .fRfiletypes.radbuttX2Y3Z1 \
     .fRfiletypes.radbuttX3Y1Z2 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+######################################################
## In FRAME '.fRsorts' -
## DEFINE-and-PACK a LABEL & several RADIOBUTTON WIDGETS.
##+######################################################

label .fRsorts.labelSORT \
   -text "$aRtext(labelSORT)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label

## We set the initial value for these radiobutton widgets in the
## GUI initialization section at the bottom of this script.

# set poly_sort  "avePolyDepth"
# set poly_sort  "maxPolyDepth"
# set poly_sort  "minPolyDepth"


radiobutton .fRsorts.radbuttSORTaveDepth \
   -text "$aRtext(radbuttSORTaveDepth)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_sort \
   -value "avePolyDepth" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRsorts.radbuttSORTmaxDepth \
   -text "$aRtext(radbuttSORTmaxDepth)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_sort \
   -value "maxPolyDepth" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRsorts.radbuttSORTminDepth \
   -text "$aRtext(radbuttSORTminDepth)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_sort \
   -value "minPolyDepth" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt


## Pack the widgets in frame 'fRsorts'.

pack .fRsorts.labelSORT \
     .fRsorts.radbuttSORTaveDepth \
     .fRsorts.radbuttSORTmaxDepth \
     .fRsorts.radbuttSORTminDepth \
   -side left \
   -anchor nw \
   -fill none \
   -expand 0


##+###########################################################################
## IN THE '.fRfillopts' frame -
## DEFINE two groups of RADIOBUTTON widgets, each preceded by a LABEL widget.
##+###########################################################################
## The possible values of the 'poly_fillout' var of the FILLOUT radiobutton:
##     'FILLonly'  or  'FILLoutline'  OR  'WIREhide'  OR  'WIREnohide'
##     where WIRE = outline-only
##
## The possible values of the 'poly_fillsrc' var of the FILLSRC radiobutton:
##    'fromButton'  OR  'fromFile'  OR  'fromColorTable' OR  'random'
## (These are used when the 'poly_fillout' var is 'FILLonly' or 'FILLoutline'.)
##+############################################################################

## FILLOUT options:

label .fRfillopts.labelFILLOUT \
   -text "$aRtext(labelFILLOUT)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label

## We set the initial value for these radiobutton widgets in the
## GUI initialization section at the bottom of this script.

# set poly_fillout "FILLonly"
# set poly_fillout "FILLoutline"
# set poly_fillout "WIREhide"
# set poly_fillout "WIREnohide"

radiobutton .fRfillopts.radbuttFILLOUTfillonly \
   -text "$aRtext(radbuttFILLOUTfillonly)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_fillout \
   -value "FILLonly" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRfillopts.radbuttFILLOUTboth \
   -text "$aRtext(radbuttFILLOUTboth)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_fillout \
   -value "FILLoutline" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRfillopts.radbuttFILLOUTwirehide \
   -text "$aRtext(radbuttFILLOUTwirehide)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_fillout \
   -value "WIREhide" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRfillopts.radbuttFILLOUTwirenohide \
   -text "$aRtext(radbuttFILLOUTwirenohide)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_fillout \
   -value "WIREnohide" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt


## FILLSRC options:

label .fRfillopts.labelFILLSRC \
   -text "$aRtext(labelFILLSRC)" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label

## We set the initial value for these radiobutton widgets in the
## GUI initialization section at the bottom of this script.

# set poly_fillsrc "fromFile"
# set poly_fillsrc "fromButton"
# set poly_fillsrc "fromColorTable"
# set poly_fillsrc "random"

## We can disable these 'filesrc' radiobuttons in the
## additional-GUI-initialization section at the bottom of
## this script, with the 'disable_filesrc_radbutts' proc,
## according to the radiobutton settings chosen in that
## GUI initialization section.

radiobutton .fRfillopts.radbuttFILLSRCfromFile \
   -text "$aRtext(radbuttFILLSRCfromFile)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_fillsrc \
   -value "fromFile" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRfillopts.radbuttFILLSRCfromButton \
   -text "$aRtext(radbuttFILLSRCfromButton)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_fillsrc \
   -value "fromButton" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRfillopts.radbuttFILLSRCrandom \
   -text "$aRtext(radbuttFILLSRCrandom)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_fillsrc \
   -value "random" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRfillopts.radbuttFILLSRCfromColorTable \
   -text "$aRtext(radbuttFILLSRCfromTable)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_fillsrc \
   -value "fromColorTable" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt


## This label is for info on how color for the
## 'wire' display options is set.

label .fRfillopts.labelWIRE \
   -text "$aRtext(labelWIRE)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -justify center \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label \
   -fg $labelFGND \
   -bg $labelBKGD


## Pack the 'fRfillopts' widgets.

pack .fRfillopts.labelFILLOUT \
     .fRfillopts.radbuttFILLOUTfillonly \
     .fRfillopts.radbuttFILLOUTboth \
     .fRfillopts.radbuttFILLOUTwirehide \
     .fRfillopts.radbuttFILLOUTwirenohide \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfillopts.labelFILLSRC \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 \
   -padx {50 0}

pack .fRfillopts.radbuttFILLSRCfromFile \
     .fRfillopts.radbuttFILLSRCfromButton \
     .fRfillopts.radbuttFILLSRCrandom \
     .fRfillopts.radbuttFILLSRCfromColorTable \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfillopts.labelWIRE \
   -side left \
   -anchor w \
   -fill x \
   -expand 1 \
   -padx {20 0}


##+#####################################################################
## IN THE '.fRshadeopts' frame -
## DEFINE one group of RADIOBUTTON widgets, preceded by a LABEL widget.
##+#####################################################################
## The possible values of the 'poly_shade' var of the SHADE radiobutton:
##   'none'  OR  'origZheight'  OR  'origYheight'  OR 'origXheight'  OR
##   'zDepth'  OR  'byLighting'
##
## (These shade-opts are to be used when the 'poly_fillout' var is
## 'FILLonly' or 'FILLoutline'.
##  For 'FILLoutline', 'SHADEnone' is recommended, for faster execution.
##  For 'FILLonly', the user will probably want to use one of the other
##  shade options, to avoid getting one big blob of solid color, when only
##  one fill color is being used --- for all of the model or large parts of
##  the model.)
##+########################################################################

## SHADE options:

label .fRshadeopts.labelSHADE \
   -text "$aRtext(labelSHADE)" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label

## We set the initial value for these radiobutton widgets in the
## GUI initialization section at the bottom of this script.

# set poly_shade "none"
# set poly_shade "origZheight"
# set poly_shade "origYheight"
# set poly_shade "origXheight"
# set poly_shade "zDepth"
# set poly_shade "byLighting"


## We can disable these 'shade' radiobuttons in the
## additional-GUI-initialization section at the bottom of
## this script, with the 'disable_shade_radbutts' proc,
## according to the 'fillout' and 'fillsrc' radiobutton settings
## chosen in that GUI initialization section.


radiobutton .fRshadeopts.radbuttSHADEnone \
   -text "$aRtext(radbuttSHADEnone)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_shade \
   -value "none" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRshadeopts.radbuttSHADEorigZheight \
   -text "$aRtext(radbuttSHADEorigZheight)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_shade \
   -value "origZheight" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRshadeopts.radbuttSHADEorigYheight \
   -text "$aRtext(radbuttSHADEorigYheight)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_shade \
   -value "origYheight" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRshadeopts.radbuttSHADEorigXheight \
   -text "$aRtext(radbuttSHADEorigXheight)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_shade \
   -value "origXheight" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRshadeopts.radbuttSHADEzDepth \
   -text "$aRtext(radbuttSHADEzDepth)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_shade \
   -value "zDepth" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRshadeopts.radbuttSHADEbyLighting \
   -text "$aRtext(radbuttSHADEbyLighting)" \
   -font fontTEMP_SMALL_varwidth \
   -anchor w \
   -variable poly_shade \
   -value "byLighting" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt


label .fRshadeopts.labelSHADEinfo \
   -text "$aRtext(labelSHADEinfo)" \
   -font fontTEMP_SMALL_varwidth \
   -justify center \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label \
   -fg $labelFGND \
   -bg $labelBKGD


## Pack ALL the widgets in the '.fRshadeopts' frame.

pack .fRshadeopts.labelSHADE \
     .fRshadeopts.radbuttSHADEnone \
     .fRshadeopts.radbuttSHADEorigZheight \
     .fRshadeopts.radbuttSHADEorigYheight \
     .fRshadeopts.radbuttSHADEorigXheight \
     .fRshadeopts.radbuttSHADEzDepth \
     .fRshadeopts.radbuttSHADEbyLighting \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRshadeopts.labelSHADEinfo \
   -side left \
   -anchor w \
   -fill x \
   -expand 1 \
   -padx {30 0}


##+########################################################
## In the '.fRscales' frame -
## DEFINE 3 SCALE widgets --- for 2 rotation angles and
## a zoom factor --- with LABEL widgets as needed.
##+########################################################

label .fRscales.labelVIEW \
   -text "$aRtext(labelVIEW)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_label


## We will set initial values for the following
## 2 rotation scales (longitude,latitude) in the
## additional-GUI-initialization section
## at the bottom of this script.

## This 'scaleLON' widget is for the longitudinal angle,
## which can range from 0 to 360 degrees.
## (The longitude may eventually go outside these bounds,
## when we add the option to use 'mouse gestures' to do the model
## rotation. But we can keep the angle in this range with
## the Tcl '%' (modulo) operator --- modulo 360.)

scale .fRscales.scaleLON \
   -orient horizontal \
   -resolution 1 \
   -from 0 -to 360 \
   -digits 4 \
   -length 360 \
   -repeatdelay 500 \
   -repeatinterval 50 \
   -font fontTEMP_SMALL_varwidth \
   -troughcolor "$scaleBKGD" \
   -tickinterval 360

#   -label "$aRtext(scaleLON)" \
#   -command {rotate-sort-draw}

## We do NOT use the '-variable' option.
## It may cause 'auto-repeat' problems.
#   -variable angLON \

## This 'scaleLAT' is for the latitudinal angle,
## which can range from -90 to 90 degrees.
## (The latitude may eventually go outside these bounds,
## when we add the option to use 'mouse gestures' to do the model
## rotation. But we can keep the angle in this range with
## the Tcl '%' (modulo) operator --- plus 90, modulo 180, minus 90.)

scale .fRscales.scaleLAT \
   -orient horizontal \
   -resolution 1 \
   -from -90 -to 90 \
   -digits 3 \
   -length 260 \
   -repeatdelay 500 \
   -repeatinterval 50 \
   -font fontTEMP_SMALL_varwidth \
   -troughcolor "$scaleBKGD" \
   -tickinterval 180

#   -label "$aRtext(scaleLAT)" \
#   -command {rotate-sort-draw}

## We do NOT use the '-variable' option.
## It may cause 'auto-repeat' problems.
#   -variable angLAT \


## Define a label widget to precede the zoom-scale.

label .fRscales.labelZOOM \
   -text "$aRtext(labelZOOM)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief $RELIEF_label \
   -bd $BDwidthPx_button

## We set the initial value for this 'scaleZOOM' widget in the
## GUI initialization section at the bottom of this script.
# set curZOOM 1.4
# set curZOOM 1.1
# set curZOOM 1.0
# set curZOOM 0.8

scale .fRscales.scaleZOOM \
   -orient horizontal \
   -resolution 0.1 \
   -from 0.1 -to 10.0 \
   -digits 3 \
   -length 150 \
   -repeatdelay 500 \
   -repeatinterval 50 \
   -font fontTEMP_SMALL_varwidth \
   -troughcolor "$scaleBKGD" \
   -variable curZOOM \
   -tickinterval 9.8

#  -command "wrap_draw_2D_pixel_polys 0"


## Pack ALL the widgets in the 'fRscales' frame.

pack .fRscales.labelVIEW \
     .fRscales.scaleLON \
     .fRscales.scaleLAT \
     .fRscales.labelZOOM \
     .fRscales.scaleZOOM \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+###############################
## In frame '.fRcanvas' -
## DEFINE-and-PACK a CANVAS widget:
##+###############################
## We set '-highlightthickness' and '-borderwidth' to
## zero, to avoid covering some of the viewable area
## of the canvas, as suggested on page 558 of the 4th
## edition of 'Practical Programming with Tcl and Tk'.
##+###################################################

canvas .fRcanvas.canvas \
   -width $initCanWidthPx \
   -height $initCanHeightPx \
   -relief flat \
   -highlightthickness 0 \
   -borderwidth 0

pack .fRcanvas.canvas \
   -side top \
   -anchor nw \
   -fill both \
   -expand 1


##+#####################################################################
## END OF SECTIONS TO SETUP THE GUI.
## ALL FRAMES and WIDGETS are DEFINED.
##+#####################################################################
## Start of BINDINGS, PROCS, Added-GUI-INIT sections.
##+#####################################################################


##+#######################################################################
## DEFINE BINDINGS SECTION:
##
## A sequence of up to 5 procs may be used to perform a draw:
##   - load_3DfileData
##   - translate_points_array
##   - rotate_points
##   - sort_polyIDs_list
##   - draw_2D_pixel_polys
##
## The bindings on the 'filetype' (data-loader) radiobuttons should do all 5 ops:
## load-translate-rotate-sort-draw.
##
## The bindings on the longitude-latitude scales should do the last 3 ops:
## rotate-sort-draw.
##
## The binding on the zoom scale should do the last proc: draw
##
## The bindings on the outline (wire-only) radiobuttons should do the last proc
## (and disable some buttons): 
##     disable_fillsrc_radbutts ; disable_shade_radbutts ; draw.
##
## The bindings on the fill/out radiobuttons should do: enable_fillsrc_radbutts
## The bindings on the fillsrc  radiobuttons should do: enable_shade_radbutts
##
## The bindings on the shade    radiobuttons should do the last proc: draw
##+#######################################################################

## For filename-entry-field:
## (These bindings are too risky. The user will probably forget to set
##  the filetype radiobutton appropriately.)
# bind .fRfile.entFILENAME <Return>           "load-translate-rotate-sort-draw"
# bind .fRfile.entFILENAME <ButtonRelease-3>  "load-translate-rotate-sort-draw"

## For filetype radiobuttons:
## NOTE: We put 'load-...-draw' bindings on the 'filetype' radiobuttons.
##       This will hopefully make the user think about the filetype
##       of the filename loaded in the filename entry field, and,
##       hopefully, the user is more likely to use the right data-loader.

bind .fRfiletypes.radbuttOBJ  <ButtonRelease-1>  "load-translate-rotate-sort-draw"
bind .fRfiletypes.radbuttPLY  <ButtonRelease-1>  "load-translate-rotate-sort-draw"
bind .fRfiletypes.radbuttOFF  <ButtonRelease-1>  "load-translate-rotate-sort-draw"
bind .fRfiletypes.radbuttSTL  <ButtonRelease-1>  "load-translate-rotate-sort-draw"
bind .fRfiletypes.radbuttNASshort   <ButtonRelease-1>  "load-translate-rotate-sort-draw"

## Bindings on widgets in the 'fRsorts' frame:

bind .fRsorts.radbuttSORTaveDepth  <ButtonRelease-1> "sort-draw"
bind .fRsorts.radbuttSORTmaxDepth  <ButtonRelease-1> "sort-draw"
bind .fRsorts.radbuttSORTminDepth  <ButtonRelease-1> "sort-draw"


if {1} {

bind .fRscales.scaleLON <ButtonRelease-1>  {rotate-sort-draw}
bind .fRscales.scaleLAT <ButtonRelease-1>  {rotate-sort-draw}

}
## END OF 'if {1/0}' SECTION

## We can use this 'if {1/0}' technique to
## easily disable groups of bindings --- for example, in testing ---
## to try to avoid weird scale behavior issues or to test the GUI
## when a proc is not ready.

bind .fRscales.scaleZOOM <ButtonRelease-1> "wrap_draw_2D_pixel_polys"

bind .fRfillopts.radbuttFILLOUTfillonly  <ButtonRelease-1> \
   "enable_fillsrc_radbutts ; enable_shade_radbutts"
bind .fRfillopts.radbuttFILLOUTboth      <ButtonRelease-1> \
   "enable_fillsrc_radbutts ; enable_shade_radbutts"

bind .fRfillopts.radbuttFILLOUTwirehide   <ButtonRelease-1> \
   "disable_fillsrc_radbutts ; disable_shade_radbutts ; wrap_draw_2D_pixel_polys"
bind .fRfillopts.radbuttFILLOUTwirenohide   <ButtonRelease-1> \
   "disable_fillsrc_radbutts ; disable_shade_radbutts ; wrap_draw_2D_pixel_polys"


bind .fRfillopts.radbuttFILLSRCfromFile       <ButtonRelease-1> "enable_shade_radbutts"
bind .fRfillopts.radbuttFILLSRCfromButton     <ButtonRelease-1> "enable_shade_radbutts"
bind .fRfillopts.radbuttFILLSRCfromColorTable <ButtonRelease-1> "enable_shade_radbutts"
bind .fRfillopts.radbuttFILLSRCrandom         <ButtonRelease-1> "enable_shade_radbutts"

bind .fRshadeopts.radbuttSHADEnone         <ButtonRelease-1> "wrap_draw_2D_pixel_polys"
bind .fRshadeopts.radbuttSHADEorigZheight  <ButtonRelease-1> "wrap_draw_2D_pixel_polys"
bind .fRshadeopts.radbuttSHADEorigYheight  <ButtonRelease-1> "wrap_draw_2D_pixel_polys"
bind .fRshadeopts.radbuttSHADEorigXheight  <ButtonRelease-1> "wrap_draw_2D_pixel_polys"
bind .fRshadeopts.radbuttSHADEzDepth       <ButtonRelease-1> "wrap_draw_2D_pixel_polys"
bind .fRshadeopts.radbuttSHADEbyLighting   <ButtonRelease-1> "wrap_draw_2D_pixel_polys"


##+######################################################################
## DEFINE PROCS SECTION:
##
## - 'get_model_filename' - presents the user with a file-selector GUI
##                          and returns a selected filename in the
##                          filename entry widget (called by the
##                          'Browse...' button on the GUI)
##
## - 'get_chars_before_last' - used in 'get_model_filename' to set curDIR
##
## - 'load_3DfileData' - (step1)
##                       called by bindings on the filetype radiobuttons.
##                       This proc calls one of the following 'data loader'
##                       procs depending on the current setting of the
##                       filetype radiobuttons.
##
##  NOTE: In the following 'data loader' procs, we are loading two main
##        arrays: aRpoints($i) and  aRconnect($i).
##        The elements of each array are of the following form:
##
##    aRpoints($i)  = [list $pointID $RGBcolor-list/$propID $x $y $z]
##    aRconnect($i) = [list $elemID $RGBcolor-list/$mtlID $numVertices $vertID1 $vertID2 ...]
##
##         The items "$pointID" and "$elemID" represent a user-helpful point ID and
##         a user-helpful element (N-gon) ID, which is often available in CAD and
##         FEA systems.
##
##         The user may assign point IDs that are not necessarily consecutive
##         integers, but they are usually integers unique to each point.
##         Similarly the user may assign element (polygon or line) IDs that
##         are not necessarily consecutive integers, but they are usually integers
##         unique to each 'element'.
##
##         We retain the pointID and elemID info in case we ever allow the user to
##         click on a point or 'element' to get info about the point or element,
##         including the user's ID for the point/element.
##
##         If the data to be loaded into the 'aRpoints' array does not include a user's
##         ID for the points, we simply put $i (the current point count) in this location.
##         Similarly, if the data to be loaded into the 'aRconnect' array does not
##         include a user's ID for the 'elements', we simply put $i (the current element
##         count) in this location.
##
##         The string '$RGBcolor-list/$propID' in the 'aRpoints' array format represents
##         a list item that may be an RGB-255 color spec (a 3 element Tcl sub-list
##         of RGB values).  Alternatively, some CAD/CAE/FEA property ID may be put in
##         that location. That property ID could possibly be used to generate a color
##         for the point.
##
##         The string '$RGBcolor-list/$mtlID' in the 'aRconnect' array format represents
##         a list item that may be an RGB-255 color spec (a 3 element Tcl sub-list
##         of RGB values).  Alternatively, some CAD/CAE/FEA material-type ID may be put
##         in that location. That material type ID could possibly be used to generate
##         a color for the connectivity element (polygon or line).
##
##  - 'load_3DfileData_cOBJ' is for ASCII Wavefront '.obj' files. This proc will load
##                        the 'v' (vertex) records into the 'aRpoints($i)' array and
##                        'f' (facet) and 'l' (line) records into the
##                        'aRconnect($i)' array.
##                         In addition, if the user replaces references to
##                         'usemtl' specs in the '.obj' file with 'c' records
##                         that contain the 3 'Kd' (diffuse) color values of the
##                         corresponding material group (in a specified external
##                         '.mtl' file), this loader will convert the 3 values
##                         (e.g. 0.64 0.64 0.85) to color values in the range 0-255
##                         and put the 3 values (as a 3-element Tcl sub-list) in the
##                         'aRconnect($i)' array. If 'c' records are not available,
##                         the current poly-fill-color (according to the
##                         poly-fill-color button of the GUI) is put in the
##                         'aRconnect($i)' array.
##
##  - 'update_counts_label'  puts counts of vertices, faces, etc. read by a dataloader
##                           routine in a GUI label.
##                         
##  - 'load_3DfileData_PLY'  is for ASCII Cyberware '.ply' files (ref: cyberware.com).
##                         PLY files contain 2 records at the top of the file
##                         that specify number of points and number of polygons.
##                         This proc will load the indicated number of point
##                         records into the 'aRpoints($i)' array and the indicated
##                         number of polygon records into the 'aRconnect($i)' array.
##                         The current poly-fill-color of the
##                         poly-fill-color button of the GUI is put in the
##                         'aRconnect($i)' array if no color information is detected
##                         (or supported, yet).
##                         
##  - 'load_3DfileData_OFF'  is for ASCII '.off' files (ref: Geomview of the U. of
##                         Minnesota). OFF files contain a single record at the top
##                         of the file that specifies number of points, polygons,
##                         and edges in the file.  This proc will load the
##                         indicated number of point records into the
##                         'aRpoints($i)' array and the indicated number of
##                         polygon records into the 'aRconnect($i)' array.
##                         The current poly-fill-color of the poly-fill-color button
##                         of the GUI is put in the 'aRconnect($i)' array if no
##                         color information is detected (or supported, yet).
##
##  - 'load_3DfileData_STL'  is for ASCII Stereolithography files. This proc will load
##                         the triplets of vertex records into the 'aRpoints($i)'
##                         array and make a polygon (triangle) record in the
##                         'aRconnect($i)' array, corresponding to each vertex triplet
##                         in the STL file.
##                         The current poly-fill-color of the poly-fill-color button
##                         of the GUI is put in the 'aRconnect($i)' array if no
##                         color information is detected (or supported, yet).
##
## - 'load_3DfileData_NASshort' is for ASCII NASTRAN 'short-field format' input files
##                         (which usually end in the suffix '.dat' or '.bdf' or '.nas').
##                         There are free-form (comma-separated values)
##                         NASTRAN records and fixed-field-format NASTRAN records.
##                         There are 2 types of fixed-field-format NASTRAN records:
##                         'short-field' and 'long-field'.  This proc is intended to
##                         support the 'short-field' format records. This proc will load
##                         the 'GRID' (point) records into the 'aRpoints($i)'
##                         array and load several types of 'C' (connectivity)
##                         records into the 'aRconnect($i)' array: CTRIA3 (triangle),
##                         CQUAD4 (quadrangle), CROD (line), CBAR (line), and
##                         CBEAM (line). Someday CTETRA, CHEXA, and CPENTA
##                         solid element records may be supported, by breaking
##                         them into triangle and quadrilateral polygons.
##                         The current poly-fill-color of the poly-fill-color button
##                         of the GUI is put in the 'aRpoints' & 'aRconnect' arrays if a
##                         group ID or some other such item in a GRID or C record
##                         cannot be used to assign a color.
##
##  - 'translate_points_array' - (step2)
##                         For the array
##                             aRpoints($i) = [list $pointID $RGBcolor/$propID $x $y $z],
##                         we calculate a new array in Cartesian coordinates:
##                            aRtranspoints($i) = [list $transx $transy $transz]
##                         based at the midpoint of the min-max ranges of xyz.
##
##                         (Note that the 3-space origin --- 0,0,0 --- may not be 
##                          within the 'point cloud' of xyz values. We need to translate
##                          the 'center point' of the model data to 0,0,0,
##                          so that we can get nice, 'local' rotation of the model.)
##
##                          We could use the array 'aRpoints' to hold the translated
##                          coordinates, but we will use some memory for the separate
##                          'aRtranspoints' array --- just in case we find it
##                          useful to have the original point values available
##                          --- for example for coloring the polygons according to
##                          the original data values (z-heights, y-heights, or x-heights).
##
##  - 'rotate_points' - (step3)
##                         For a given longitude and latitude (view direction),
##                         this proc loops thru all the POINTS, in array 'aRtranspoints',
##                         rotating each point according to the current 2 longitude
##                         and latitude angles --- angLON,angLAT --- and calculating
##                         the new Cartesian (xyz) coordinates. The xyz data for
##                         the 'new points' are put into a new array:
##                               aRnew_points($i) = [list $newx $newy $newz].
##
##                         Thus if we make a 'simple' change like polygon fill or outline
##                         color, or change to wireframe mode from fill mode (changes that
##                         do not change the 3D model geometry or view direction),
##                         we do not have to go through a lot of mathematical
##                         calculations again. We can work off of the
##                         'aRnew_points' array.
##
##                         We are using memory for 3 points arrays --- 'aRpoints',
##                         'aRtranspoints', and 'aRnew_points' --- to give us some
##                         processing efficiency when we make changes that should not
##                         require sweeping through the points and performing math
##                         calculations that we have already done once.
##
##                         Because the canvas area will usually be no more than about
##                         900x900 pixels, and the smallest polygons we will want will
##                         be about 3 pixels across, we will probably deal with models
##                         with no more than about 300x300=90,000 quadrilaterals or
##                         180,000 triangles to a side (front, back, top, bottom,
##                         left, right) --- so we should not be attempting to read in
##                         more than about 6 sides x 180,000 faces/side = 1,080,000 faces
##                         to a model.
##
##                         In fact, we probably should stop loading a model when we get to
##                         about 500,000 points. If we limit ourselves to no more than
##                         500,000 points, this would mean we would have no more than
##                         3 x 500,000 = 1,500,000 xyz coords per array. At about 8bytes
##                         per coord, this means about 8 x 1.5 million = 12 Megabytes
##                         per array. For 3 points arrays, this is about 36 Megabytes
##                         for the 3 arrays.
##
##                         Since most computers nowadays will have at least 1 Gigabyte
##                         = 1,000 Megabytes of memory available if no more than the
##                         basic operating system tasks are running, this means that
##                         less than 4% of the free memory will be used by the 3 points
##                         arrays. Even with the color-info and user-point-id-info for
##                         each point, the 3 points arrays will probably use less than
##                         5% of free memory.
##
##                         When we deal with only quads in a model, the number of polygons
##                         is roughly equal to the number of points (vertices). When
##                         we deal with all triangles, the number of polygons is roughly
##                         twice the number of vertices. The polygon/line info in each
##                         'record' of the 'aRconnect' array --- a color and some integers
##                         --- is roughly the same as each member of the aRpoints array.
##                         So the 'aRconnect' array may consume another 2% (approx.) of
##                         available memory.
##
##                         In total, our 3 points arrays and one connectivity array
##                         should, typically, consume no more than about 8% of free memory.
##
##    - 'sort_polyIDs_list' - (step 4)
##                         For poly ID's "$i" (1 integer) in a Tcl list 'LISTpolyIDs',
##                         we generate a new list called 'sortedLISTpolyIDs' ---
##                         by sorting the polyIDs according to the z-depth of each poly.
##                         We get the z coordinate from the 'new points' array:
##                                aRnew_points($i) = [list $newx $newy $newz].
##                         Uses the proc 'compare_2polyIDs_by_zdepth'. See comments in
##                         that proc for current details of the sort method.
##
##   - 'compare_2polyIDs_by_zdepth' (3 different sort procs are offered at this time)
##                        Used by a Tcl 'lsort' command in proc 'sort_polyIDs_list'
##                        in order to sort a list of polygon IDs according to the
##                        'bigger-max' or 'bigger-average' or 'bigger-min' of current
##                        z-depth of the vertices of any pair of the polygons.
##                        Input is a pair of polygon IDs of the form "$i" and "$j"
##                        as arguments. Output is 1 or -1.
##
##    - 'draw_2D_pixel_polys' - (step5)
##                         For the current 'aRnew_points' array, this proc maps
##                         the x,y values of the vertices of the polygons
##                         (usually triangles or quadrangles, but could be any
##                          N-gon --- including a 2-gon --- a line)
##                         into pixels and the polygons are placed on the current
##                         Tk canvas area with 'create polygon' commands --- or
##                         'create line' commands for 2-gons --- with the requested
##                         '-fill' and '-outline' options and the requested
##                         color values and shading options.
##
##                         The list 'sortedLISTpolyIDs' is used to determine the order
##                         in which to plot the elements (polygons, lines) from the 
##                         'aRconnect' array.
##
##                         The initial mapping of world-units to pixels is based on
##                         mapping the canvas dimensions into world-units of at least
##                         the diameter of the data cloud of x,y,z points.
##
##                         The mapping of world-units to pixels can be altered by
##                         the zoom scale of the GUI.
##                         Zooming logic is located solely in this proc.
##
##                         Note that the procs that built the arrays 'aRpoints',
##                         'aRtranspoints', and 'aRnew_points' are all dealing with
##                         'world coordinates'. 'draw_2D_pixel_polys' is the only proc
##                         dealing with pixel coordinates.
##
## - 'load-translate-rotate-sort-draw' -
##                         a proc that calls 5 procs:
##                              - load_3DfileData
##                              - translate_points_array
##                              - rotate_points
##                              - sort_polyIDs_list
##                              - draw_2D_pixel_polys
##                         and also shows the draw-time (millisecs).
##
##    - 'rotate-sort-draw' -
##                         a proc that calls 3 procs:
##                              - rotate_points
##                              - sort_polyIDs_list
##                              - draw_2D_pixel_polys
##                         and also shows the draw-time (millisecs).
##
##    - 'wrap_draw_2D_pixel_polys' -
##                         a proc that calls 1 proc:
##                              - draw_2D_pixel_polys
##                         and also shows the draw-time (millisecs).
##
##    - 'update_drawtime_label'   - shows the draw-time (millsecs) in a label
##
##    - 'set_polygon_color1'    - sets fill color for the polygons after
##                                calling on an external color selector to
##                                get the color from the user. This proc is
##                                called when the user clicks on the
##                                polygon-fill-color button.
##
##    - 'set_polygon_color2'    - sets outline color for the polygons after
##                                calling on an external color selector to
##                                get the color from the user. This proc is
##                                called when the user clicks on the
##                                polygon-outline-color button.
##
##    - 'set_background_color'  - sets background (canvas) color after
##                                calling on an external color selector to
##                                get the color from the user. This proc is
##                                called when the user clicks on the
##                                background-color button.
##
##    - 'update_colors_label'   - to color the 3 color buttons and reset a
##                                colors label with the current fill, outline,
##                                and background colors. Called by the 3
##                                color-setting procs above.
##
##    'enable_fillsrc_radbutts' - to  enable the indicated radiobuttons
##   'disable_fillsrc_radbutts' - to disable the indicated radiobuttons
##
##    'enable_shade_radbutts'   - to  enable the indicated radiobuttons
##   'disable_shade_radbutts'   - to disable the indicated radiobuttons
##
##  'cross_product_normz'       - to perform 'shading' of the fill color of
##                                the polygons, according to the angle
##                                that the polygon normals make to a
##                                'lighting' vector --- such as the 'view vector',
##                                if we assume the lighting is attached to the
##                                viewer's forehead, say.
##
##   'popup_msgVarWithScroll' - to show Help text (and other msgs).
##
##   'popup_msgVarWithScroll_wait' - to show error msgs.
##
##+########################################################################


##+#########################################################################
## proc  'get_model_filename'
##+#########################################################################
## PURPOSE: To get the name of a 3D model file and put the
##          filename into global var 'ENTRYfilename'.
##
##          We do NOT go ahead and load the 3D model onto the canvas.
##          We hold off so that the user can indicate a 'data loader' to use.
##
##          A binding on the filename entry field will trigger the
##          loading of the data and its display on the canvas.
##
## USED BY: the '-command' option of the 'Browse ...' button.
##+#########################################################################

# set curDIR "$env(HOME)"

## FOR TESTING:
  set curDIR "pwd"

proc get_model_filename {} {

   global ENTRYfilename env curDIR

   ## Provide the user a way to select an image file.

   set fName [tk_getOpenFile -parent . -title "Select 3D model file to load" \
            -initialdir "$curDIR" ]

   ## FOR TESTING:
   #   puts "fName : $fName"

   ## Put the selected filename, if any and if it exists,
   ## into the filename entry field.

   if {[file exists $fName]} {

      set ENTRYfilename "$fName"
      set curDIR [ get_chars_before_last / in "$ENTRYfilename" ]

   }
   ## END OF   if {[file exists $fName]}
}
## END OF PROC  'get_model_filename'


##+######################################################################
## proc  'get_chars_before_last'
##+######################################################################
## PURPOSE: Gets the chars before the last occurrence of a char in a string.
##
## INPUT:  A character and a string.
##         Note: The "in" parameter is there only for clarity.
##
## OUTPUT: Returns all of the characters in the string "strng" that
##         are BEFORE the last occurence of the characater "char".
##
## EXAMPLE CALL: To extract the directory from a fully qualified file name:
##
## set directory [ get_chars_before_last "/" in "/home/abc01/junkfile" ]
##
##      $directory will now be the string "/home/abc01"
##
##+######################################################################

proc get_chars_before_last { char in strng } {

   set endIDX [ expr {[string last $char $strng ] - 1} ]
   set output [ string range $strng 0 $endIDX ]

   ## FOR TESTING:
   # puts "From 'get_chars_before_last' proc:"
   # puts "STRING: $strng"
   # puts "CHAR: $char"
   # puts "RANGE up to LAST CHAR - start: 0   end: $end"

   return $output

}
## END OF PROC 'get_chars_before_last'


##+########################################################################
## PROC 'load_3DfileData'
##+########################################################################
## PURPOSE: Calls on a 'load_3DfileData_XXX' proc according to the
##          current setting of the filetype radiobuttons.
##
## CALLED BY: an 'event' binding on the filename entry field ---
##            where the event is a Return/Enter-key press or
##            a mouse-button3-release on the entry field.
##+########################################################################

proc load_3DfileData {} {

   global VARfiletype

   if { "$VARfiletype" == "cOBJ" } {
      load_3DfileData_cOBJ
   }

   if { "$VARfiletype" == "PLY" } {
      load_3DfileData_PLY
   }

   if { "$VARfiletype" == "OFF" } {
      load_3DfileData_OFF
   }

   if { "$VARfiletype" == "STL" } {
      load_3DfileData_STL
   }

   if { "$VARfiletype" == "NASshort" } {
      load_3DfileData_NASshort
   }

}
## END OF PROC 'load_3DfileData'



##+########################################################################
## PROC 'load_3DfileData_cOBJ'
##+########################################################################
## PURPOSE: Reads 'basic' data from 'v' (vertex), 'f' (face), and 'l' (line)
##          records in Wavefront '.obj' files.
##          Puts the data in two array variables --- aRpoints and  aRconnect.
##
##          The elements of each array are of the following form:
##  aRpoints($i)  = [list $pointID $RGBcolorList/propID $transx $transy $transz]
##  aRconnect($i) = [list $elemID $RGBcolorList/mtlID $numVertices $vertID1 $vertID2 ...]
##
##          The items "$pointID" and "$elemID" represent a user-helpful point ID and
##          a user-helpful element (N-gon) ID, which is often available in CAD and
##          FEA systems.
##
##          Since Wavefront OBJ files do not support user-specified IDs for
##          points, polygons, or lines, we simply put the current CNTpoints
##          or CNTconnects value in the $pointID or $elemID location.
##
##          NOTE: This proc does not perform the 'translate-rotate-sort-draw'
##          sequence. That is left to the other parts of this script ---
##          such as bindings on the file-entry field.
##
## METHOD:  This proc uses the 'gets' command to read records. See the info
##          below on the format of a Wavefront OBJ file.
##
##          This proc stores all but the first 2 characters of the OBJ records
##          in array variables ('aRpoints','aRconnect') using the item count
##          as the index into each array.
##
##          We use the 'string range' command to get the data from
##          each record.
##
## SETTING OTHER DATA besides aRpoints and aRconnect arrays:
##
##          While loading the array aRpoints, we find the min,max values of
##          the xyz coords and use these to calculate Xmid,Ymid,Zmid values.
##
##          We also use the min,max values to compute a 'diameter' for the
##          'point cloud'.
##
##          We also create a list, 'LISTpolyIDs', consisting of entries
##          "$i" --- sucessive integer IDs --- for 1 <= i <= $CNTconnects.
##          The length of the list is $CNTconnects --- the number of
##          polygons and lines in the array 'aRconnect'.
##
##          We set the following global variables:
##          minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##
## INPUT: an 'OBJ' data filename in global var ENTRYfilename
##
## OUTPUT:  2 global array variables, 'aRpoints' and 'aRconnect',
##          to hold data for the components of the 3D model (points and
##          polygons --- and lines, if any).
##          There are also 2 global variables to hold the count totals
##          for the 'aRpoints' and 'aRconnect' arrays.
##          Also the global variables
##     minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##          are set.
##
## CALLED BY: the 'load_3DfileData' proc
##
##+####################
## OBJ FILE FORMAT INFO:
## The Wavefront .obj file format is a standard 3D object
## file format created for use with Wavefront's Advanced Visualizer.
## Object Files are text based files supporting both polygonal and
## free-form geometry (curves and surfaces). This 'data-loader'
## supports a subset of the file format (not the curves and surfaces),
## but it is enough to load almost all commonly available '.obj' Files.
## 
## The following text is a very brief description of the 'v' and 'f'
## and 'l' records in .obj files.
## 
## Lines other than 'v ' and 'f ' and 'l' lines are skipped by this
## data loader. This proc reads 'v ' records, but not 'vn' and 'vt' records.
##
##
##    # some text
##      Line is a comment until the end of the line
##
##    v float float float
##        A single vertex's geometric position in space. The first
##        vertex listed in the file has index 1, and subsequent
##        vertices are numbered sequentially.
##
##    vn float float float
##        A normal. The first normal in the file is index 1,
##        and subsequent normals are numbered sequentially.
##
##    vt float float
##        A texture coordinate. The first texture coordinate in
##        the file is index 1, and subsequent textures are numbered
##        sequentially.
##
##    f int int int ...
##        or
##    f int/int int/int int/int . . .
##        or
##    f int/int/int int/int/int int/int/int ...
##        A polygonal face. The numbers are indexes into the arrays
##        of vertex positions, texture coordinates, and normals
##        respectively. A number may be omitted if, for example,
##        texture coordinates are not being defined in the model.
##        There is no maximum number of vertices that a single
##        polygon may contain. The .obj file specification says
##        that each face must be flat and convex.
##
## A very elementary example file is given below (it is a cube):
##
## v 1 1 1
## v 1 1 -1
## v 1 -1 1
## v 1 -1 -1
## v -1 1 1
## v -1 1 -1
## v -1 -1 1
## v -1 -1 -1
## f 1 3 4 2
## f 5 7 8 6
## f 1 5 6 2
## f 3 7 8 4
## f 1 5 7 3
## f 2 6 8 4
##
## The top of a '.obj' file typically contains a few '#' comment records
## indicating the source of the data and, often, the number of vertices
## and faces in the file.
## But you cannot count on the number of vertices being given in the file.
## A program must read the 'v' records and assign vertex ID numbers
## as the 'v' records are being read.
##
## Here is a variety of 'v' records:
## v 16 17 5
## v 19 61 18.3073
## v 3.5 88 8.50596
## v 19.9352 12.0843 -2.24927
## Note that integers are allowed along with decimal numbers.
##
## When there are 'vt' and/or 'vn' records in the file,
## it is quite common to see 'f' records in one of the
## following formats:
##
##    f 1//1 2//2 3//3 4//4  (for 'vn' recs in the file)
##    f 1/1 2/2 3/3 4/4      (for 'vt' recs in the file)
##    f 6/4/1 3/5/3 7/6/5    (for both in the file)
##
## The integers between two slashes in a group are IDs of
## texture-vertices.
## The integers after the 2nd slash in a group are IDs of
## normals at vertices.
##
## This dataloader extracts the first number (the vertex ID) from
## each of the whitespace separated fields. The slashes and the
## integers after the first slash in each group are not used.
##
## The 'l' (line) records have a simple syntax, like the 'f' records
## --- but no slashes (no textures, no normals).
## Example:
## l 7 8
## l 6 7
## l 5 6
##
## This OBJ data loader does not use records such as records starting with
## 'mtllib' 'usemtl' 'vt' 'vn' 'o' (object) 's' (shininess) 'g' (group).
##
## We add the following 'color extension' --- a 'c' record ---
## to the OBJ file specification --- for this proc to support:
##
##     The user can replace references to 'usemtl' specs in the
##     '.obj' file with 'c' records that contain the 3 'Kd' (diffuse)
##     color values of the corresponding material group (in a
##     specified external '.mtl' file).
##
##     This OBJ data loader will convert the 3 values (e.g. 0.64 0.64 0.85)
##     to color values in the range 0-255 and put the 'current' 3 values
##     (as a 3-element Tcl list or as a single element hex-color-code,
##     like #ff00ff) in the 'aRpoints($i)' array each time a 'v' record
##     is encountered --- and in the 'aRconnect($i)' array each time an
##     'f' or 'l' record is encountered.
##
##     Example format:
##         c 0.64 0.64 0.85
##
##     When a 'c' record is encountered, its RGB-color values
##     are used as the color for any following 'v', 'f', and 'l' recs.
##
##     If a 'c' record has not been encountered, the current
##     poly-fill-color (according to the poly-fill-color button of
##     the GUI) is put in the 'aRconnect($i)' array --- and in the
##     'aRpoints($i)' array.
##+###################################################################

proc load_3DfileData_cOBJ {} {

   global aRpoints aRconnect CNTpoints CNTconnects ENTRYfilename VAR123toxyz \
      COLOR1r COLOR1g COLOR1b COLOR1hex \
      minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam TOLfactor LISTpolyIDs

   ############################################
   ## Open the file, with some error checking.
   ############################################
   ## This is too simplistic:
   ## set f [open "$ENTRYfilename" r]
   ############################################

   set RETcode [catch "open $ENTRYfilename r" f]

   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_cOBJ' > RETcode: $RETcode"


   if {"$RETcode" == "1"} {
      set FILEopenERRmsg "Cannot open file
$ENTRYfilename
It is likely that the file is not found. Check spelling."
      popup_msgVarWithScroll_wait .fRerrmsg "$FILEopenERRmsg"
      return
   }

   #########################################
   ## Initialize count and color variables.
   #########################################

   set CNTpoints 0
   set CNTconnects 0
   set CNTfaces 0
   set CNTlines 0

   set CURcolorLIST255 {}
   set FILLcolorLIST255 [list $COLOR1r $COLOR1g $COLOR1b]

   ########################################################################
   ## Make sure the 'diam' variable does not exist, for this file.
   ## We use its non-existence in various procs to bail out of
   ## the 'graphics pipeline' if the model did not load.
   ########################################################################

   catch {unset diam}


   ########################################################################
   ## If a line is too long, we will bail out (or we will truncate it).
   ## (This serves as a safety factor --- to prevent severe read errors ---
   ##  in case the user accidentally selects a binary file, for example.)
   ########################################################################

   set maxLen 1000

   ###################################################
   ## Set an error message in case we hit a long line.
   ###################################################

   set TOOLONGmsg "EXITING the OBJ-file loader routine while processing
file $ENTRYfilename.
Encountered a very long record in the file.
This is not normal for an ASCII OBJ file.
Check that you have not selected a binary file."

   #############################################################
   ## START OF WHILE-LOOP for the 'gets' file-READING.
   ## The while-test below is equivalent to 'while {![eof $f]}'.
   #############################################################

   while {[eof $f] == 0} {

      ############################################################
      ## GET the next line (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      ####################################
      ## Bail out if the line is too long.
      ####################################

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return

         ## If we did not want to bail out, we could try truncating the line
         ## and continuing. For example:

         # set line "[string range $line 0 $maxLen]...proc-'load_3DfileData_cOBJ'-TRUNCATED-this-line"
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      ######################################################
      ## If the record is empty, skip it, read the next rec.
      ######################################################

      if {"$line" == ""} {continue}

      #################################################################
      ## If the record is a comment record, skip it, read the next rec.
      #################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "#"} {continue}

      ########################################################
      ## Replace each tab by a single space.
      ########################################################

      set line [string map {\t \x20} $line]

      ########################################################
      ## Replace multiple spaces by a single space.
      ## Ref: page 163 of 'Practical Programming in Tcl & Tk',
      ## 4th edition, by Welch, Hobbs, Jones.
      ########################################################

      regsub -all {\s+} $line " " line

      ########################################################
      ## Get the record type indicator in the first 2 columns.
      ########################################################

      set recTYPE [ string range $line 0 1 ]

      ## FOR TESTING:
      #   puts "proc 'load_3DfileData_cOBJ' >  recTYPE: '$recTYPE'"


      ###############################################################
      ## If the record is not a 'v', 'f', 'l', or 'c' record, skip it.
      ###############################################################

      if {"$recTYPE" != "v " && "$recTYPE" != "f " && \
          "$recTYPE" != "l " && "$recTYPE" != "c "} {continue}

      ###################################################################
      ## We will repeatedly use this substring below, col3 to end of rec.
      ###################################################################

      set line3toEND [ string range $line 2 end ]

      #####################################################
      ## Load 'c' data into local variable CURcolorLIST255.
      #####################################################

      if {"$recTYPE" == "c "} {

         ## Get the 3 values between 0 and 1 and convert them
         ## to the range 0 to 255.

         foreach {color1 color2 color3}  $line3toEND {break}
         set r255 [expr {int(double($color1) * 255)}]
         set g255 [expr {int(double($color2) * 255)}]
         set b255 [expr {int(double($color3) * 255)}]

         ## Put the 3 base255-values into var CURcolorLIST255.

         set CURcolorLIST255 [list $r255 $g255 $b255]

         continue
      }
      ## END OF  if {"$recTYPE" == "c "}


      ####################################################################
      ## Load 'v' data into array 'aRpoints' so that it looks like:
      ## aRpoints($i)  = [list $pointID $colorlist $transx $transy $transz]
      ## The OBJ face recs expect the vertex recs to starting counting at
      ## one, not zero.
      ####################################################################

      if {"$recTYPE" == "v "} {

         incr CNTpoints

         ## Split the line (after 'v ') into separate list items.
         ## (Should be x,y,z coords.)
 
         set SPLITlist [split $line3toEND]
         if {"$VAR123toxyz" == "x1y2z3"} {
            set x [lindex $SPLITlist 0]
            set y [lindex $SPLITlist 1]
            set z [lindex $SPLITlist 2]
         } elseif {"$VAR123toxyz" == "x2y3z1"} {
            set x [lindex $SPLITlist 1]
            set y [lindex $SPLITlist 2]
            set z [lindex $SPLITlist 0]
         } elseif {"$VAR123toxyz" == "x3y1z2"} {
            set x [lindex $SPLITlist 2]
            set y [lindex $SPLITlist 0]
            set z [lindex $SPLITlist 1]
         }


         ## FOR TESTING: (Write out every Nth aRpoints record.)
         #  if {[expr {$CNTpoints % 2}] == 1} {
         #      puts "proc 'load_3DfileData_cOBJ' > At 'v' record number $CNTpoints,"
         #      puts "     line3toEND: $line3toEND"
         #      puts "     SPLITlist: $SPLITlist"
         #      puts "     x: $x   y: $y   z:$z"
         #  }

         ## We put a pointID and colorlist(0-255) in the aRpoints record,
         ## along with the xyz coordinates.

         if {$CURcolorLIST255 == {}} {
            set aRpoints($CNTpoints) [list $CNTpoints $FILLcolorLIST255 $x $y $z]
         } else {
            set aRpoints($CNTpoints) [list $CNTpoints $CURcolorLIST255 $x $y $z]
         }

         ## Assure that xyz are in numeric form for the following comparisons.

         set x [expr {double($x)}]
         set y [expr {double($y)}]
         set z [expr {double($z)}]

         ## Set the minX,maxX,minY,maxY,minZ,maxZ values of the points.
         ## We will use these to get a mid-point of the 'point cloud'.

         if {$CNTpoints == 1} {
            set minX $x
            set maxX $x
            set minY $y
            set maxY $y
            set minZ $z
            set maxZ $z
         } else {
            if {$x < $minX} {set minX $x}
            if {$x > $maxX} {set maxX $x}
            if {$y < $minY} {set minY $y}
            if {$y > $maxY} {set maxY $y}
            if {$z < $minZ} {set minZ $z}
            if {$z > $maxZ} {set maxZ $z}
         }
         ## END OF if {$CNTpoints == 1}


         ## FOR TESTING: (Write out every Nth aRpoints record.)
         #  if {[expr {$CNTpoints % 2}] == 1} {
         #      puts "proc 'load_3DfileData_cOBJ' > At 'v' record number $CNTpoints,"
         #      puts "     aRpoints($CNTpoints): $aRpoints($CNTpoints)"
         #  }

      }
      ## END OF if {"$recTYPE" == "v "}


      ###############################################################
      ## Load 'f' data into array 'aRconnect':
      ## aRconnect($i) = [list $elemID $color $numVertices $vertID1 $vertID2 ...]
      ###############################################################

      if {"$recTYPE" == "f "} {

         incr CNTconnects
         incr CNTfaces

         ## Split the 'f ' record up into fields (which may contain slashes
         ## and vertex, vertex-texture, and vertex-normal ID numbers) ---
         ## to extract the vertex IDnums from the fields.

         set SPLITlist [split $line3toEND]
         set numVertices [llength $SPLITlist]

         ## FOR TESTING:
         # if {$CNTconnects == 1} {
         #    puts "proc 'load_3DfileData_cOBJ' > At 'f' record number $CNTconnects,"
         #    puts "  split 'line3toEND' (the cols after 'f ') into SPLITlist: $SPLITlist"
         #    puts "  Length of SPLITlist (numVertices): $numVertices"
         # }


         ## Make the start of the aRconnect($CNTconnect) list.
         ## (We put a pointID and colorlist(0-255) in the aRconnect record,
         ## along with the number-of-vertices.)

         if {$CURcolorLIST255 == {}} {
            set aRconnect($CNTconnects) [list $CNTconnects $FILLcolorLIST255 $numVertices]
         } else {
            set aRconnect($CNTconnects) [list $CNTconnects $CURcolorLIST255 $numVertices]
         }

         ## FOR TESTING:
         #  if {$CNTconnects == 1} {
         #     puts "proc 'load_3DfileData_cOBJ' > At 'f' record number $CNTconnects,"
         #     puts "  found numVertices (cols after 'f '): $numVertices"
         #     puts "  initialized aRconnect($CNTconnects) to: $aRconnect($CNTconnects)"
         #  }


         ## Add the vertex numbers to the  aRconnect($CNTconnect) list.
         ## (Separate the vertex numbers from following slashes, if any.)

         foreach TEMPfield $SPLITlist {
            set IDXslash [string first "/" $TEMPfield]
            if {$IDXslash == -1} {
               set TEMPvert $TEMPfield
            } else {
               set IDXb4slash [expr {$IDXslash - 1}]
               set TEMPvert [string range $TEMPfield 0 $IDXb4slash]
            }
            if {$TEMPvert < 0} {
               set NEGfaceERRmsg "Found a negative vertex number in file
$ENTRYfilename
This OBJ data-loader does not support OBJ files with negative vertex
number in the face records.  Try changing to the positive OBJ format.
You could try reading the OBJ file into a modeler like Blender or Wings3D
and then immediately export the file as a new OBJ file."
               popup_msgVarWithScroll_wait .fRerrmsg "$NEGfaceERRmsg"
               return
            }
            lappend aRconnect($CNTconnects) $TEMPvert
         }


         ## FOR TESTING: (Write out every Nth aRconnect record.)
         #  if {[expr {$CNTconnects % 2}] == 1} {
         #     puts "proc 'load_3DfileData_cOBJ' > At 'f' record number $CNTconnects,"
         #     puts "     aRconnect($CNTconnects) was loaded with: $aRconnect($CNTconnects)"
         #  }

      }
      ## END OF if {"$recTYPE" == "f "}



      ######################################################################
      ## Load 'l' (line) data into array 'aRconnect':
      ## aRconnect($i) = [list $elemID $color $numVertices $vertID1 $vertID2 ...]
      ## where the number of vertices is always 2 in this case.
      ######################################################################

      if {"$recTYPE" == "l "} {

         incr CNTconnects
         incr CNTlines

         ## Split the 'l ' record up into fields (which should contain
         ## exactly 2 vertex numbers).

         set SPLITlist [split $line3toEND]
         set ID1 [lindex $SPLITlist 0]
         set ID2 [lindex $SPLITlist 1]

         ## We put an 'element' ID and colorlist in the aRconnect record,
         ## along with the number of vertices (two) and the vertex IDs
         ## (should be just two of them in the 'l' record).

         if {$CURcolorLIST255 == {}} {
            set aRconnect($CNTconnects) [list $CNTconnects $FILLcolorLIST255 2 $ID1 $ID2]
         } else {
            set aRconnect($CNTconnects) [list $CNTconnects $CURcolorLIST255 2 $ID1 $ID2]
         }

         ## FOR TESTING: (Write out every Nth aRconnect record.)
           if {[expr {$CNTconnects % 20}] == 1} {
              puts "proc 'load_3DfileData_cOBJ' > At 'l' record number $CNTconnects,"
              puts "     aRconnect($CNTconnects): $aRconnect($CNTconnects)"
           }
      }
      ## END OF if {"$recTYPE" == "l "}


   }
   ## END OF LOOP  while {[eof $f] == 0}

   ######################################
   ## We hit end of file. Close the file.
   ######################################

   close $f


   #######################################################
   ## Calculate a diameter for our 'cloud' of data points.
   #######################################################

   set diam [expr {$maxX - $minX}]
   set maxDeltaY [expr {$maxY - $minY}]
   if {$maxDeltaY > $diam} {set diam $maxDeltaY}
   set maxDeltaZ [expr {$maxZ - $minZ}]
   if {$maxDeltaZ > $diam} {set diam $maxDeltaZ}


   ##########################################
   ## Get the mid-points of the x,y,z ranges.
   ##########################################

   set Xmid [expr {($maxX + $minX) / 2.0}]
   set Ymid [expr {($maxY + $minY) / 2.0}]
   set Zmid [expr {($maxZ + $minZ) / 2.0}]

   ##########################################
   ## Set a near-zero-tolerance value for in
   ## setting near-zero numbers to 0.0.
   ##########################################

   set zeroTOL [expr {$TOLfactor * $diam}]

   if {[expr {abs($Xmid)}] < $zeroTOL} {set Xmid [expr {double(0.0)}]}
   if {[expr {abs($Ymid)}] < $zeroTOL} {set Ymid [expr {double(0.0)}]}
   if {[expr {abs($Zmid)}] < $zeroTOL} {set Zmid [expr {double(0.0)}]}


   ################################################
   ## Load the list of IDs of the aRconnect array,
   ## for z-depth sorting later.
   ################################################

   set LISTpolyIDs {}

   for {set k 1} {$k <= $CNTconnects} {incr k} {
      lappend LISTpolyIDs "$k"
   }
   ## END OF k loop

   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_cOBJ' > CNT totals:"
   #   puts "  CNTpoints: $CNTpoints   CNTconnects: $CNTconnects"
   #   puts "  minX: $minX  maxX: $maxX     minY: $minY  maxY: $maxY    minZ: $minZ  maxZ: $maxZ"
   #   puts "  Xmid: $Xmid   Ymid: $Ymid   Zmid: $Zmid    diam: $diam"

   ## Show the count totals in a GUI label.

   update_counts_label "\
In OBJ file - Vertices: $CNTpoints   Faces: $CNTfaces  Lines: $CNTlines"

}
## END OF PROC 'load_3DfileData_cOBJ'


##+########################################################################
## PROC 'update_counts_label'
##+########################################################################
## PURPOSE: Puts model file vertex,polygon,line counts in a label
##          on the GUI.
##
## CALLED BY: the 'load_3DfileData_XXXX' procs.
##+########################################################################

proc update_counts_label {text} {

   .fRbuttons.labelSTATUS configure -text "\
Click on a data-loader radiobutton to cause a selected file to be loaded.
$text"

#   .fRbuttons.labelSTATUS configure -text "\
# Use the 'Help' button to get info on the file types (supported records).
# $text"

}
## END OF PROC 'update_counts_label'


##+########################################################################
## PROC 'load_3DfileData_PLY'
##+########################################################################
## PURPOSE: Reads 'basic' data from PLY files --- vertex and face data ---
##          and puts the data in 2 arrays: 'aRpoints' & 'aRconnect'.
##
##          The elements of each array are of the following form:
##  aRpoints($i)  = [list $pointID $RGBcolorList/propID $transx $transy $transz]
##  aRconnect($i) = [list $elemID $RGBcolorList/mtlID $numVertices $vertID1 $vertID2 ...]
##
## METHOD:  Reads records in 3 separate loops.
##
##   LOOP1: Loop until the 'end_header' record is read.
##          Reads a 'element vertex' record near the top of the file to get
##          the number of vertex records, Nvertices, to read.
##          Reads a 'element face' record near the top of the file to get
##          the number of face records, Nfaces, to read.
##
##  LOOP2:  Loads the vertex data into the 'aRpoints' array until
##          Nvertices records are read --- skipping comment and empty
##          records.
##
##  LOOP3:  Loads the face data into the 'aRconnect' array
##          until Nfaces records are read ---skipping comment and empty
##          records. Done.
##
##          Uses the 'gets' command to read records.
##
##          Stores data extracted from the vertex and face records
##          in the 2 array variables (aRpoints,aRconnect)
##          using the counts CNTpoints, CNTconnects as the index
##          into each array.
##
##          We use the Tcl 'string range' command, along with some
##          other Tcl commands, to get the data from each PLY record.
##
## SETTING OTHER DATA besides aRpoints and aRconnect arrays:
##
##          While loading the array aRpoints, we find the min,max values of
##          the xyz coords and use these to calculate Xmid,Ymid,Zmid values
##          at the bottom of this proc.
##
##          We also use the min,max values to compute a 'diameter' for the
##          'point cloud'.
##
##          We also create a list, 'LISTpolyIDs', consisting of entries
##          "$i" --- successive integer IDs --- for 1 <= i <= $CNTconnects.
##          The length of the list is $CNTconnects --- the number of
##          polygons and lines in the array 'aRconnect'.
##
##          We set the following global variables:
##          minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##
## INPUT: a PLY data filename in global var ENTRYfilename
##        (Typical file suffix is '.ply' --- almost always.)
##
## OUTPUT:  2 global array variables for the 2 arrays (aRpoints,aRconnect).
##          There are also 2 global variables (CNTpoints, CNTconnects)
##          to hold the count totals. Also the global variables
##          minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##          are set.
##
## CALLED BY: the 'load_3DfileData' proc
##
##+######################
## 'PLY' FILE FORMAT INFO:
##
## Example PLY file header records:
##
## ply
## format ascii 1.0
## element vertex 486
## property float32 x
## property float32 y
## property float32 z
## element face 912
## property list uint8 int32 vertex_indices
## end_header
##
## Example PLY 'vertex' records:
##
## 16.01 -9.378 -1.24 
## 16 -9.383 -1.24 
## -0.7932 4.462 -7.734 
##
## Example PLY 'face' records:
##
## 3 475 476 474      (for triangles)
## 4 100 101 102 103  (for quadrangles)
## 5 2 17 9 10 18     (for pentagons)
##+#########################################################################

proc load_3DfileData_PLY {} {

   global aRpoints aRconnect CNTpoints CNTconnects ENTRYfilename VAR123toxyz \
      COLOR1r COLOR1g COLOR1b COLOR1hex \
      minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam TOLfactor LISTpolyIDs

   ## FOR TESTING: (to dummy out this proc)
   #   return


   ############################################
   ## Open the file, with some error checking.
   ############################################
   ## This is too simplistic:
   ## set f [open "$ENTRYfilename" r]
   ############################################

   set RETcode [catch "open $ENTRYfilename r" f]

   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_PLY' > RETcode: $RETcode"


   if {"$RETcode" == "1"} {
      set FILEopenERRmsg "Cannot open file
$ENTRYfilename
It is likely that the file is not found. Check spelling."
      popup_msgVarWithScroll_wait .fRerrmsg "$FILEopenERRmsg"
      return
   }


   ################################################
   ## Initialize count and color variables.
   ################################################

   set CNTpoints 0
   set CNTconnects  0

   ## Set the color to use for all the vertices and faces.
   ## (We seldom, if ever, see a ply file with color data to use.)

   set FILLcolorLIST255 [list $COLOR1r $COLOR1g $COLOR1b]

   ########################################################################
   ## Make sure the 'diam' variable does not exist, for this file.
   ## We use its non-existence in various procs to bail out of
   ## the 'graphics pipeline' if the model did not load.
   ########################################################################

   catch {unset diam}


   #######################################################################
   ## If a line is too long,  we will bail out (or we will truncate it).
   ## (This serves as a safety factor --- to prevent severe read errors ---
   ##  in case, the user accidentally selects a binary file, for example.)
   #######################################################################

   set maxLen 1000

   ###################################################
   ## Set an error message in case we hit a long line,
   ## in one of the 3 loops below.
   ###################################################

   set TOOLONGmsg "EXITING PLY-file loader routine while processing
file $ENTRYfilename.
Encountered a very long record in the file.
This is not normal for an ASCII PLY file.
Check that you have not selected a binary file."


   ##################################################################
   ## LOOP1:
   ## Start the first loop --- to the 'end_header' record --- to get
   ## Nvertices, Nfaces of the PLY file.
   ##################################################################

   set endHeader0or1 0
   set CNTrecs 0

   while { $endHeader0or1 == 0} {

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      incr CNTrecs

      if {$CNTrecs > 30} {
         set TOOmanyRECSreadMSG "EXITING PLY-file loader routine while processing
file $ENTRYfilename.
Have not found the 'end_header' record after reading 30 records.
This is not normal for an ASCII PLY file.
Check that you have selected an ASCII PLY file."
         popup_msgVarWithScroll_wait .fRerrmsg $TOOmanyRECSreadMSG
         return
      }

      ####################################
      ## Bail out if the line is too long.
      ####################################

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      #######################################################
      ## If the record is empty, skip it, read the next rec.
      #######################################################

      if {"$line" == ""} {continue}

      ####################################################################
      ## If the record is a '#' comment record, skip it, read the next rec.
      ####################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "#"} {continue}

      ########################################################
      ## Replace each tab by a single space.
      ########################################################

      set line [string map {\t \x20} $line]

      ########################################################
      ## Replace multiple spaces by a single space.
      ## Ref: page 163 of 'Practical Programming in Tcl & Tk',
      ## 4th edition, by Welch, Hobbs, Jones.
      ########################################################

      regsub -all {\s+} $line " " line


      ##############################################################
      ## If the record is a 'format binary' comment record, bail out.
      ##############################################################

      set recCOL13 [ string range $line 0 12 ]

      if {"$recCOL13" == "format binary"} {
         
         set BINARYmsg "EXITING PLY-file loader routine while processing
file $ENTRYfilename.
The file contains a 'format binary' record.
Check that you have not selected a binary file.
This loader is for processing ASCII PLY files."
         popup_msgVarWithScroll_wait .fRerrmsg $BINARYmsg
         return
      }

      ###############################################################
      ## If we encounter the 'end_header' record, break out of LOOP1.
      ###############################################################

      set recCOL10 [ string range $line 0 9 ]

      if {"$recCOL10" == "end_header"} {
         set endHeader0or1 1
         break
      }

      #########################################
      ## Get Nvertices from a record like:
      ## element vertex 486
      #########################################

      set recCOL14 [ string range $line 0 13 ]

      if {"$recCOL14" == "element vertex"} {
         set Nvertices  [ string range $line 14 end ]
         continue
      }
      ## END OF getting 'element vertex' record data


      ##################################
      ## Get Nfaces from a record like:
      ## element face 912
      ##################################

      set recCOL12 [ string range $line 0 11 ]

      if {"$recCOL12" == "element face"} {
         set Nfaces  [ string range $line 12 end ]
         continue
      }
      ## END OF getting 'element vertex' record data

      ## We do nothing with the other header records.
      ## (This is OK at least 99% of the time.)

   }
   ## END OF while { $endHeader0or1 == 0} - LOOP1


   ######################################################
   ## LOOP2:
   ## START OF LOOP to put the PLY VERTEX data into
   ## the array 'aRpoints'.
   ######################################################

    while { $CNTpoints < $Nvertices} {

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      ############################################################
      ## If the line is too long, bail out.
      ############################################################

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      #######################################################
      ## If the record is empty, skip it, read the next rec.
      #######################################################

      if {"$line" == ""} {continue}

      ####################################################################
      ## If the record is a '#' comment record, skip it, read the next rec.
      ####################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "#"} {continue}

      ########################################################
      ## Replace each tab by a single space.
      ########################################################

      set line [string map {\t \x20} $line]

      ########################################################
      ## Replace multiple spaces by a single space.
      ## Ref: page 163 of 'Practical Programming in Tcl & Tk',
      ## 4th edition, by Welch, Hobbs, Jones.
      ########################################################

      regsub -all {\s+} $line " " line


      #######################################################################
      ## It wasn't a comment or null record. We assume it is a vertex record.
      ## Put the vertex data in the aRpoints array and get min,max of xyz.
      #######################################################################

      incr CNTpoints

      ## Split the (vertex) line into separate list items.
      ## (Should be x,y,z coords.)
 
      set SPLITlist [split $line]
      if {"$VAR123toxyz" == "x1y2z3"} {
         set x [lindex $SPLITlist 0]
         set y [lindex $SPLITlist 1]
         set z [lindex $SPLITlist 2]
      } elseif {"$VAR123toxyz" == "x2y3z1"} {
         set x [lindex $SPLITlist 1]
         set y [lindex $SPLITlist 2]
         set z [lindex $SPLITlist 0]
      } elseif {"$VAR123toxyz" == "x3y1z2"} {
         set x [lindex $SPLITlist 2]
         set y [lindex $SPLITlist 0]
         set z [lindex $SPLITlist 1]
      }

      ## FOR TESTING: (Write out every Nth aRpoints record.)
      #  if {[expr {$CNTpoints % 2}] == 1} {
      #      puts "proc 'load_3DfileData_PLY' > At vertex record number $CNTpoints,"
      #      puts "     line: $line"
      #      puts "     SPLITlist: $SPLITlist"
      #      puts "     x: $x   y: $y   z:$z"
      #  }


      ## We put a pointID and colorlist(0-255) in the aRpoints record,
      ## along with the xyz coordinates.

      set aRpoints($CNTpoints) [list $CNTpoints $FILLcolorLIST255 $x $y $z]

      ##########################
      ## Collect min,max of xyz.
      ##########################

      ## Assure that xyz are in numeric form for the following comparisons.

      set x [expr {double($x)}]
      set y [expr {double($y)}]
      set z [expr {double($z)}]


      ## Set the minX,maxX,minY,maxY,minZ,maxZ values of the points.
      ## We will use these to get a mid-point of the 'point cloud'.

      if {$CNTpoints == 1} {
            set minX $x
            set maxX $x
            set minY $y
            set maxY $y
            set minZ $z
            set maxZ $z
      } else {
            if {$x < $minX} {set minX $x}
            if {$x > $maxX} {set maxX $x}
            if {$y < $minY} {set minY $y}
            if {$y > $maxY} {set maxY $y}
            if {$z < $minZ} {set minZ $z}
            if {$z > $maxZ} {set maxZ $z}
      }
      ## END OF if {$CNTpoints == 1}

      ## Get the next vertex record.
   }
   ## END OF LOOP2 - while { $CNTpoints < $Nvertices} 


   ###########################################################
   ## LOOP3:
   ## START OF LOOP to put the PLY FACE data into
   ## the array 'aRconnect'.
   ######################################################

   while { $CNTconnects < $Nfaces}  {

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      ############################################################
      ## If the line is too long, bail out.
      ############################################################

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      #######################################################
      ## If the record is empty, skip it, read the next rec.
      #######################################################

      if {"$line" == ""} {continue}

      ####################################################################
      ## If the record is a '#' comment record, skip it, read the next rec.
      ####################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "#"} {continue}

      ########################################################
      ## Replace each tab by a single space.
      ########################################################

      set line [string map {\t \x20} $line]

      ########################################################
      ## Replace multiple spaces by a single space.
      ## Ref: page 163 of 'Practical Programming in Tcl & Tk',
      ## 4th edition, by Welch, Hobbs, Jones.
      ########################################################

      regsub -all {\s+} $line " " line


      #######################################################################
      ## It wasn't a comment or null record. We assume it is a face record.
      ## Put the face data in the aRconnect array.
      #######################################################################

      incr CNTconnects

      ## Split the FACE record up into fields (which should be integers:
      ## number-of-vertices followed by that many vertex-IDs).

      set SPLITlist [split $line]
      set numVertices [lindex $SPLITlist 0]

      ## FOR TESTING:
      # if {$CNTconnects == 1} {
      #    puts "proc 'load_3DfileData_PLY' > At FACE record number $CNTconnects,"
      #    puts "  split the record into SPLITlist: $SPLITlist"
      #    puts "  numVertices: $numVertices"
      # }

      ## Make the start of the aRconnect($CNTconnect) list.
      ## (We put a pointID and colorlist(0-255) in the aRconnect record,
      ## along with the number-of-vertices.)

      set aRconnect($CNTconnects) [list $CNTconnects $FILLcolorLIST255 $numVertices]

      ## In the following loop,
      ## add the vertex numbers to the  aRconnect($CNTconnect) list.
      ## NOTE: We add 1 to each of the vertices because PLY file vertex IDs
      ## in face records start counting from 0, but in our internal aRpoints
      ## array we start counting vertices from 1.

      for {set k 1} {$k <= $numVertices} {incr k} {
         set TEMPvert [lindex $SPLITlist $k]
         lappend aRconnect($CNTconnects) [expr {int($TEMPvert) + 1}]
      }
      ## END OF loop over vertices of this FACE record


      ## FOR TESTING: (Write out every Nth aRconnect record.)
      #  if {[expr {$CNTconnects % 2}] == 1} {
      #     puts "proc 'load_3DfileData_PLY' > At FACE record number $CNTconnects,"
      #     puts "     aRconnect($CNTconnects) was loaded with: $aRconnect($CNTconnects)"
      #  }

      ## Get the next face record.
   }
   ## END OF LOOP3  -  while { $CNTconnects < $Nfaces} 

   ## We should be finished reading the file.
   ## The face records should be the last in the PLY file.
   ## Close the file.

   close $f

   #######################################################
   ## Calculate a diameter for our 'cloud' of data points.
   #######################################################

   set diam [expr {$maxX - $minX}]
   set maxDeltaY [expr {$maxY - $minY}]
   if {$maxDeltaY > $diam} {set diam $maxDeltaY}
   set maxDeltaZ [expr {$maxZ - $minZ}]
   if {$maxDeltaZ > $diam} {set diam $maxDeltaZ}


   ###################################################
   ## Get the mid-points of the x,y,z ranges from the
   ## max,min vars that we set in LOOP2 above.
   ###################################################

   set Xmid [expr {($maxX + $minX) / 2.0}]
   set Ymid [expr {($maxY + $minY) / 2.0}]
   set Zmid [expr {($maxZ + $minZ) / 2.0}]

   ##########################################
   ## Set a near-zero-tolerance value for in
   ## setting near-zero numbers to 0.0.
   ##########################################

   set zeroTOL [expr {$TOLfactor * $diam}]

   if {[expr {abs($Xmid)}] < $zeroTOL} {set Xmid [expr {double(0.0)}]}
   if {[expr {abs($Ymid)}] < $zeroTOL} {set Ymid [expr {double(0.0)}]}
   if {[expr {abs($Zmid)}] < $zeroTOL} {set Zmid [expr {double(0.0)}]}

   ###############################################
   ## Load the list of IDs of the aRconnect array,
   ## for z-depth sorting later.
   ###############################################

   set LISTpolyIDs {}

   for {set k 1} {$k <= $CNTconnects} {incr k} {
      lappend LISTpolyIDs "$k"
   }
   ## END OF k loop


   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_PLY' > CNT totals:"
   #   puts "  Nvertices: $Nvertices   CNTpoints: $CNTpoints"
   #   puts "  Nfaces:    $Nfaces      CNTconnects: $CNTconnects"
   #     puts "  minX: $minX  maxX: $maxX     minY: $minY  maxY: $maxY    minZ: $minZ  maxZ: $maxZ"
   #     puts "  Xmid: $Xmid   Ymid: $Ymid   Zmid: $Zmid    diam: $diam"


   ## Show the count totals in a GUI label.

   update_counts_label "\
PLY file - Vertices: $CNTpoints   Faces: $CNTconnects"

}
## END OF PROC 'load_3DfileData_PLY'



##+########################################################################
## PROC 'load_3DfileData_OFF'
##+########################################################################
## PURPOSE: Reads 'basic' data from OFF files --- vertex and face data ---
##          and puts the data in 2 arrays: 'aRpoints' & 'aRconnect'.
##
##          The elements of each array are of the following form:
##            aRpoints($i)  = [list $pointID $RGBcolor $transx $transy $transz]
##            aRconnect($i) = [list $elemID $RGBcolor $numVertices $vertID1 $vertID2 ...]
##
## METHOD:  In OFF files, there is a single counts record near the top of
##          the file that contains the number of vertices, faces, and edges
##          in the model.
##
##          This data loader uses the number of vertices (Nvertices)
##          and the number of faces (Nfaces) to read the data from the
##          file and put the data into the 2 arrays: 'aRpoints' & 'aRconnect'.
##
##          In OFF files, the vertex data should precede the face data.
##
##          We will check that the first rec has string 'OFF' in it.
##
##          Like the PLY dataloader, this dataloader has 3 loops --- to
##          (1) read header data that gives number of vertices and faces,
##          then (2) read vertex data, then (3) read face data.
##
##          This dataloader proceeds to read records, after the top counts
##          record, skipping comment and empty records,
##          loading the vertex data into the 'aRpoints' array until
##          Nvertices records are read.
##
##          Then the proc proceeds to read records, skipping comment and empty
##          records, loading the face data into the 'aRconnect' array
##          until Nfaces records are read.
##
##          This proc uses the 'gets' command to read the OFF file records.
##
##          This proc stores data extracted from the vertex and face records
##          in the 2 array variables (aRpoints,aRconnect) using the
##          current counts CNTpoints, CNTconnects as the index for creating
##          each array element.
##
##          We use the Tcl 'string range' command, along with some
##          other Tcl commands, to get the data from each OFF record.
##
## SETTING OTHER DATA besides aRpoints and aRconnect arrays:
##
##          While loading the array aRpoints, we find the min,max values of
##          the xyz coords and use these to calculate Xmid,Ymid,Zmid values.
##
##          We also use the min,max values to compute a 'diameter' for the
##          'point cloud'.
##
##          We also create a list, 'LISTpolyIDs', consisting of entries
##          "$i" --- sucessive integer IDs --- for 1 <= i <= $CNTconnects.
##          The length of the list is $CNTconnects --- the number of
##          polygons and lines in the array 'aRconnect'.
##
##          We set the following global variables:
##          minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##
## INPUT: an OFF data filename in global var ENTRYfilename
##        (Typical file suffixes are '.off' and '.nff'.)
##
## OUTPUT:  2 global array variables for the 2 arrays (aRpoints,aRconnect).
##          There are also 2 global variables (CNTpoints, CNTconnects)
##          to hold the count totals. Also the global variables
##          minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##          are set.
##
## CALLED BY: the 'load_3DfileData' proc
##
##+######################
## 'OFF' FILE FORMAT INFO:
##
##  Example OFF header records:
##
##  OFF
##  #created by dirichlet domain computation.
##  36   20        54
##
##  I have seen 'OFF'-type files that start with
##  'NOFF' and '4OFF' instead of 'OFF'.
##  We may simply check that the first record contains
##  the string 'OFF' --- and pop a message and bail out,
##  if there is no such string.
##
##  NOTE: The 3 integers are for vertices,faces,edges.
##
##
##  Example OFF 'vertex' records:
##
##  0.272269        0.118712        0.520703
##  -0.000222        0.002388        0.617335
##  0.000222        -0.002388        0.617335
##
##
##  Example OFF 'face' record format:
##
##  3   14 51 54              (for a triangle)
##  4           8        7        10        9           (for a quadrangle)
##  5         27        6        5        1        0        (for a pentagon)
##  6           29        20        21        9        10        28    (for a hexagon)
##  7         32        15        18        25        24        2        35 (for a 7-gon)
## ...
## 10   128 48 49 45 46 47 125 126 129 127  (for a 10-gon)
##
## I have seen an extra integer at the end of face records
## in an OFF file. This does not appear to be normal.
## I will assume we can simply take the number of integers
## indicated by the first integer in the record.
##
##+#########################################################################

proc load_3DfileData_OFF {} {

   global aRpoints aRconnect CNTpoints CNTconnects ENTRYfilename VAR123toxyz \
         COLOR1r COLOR1g COLOR1b COLOR1hex \
         minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam TOLfactor LISTpolyIDs

   ## FOR TESTING: (to dummy out this proc)
   #   return


   ############################################
   ## Open the file, with some error checking.
   ############################################
   ## This is too simplistic:
   ## set f [open "$ENTRYfilename" r]
   ############################################

   set RETcode [catch "open $ENTRYfilename r" f]

   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_OFF' > RETcode: $RETcode"


   if {"$RETcode" == "1"} {
      set FILEopenERRmsg "Cannot open file
$ENTRYfilename
It is likely that the file is not found. Check spelling."
      popup_msgVarWithScroll_wait .fRerrmsg "$FILEopenERRmsg"
      return
   }


   ################################################
   ## Initialize count and color variables.
   ################################################

   set CNTpoints 0
   set CNTconnects  0

   ## Set the color to use for all the vertices and faces.
   ## (We seldom, if ever, see an OFF file with color data to use.)

   set FILLcolorLIST255 [list $COLOR1r $COLOR1g $COLOR1b]

   ########################################################################
   ## Make sure the 'diam' variable does not exist, for this file.
   ## We use its non-existence in various procs to bail out of
   ## the 'graphics pipeline' if the model did not load.
   ########################################################################

   catch {unset diam}


   #######################################################################
   ## If a line is too long,  we will bail out (or we will truncate it).
   ## (This serves as a safety factor --- to prevent severe read errors ---
   ##  in case, the user accidentally selects a binary file, for example.)
   #######################################################################

   set maxLen 1000

   ##########################################################
   ## Set an error message to use in case we hit a long line.
   ##########################################################

   set TOOLONGmsg "EXITING OFF-file loader routine while processing
file $ENTRYfilename.
Encountered a very long record in the file.
This is not normal for an ASCII OFF file.
Check that you have not selected a binary file."

   ############################################################
   ## GET THE FIRST LINE (up to a line feed) --- and its length.
   ############################################################

   set lineLen [gets $f line]

   ####################################
   ## Bail out if the line is too long.
   ####################################

   if { $lineLen > $maxLen } {
      popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
      return
   }

   #######################################################
   ## Check that the first rec contains the string 'OFF',
   ## starting in col 1 or 2.
   #######################################################

    set col1thru3 [ string range $line 0 2 ]
    set col2thru4 [ string range $line 1 3 ]

   if {"$col1thru3" != "OFF" && "$col2thru4" != "OFF"} {
         set notOFFmsg "EXITING OFF-file loader routine while processing
file $ENTRYfilename.
The first record of the file does not appear to start with the string 'OFF'.
Check whether you have selected an OFF file --- non-binary.
This data loader is for processing ASCII OFF files."
         popup_msgVarWithScroll_wait .fRerrmsg $notOFFmsg
         return
   }


   ##################################################################
   ## LOOP1: (to encounter the vertices-faces-edges counts record)
   ## Start the first loop --- to read to the first record other than
   ## comment (#) or empty/null records.
   ## We need to get Nvertices, Nfaces of the OFF file ---
   ## then go to LOOP2.
   ##################################################################

   set endHeader0or1 0

   while { $endHeader0or1 == 0} {

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      ####################################
      ## Bail out if the line is too long.
      ####################################

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      ########################################################
      ## If the record is empty, skip it, read the next rec.
      ########################################################

      if {"$line" == ""} {continue}

      ################################################################
      ## If the record is a comment record, skip it, read the next rec.
      ################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "#"} {continue}

      ########################################################
      ## Replace each tab by a single space.
      ########################################################

      set line [string map {\t \x20} $line]

      ########################################################
      ## Replace multiple spaces by a single space.
      ## Ref: page 163 of 'Practical Programming in Tcl & Tk',
      ## 4th edition, by Welch, Hobbs, Jones.
      ########################################################

      regsub -all {\s+} $line " " line


      ################################################################
      ## This should be the first non-empty, non-comment record after
      ## the 'OFF' record at the top of the file. This record should
      ## contain the counts we want. Break out of this loop and
      ## drop into LOOP2.
      ################################################################

      foreach {Nvertices Nfaces Nedges}  $line {break}
      set  endHeader0or1 1
      break

   }
   ## END OF LOOP1.


   ## FOR TESTING: (Show the counts from the header rec.)
   #   puts "proc 'load_3DfileData_OFF' > Counts from OFF header record:"
   #   puts "     Nvertices: $Nvertices  Nfaces: $Nfaces  Nedges: $Nedges"


   ## Make sure Nvertices and Nfaces are integers.

   # set Nvertices [expr {int($Nvertices)}]
   # set Nfaces [expr {int($Nfaces)}]


   ######################################################
   ## LOOP2:
   ## START OF LOOP to put the OFF VERTEX data into
   ## the array 'aRpoints'.
   ######################################################

   while { $CNTpoints < $Nvertices} {

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      ############################################################
      ## If the line is too long, bail out.
      ############################################################

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      #######################################################
      ## If the record is empty, skip it, read the next rec.
      #######################################################

      if {"$line" == ""} {continue}

      ####################################################################
      ## If the record is a '#' comment record, skip it, read the next rec.
      ####################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "#"} {continue}

      ########################################################
      ## Replace each tab by a single space.
      ########################################################

      set line [string map {\t \x20} $line]

      ########################################################
      ## Replace multiple spaces by a single space.
      ## Ref: page 163 of 'Practical Programming in Tcl & Tk',
      ## 4th edition, by Welch, Hobbs, Jones.
      ########################################################

      regsub -all {\s+} $line " " line


      #######################################################################
      ## It wasn't a comment or null record. We assume it is a vertex record.
      ## Put the vertex data in the aRpoints array and get min,max of xyz.
      #######################################################################

      incr CNTpoints

      ## Split the (vertex) line into separate list items.
      ## (Should be x,y,z coords.)

      set SPLITlist [split $line]
      if {"$VAR123toxyz" == "x1y2z3"} {
         set x [lindex $SPLITlist 0]
         set y [lindex $SPLITlist 1]
         set z [lindex $SPLITlist 2]
      } elseif {"$VAR123toxyz" == "x2y3z1"} {
         set x [lindex $SPLITlist 1]
         set y [lindex $SPLITlist 2]
         set z [lindex $SPLITlist 0]
      } elseif {"$VAR123toxyz" == "x3y1z2"} {
         set x [lindex $SPLITlist 2]
         set y [lindex $SPLITlist 0]
         set z [lindex $SPLITlist 1]
      }

      ## We put a pointID and colorlist(0-255) in the aRpoints record,
      ## along with the xyz coordinates.

      set aRpoints($CNTpoints) [list $CNTpoints $FILLcolorLIST255 $x $y $z]

      ## FOR TESTING: (Write out every Nth aRpoints record.)
      #  if {[expr {$CNTpoints % 2}] == 1} {
      #      puts "proc 'load_3DfileData_OFF' > At vertex record number $CNTpoints:"
      #      puts "     line: $line"
      #      puts "     SPLITlist: $SPLITlist"
      #      puts "     x: $x   y: $y   z:$z"
      #      puts "     aRpoints($CNTpoints): $aRpoints($CNTpoints)"
      #  }


      ##########################
      ## Collect min,max of xyz.
      ##########################

      ## Assure that xyz are in numeric form for the following comparisons.

      set x [expr {double($x)}]
      set y [expr {double($y)}]
      set z [expr {double($z)}]


      ##############################################################
      ## Set the minX,maxX,minY,maxY,minZ,maxZ values of the points.
      ## We will use these to get a mid-point of the 'point cloud'
      ## as well as set 'diam'.
      ##############################################################

      if {$CNTpoints == 1} {
            set minX $x
            set maxX $x
            set minY $y
            set maxY $y
            set minZ $z
            set maxZ $z
      } else {
            if {$x < $minX} {set minX $x}
            if {$x > $maxX} {set maxX $x}
            if {$y < $minY} {set minY $y}
            if {$y > $maxY} {set maxY $y}
            if {$z < $minZ} {set minZ $z}
            if {$z > $maxZ} {set maxZ $z}
      }
      ## END OF if {$CNTpoints == 1}

      ## Get the next vertex record.
   }
   ## END OF LOOP2 -   while { $CNTpoints < $Nvertices}


   ###########################################################
   ## LOOP3:
   ## START OF LOOP to put the OFF FACE data into
   ## the array 'aRconnect'.
   ######################################################

   while { $CNTconnects < $Nfaces} {

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      ############################################################
      ## If the line is too long, bail out.
      ############################################################

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      #######################################################
      ## If the record is empty, skip it, read the next rec.
      #######################################################

      if {"$line" == ""} {continue}

      ####################################################################
      ## If the record is a '#' comment record, skip it, read the next rec.
      ####################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "#"} {continue}

      ########################################################
      ## Replace each tab by a single space.
      ########################################################

      set line [string map {\t \x20} $line]

      ########################################################
      ## Replace multiple spaces by a single space.
      ## Ref: page 163 of 'Practical Programming in Tcl & Tk',
      ## 4th edition, by Welch, Hobbs, Jones.
      ########################################################

      regsub -all {\s+} $line " " line


      #######################################################################
      ## It wasn't a comment or null record. We assume it is a face record.
      ## Put the face data in the aRconnect array.
      #######################################################################

      incr CNTconnects

      ## Split the FACE record up into fields (which should be integers:
      ## number-of-vertices followed by that many vertex-IDs).

      set SPLITlist [split $line]
      set numVertices [lindex $SPLITlist 0]

      ## FOR TESTING:
      # if {$CNTconnects == 1} {
      #    puts "proc 'load_3DfileData_OFF' > At FACE record number $CNTconnects,"
      #    puts "  split the record into SPLITlist: $SPLITlist"
      #    puts "  numVertices: $numVertices"
      # }

      ## Make the start of the aRconnect($CNTconnect) list.
      ## (We put a pointID and colorlist(0-255) in the aRconnect record,
      ## along with the number-of-vertices.)

      set aRconnect($CNTconnects) [list $CNTconnects $FILLcolorLIST255 $numVertices]

      ## In the following loop,
      ## add the vertex numbers to the  aRconnect($CNTconnect) list.
      ## NOTE: We add 1 to each of the vertices because OFF file vertex IDs
      ## in face records start counting from 0, but in our internal aRpoints
      ## array we start counting vertices from 1.

      for {set k 1} {$k <= $numVertices} {incr k} {
         set TEMPvert [lindex $SPLITlist $k]
         lappend aRconnect($CNTconnects) [expr {int($TEMPvert) + 1}]
      }
      ## END OF loop over vertices of this FACE record


      ## FOR TESTING: (Write out every Nth aRconnect record.)
      #  if {[expr {$CNTconnects % 2}] == 1} {
      #     puts "proc 'load_3DfileData_OFF' > At FACE record number $CNTconnects:"
      #     puts "     aRconnect($CNTconnects) was loaded with: $aRconnect($CNTconnects)"
      #  }

      ## Get the next face record.
   }
   ## END OF LOOP3  -  while { $CNTconnects < $Nfaces} 

   ## We should be finished reading the file.
   ## The face records should be the last in the OFF file.
   ## Close the file.

   close $f

   #######################################################
   ## Calculate a diameter for our 'cloud' of data points.
   #######################################################

   set diam [expr {$maxX - $minX}]
   set maxDeltaY [expr {$maxY - $minY}]
   if {$maxDeltaY > $diam} {set diam $maxDeltaY}
   set maxDeltaZ [expr {$maxZ - $minZ}]
   if {$maxDeltaZ > $diam} {set diam $maxDeltaZ}


   ##################################################
   ## Get the mid-points of the x,y,z ranges from the
   ## max,min vars that we set in LOOP2 above.
   ##################################################

   set Xmid [expr {($maxX + $minX) / 2.0}]
   set Ymid [expr {($maxY + $minY) / 2.0}]
   set Zmid [expr {($maxZ + $minZ) / 2.0}]

   ##########################################
   ## Set a near-zero-tolerance value for in
   ## setting near-zero numbers to 0.0.
   ##########################################

   set zeroTOL [expr {$TOLfactor * $diam}]

   if {[expr {abs($Xmid)}] < $zeroTOL} {set Xmid [expr {double(0.0)}]}
   if {[expr {abs($Ymid)}] < $zeroTOL} {set Ymid [expr {double(0.0)}]}
   if {[expr {abs($Zmid)}] < $zeroTOL} {set Zmid [expr {double(0.0)}]}


   ###############################################
   ## Load the list of IDs for the aRconnect array,
   ## for z-depth sorting later.
   ###############################################

   set LISTpolyIDs {}

   for {set k 1} {$k <= $CNTconnects} {incr k} {
      lappend LISTpolyIDs "$k"
   }
   ## END OF k loop


   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_OFF' > CNT totals:"
   #   puts "  Nvertices: $Nvertices   CNTpoints: $CNTpoints"
   #   puts "  Nfaces:    $Nfaces      CNTconnects: $CNTconnects"
   #   puts "  minX: $minX  maxX: $maxX     minY: $minY  maxY: $maxY    minZ: $minZ  maxZ: $maxZ"
   #   puts "  Xmid: $Xmid   Ymid: $Ymid   Zmid: $Zmid    diam: $diam"

   ## Show the count totals in a GUI label.

   update_counts_label "\
OFF file - Vertices: $CNTpoints   Faces: $CNTconnects"

}
## END OF PROC 'load_3DfileData_OFF'



##+########################################################################
## PROC 'load_3DfileData_STL'
##+########################################################################
## PURPOSE: Reads 'basic' data from 'facet' and 'vertex' data in
##          ASCII STL (stereolithography) files.
##          Puts the data in array variables --- aRpoints and  aRconnect.
##
##          The elements of each array are of the following form:
##            aRpoints($i)  = [list $pointID $RGBcolor $transx $transy $transz]
##            aRconnect($i) = [list $elemID $RGBcolor $numVertices $vertID1 $vertID2 ...]
##
## METHOD:  Uses the 'gets' command to read records.
##          The format of STL files is very simple.
##          Here is an example of a 'facet group' of data:
##
##  facet normal  0.0   0.0  -1.0    
##    outer loop
##      vertex    0.0   0.0   0.0    
##      vertex    1.0   1.0   0.0    
##      vertex    1.0   0.0   0.0    
##    endloop
##  endfacet
##
##          These are usually the only types of data in the file,
##          except for a record like 'solid MYSOLID' at the top of the file
##          and a record like 'endsolid MYSOLID' at the bottom of the file.
##
##          This dataloader puts each of the 3 vertex records in
##          the 'aRpoints' array and assigns each vertex an ID number.
##          Then this dataloader makes a polygon record for the 'aRconnect'
##          array that contains the 3 vertex ID numbers.
##
##          We use the 'string range' command to get the xyz data from
##          each '      vertex' record.
##
## SETTING OTHER DATA besides aRpoints and aRconnect arrays:
##
##          While loading the array aRpoints, we find the min,max values of
##          the xyz coords and use these to calculate Xmid,Ymid,Zmid values.
##
##          We also use the min,max values to compute a 'diameter' for the
##          'point cloud'.
##
##          We also create a list, 'LISTpolyIDs', consisting of entries
##          "$i" --- sucessive integer IDs --- for 1 <= i <= $CNTconnects.
##          The length of the list is $CNTconnects --- the number of
##          polygons and lines in the array 'aRconnect'.
##
##          We set the following global variables:
##          minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##
## INPUT: an ASCII STL data filename in global var ENTRYfilename
##
## OUTPUT:  global array variables to hold data for the components
##          of the 3D model (points and polygons --- no lines in STL files).
##          There are also 2 global variables to hold the count totals
##          for 'aRpoints' and 'aRconnect' arrays. Also the global variables
##          minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##          are set.
##
## CALLED BY: the 'load_3DfileData' proc
##+########################################################################

proc load_3DfileData_STL {} {

   global aRpoints aRconnect CNTpoints CNTconnects ENTRYfilename VAR123toxyz \
      COLOR1r COLOR1g COLOR1b COLOR1hex \
      minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam TOLfactor LISTpolyIDs

   ## FOR TESTING: (to dummy out this proc)
   #  return


   ############################################
   ## Open the file, with some error checking.
   ############################################
   ## This is too simplistic:
   ## set f [open "$ENTRYfilename" r]
   ############################################

   set RETcode [catch "open $ENTRYfilename r" f]

   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_cOBJ' > RETcode: $RETcode"


   if {"$RETcode" == "1"} {
      set FILEopenERRmsg "Cannot open file
$ENTRYfilename
It is likely that the file is not found. Check spelling."
      popup_msgVarWithScroll_wait .fRerrmsg "$FILEopenERRmsg"
      return
   }


   ################################################
   ## Initialize count variables.
   ################################################

   set CNTpoints 0
   set CNTconnects  0

   #########################################################
   ## Set the color to use for all the vertices and faces.
   ## (We seldom, if ever, see an STL file with color data.)
   #########################################################

   set FILLcolorLIST255 [list $COLOR1r $COLOR1g $COLOR1b]

   ########################################################################
   ## Make sure the 'diam' variable does not exist, for this file.
   ## We use its non-existence in various procs to bail out of
   ## the 'graphics pipeline' if the model did not load.
   ########################################################################

   catch {unset diam}


   #########################################################################
   ## If a line is too long, we will truncate it (or we will bail out).
   ## (This serves as a safety factor --- to prevent severe read errors ---
   ##  in case, the user accidentally selects a binary file, for example.)
   #######################################################################

   set maxLen 1000

   #####################################################
   ## Set an error message in case we hit a long line.
   #####################################################

   set TOOLONGmsg "EXITING ASCII-STL-file loader routine while processing
file $ENTRYfilename.
Encountered a very long record in the file.
This is not normal for an ASCII STL file.
Check that you have not selected a binary file."

   #############################################################
   ## START OF WHILE-LOOP for the 'gets' file-READING.
   ## The while-test below is equivalent to 'while {![eof $f]}'.
   ##
   ## We add 3 points at a time to the aRpoints array, as we
   ## encounter triplets of records with coords for 3 vertices.
   ## After we load those three recs into the aRpoints array,
   ## we make a face data record in the aRconnect array.
   #############################################################

   while {[eof $f] == 0} {

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      ####################################
      ## Bail out if the line is too long.
      ####################################

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      ######################################################
      ## If the record is empty, skip it, read the next rec.
      ######################################################

      if {"$line" == ""} {continue}

      #####################################################################
      ## If the record is a '#' comment record, skip it, read the next rec.
      #####################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "#"} {continue}

      ########################################################
      ## Replace each tab by a single space.
      ########################################################

      set line [string map {\t \x20} $line]

      ########################################################
      ## Replace multiple spaces by a single space.
      ## Ref: page 163 of 'Practical Programming in Tcl & Tk',
      ## 4th edition, by Welch, Hobbs, Jones.
      ########################################################

      regsub -all {\s+} $line " " line


      ####################################################################
      ## If the record is a 'solid' or 'endsolid' record, skip it.
      ###################################################################
      if {"$recCOL1" == "s" || "$recCOL1" == "S"} {continue}
      if {"$recCOL1" == "e" || "$recCOL1" == "E"} {continue}

      #########################################################
      ## Get the record type indicator in the first 14 columns.
      #########################################################

      set cols7 [ string range $line 0 6 ]

      ###############################################
      ## Skip several types of non-data recs.
      ## "endloop" , "endfacet" , "outer loop"
      ###############################################

      if {"$cols7" == "endloop"} {continue}
      if {"$cols7" == "endface"} {continue}
      if {"$cols7" == "outer l"} {continue}

      ## FOR TESTING:
      #   puts "proc 'load_3DfileDataSTL' >  recTYPE: '$recTYPE'"

      ###############################################################################
      ## Start reading the group of 6 other records that go with a 'facet normal' rec.
      ###############################################################################

      if {"$cols7" == "facet n"} {

         ####################################################
         ## Skip the next rec. Should be an 'outer loop' rec.
         ####################################################

         gets $f line

         #########################################################
         ## The next 3 recs should be 'vertex' recs --- the
         ## string 'vertex' and 3 space-separated xyz coordinates.
         ## Read each record and
         ## put the coordinate data from each into aRpoints array
         ## records, along with a point count and a color-list.
         #########################################################

         ########################################
         ## For the 1st of 3 vertex recs, 'gets':
         ########################################

         set lineLen [gets $f line]
         if { $lineLen > $maxLen } {
            popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
            return
         }
         ## Trim leading and trailing spaces.
         set line [string trim $line]
         ## Replace each tab by a single space.
         set line [string map {\t \x20} $line]
         ## Replace multiple spaces by a single space.
         regsub -all {\s+} $line " " line
         incr CNTpoints
         set line7toEND [string range $line 7 end]
         set SPLITlist [split $line7toEND]
         if {"$VAR123toxyz" == "x1y2z3"} {
            set x [lindex $SPLITlist 0]
            set y [lindex $SPLITlist 1]
            set z [lindex $SPLITlist 2]
         } elseif {"$VAR123toxyz" == "x2y3z1"} {
            set x [lindex $SPLITlist 1]
            set y [lindex $SPLITlist 2]
            set z [lindex $SPLITlist 0]
         } elseif {"$VAR123toxyz" == "x3y1z2"} {
            set x [lindex $SPLITlist 2]
            set y [lindex $SPLITlist 0]
            set z [lindex $SPLITlist 1]
         }
         set aRpoints($CNTpoints) [list $CNTpoints $FILLcolorLIST255 $x $y $z]
         ## Assure that xyz are in numeric form for the following comparisons.
         set x [expr {double($x)}]
         set y [expr {double($y)}]
         set z [expr {double($z)}]
         ## Set the minX,maxX,minY,maxY,minZ,maxZ values of the points.
         ## We will use these to get a mid-point of the 'point cloud'.
         if {$CNTpoints == 1} {
            set minX $x
            set maxX $x
            set minY $y
            set maxY $y
            set minZ $z
            set maxZ $z
         } else {
            if {$x < $minX} {set minX $x}
            if {$x > $maxX} {set maxX $x}
            if {$y < $minY} {set minY $y}
            if {$y > $maxY} {set maxY $y}
            if {$z < $minZ} {set minZ $z}
            if {$z > $maxZ} {set maxZ $z}
         }
         ## END OF if {$CNTpoints == 1}

         ## FOR TESTING: (Write out every Nth aRpoints record.)
         #  if {[expr {$CNTpoints % 3}] == 1} {
         #       puts "proc 'load_3DfileData_STL' > At STL 'vertex' record number $CNTpoints:"
         #       puts "     aRpoints($CNTpoints): $aRpoints($CNTpoints)"
         #  }

         ########################################
         ## For the 2nd of 3 vertex recs, 'gets':
         ########################################

         set lineLen [gets $f line]
         if { $lineLen > $maxLen } {
            popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
            return
         }
         ## Trim leading and trailing spaces.
         set line [string trim $line]
         ## Replace each tab by a single space.
         set line [string map {\t \x20} $line]
         ## Replace multiple spaces by a single space.
         regsub -all {\s+} $line " " line
         incr CNTpoints
         set line7toEND [string range $line 7 end]
         set SPLITlist [split $line7toEND]
         if {"$VAR123toxyz" == "x1y2z3"} {
            set x [lindex $SPLITlist 0]
            set y [lindex $SPLITlist 1]
            set z [lindex $SPLITlist 2]
         } elseif {"$VAR123toxyz" == "x2y3z1"} {
            set x [lindex $SPLITlist 1]
            set y [lindex $SPLITlist 2]
            set z [lindex $SPLITlist 0]
         } elseif {"$VAR123toxyz" == "x3y1z2"} {
            set x [lindex $SPLITlist 2]
            set y [lindex $SPLITlist 0]
            set z [lindex $SPLITlist 1]
         }
         set aRpoints($CNTpoints) [list $CNTpoints $FILLcolorLIST255 $x $y $z]
         ## Assure that xyz are in numeric form for the following comparisons.
         set x [expr {double($x)}]
         set y [expr {double($y)}]
         set z [expr {double($z)}]
         ## Set the minX,maxX,minY,maxY,minZ,maxZ values of the points.
         ## We will use these to get a mid-point of the 'point cloud'.
         if {$CNTpoints == 1} {
            set minX $x
            set maxX $x
            set minY $y
            set maxY $y
            set minZ $z
            set maxZ $z
         } else {
            if {$x < $minX} {set minX $x}
            if {$x > $maxX} {set maxX $x}
            if {$y < $minY} {set minY $y}
            if {$y > $maxY} {set maxY $y}
            if {$z < $minZ} {set minZ $z}
            if {$z > $maxZ} {set maxZ $z}
         }
         ## END OF if {$CNTpoints == 1}

         ## FOR TESTING: (Write out every Nth aRpoints record.)
         #  if {[expr {$CNTpoints % 3}] == 1} {
         #       puts "proc 'load_3DfileData_STL' > At STL 'vertex' record number $CNTpoints:"
         #       puts "     aRpoints($CNTpoints): $aRpoints($CNTpoints)"
         #  }


         ########################################
         ## For the 3rd of 3 vertex recs, 'gets':
         ########################################

         set lineLen [gets $f line]
         if { $lineLen > $maxLen } {
            popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
            return
         }
         ## Trim leading and trailing spaces.
         set line [string trim $line]
         ## Replace each tab by a single space.
         set line [string map {\t \x20} $line]
         ## Replace multiple spaces by a single space.
         regsub -all {\s+} $line " " line
         incr CNTpoints
         set line7toEND [string range $line 7 end]
         set SPLITlist [split $line7toEND]
         if {"$VAR123toxyz" == "x1y2z3"} {
            set x [lindex $SPLITlist 0]
            set y [lindex $SPLITlist 1]
            set z [lindex $SPLITlist 2]
         } elseif {"$VAR123toxyz" == "x2y3z1"} {
            set x [lindex $SPLITlist 1]
            set y [lindex $SPLITlist 2]
            set z [lindex $SPLITlist 0]
         } elseif {"$VAR123toxyz" == "x3y1z2"} {
            set x [lindex $SPLITlist 2]
            set y [lindex $SPLITlist 0]
            set z [lindex $SPLITlist 1]
         }
         set aRpoints($CNTpoints) [list $CNTpoints $FILLcolorLIST255 $x $y $z]
         ## Assure that xyz are in numeric form for the following comparisons.
         set x [expr {double($x)}]
         set y [expr {double($y)}]
         set z [expr {double($z)}]
         ## Set the minX,maxX,minY,maxY,minZ,maxZ values of the points.
         ## We will use these to get a mid-point of the 'point cloud'.
         if {$CNTpoints == 1} {
            set minX $x
            set maxX $x
            set minY $y
            set maxY $y
            set minZ $z
            set maxZ $z
         } else {
            if {$x < $minX} {set minX $x}
            if {$x > $maxX} {set maxX $x}
            if {$y < $minY} {set minY $y}
            if {$y > $maxY} {set maxY $y}
            if {$z < $minZ} {set minZ $z}
            if {$z > $maxZ} {set maxZ $z}
         }
         ## END OF if {$CNTpoints == 1}

         ## FOR TESTING: (Write out every Nth aRpoints record.)
         #  if {[expr {$CNTpoints % 3}] == 1} {
         #       puts "proc 'load_3DfileData_STL' > At STL 'vertex' record number $CNTpoints:"
         #       puts "     aRpoints($CNTpoints): $aRpoints($CNTpoints)"
         #  }


         ################################################################
         ## NOW make the triangle record using the 3 vertices just read.
         ## We put a recIDnum and color-list in the 'aRconnect' record,
         ## along with the vertex numbers.
         ################################################################
         ## Recall that the elements of each array are of the following form:
         ##       aRconnect($i) = [list $elemID $RGBcolor $numVertices $vertID1 $vertID2 ...]
         ################################################################

         incr CNTconnects

         set aRconnect($CNTconnects)  \
            [list $CNTconnects $FILLcolorLIST255 3 [expr {$CNTpoints - 2}] [expr {$CNTpoints - 1}] $CNTpoints]

         ## FOR TESTING: (Write out every Nth aRconnect record.)
         #  if {[expr {$CNTconnects % 3}] == 1} {
         #    puts "proc 'load_3DfileDataSTL' > At STL 'face' record number $CNTconnects:"
         #    puts "     aRconnect($CNTconnects): $aRconnect($CNTconnects)"
         #  }

      }
      ## END OF    if {"$cols7" == "facet n"}

   }
   ## END OF LOOP  while {[eof $f] == 0}

   ######################################################
   ## We should have encountered end-of-file at this point.
   ## Close the file.
   ######################################################

   close $f

   #######################################################
   ## Calculate a diameter for our 'cloud' of data points.
   #######################################################

   set diam [expr {$maxX - $minX}]
   set maxDeltaY [expr {$maxY - $minY}]
   if {$maxDeltaY > $diam} {set diam $maxDeltaY}
   set maxDeltaZ [expr {$maxZ - $minZ}]
   if {$maxDeltaZ > $diam} {set diam $maxDeltaZ}


   ###########################################
   ## Get the mid-points of the x,y,z ranges.
   ###########################################

   set Xmid [expr {($maxX + $minX) / 2.0}]
   set Ymid [expr {($maxY + $minY) / 2.0}]
   set Zmid [expr {($maxZ + $minZ) / 2.0}]

   ##########################################
   ## Set a near-zero-tolerance value for in
   ## setting near-zero numbers to 0.0.
   ##########################################

   set zeroTOL [expr {$TOLfactor * $diam}]

   if {[expr {abs($Xmid)}] < $zeroTOL} {set Xmid [expr {double(0.0)}]}
   if {[expr {abs($Ymid)}] < $zeroTOL} {set Ymid [expr {double(0.0)}]}
   if {[expr {abs($Zmid)}] < $zeroTOL} {set Zmid [expr {double(0.0)}]}


   #################################################
   ## Load the list of IDs for the aRconnect array.
   ## for z-depth sorting later.
   #################################################

   set LISTpolyIDs {}

   for {set k 1} {$k <= $CNTconnects} {incr k} {
      lappend LISTpolyIDs "$k"
   }
   ## END OF k loop


   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_STL' > CNT totals:"
   #   puts "     CNTpoints: $CNTpoints    CNTconnects: $CNTconnects"
   #   puts "  minX: $minX  maxX: $maxX     minY: $minY  maxY: $maxY    minZ: $minZ  maxZ: $maxZ"
   #   puts "  Xmid: $Xmid   Ymid: $Ymid   Zmid: $Zmid    diam: $diam"

   ## Show the count totals in a GUI label.

   update_counts_label "\
STL file - Points: $CNTpoints  Triangles: $CNTconnects"

}
## END OF PROC 'load_3DfileData_STL'



##+########################################################################
## PROC 'load_3DfileData_NASshort'         (UNFINISHED; preliminary)
##+########################################################################
## PURPOSE: Reads 'basic' data from 'fixed-length,short-format' NASTRAN
##          records and puts the data in 2 arrays: 'aRpoints' & 'aRconnect'.
##
##          The elements of each array are of the following form:
##            aRpoints($i)  = [list $pointID $RGBcolor $transx $transy $transz]
##            aRconnect($i) = [list $elemID $RGBcolor $numVertices $vertID1 $vertID2 ...]
##
## METHOD:  Reads 'GRID' records to get data for the 'aRpoints' array,
##          in one loop through the data file, while building a list, 'LISTgridIDs',
##          that correlates ID's in the GRID records with the GRID-count.
##
##          Then, in a SECOND LOOP through the file, ...
##
##          Reads the following records to get data for the 'aRconnect'
##          array:
##            'CTRIA3' records - triangle data
##            'CQUAD4' records - quadrangle data
##            'CROD'   records - line data
##            'CBAR'   records - line data
##            'CBEAM'  records - line data
##
##          We may someday add the ability to read CTRIA6 and CQUAD8
##          and CTRIAR and CQUADR records to make triangle and quadrangle
##          records in the 'aRconnect' array. Also ...
##
##          We may someday add the ability to read solid element records
##          CTETRA, CHEXA, CPENTA) and make triangle and quadrangle
##          records in the 'aRconnect' array. Also ...
##
##          We may someday add the ability to read NASTRAN free-format
##          records (comma-separated values), either in this proc
##          or, to keep the logic from getting way too confusing,
##          in a separate proc --- say 'load_3DfileData_NASfree'
##          --- someday. Also ...
##
##          We may someday add the ability to read NASTRAN fixed-length
##          fields with 'long-field' format records, either in this proc
##          or, to keep the logic from getting way too confusing,
##          in a separate proc --- say 'load_3DfileData_NASlong'
##          --- someday.
##
##          This proc uses the 'gets' command to read records.
##          We read the records in two loops through the file, collecting
##          data from 'GRID' records in the 1st loop, and from
##          'C' (connectivity) records in the 2nd loop.
##
##          This proc stores data extracted after the first 8 characters of
##          the NASTRAN records in the 2 array variables (aRpoints,aRconnect)
##          using the counts (CNTpoints, CNTconnects) as the index into
##          each array.
##
##          We use the Tcl 'string range' command, along with some
##          other Tcl commands, to get the data from each NASTRAN record.
##
## SETTING OTHER DATA besides aRpoints and aRconnect arrays:
##
##          While loading the array aRpoints, we find the min,max values of
##          the xyz coords and use these to calculate Xmid,Ymid,Zmid values.
##
##          We also use the min,max values to compute a 'diameter' for the
##          'point cloud'.
##
##          We also create a list, 'LISTpolyIDs', consisting of entries
##          "$i" --- sucessive integer IDs --- for 1 <= i <= $CNTconnects.
##          The length of the list is $CNTconnects --- the number of
##          polygons and lines in the array 'aRconnect'.
##
##          We set the following global variables:
##          minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##
## INPUT: NASTRAN data filename in global var ENTRYfilename
##        (Typical file suffixes used are '.dat', '.bdf', and 'nas.
##         bdf = Bulk Data File)
##
## OUTPUT:  2 global array variables for the 2 arrays (aRpoints,aRconnect).
##          There are also 2 global variables (CNTpoints, CNTconnects)
##          to hold the count totals. Also the global variables
##          minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs
##          are set.
##
## CALLED BY: the 'load_3DfileData' proc
##
##+###########################
## NASTRAN FILE FORMAT INFO:   ('fixed-length,short-format')
##
##    (We do not support free format or long format, yet).
##
## Before getting to the GRID and 'C' records, here are a few notes
## on the NASTRAN 'small field' input format:
##
## - A dollar-sign in column 1 indicates a comment record.
##
## - There are 10 8-character fields in each GRID and 'C' record.
##
## - Fields 1 and 10 must be left justified.
##
## - Fields 2 through 9 don't need to be either right or left justified.
##   However, aligning the data fields is a good practice.
##
## - You can't include any embedded blanks in small field format input data.
##   For example '7 .65' is not allowed, but '7.65' is OK.
##
##
## Example 'GRID' record format:  ('fixed-length,short-format')
##
## GRID          11        -.088532.0782987.1716192
## GRID          12        0.084994     7.9-1.338-3
## GRID          13        -.125046.1569211 .145338
## $        1         2         3         4         5         6
## $23456789012345678901234567890123456789012345678901234567890
##
##  Note that the GRID data is in 8-column groups, up to 72-columns,
##  at which point a continuation indicator is put in the last 8
##  columns and data-heavy records are continued onto the next 80-column
##  record.
##  Data (integers and floating-point) may be right or left justified
##  in the 8-character columns.
##  (Long-format records use 16-character columns, between starting
##   and ending columns of 8-characters.)
##
##------------------------------------------------------------
## 'C' (connectivity) RECORDS:
##
## In the following CTRIA3 and CQUAD4 cards/records,
## the vertex (GRID) IDs start in column 4.
## The GRID IDs are in columns 4,5,6 for CTRIA3 records.
## The GRID IDs are in columns 4,5,6,7 for CQUAD4 records.
##
## In columns 2 and 3 of most 'C' (connectivity) records are
## EID (element ID) and PID (property ID).
##
## We put the EID in the 'aRconnect' array, in case we ever
## get around to allowing the user to click on an element in
## the display and show info for the element, including the EID.
##
## We could use the PID to assign the same color to all elements
## with the same PID. This would make sense because often
## the elements with the same PID have the same material properties,
## which may translate into visual properties.
##
## Example 'CTRIA3' record format ('fixed-length,short-format'):
##
## CTRIA3        48       1      35      38      39
##
## $        1         2         3         4         5         6
## $234567890123456789012345678901234567890123456789012345678901234
## $-------++++++++--------++++++++--------++++++++--------++++++++
##
##
## Example 'CQUAD4' record format ('fixed-length,short-format'):
##
## CQUAD4        49       1      37      34      39      41
## $        1         2         3         4         5         6
## $234567890123456789012345678901234567890123456789012345678901234
## $-------++++++++--------++++++++--------++++++++--------++++++++
##
## In the following CROD, CBAR,and CBEAM cards/records,
## the 2 vertex (GRID) IDs are in columns 4 and 5.
##
## Example 'CROD' record format   ('fixed-length,short-format'):
##
## CROD     100     5       10003   10000
## $        1         2         3         4         5         6
## $234567890123456789012345678901234567890123456789012345678901234
## $-------++++++++--------++++++++--------++++++++--------++++++++
##
## Example 'CBAR' record format   ('fixed-length,short-format'):
##
## CBAR     767     4       1       428     0.      0.      1.
## $        1         2         3         4         5         6
## $234567890123456789012345678901234567890123456789012345678901234
## $-------++++++++--------++++++++--------++++++++--------++++++++
##
## Example 'CBEAM' record format  ('fixed-length,short-format'):
##
## CBEAM   2       2       1       2       0.0     0.0     1.0 
## $        1         2         3         4         5         6
## $234567890123456789012345678901234567890123456789012345678901234
## $-------++++++++--------++++++++--------++++++++--------++++++++
##
##+#########################################################################

proc load_3DfileData_NASshort {} {

   global aRpoints aRconnect CNTpoints CNTconnects ENTRYfilename \
      COLOR1r COLOR1g COLOR1b COLOR1hex \
      minX maxX minY maxY minZ maxZ Xmid Ymid Zmid diam LISTpolyIDs

   ## FOR TESTING: (to dummy out this proc)
      return


   ############################################
   ## Open the file, with some error checking.
   ############################################

   # set f [open "$ENTRYfilename" r]

   set RETcode [catch "open $ENTRYfilename r" f]

   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_cOBJ' > RETcode: $RETcode"


   if {"$RETcode" == "1"} {
      set FILEopenERRmsg "Cannot open file
$ENTRYfilename
It is likely that the file is not found. Check spelling."
      popup_msgVarWithScroll_wait .fRerrmsg "$FILEopenERRmsg"
      return
   }


   ################################################
   ## Initialize count and color variables.
   ################################################

   set CNTpoints 0
   set CNTconnects  0
   set FILLcolorLIST255 [list $COLOR1r $COLOR1g $COLOR1b]

   #########################################################################
   ## If a line is too long, we will truncate it.
   ## (This serves as a safety factor --- to prevent severe read errors ---
   ##  in case, the user accidentally selects a binary file, for example.)
   #########################################################################

   set maxLen 1000

   ####################################################
   ## Set an error message in case we hit a long line.
   ####################################################

   set TOOLONGmsg "EXITING NASTRAN-file loader routine while processing
file $ENTRYfilename.
Encountered a very long record in the file.
The records of a NASTRAN input file should be no more than 80 characters long.
Check that you have not selected a binary file."

   ##############################################################
   ## LOOP1 - for GRID records:
   ## START OF WHILE-LOOP for the 'gets' file-READING ---
   ## to gather data from the GRID records --- into array
   ## 'aRpoints' and list 'LISTgridIDs'.
   ## The while-test below is equivalent to 'while {![eof $f]}'.
   ##############################################################

   while {[eof $f] == 0} {

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      ######################################################
      ## If the record is empty, skip it, read the next rec.
      ######################################################

      if {"$line" == ""} {continue}

      #################################################################
      ## If the record is a comment record, skip it, read the next rec.
      #################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "$"} {continue}

      ########################################################
      ## Get the record type indicator in the first 8 columns.
      ########################################################

      set cols8 [ string range $line 0 7 ]

      ## FOR TESTING:
      #   puts "proc 'load_3DfileData_NASshort' >  cols8: $cols8"


      ###############################################################
      ## If the record is not a 'GRID', 'CTRIA3', 'CQUAD4',
      ## 'CROD', 'CBAR', or 'CBEARM' record, skip it.
      ###############################################################

      if {"$cols8" != "GRID    " && "$cols8" != "CTRIA3  " && \
          "$cols8" != "CQUAD4  " && "$cols8" != "CROD    " && \
          "$cols8" != "CBAR    " && "$cols8" != "CBEAM   "} {continue}

      ###################################################################
      ## We will repeatedly use the substrings below in following sections.
      ###################################################################

      ## For all except 1st and last field:
      set cols9thru72 [ string range $line 8 71 ]
      ## For grid/element id (1 field):
      set cols9thru16 [ string range $line 8 15 ]
      ## For grid/element property (1 field):
      set cols17thru24 [ string range $line 16 23 ]
      ## For grid coords and ctria3 grid-ids (3 fields):
      set cols25thru48 [ string range $line 24 47 ]
      ## For cquad4 grid-ids (4 fields):
      set cols25thru56 [ string range $line 24 55 ]
      ## For crod,cbar,cbeam grid-ids (2 fields):
      set cols25thru40 [ string range $line 24 39 ]
      ## For grid coord1 (1 field):
      set cols25thru32 [ string range $line 24 31 ]
      ## For grid coord2 (1 field):
      set cols33thru40 [ string range $line 32 39 ]
      ## For grid coord3 (1 field):
      set cols41thru48 [ string range $line 40 47 ]

      ####################################################################
      ## Load 'GRID' data into array 'aRpoints' so that it looks like:
      ## aRpoints($i)  = [list $pointID $colorinfo $transx $transy $transz]
      ####################################################################

      if {"$cols8" == "GRID    "} {

         incr CNTpoints

         ## We put a pointID and colorlist in the aRpoints record,
         ## along with the xyz coordinates.

         ## We put prop-id into color place if it is not blank. (Change?)
         if {"$cols17thru24" == "        "} {
            set aRpoints($CNTpoints) "$cols9thru16 $FILLcolorLIST255 $cols25thru48"
         } else {
            set aRpoints($CNTpoints) "$cols9thru16 $cols17thru24 $cols25thru48"
         }

         if {"$VAR123toxyz" == "x1y2z3"} {
            set x [expr {double($cols25thru32)}]
            set y [expr {double($cols33thru40)}]
            set z [expr {double($cols41thru48)}]
         } elseif {"$VAR123toxyz" == "x2y3z1"} {
            set x [expr {double($cols33thru40)}]
            set y [expr {double($cols41thru48)}]
            set z [expr {double($cols25thru32)}]
         } elseif {"$VAR123toxyz" == "x3y1z2"} {
            set x [expr {double($cols41thru48)}]
            set y [expr {double($cols25thru32)}]
            set z [expr {double($cols33thru40)}]
         }

         ## Set the minX,maxX,minY,maxY,minZ,maxZ values of the points.
         ## We will use these to get a mid-point of the 'point cloud'.

         if {$CNTpoints == 1} {
            set minX $x
            set maxX $x
            set minY $y
            set maxY $y
            set minZ $z
            set maxZ $z
         } else {
            if {$x < $minX} {set minX $x}
            if {$x > $maxX} {set maxX $x}
            if {$y < $minY} {set minY $y}
            if {$y > $maxY} {set maxY $y}
            if {$z < $minZ} {set minZ $z}
            if {$z > $maxZ} {set maxZ $z}
         }
         ## END OF if {$CNTpoints == 1}


         ## FOR TESTING:
         #  puts "proc 'load_3DfileData_cOBJ' >"
         #  puts "     aRpoints($CNTpoints): $aRpoints($CNTpoints)"

      }
      ## END OF if {"$cols8" == "GRID    "}

   }
   ## END OF LOOP1 - while {[eof $f] == 0} --- to gather data from the 'C' records.


   ##############################################################
   ## LOOP2 - for 'C' records:
   ## START OF WHILE-LOOP for the 'gets' file-READING ---
   ## to gather data from the 'C' records --- into array
   ## 'aRconnect' --- using list 'LISTgridIDs'.
   ## The while-test below is equivalent to 'while {![eof $f]}'.
   ##############################################################

   while {[eof $f] == 0} {

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and its length.
      ############################################################

      set lineLen [gets $f line]

      if { $lineLen > $maxLen } {
         popup_msgVarWithScroll_wait .fRerrmsg $TOOLONGmsg
         return
      }

      #################################################
      ## Remove white space from both ends of the line.
      #################################################

      set line [string trim $line]

      ######################################################
      ## If the record is empty, skip it, read the next rec.
      ######################################################

      if {"$line" == ""} {continue}

      #################################################################
      ## If the record is a comment record, skip it, read the next rec.
      #################################################################

      set recCOL1 [ string range $line 0 0 ]

      if {"$recCOL1" == "$"} {continue}

      ########################################################
      ## Get the record type indicator in the first 8 columns.
      ########################################################

      set cols8 [ string range $line 0 7 ]

      ## FOR TESTING:
      #   puts "proc 'load_3DfileData_NASshort' >  cols8: $cols8"


      ######################################################################
      ## Load 'CTRIA3' data into array 'aRconnect':
      ## aRconnect($i) = [list $elemID $color $numVertices $vertID1 $vertID2 ...]
      ## where $numVertices is 3 in this case --- and the vertID's
      ## come from the CTRIA3 record and should match GRID ID's that
      ## we we used for indexes into the aRpoints array.
      ######################################################################

      if {"$cols8" == "CTRIA3  "} {

         incr CNTconnects
         # incr CNTtrias

         ## We put a pointID and colorlist in the aRconnect record,
         ## along with the number of vertices and the vertex IDs.
         ## (This color-setting/index-setting may need some changes.)

         ## We put prop-id into color place if it is not blank. (Change?)
         if {"$cols17thru24" == "        "} {
            set aRconnect($CNTconnects) "$cols9thru16 $FILLcolorLIST255 3 $cols25thru48"
         } else {
            set aRconnect($CNTconnects) "$cols9thru16 $cols17thru24 3 $cols25thru48"
         }
      }
      ## END OF if {"$cols8" == "CTRIA3  "}


      ######################################################################
      ## Load 'CQUAD4' data into array 'aRconnect':
      ## aRconnect($i) = [list $elemID $color $numVertices $vertID1 $vertID2 ...]
      ## where $numVertices is 4 in this case --- and the vertID's
      ## come from the CQUAD4 record and should match GRID ID's that
      ## we we used for indexes into the aRpoints array.
      ######################################################################

      if {"$cols8" == "CQUAD4  "} {

         incr CNTconnects
         # incr CNTquads

         ## We put a pointID and colorlist in the aRconnect record,
         ## along with the number of vertices and the vertex IDs.
         ## (This color-setting/index-setting may need some changes.)

         ## We put prop-id into color place if it is not blank. (Change?)
         if {"$cols17thru24" == "        "} {
            set aRconnect($CNTconnects) "$cols9thru16 $FILLcolorLIST255 4 $cols25thru56"
         } else {
            set aRconnect($CNTconnects) "$cols9thru16 $cols17thru24 4 $cols25thru56"
         }
      }
      ## END OF if {"$cols8" == "CQUAD4  "}

      ######################################################################
      ## Load 'CROD' data into array 'aRconnect':
      ## aRconnect($i) = [list $elemID $color $numVertices $vertID1 $vertID2 ...]
      ## where $numVertices is 2 in this case --- and the vertID's
      ## come from the CROD record and should match GRID ID's that
      ## we we used for indexes into the aRpoints array.
      ######################################################################

      if {"$cols8" == "CROD    "} {

         incr CNTconnects
         # incr CNTrods

         ## We put a pointID and colorlist in the aRconnect record,
         ## along with the number of vertices and the vertex IDs.
         ## (This color-setting/index-setting may need some changes.)

         ## We put prop-id into color place if it is not blank. (Change?)
         if {"$cols17thru24" == "        "} {
            set aRconnect($CNTconnects) "$cols9thru16 $FILLcolorLIST255 2 $cols25thru40"
         } else {
            set aRconnect($CNTconnects) "$cols9thru16 $cols17thru24 2 $cols25thru40"
         }
      }
      ## END OF if {"$cols8" == "CROD    "}

      ######################################################################
      ## Load 'CBAR' data into array 'aRconnect':
      ## aRconnect($i) = [list $elemID $color $numVertices $vertID1 $vertID2 ...]
      ## where $numVertices is 2 in this case --- and the vertID's
      ## come from the CROD record and should match GRID ID's that
      ## we we used for indexes into the aRpoints array.
      ######################################################################

      if {"$cols8" == "CBAR    "} {

         incr CNTconnects
         # incr CNTbars

         ## We put a pointID and colorlist in the aRconnect record,
         ## along with the number of vertices and the vertex IDs.
         ## (This color-setting/index-setting may need some changes.)

         ## We put prop-id into color place if it is not blank. (Change?)
         if {"$cols17thru24" == "        "} {
            set aRconnect($CNTconnects) "$cols9thru16 $FILLcolorLIST255 2 $cols25thru40"
         } else {
            set aRconnect($CNTconnects) "$cols9thru16 $cols17thru24 2 $cols25thru40"
         }
      }
      ## END OF if {"$cols8" == "CBAR    "}

      ######################################################################
      ## Load 'CBEAM' data into array 'aRconnect':
      ## aRconnect($i) = [list $elemID $color $numVertices $vertID1 $vertID2 ...]
      ## where $numVertices is 2 in this case --- and the vertID's
      ## come from the CROD record and should match GRID ID's that
      ## we we used for indexes into the aRpoints array.
      ######################################################################

      if {"$cols8" == "CBEAM   "} {

         incr CNTconnects
         # incr CNTbeams

         ## We put a pointID and colorlist in the aRconnect record,
         ## along with the number of vertices and the vertex IDs.
         ## (This color-setting/index-setting may need some changes.)

         ## We put prop-id into color place if it is not blank. (Change?)
         if {"$cols17thru24" == "        "} {
            set aRconnect($CNTconnects) "$cols9thru16 $FILLcolorLIST255 2 $cols25thru40"
         } else {
            set aRconnect($CNTconnects) "$cols9thru16 $cols17thru24 2 $cols25thru40"
         }
      }
      ## END OF if {"$cols8" == "CBEAM   "}

   }
   ## END OF LOOP  while {[eof $f] == 0} --- to gather data from the 'C' records.


   ######################################
   ## We hit end of file. Close the file.
   ######################################

   close $f

   ##########################################
   ## Get the mid-points of the x,y,z ranges.
   ##########################################

   set Xmid [expr {($maxX + $minX) / 2.0}]
   set Ymid [expr {($maxY + $minY) / 2.0}]
   set Zmid [expr {($maxZ + $minZ) / 2.0}]

   ########################################################
   ## Calculate a diameter for our 'cloud' of data points.
   ########################################################

   set diam [expr {$maxX - $minX}]
   set maxDeltaY [expr {$maxY - $minY}]
   if {$maxDeltaY > $diam} {set diam $maxDeltaY}
   set maxDeltaZ [expr {$maxZ - $minZ}]
   if {$maxDeltaZ > $diam} {set diam $maxDeltaZ}

   ################################################
   ## Load the list of IDs for the aRconnect array,
   ## to be sorted by z-depth.
   ################################################

   set LISTpolyIDs {}

   for {set k 1} {$k <= $CNTconnects} {incr k} {
      lappend LISTpolyIDs "$k"
   }
   ## END OF k loop


   ## FOR TESTING:
   #   puts "proc 'load_3DfileData_NAS' >
   #   puts "     CNTpoints: $CNTpoints    CNTconnects: $CNTconnects"

   update_counts_label "\
NAS file - Points: $CNTpoints  Triangles: $CNTtrias \
    Quads: $CNTquads  Lines: $CNTlines"

}
## END OF PROC 'load_3DfileData_NASshort'



##+#####################################################################
## proc  translate_points_array
##
## PURPOSE: From the array 'aRpoints' whose elements are of the form
##            aRpoints($i)  = [list $pointID $RGBcolor/$propID $x $y $z]
##          we calculate a new array in Cartesian coordinates:
##            aRtranspoints($i) = [list $transx $transy $transz]
##          where the new coordinates --- $transx $transy $transz ---
##          are based at the midpoint of the min-max ranges of xyz.
##
##          The reason we are doing this:
##
##           Note that the  3-space origin --- 0,0,0 --- may not be contained
##           in the 'point cloud' of xyz values of array 'aRpoints'.
##           When we apply a rotation matrix to the points, the rotation is
##           about the origin of the points.
##
##           If the origin is not contained in the 'data cloud',
##           when we rotate the model, the model may 'swing' in a wide swath
##           around the origin, going off screen.
##
##           We need to translate the origin to the 'center point' of the
##           'point cloud' so that we perform our rotations around the
##           center of the 'point cloud'.
##
## METHOD:  We sweep through the aRpoints array with integer indices
##          going from 1 through $CNTpoints.
##
##          NOTE1:
##          We could use the array 'aRpoints' to hold the translated
##          coordinates, but we will use some memory for the separate
##          'aRtranspoints' array just in case we find it useful to
##          have the original point values available
##          --- for example for coloring the polygons according to
##          the original data values.
##
##          NOTE2:
##          We do not copy the $pointID $RGBcolor/$propID values from
##          the aRpoints array to the aRtranspoints array. This avoids
##          using about 20% more memory for the aRtranspoints array.
##          If we need the '$pointID $RGBcolor/$propID' info for any
##          particular point ID, we will refer back to the 'aRpoints' array
##          for that data. 
##
## CALLED BY: proc 'load-translate-rotate-sort-draw', which is called
##            by the <Return> or button3-release binding on the model-file
##            entry field.
##+#####################################################################

proc translate_points_array {} {

   global aRpoints CNTpoints aRtranspoints Xmid Ymid Zmid

   ##################################################################
   ## Start looping thru the aRpoints array over an integers i,
   ## from 1 to $CNTpoints.
   ## For each i:
   ##    - get coords x,y,z from array 'aRpoints' , translate them
   ##    - to coordinates relative to Xmid,Ymid,Zmid.
   ##      Put the translated values into array aRtranspoints such that
   ##         aRtranspoints($i,$j) =  [list $transx $transy $transz]
   ###################################################################

   for {set k 1} {$k <= $CNTpoints} {incr k} {

      ## Get the xyz coords from aRpoints($k).
      ## Recall that the array 'aRpoints' is of the form
      ##   aRpoints($i)  = [list $pointID $RGBcolor/$propID $x $y $z]

      foreach {dummy dummy x y z}  $aRpoints($k) {break}

      ## Translate the xyz coords to the mid-point of the data.
      
      set transx [expr {double($x) - $Xmid}]
      set transy [expr {double($y) - $Ymid}]
      set transz [expr {double($z) - $Zmid}]

      ## HERE IS where we load array aRtranspoints.

      set aRtranspoints($k) [list $transx $transy $transz]

   }
   ## END OF k loop

   ## FOR TESTING:
   #   puts "proc 'translate_points_array' >  After k loop - some translated points :"
   #   puts "aRtranspoints(1): $aRtranspoints(1)"
   #   puts "aRtranspoints($CNTpoints): $aRtranspoints($CNTpoints)"

}
## END OF PROC translate_points_array


##+###################################################################################
## proc 'rotate_points'
##
## PURPOSE: For a given longitude and latitude (view direction),
##          this proc loops thru all the POINTS, in array 'aRtranspoints'
##          of the form
##             aRtranspoints($i) = [list $transx $transy $transz]
##          rotating each point according to the current 2 longitude
##          and latitude angles --- angLON,angLAT --- and calculating
##          the new Cartesian (xyz) coordinates.
##
##          The xyz data for the 'new points' are put into a new array,
##          'aRnew_points' such that the array is of the form
##             aRnew_points($i) = [list $newx $newy $newz]
##
##          Thus if we make a 'simple' change like change fill or outline color,
##          or change to wireframe mode from fill mode (changes that do
##          not change the model points and connectivity data nor the
##          view direction), we do not have to go through a lot of mathematical
##          calculations again. We can work off of this 'aRnew_points' array.
##
##          We are using memory for 3 points arrays --- 'aRpoints',
##          'aRtranspoints, and 'aRnew_points' --- to give us some processing
##          efficiency when we make changes that should not require sweeping
##          through the points or polygons and performing math calculations
##          that we have already done once.
##
## CALLED BY:  proc 'load-translate-rotate-sort-draw'  which is called by
##             <Return> or button3-release binding on the model-file
##             entry field 
##             OR
##             proc 'rotate-sort-draw' which is called by button1-release
##             bindings on the longitude & latitude angle scale widgets.
##+#####################################################################

## We set some 'universal' constants that will be used in the
## 'rotate_points' proc.

set pi [expr {4.0 * atan(1.0)}]
set twopi [expr { 2.0 * $pi }]
# set pihalf  [expr { $pi / 2.0 }]
# set minuspihalf [expr {-$pihalf}]

set radsPERdeg [expr { $twopi / 360 }]

proc rotate_points {} {

   global radsPERdeg CNTpoints aRtranspoints aRnew_points maxnewZ minnewZ


   ## We could calculate new min,max values for use in the draw-pixels routine.
   #       minnewX maxnewX minnewY maxnewY minnewZ maxnewZ

   ## Get the 2 rotation angles (in degrees).

   set angLON [.fRscales.scaleLON get]
   set angLAT [.fRscales.scaleLAT get]

   ## Convert the 2 rotation angles from degrees to radians.

   set angLON [expr {$radsPERdeg * $angLON}]
   set angLAT [expr {$radsPERdeg * $angLAT}]

   ## THIS IS THE STEP WE HAVE BEEN LEADING UP TO ---
   ## THE ACTUAL ROTATION OF EACH 3D POINT --- done via a
   ## Ry * Rz rotation matrix --- a product of a longitudinal rotation about
   ## the z-axis, followed by a latitudinal rotation about the x-axis.
   ##
   ##                    y|           . point(x,y,z)
   ##                     |       .     
   ##                     |   .      angLAT
   ##                     |___________________x
   ##                    /   .
   ##                  /          .
   ##                /    angLON       . point(x,0,z)
   ##            z /
   ##
   ## Referring to a computer graphics textbook such as 'Draft Lecture Notes:
   ## Computer Graphics for Engineers', by Victor Saouma, U. of Colorado,
   ## 2000, page 24:
   ## If we rotate a model/point around the y and then x axes,
   ## by angles 'thetay' and 'thetax' resp., and if we let
   ## cy=cos(thetay),sy=sin(thetay),cx=cos(thetax),sx=sin(thetax),
   ## the product of the 2 rotation matrices is
   ## 
   ##          |  1    0    0 | |  cy   0  sy |
   ##  Rx*Ry = |  0   cx  -sx | |   0   1   0 |
   ##          |  0   sx   cx | | -sy   0  cy |
   ##
   ##          |    cy      0     sy  |
   ##        = |  sx*sy    cx  -sx*cy |
   ##          | -cx*sy    sx   cx*cy |
   ##
   ## when the point vectors are represented as column vectors
   ## (not row vectors).
   ##
   ## To reduce the number of math operations in rotating each point,
   ## we 'pre-compute' the 4 products and denote them as
   ## sxsy, sxcy, cxsy, cxcy.
   ##
   ## Then
   ##  newx =   cy  * x              +   sy * z
   ##  newy =  sxsy * x   +  cx * y  - sxcy * z
   ##  newz = -cxsy * x   +  sx * y  + cxcy * z

   ## Note that 'thetay' and 'thetax' (the amounts to rotate the points),
   ## are related to the view angles in our case.
   ##
   ## angLON (an angle in the zx plane) is the longitudinal view angle, and
   ## angLAT (ordinarily an angle from the zx plane toward the y axis) is
   ## what we are calling the latitudinal view angle.
   ##
   ## We let angLON be 'thetay' and angLAT be 'thetax', up to the sign.
   ##
   ## For 'thetay' and 'thetax', we adjust the view angles, as needed, by
   ## a negative sign to get an 'appropriate' rotation of the point cloud.

   # set cy [expr {cos(-$angLON)}]
   # set sy [expr {sin(-$angLON)}]

   set cy [expr {cos($angLON)}]
   set sy [expr {sin($angLON)}]

   set cx [expr {cos($angLAT)}]
   set sx [expr {sin($angLAT)}]

   set sxsy [expr {$sx * $sy}]
   set sxcy [expr {$sx * $cy}]
   set cxsy [expr {$cx * $sy}]
   set cxcy [expr {$cx * $cy}]


   #########################################################################
   ## Start looping thru the points of the 'aRtranspoints' array
   ## --- using integer i going from 1 through $CNTpoints --- where the
   ## contents of an 'aRtranspoints' element looks like:
   ##   aRtranspoints($i)  = [list $transx $transy $transz]
   ## For each i:
   ##   - get coords transx,transy,transz from array 'aRtranspoints'
   ##   - apply the rotation matrix Rx*Ry to the point
   ##     to calculate the new Cartesian coords newX,newY,newZ
   ##   - store the values in the new-points array
   ##     aRnew_points($i) =  [list $newX $newY $newZ]
   #########################################################################

   for {set i 1} {$i <= $CNTpoints} {incr i} {

      ## Get the cartesian coords from aRtranspoints($i).

      foreach {transx transy transz} $aRtranspoints($i) {break}

      ## Calc the new Cartesian coords using
      ##  newx =   cy  * x              +   sy * z
      ##  newy =  sxsy * x   +  cx * y  - sxcy * z
      ##  newz = -cxsy * x   +  sx * y  + cxcy * z

      set newX [expr { (  $cy  * $transx)  +  ($sy * $transz) }]
      set newY [expr { ( $sxsy * $transx)  +  ($cx * $transy)  -  ($sxcy * $transz) }]
      set newZ [expr { (-$cxsy * $transx)  +  ($sx * $transy)  +  ($cxcy * $transz) }]

      ## FOR TESTING:
      #   puts "proc 'rotate_points' >  For POINT $i, the new translated-rotated point is:"
      #   puts "  newX: $newX    newY: $newY   newZ: $newZ"

      ## HERE IS where we load array 'aRnew_points'.

      set aRnew_points($i) [list $newX $newY $newZ]

      ## While we are rotating the points, we set maxnewZ and minnewZ
      ## for (possible) use in z-depth-shading in the draw proc.

      if {$i == 1} {
            set minnewZ $newZ
            set maxnewZ $newZ
      } else {
            if {$newZ < $minnewZ} {set minnewZ $newZ}
            if {$newZ > $maxnewZ} {set maxnewZ $newZ}
      }
      ## END OF if {$i == 1}

   }
   ## END OF i loop

   ## FOR TESTING:
   #    puts "proc 'rotate_points' > After the loop over the translated points,"
   #    puts "                       some rotated points are:"
   #    puts "  aRnew_points(1): $aRnew_points(1)"
   #    puts "  aRnew_points($CNTpoints): $aRnew_points($CNTpoints)"

}
## END OF PROC 'rotate_points'


##+#####################################################################
## proc  sort_polyIDs_list
##
## PURPOSE: For poly ID's (single integers) in a Tcl list 'LISTpolyIDs',
##          we generate a new list called 'sortedLISTpolyIDs' ---
##          by sorting the polyIDs according to the current 'z-depth' of
##          each polygon.
##
##          (The list 'LISTpolyIDs' may contain IDs of lines as well as
##           polygons --- i.e. we use a generalized meaning of 'poly' here
##           --- meaning N-gons where N may start with 2, not 3.)
##
##           'z-depth' in our case, means that we will be plotting
##           x,y coordinates --- not z --- in the draw routine --- and we
##           imagine looking along the z-axis to a 2D projection plane
##           (which is parallel to or identical to the xy plane, in
##           the monitor screen). We use the z-coordinates of the points
##           not for plotting, but to determine the depth (distance from
##           the viewer's eye) of the 'polys'.
##
##          This proc uses the proc 'compare_2polyIDs_by_zdepth'.
##          See comments in that proc (below) for current details on
##          the sort method.
##
## CALLED BY: proc 'load-translate-rotate-sort-draw'
##            or proc 'rotate-sort-draw'.
##            Proc 'load-translate-rotate-sort-draw' is called
##            by the <Return> or button3-release binding on the model-file
##            entry field.
##            Proc  'rotate-sort-draw' is called by a button1-release on the
##            longitude or latitude scale widgets.
##+#####################################################################

proc sort_polyIDs_list {} {

   global LISTpolyIDs sortedLISTpolyIDs poly_sort

   if {"$poly_sort" == "avePolyDepth"} {
      set sortedLISTpolyIDs \
         [lsort  -command compare_2polyIDs_by_AVEzdepth  $LISTpolyIDs]
   } elseif {"$poly_sort" == "maxPolyDepth"} {
      set sortedLISTpolyIDs \
         [lsort  -command compare_2polyIDs_by_MAXzdepth  $LISTpolyIDs]
   } elseif {"$poly_sort" == "minPolyDepth"} {
      set sortedLISTpolyIDs \
         [lsort  -command compare_2polyIDs_by_biggerMINzdepth  $LISTpolyIDs]
   } else {
      ## Just in case one of the previous if-statements does not trigger,
      ## we use this sort.
      set sortedLISTpolyIDs \
         [lsort  -command compare_2polyIDs_by_AVEzdepth  $LISTpolyIDs]
   }

}
## END OF PROC 'sort_polyIDs_list'


##+################################################################
## proc  compare_2polyIDs_by_biggerMINzdepth
##
## PURPOSE: To be used by a Tcl 'lsort' command in order to sort
##          a list of polygon IDs according to the 'bigger-MIN' z-depth
##          of the vertices of the entire list of polygons.
##
##          (Alternatively, we could use an average z-depth or max z-depth
##           or the z-depth at just one of the vertices. If there is
##           some processing advantage, without significant adverse
##           viewing consequence, we could try an alternative method.)
##
## INPUT:   Input is a pair of polygon IDs (integers) as arguments to
##          this proc. That is, input is a pair of indexes into the
##          array 'aRconnect'.
##
## OUTPUT:  Output is passed by the 'return' statement.
##
##          Output is 1 (one) if the first polygon ID is
##          considered greater than the 2nd polygon ID, and
##          -1 (minus one) otherwise.
##
##          (If the z-depth of the 2 polygons is the same,
##           it does not matter much which one we choose to be
##           the greater.)
##
## OTHER CONSIDERATIONS:
##          We will need data from the two arrays 'aRnew_points' and 'aRconnect'.
##
##          Recall that the elements of each array are of the following form:
##            aRnew_points($i)  = [list $x $y $z]
##            aRconnect($i) = [list $elemID $RGBcolor $numVertices $vertID1 $vertID2 ...]
##          
##          We use the pair of polygon IDs to get the integers
##          $numVertices $vertID1 $vertID2 ... from array 'aRconnect',
##          for 2 different integer IDs.
##
##          Then we will need the 'aRnew_points' array (the translated and rotated
##          points) to get the z-values for the multiple vertex IDs
##          for each of the 2 specified 'polys'.
##
##          In this implementation, we compare the AVERAGE z-value of
##          one set of N1-gon points to the AERAGE z-value of the other
##          set of N2-gon points.
##
## REFERENCE: pages 70-71 on the 'lsort' command in the book
##            'Practical Programming in Tcl and Tk', 4th edition,
##            by Welch, Jones, and Hobbs.
##
## CALLED BY: a Tcl command of the form
##
##   set sortedLISTpolyIDs \
##       [lsort  -command compare_2polyIDs_by_zdepth  $LISTpolyIDs]
##
##            in proc 'sort_polyIDs_list'.
##+################################################################

proc compare_2polyIDs_by_biggerMINzdepth {id1 id2} {

   global aRnew_points aRconnect

   ## Get the number of vertices of poly id1.
   ## Recall our format for aRconnect:
   ##   aRconnect($i) = [list $elemID $RGBcolorList/$mtlID $numVertices $vertID1 $vertID2 ...]

   # foreach {dummy dummy N1}  $aRconnect($id1) {break}
   set N1 [lindex $aRconnect($id1) 2]

   ## FOR TESTING:
   #   if {$id1 == 1 && $id2 == 2} {
   #     puts "proc 'compare_2polyIDs_by_zdepth' > Comparing connected elements - id1: $id1  id2: $id2"
   #     puts "  aRconnect($id1): $aRconnect($id1)"
   #     puts "  aRconnect($id2): $aRconnect($id2)"
   #   } 

   ## Successively get the z-depths of the N1 vertices of the poly id1
   ## and determine the MIN z-depth, MIN1, as we loop thru the vertices.
   ## Recall our format for aRnew_points:
   ##    aRnew_points($i)  = [list $x $y $z]

   for {set k 0} {$k < $N1} {incr k} {
      set idx [expr {$k + 3}]
      set vertID [lindex $aRconnect($id1) $idx]
      foreach {dummy dummy thisZ}  $aRnew_points($vertID) {break}
      ## Assure that the MIN1-value is considered floating point, not chars.
      if {$k == 0} {
         set MIN1 [expr {double($thisZ)}]
      } else {
         set thisZ [expr {double($thisZ)}]
         if {$thisZ < $MIN1} {set MIN1 $thisZ}
      }
   }
   ## END OF k loop for poly id1

   ###########################################################
   ## NOW get the MIN z-depth, MIN2, of the polygon id2.
   ###########################################################

   ## Get the number of vertices of poly id2.
   ## Recall our format for aRconnect:
   ##   aRconnect($i) = [list $elemID $RGBcolor/$mtlID $numVertices $vertID1 $vertID2 ...]

   # foreach {dummy dummy N2}  $aRconnect($id2) {break}
   set N2 [lindex $aRconnect($id2) 2]

   ## Successively get the z-depths of the N2 vertices of the poly id2
   ## and determine the MIN z-depth, MIN2, as we loop thru the vertices.
   ## Recall our format for aRnew_points:
   ##    aRnew_points($i)  = [list $x $y $z]

   for {set k 0} {$k < $N2} {incr k} {
      set idx [expr {$k + 3}]
      set vertID [lindex $aRconnect($id2) $idx]
      foreach {dummy dummy thisZ}  $aRnew_points($vertID) {break}
      ## Assure that the MIN2-value is considered floating point, not chars.
      if {$k == 0} {
         set MIN2 [expr {double($thisZ)}]
      } else {
         set thisZ [expr {double($thisZ)}]
         if {$thisZ < $MIN2} {set MIN2 $thisZ}
      }
   }
   ## END OF k loop for poly id2

   ## Compare the 2 MIN z-depths and return 1 or -1.

   if {$MIN1 >= $MIN2} {
      return 1
   } else {
      return -1
   }

}
## END OF PROC 'compare_2polyIDs_by_biggerMINzdepth'



##+################################################################
## proc  compare_2polyIDs_by_AVEzdepth
##
## PURPOSE: To be used by a Tcl 'lsort' command in order to sort
##          a list of polygon IDs according to the 'AVERAGE' z-depth
##          of the vertices of the entire list of polygons.
##
##          (Alternatively, we could use a max z-depth or
##           the z-depth at just one of the vertices. If there is
##           some processing advantage, without significant adverse
##           viewing consequence, we could try an alternative method.)
##
## INPUT:   Input is a pair of polygon IDs (integers) as arguments to
##          this proc. That is, input is a pair of indexes into the
##          array 'aRconnect'.
##
## OUTPUT:  Output is passed by the 'return' statement.
##
##          Output is 1 (one) if the first polygon ID is
##          considered greater than the 2nd polygon ID, and
##          -1 (minus one) otherwise.
##
##          (If the z-depth of the 2 polygons is the same,
##           it does not matter much which one we choose to be
##           the greater.)
##
## OTHER CONSIDERATIONS:
##          We will need data from the two arrays 'aRnew_points' and 'aRconnect'.
##
##          Recall that the elements of each array are of the following form:
##            aRnew_points($i)  = [list $x $y $z]
##            aRconnect($i) = [list $elemID $RGBcolor $numVertices $vertID1 $vertID2 ...]
##          
##          We use the pair of polygon IDs to get the integers
##          $numVertices $vertID1 $vertID2 ... from array 'aRconnect',
##          for 2 different integer IDs.
##
##          Then we will need the 'aRnew_points' array (the translated and rotated
##          points) to get the z-values for the multiple vertex IDs
##          for each of the 2 specified 'polys'.
##
##          In this implementation, we compare the AVERAGE z-value of
##          one set of N1-gon points to the AERAGE z-value of the other
##          set of N2-gon points.
##
## REFERENCE: pages 70-71 on the 'lsort' command in the book
##            'Practical Programming in Tcl and Tk', 4th edition,
##            by Welch, Jones, and Hobbs.
##
## CALLED BY: a Tcl command of the form
##
##   set sortedLISTpolyIDs \
##       [lsort  -command compare_2polyIDs_by_zdepth  $LISTpolyIDs]
##
##            in proc 'sort_polyIDs_list'.
##+################################################################

proc compare_2polyIDs_by_AVEzdepth {id1 id2} {

   global aRnew_points aRconnect

   ## Get the number of vertices of poly id1.
   ## Recall our format for aRconnect:
   ##   aRconnect($i) = [list $elemID $RGBcolorList/$mtlID $numVertices $vertID1 $vertID2 ...]

   # foreach {dummy dummy N1}  $aRconnect($id1) {break}
   set N1 [lindex $aRconnect($id1) 2]

   ## FOR TESTING:
   #   if {$id1 == 1 && $id2 == 2} {
   #     puts "proc 'compare_2polyIDs_by_zdepth' > Comparing connected elements - id1: $id1  id2: $id2"
   #     puts "  aRconnect($id1): $aRconnect($id1)"
   #     puts "  aRconnect($id2): $aRconnect($id2)"
   #   } 

   ## Successively get the z-depths of the N1 vertices of the poly id1
   ## and add them up. Then divide the sum by N1 to determine the
   ## AVERAGE z-depth, AVE1, after we loop thru the vertices.
   ## Recall our format for aRnew_points:
   ##    aRnew_points($i)  = [list $x $y $z]

   set SUM1 [expr {double(0.0)}]
   for {set k 0} {$k < $N1} {incr k} {
      set idx [expr {$k + 3}]
      set vertID [lindex $aRconnect($id1) $idx]
      foreach {dummy dummy thisZ}  $aRnew_points($vertID) {break}
      ## Accumulate SUM1 is considered floating point, not chars.
      set SUM1 [expr {$SUM1 + double($thisZ)}]
   }
   ## END OF k loop for poly id1
   set AVE1 [expr {$SUM1 / double($N1)}]

   ###########################################################
   ## NOW get the AVERAGE z-depth, AVE2, of the polygon id2.
   ###########################################################

   ## Get the number of vertices of poly id2.
   ## Recall our format for aRconnect:
   ##   aRconnect($i) = [list $elemID $RGBcolor/$mtlID $numVertices $vertID1 $vertID2 ...]

   # foreach {dummy dummy N2}  $aRconnect($id2) {break}
   set N2 [lindex $aRconnect($id2) 2]

   ## Successively get the z-depths of the N2 vertices of the poly id2
   ## and add them up. Then divide the sum by N2 to determine the
   ## AVERAGE z-depth, AVE2, after we loop thru the vertices.
   ## Recall our format for aRnew_points:
   ##    aRnew_points($i)  = [list $x $y $z]

   set SUM2 [expr {double(0.0)}]
   for {set k 0} {$k < $N2} {incr k} {
      set idx [expr {$k + 3}]
      set vertID [lindex $aRconnect($id2) $idx]
      foreach {dummy dummy thisZ}  $aRnew_points($vertID) {break}
      ## Accumulate SUM2 is considered floating point, not chars.
      set SUM2 [expr {$SUM2 + double($thisZ)}]
   }
   ## END OF k loop for poly id2
   set AVE2 [expr {$SUM2 / double($N2)}]

   ## Compare the 2 AVERAGE z-depths and return 1 or -1.

   if {$AVE1 >= $AVE2} {
      return 1
   } else {
      return -1
   }

}
## END OF PROC 'compare_2polyIDs_by_AVEzdepth'


##+################################################################
## proc  compare_2polyIDs_by_MAXzdepth
##
## PURPOSE: To be used by a Tcl 'lsort' command in order to sort
##          a list of polygon IDs according to the 'MAX' z-depth
##          of the vertices of the entire list of polygons.
##
##          (Alternatively, we could use an average z-depth or
##           the z-depth at just one of the vertices. If there is
##           some processing advantage, without significant adverse
##           viewing consequence, we could try an alternative method.)
##
## INPUT:   Input is a pair of polygon IDs (integers) as arguments to
##          this proc. That is, input is a pair of indexes into the
##          array 'aRconnect'.
##
## OUTPUT:  Output is passed by the 'return' statement.
##
##          Output is 1 (one) if the first polygon ID is
##          considered greater than the 2nd polygon ID, and
##          -1 (minus one) otherwise.
##
##          (If the z-depth of the 2 polygons is the same,
##           it does not matter much which one we choose to be
##           the greater.)
##
## OTHER CONSIDERATIONS:
##          We will need data from the two arrays 'aRnew_points' and 'aRconnect'.
##
##          Recall that the elements of each array are of the following form:
##            aRnew_points($i)  = [list $x $y $z]
##            aRconnect($i) = [list $elemID $RGBcolor $numVertices $vertID1 $vertID2 ...]
##          
##          We use the pair of polygon IDs to get the integers
##          $numVertices $vertID1 $vertID2 ... from array 'aRconnect',
##          for 2 different integer IDs.
##
##          Then we will need the 'aRnew_points' array (the translated and rotated
##          points) to get the z-values for the multiple vertex IDs
##          for each of the 2 specified 'polys'.
##
##          In this implementation, we compare the MAX z-value of
##          one set of N1-gon points to the MAX z-value of the other
##          set of N2-gon points.
##
## REFERENCE: pages 70-71 on the 'lsort' command in the book
##            'Practical Programming in Tcl and Tk', 4th edition,
##            by Welch, Jones, and Hobbs.
##
## CALLED BY: a Tcl command of the form
##
##   set sortedLISTpolyIDs \
##       [lsort  -command compare_2polyIDs_by_zdepth  $LISTpolyIDs]
##
##            in proc 'sort_polyIDs_list'.
##+################################################################

proc compare_2polyIDs_by_MAXzdepth {id1 id2} {

   global aRnew_points aRconnect

   ## Get the number of vertices of poly id1.
   ## Recall our format for aRconnect:
   ##   aRconnect($i) = [list $elemID $RGBcolorList/$mtlID $numVertices $vertID1 $vertID2 ...]

   # foreach {dummy dummy N1}  $aRconnect($id1) {break}
   set N1 [lindex $aRconnect($id1) 2]

   ## FOR TESTING:
   #   if {$id1 == 1 && $id2 == 2} {
   #     puts "proc 'compare_2polyIDs_by_zdepth' > Comparing connected elements - id1: $id1  id2: $id2"
   #     puts "  aRconnect($id1): $aRconnect($id1)"
   #     puts "  aRconnect($id2): $aRconnect($id2)"
   #   } 

   ## Successively get the z-depths of the N1 vertices of the poly id1
   ## and determine the MAX z-depth, MAX1, as we loop thru the vertices.
   ## Recall our format for aRnew_points:
   ##    aRnew_points($i)  = [list $x $y $z]

   for {set k 0} {$k < $N1} {incr k} {
      set idx [expr {$k + 3}]
      set vertID [lindex $aRconnect($id1) $idx]
      foreach {dummy dummy thisZ}  $aRnew_points($vertID) {break}
      ## Assure that the MAX1-value is considered floating point, not chars.
      if {$k == 0} {
         set MAX1 [expr {double($thisZ)}]
      } else {
         set thisZ [expr {double($thisZ)}]
         if {$thisZ > $MAX1} {set MAX1 $thisZ}
      }
   }
   ## END OF k loop for poly id1


   ########################################################
   ## NOW get the max z-depth, MAX2, of the polygon at id2.
   ########################################################

   ## Get the number of vertices of poly id2.
   ## Recall our format for aRconnect:
   ##   aRconnect($i) = [list $elemID $RGBcolor/$mtlID $numVertices $vertID1 $vertID2 ...]

   # foreach {dummy dummy N2}  $aRconnect($id2) {break}
   set N2 [lindex $aRconnect($id2) 2]

   ## Successively get the z-depths of the N2 vertices of the poly id2
   ## and determine the MAX z-depth, MAX2, as we loop thru the vertices.
   ## Recall our format for aRnew_points:
   ##    aRnew_points($i)  = [list $pointID $RGBcolor/$propID $x $y $z]

   for {set k 0} {$k < $N2} {incr k} {
      set idx [expr {$k + 3}]
      set vertID [lindex $aRconnect($id2) $idx]
      foreach {dummy dummy thisZ}  $aRnew_points($vertID) {break}
      ## Assure that the MAX2-value is considered floating point, not chars.
      if {$k == 0} {
         set MAX2 [expr {double($thisZ)}]
      } else {
         set thisZ [expr {double($thisZ)}]
         if {$thisZ > $MAX2} {set MAX2 $thisZ}
      }
   }
   ## END OF k loop for poly id2


   ## Compare the 2 z-depths and return 1 or -1.

   if {$MAX1 >= $MAX2} {
      return 1
   } else {
      return -1
   }

}
## END OF PROC 'compare_2polyIDs_by_MAXzdepth'


##+###########################################################
## proc draw_2D_pixel_polys -
##
## PURPOSE:
##    We plot the projection onto the XY plane of the 'polys'
##    in the array 'aRconnect', an array of the form
##       aRconnect($i) = [list $elemID $RGBcolor $numVertices $vertID1 $vertID2 ...]
##    and indexed from 1 through $CNTconnects.
##
##    We 'sweep' through the list 'sort_polyIDs_list', indexed from 0 up to $CNTconnects.
##    That sorted list determines the order in which we will plot the 'polys'
##    (polygons and lines) onto the Tk canvas with 'create polygon' and
##    'create line' commands.
##
##    For each poly (an N-gon where N can be 2,3,...), we use the
##    $vertID1 $vertID2 ... integers to get the x,y coordinates
##    from the data array 'aRnew_points', which is of the form
##            aRnew_points($i)  = [list $x $y $z]
##
##    We convert the 2D points (x,y) from 'world coordinate' units to
##    pixel units within the current canvas dimensions --- based
##    on the 'diameter of the point cloud' as determined in the
##    proc 'load_points_array', and held in the 'diam' variable.
##
##    We draw each N-gon with a 'create polygon' command (when N>2) or
##    with a 'create line' command  (when N=2).
##
##    For 'create polygon' commands, we use the settings of
##    the fill-only/fill-and-outline/wire-hide/wire-nohide radiobutton
##    to determine how to use the '-fill' and '-outline' options.
##    And if 'fill-only' is the setting, we use the settings of the
##    color-source and color-shading radiobuttons to determine how to
##    proceed to calculate the color for each polygon.
##
##    For 'create line' commands, unless otherwise indicated,
##    we use the current polygon-fill-color as the fill color
##    for the 'create line' command.
##
##    In addition, we use the curZOOM variable from the 'scaleZOOM'
##    widget of the GUI to allow for zooming (by adjusting the
##    'diameter of the point cloud').
##
## FOR REFERENCE:
##
## The possible values of the 'poly_fillout' var of the FILLOUT radiobutton:
##     'FILLonly' OR  'FILLoutline'  OR  'WIREhide'  OR  'WIREnohide'
##
## The possible values of the 'poly_fillsrc' var of the FILLTYPE radiobutton:
##     'fromButton'  OR  'fromFile'  OR  'fromColorTable' OR  'random'
##
##  The possible values of the 'poly_shade' var of the SHADE radiobutton:
##     'none'    OR  'origZheight'  OR  'origYheight'  OR 'origXheight'  OR
##     'zDepth'  OR  'byLighting'.
## 
## INPUTS:
##    All the global vars declared below.
##
## CALLED BY:  proc 'load-translate-rotate-sort-draw'
##             or proc 'rotate-sort-draw' or proc 'sort-draw'
##             or proc 'wrap_draw_2D_pixel_polys'.
##         See the BINDINGS section above for what triggers
##         the use of each of these procs.
##+#########################################################

proc draw_2D_pixel_polys {} {

   global aRnew_points CNTpoints aRconnect CNTconnects sortedLISTpolyIDs \
          diam curZOOM \
          poly_fillout poly_fillsrc poly_shade \
          COLOR1hex COLOR2hex COLORbkGNDhex \
          COLOR1r COLOR1g COLOR1b \
          minZ maxZ minY maxY minX maxX maxnewZ minnewZ \
          TOLfactor aRpoints \
          Ncolors  aRcolor

   ## BEFORE the loop to plot POLYGONS,
   ## we get the PIXELS-PER-WORLD-UNITS CONVERSION FACTOR,
   ## by dividing the minimum canvas dimension by
   ## the model diameter --- throwing in the factor curZOOM,
   ## where curZOOM is allowed to go from about 0.1 to 10.

   ## Get the current canvas size.

   set curCanWidthPx  [winfo width  .fRcanvas.canvas]
   set curCanHeightPx [winfo height .fRcanvas.canvas]

   set minCanDimPx $curCanWidthPx
   if {$curCanHeightPx < $minCanDimPx} {set minCanDimPx $curCanHeightPx}

   ## To preserve distances nicely, we want to use the same
   ## pixels-per-world-units factor in both x and y directions
   ## (assuming the pixels are square).
   ##
   ## We could put curZOOM in either the denominator or the numerator.
   ## In either case,
   ## we may intialize curZOOM so that the rotated surface comfortably
   ## fits into the canvas --- that is, so that the 'plot' is 'set in' from
   ## the boundary of the canvas.

   # set curZOOM [expr {double($curZOOM)}]

   # set PxPerUnit [expr { double(  $minCanDimPx /  ($curZOOM * $diam) ) }]
   set PxPerUnit [expr { double( ( $curZOOM * double($minCanDimPx) ) /  $diam ) }]

   ## FOR TESTING:
   #   puts "proc 'draw_2D_pixel_polys' > Calculating 'PxPerUnit':"
   #   puts "  minCanDimPx: $minCanDimPx   curZOOM: $curZOOM     diam: $diam "
   #   puts "  PxPerUnit(= curZOOM * minCanDimPx/diam): $PxPerUnit"


   ## Get the half width and height of the canvas rectangle ---
   ## which serve as the pixel-coordinates of the (near?) center of the plot.

   set imgWidthPx  $curCanWidthPx
   set imgHeightPx $curCanHeightPx
   if {$imgWidthPx  % 2 == 1} { incr imgWidthPx -1 }
   if {$imgHeightPx % 2 == 1} { incr imgHeightPx -1 }

   set xmidPx [expr {$imgWidthPx  / 2}]
   set ymidPx [expr {$imgHeightPx / 2}]


   #############################################################################
   ## BEFORE WE DROP INTO THE DRAWING LOOP over polyIDs,
   ##
   ## SET-SOME-COLOR-and-SHADING-PARAMETERS.
   ##
   ## Set some parms we will need if a shading type is requested.
   ## We set the parms here rather than repeatedly within the loop.
   ############################################################################
   ## RECALL THAT:
   ## The possible values of the 'poly_fillout' var of the OUTLINE radiobutton:
   ##     'FILLonly'  or  'FILLoutline'  OR  'WIREhide'  OR  'WIREnohide'
   ##
   ## The possible values of the 'poly_fillsrc' var of the FILL radiobutton:
   ##    'fromButton'  OR  'fromFile'  OR  'fromColorTable' OR  'random'
   ## (These are used when the 'poly_fillout' var is 'FILLonly' or 'FILLoutline'.)
   ##
   ## The possible values of the 'poly_shade' var of the SHADE radiobutton:
   ##   'none'   OR  'origZheight'  OR  'origYheight'  OR 'origXheight'  OR
   ##  'zDepth'  OR  'byLighting'
   ## (These are used when the 'poly_fillout' var is 'FILLonly' or 'FILLoutline'.
   ##  For 'FILLoutline', shading 'none' is allowed and is 'suggested'
   ##  to attain a faster drawing speed.
   ##  For 'FILLonly', shading 'none' gives a blob of solid color, so one of
   ##  the other, more-processing-intensive shading options is suggested.)
   ###########################################################################

   ## Make sure that shading is not requested when one of the
   ## 'wire' display modes is requested.

   if {"$poly_fillout" == "WIREhide" || "$poly_fillout" == "WIREnohide"} {
      set poly_shade "none"
   }

   ## Set a near-zero-tolerance value for use in computing color-shading ratios.

   set zeroTOL [expr {$TOLfactor * $diam}]


   ##############################################################################
   ## SET A HEIGHT-UNITS-PER-COLOR PARM:
   ## If 'fillsrc' is from a color table, set a factor 'deltaZHGTperCOLOR'
   ## to be used, in the loop over polyIDs below, to convert a z-height
   ## of a poly to a color in a table.
   ##############################################################################

   if {"$poly_fillsrc" == "fromColorTable"} {
      ## Get the spread of height-units over the Ncolors
      ## of the color table.
      set deltaZHGTperCOLOR [expr {double(($maxZ - $minZ)/$Ncolors)}]
      if {$deltaZHGTperCOLOR < $zeroTOL} {
         set deltaZHGTperCOLOR 0.0
      }
   }


   ##########################################################################
   ## SECTION for SETTING a few SHADING FACTORS:
   ## If a height/depth shading option is requested (i.e. NOT noshade and NOT
   ## byLighting), set vars Low0to1, deltaShadeHeight, shadeRatio.
   ##########################################################################

   if {"$poly_shade" != "none" && "$poly_shade" != "byLighting"} {


      ## We are mapping a color-shading-measure (origZheight, origYheight,
      ## origXheight, or zDepth) into the interval 0.0 to 1.0 --- to be applied
      ## to RGB-base-25 color values. But we do not want to go all the
      ## way down to 0.0 (black). So we set a 'floor' in Low0to1.

      # set Low0to1 0.5
      # set Low0to1 0.35
      # set Low0to1 0.25
        set Low0to1 0.15
      # set Low0to1 0.0

      ## For the depth-type shading options (origZheight, origYheight,
      ## origXheight or zDepth), we set a value 'deltaShadeHeight', that
      ## will be used to compute a 'shadeRatio', just below,
      ## to be used in shading RGB-base-255 colors, from the 'floor'
      ## proportion set above to the specified R, G, and B values.

      if {"$poly_shade" == "origZheight"} {
         set deltaShadeHeight [expr {$maxZ - $minZ}]
      }

      if {"$poly_shade" == "origYheight"} {
         set deltaShadeHeight [expr {$maxY - $minY}]
      }

      if {"$poly_shade" == "origXheight"} {
         set deltaShadeHeight [expr {$maxX - $minX}]
      }

      if {"$poly_shade" == "zDepth"} {
         set deltaShadeHeight [expr {$maxnewZ - $minnewZ}]
      }

      ## Now that we have 'deltaShadeHeight', we use it to set the
      ## 'shadeRatio' factor that we will use in 'scaling' RGB values
      ## --- provided 'deltaShadeHeight' is not near-zero.

      if {$deltaShadeHeight < $zeroTOL} {
         set deltaShadeHeight 0.0
      } else {
         set shadeRatio [expr {(1.0 - $Low0to1)/$deltaShadeHeight}]
      } 

   }
   ## END OF {"$poly_shade" != "none" && "$poly_shade" != "byLighting"}
   ## that is, END OF setting shadeRatio, Low0to1, deltaShadeHeight.


   ## Clear the canvas before starting to lay down the (new) polygons.

   .fRcanvas.canvas delete all


   ##################################################################
   ## DRAW-SECTION: LOOP-OVER-THE-POLYS.
   ##
   ## Start looping thru the 'polys' (polygons and lines) of the
   ## 'aRconnect' array using the list of depth-sorted 'poly' IDs,
   ## 'sortedLISTpolyIDs'. Recall:
   ## aRconnect($i) = [list $elemID $RGBcolor/$mtlID $numVertices $vertID1 $vertID2 ...]
   ## 
   ## For each 'polyID' (which is an integer, from 1 to CNTconnects):
   ##
   ##     Convert the xy coords of array 'aRnew_points' to pixel coords,
   ##     for each of the N vertices of the N-gon of poly 'polyID'.
   ##
   ##     NOTE:
   ##       Rather than store another array, say 'aRpixel_xypoints',
   ##       of the xy coords of ALL the points converted to pixel-coords,
   ##       we convert most points to pixel coords several times, because
   ##       most points are used in several different polygons.
   ##       We may go back someday and build array 'aRpixel_xypoints',
   ##       say, in a 'convert_all_xy_coords_to_pixels' proc, say.
   ##       That proc could be called at the top of this 'draw' proc.
   ##       That proc needs to execute after the translate and rotate
   ##       procs have done their job.
   ##
   ##     As we process each polygon, we set RGB color vars for the
   ##     current polyID --- polyCOLORr,polyCOLORg,polyCOLORb --- according
   ##     to the source specified by the user (fromFile,fromButton,random,
   ##     fromColorTable).
   ##
   ##     If shading is requested, we use the height/depth of the 'polyID' poly
   ##     (or the lighting determined by a cross-product) to shade the 3
   ##     values previously set --- polyCOLORr,polyCOLORg,polyCOLORb.
   ##
   ##     For ColorTable coloring, we set polyCOLORr,polyCOLORg,polyCOLORb
   ##     according to the origZheight relative to the max-min-Z-range
   ##     --- and according to the number of colors in the table.
   ##
   ##     Plot each N-gon polygon according to the requested fill-outline
   ##     type --- and, if fill is requested, using the current RGB values
   ##     in polyCOLORr,polyCOLORg,polyCOLORb.
   ##
   ###################################################################

   for {set k 0} {$k < $CNTconnects} {incr k} {

      ## Get the polyID from the depth-sorted list of polyIDs.

      set polyID [lindex $sortedLISTpolyIDs $k]

      ##############################################
      ## Get the number of vertices of poly $polyID.
      ## Recall our format for aRconnect:
      ##   aRconnect($i) = [list $elemID $RGBcolor/$mtlID $numVertices $vertID1 $vertID2 ...]
      #########################################################################

      foreach {dummy dummy Nverts}  $aRconnect($polyID) {break}

      #############################################################################
      ## DRAW-SECTION: SET-XY-PIXEL-COORDS-LIST for poly $polyID:
      ##
      ## Successively get the xyz-coords of the Nverts vertices of the poly $polyID
      ## as we loop thru the vertices. Put the xy coords, converted to
      ## pixel coordinates, into list 'XYlistPx' --- to be used by the
      ## 'create polygon' or 'create line' command to draw the 'poly'. ALSO
      ## put the biggest Z in 'bigZ', in case we need it to depth-shade the 'poly'
      ## --- when poly_shade="zDepth".
      ##
      ## Recall our format for aRnew_points:
      ##    aRnew_points($i)  = [list $x $y $z]
      ## and the format of aRconnect above.
      #############################################################################

      set XYlistPx {}

      for {set n 0} {$n < $Nverts} {incr n} {

         set idx [expr {$n + 3}]
         set vertID [lindex $aRconnect($polyID) $idx]
         foreach {thisX thisY thisZ}  $aRnew_points($vertID) {break}

         #########################################################################
         ## NOTES on WORLD-to-PIXELS CONVERSION:
         ##
         ## Append the xy coords to XYlistPx, after converting x and y to
         ## pixel coordinates.
         ##
         ## Convert the xy coordinates to pixel values, using the 'PxPerUnit'
         ## factor determined above. Then add $xmidPx or $ymidPx to account for the
         ## fact that aRnew_points world-coordinates are relative to (x,y,z)=(0,0,0),
         ## and (0,0,0) should be positioned in the middle of the canvas.
         ##
         ## Note: We are plotting the xy world coords about the center of the canvas
         ## at ($xmidPx,$ymidPx). We are plotting the x coords in the x-direction
         ## of the canvas and the y coords in the y direction of the canvas.
         ##
         ## Note that the y-pixels coords are based at the top of the canvas, yet the
         ## world-coordinates are based at the bottom. We convert the y-world-coords
         ## to pixels and then subtract from the canvas height.
         ###########################################################################

         set xPx [expr { round( ($PxPerUnit * $thisX) + $xmidPx ) }]
         set yPx [expr { round( $curCanHeightPx - (($PxPerUnit * $thisY) + $ymidPx) ) }]

         lappend XYlistPx $xPx $yPx

         ##########################################################################
         ## Set 'bigZ', the biggest Z, for this 'poly', if we need to z-depth-shade
         ## the 'poly' because the user requested poly_shade="zDepth".
         ##########################################################################

         if {"$poly_shade" == "zDepth"} {
            set thisZ [expr {double($thisZ)}]
            if {$n == 0} {
               set bigZ $thisZ
            } else {
               if {$thisZ > $bigZ} {set bigZ $thisZ}
            }
         }
         ## END OF if {"$poly_shade" == "zDepth"}

      }
      ## END OF n loop for poly $polyID


      ## FOR TESTING:
      #   puts "proc 'draw_2D_pixel_polys' > In a loop over the 'polys',"
      #   puts "     converted the xy vertex-points to pixel units for POLYGON $polyID :"
      #   puts "   XYlistPx: $XYlistPx"
      #   puts "     Also found the biggest Z for POLYGON $polyID -  bigZ: $bigZ"


      ##########################################################################
      ## DRAW-SECTION: SET-FILL-COLOR for poly $polyID.
      ##
      ## If fill is requested (var 'poly_fillout' is 'FILLonly' or 'FILLoutline'
      ## --- not 'WIREhide' or 'WIREnohide'):
      ##
      ## Set polyCOLORr,polyCOLORg,polyCOLORb according to the value of var
      ## poly_fillsrc: fromFile, fromButton, random, or fromColorTable.
      ##
      ## (In a following SHADE section,
      ##  we will shade polyCOLORr,polyCOLORg,polyCOLORb according to the
      ##  shading request in var poly_shade.)
      ##########################################################################

      ## As a default (to make sure these color vars are set),
      ## we COULD set the RGB colors for this $polyID from the current setting
      ## of the fill-color button of this GUI.
      ## OR, we could leave these unset and test for errors in logic by
      ## seeing if we get 'polyCOLOR not set' errors.

      # set polyCOLORr $COLOR1r
      # set polyCOLORg $COLOR1g
      # set polyCOLORb $COLOR1b


      if {"$poly_fillout" == "FILLonly" ||  "$poly_fillout" == "FILLoutline"} {

         if {"$poly_fillsrc" == "fromButton"} {
            set polyCOLORr $COLOR1r
            set polyCOLORg $COLOR1g
            set polyCOLORb $COLOR1b
         }
 
         if {"$poly_fillsrc" == "fromFile"} {
            ## Get the color info that came from the file, for poly $polyID.
            ## Recall our format for aRconnect:
            ##   aRconnect($i) = [list $elemID $RGBcolorList/$mtlID $numVertices $vertID1 $vertID2 ...]

            # foreach {dummy COL2ofPolyData dummy}  $aRconnect($polyID) {break}
            set COL2ofPolyData [lindex $aRconnect($polyID) 1]
            set COL2length [llength $COL2ofPolyData]

            if {$COL2length == 3} {
               set polyCOLORr [lindex $COL2ofPolyData 0]
               set polyCOLORg [lindex $COL2ofPolyData 1]
               set polyCOLORb [lindex $COL2ofPolyData 2]
            } else {
               ## If there is not a 3-element (RGB255) color list in the
               ## 2nd column of aRconnect($polyID), we will simply use
               ## the color of the fill-color button of the GUI.
               ## But we could change this. If the 2nd column is a single
               ## integer (a material-ID), we could use it to look up a
               ## color in the current color table (modulo the size of the table).
               set polyCOLORr $COLOR1r
               set polyCOLORg $COLOR1g
               set polyCOLORb $COLOR1b
               ## Reset poly_fillsrc so that the FILLSRC radiobuttons will
               ## indicate from whence we got the fill color.
               set poly_fillsrc "fromButton"
            }         
            ## END OF if {$COL2length == 3}

            ## FOR TESTING:
            # if {$polyID == 1} {
            #   puts "proc 'draw_2D_pixel_polys' > Setting RGB polyCOLOR's for a 'fromFile' request."
            #   puts "   For POLYGON $polyID :"
            #   puts "   polyCOLORr: $polyCOLORr  polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"
            # }

         }
         ## END OF if {"$poly_fillsrc" == "fromFile"}
 

         if {"$poly_fillsrc" == "random"} {
            set polyCOLORr  [expr {int(rand() * 255)}]
            set polyCOLORg  [expr {int(rand() * 255)}]
            set polyCOLORb  [expr {int(rand() * 255)}]
         }
         ## END OF if {"$poly_fillsrc" == "random"}


         if {"$poly_fillsrc" == "fromColorTable"} {
            ########################################################
            ## For poly $polyID, get the max Z height of poly $polyID from
            ## the ORIGINAL 'aRpoints' array. We still have 'Nverts' for
            ## this poly from the world-coords-to-pixels conversion
            ## above.  Recall the format of aRpoints:
            ##   aRpoints($i)  = [list $pointID $RGBcolor/$propID $x $y $z]
            ## and use the fixed color table in array 'aRcolor'
            ## to assign a color according to the z-height within the
            ## range minZ to maxZ.
            ##
            ## Recall our format for aRconnect:
            ##   aRconnect($k) = [list $vertID1 $vertID2 $vertID3 $vertID4]
            ##
            ## Recall the color table was set as follows,
            ## starting with index 1:
            ##   set aRcolor(1) [list 0 153 255]
            ##
            ## For the depth-type options (origZheight, origYheight,
            ## origXheight or zDepth) in poly_shade, we set a value
            ## 'deltaShadeHeight', above. If it is zero just use the first
            ## value in the color table to set the poly RGB colors.
            ########################################################

            if {$deltaZHGTperCOLOR == 0.0} {
               set polyCOLORr [lindex $aRcolor(1) 0]
               set polyCOLORg [lindex $aRcolor(1) 1]
               set polyCOLORb [lindex $aRcolor(1) 2]
            } else {
               for {set n 0} {$n < $Nverts} {incr n} {
                  set idx [expr {$n + 3}]
                  set vertID [lindex $aRconnect($polyID) $idx]
                  foreach {dummy dummy thisX thisY thisZ}  $aRpoints($vertID) {break}
                  set thisZ [expr {double($thisZ)}]
                  if {$n == 0} {
                     set HEIGHTpoly $thisZ
                  } else {
                     if {$thisZ > $HEIGHTpoly} {set HEIGHTpoly $thisZ}
                  }
               }
               ## END OF for {set n 0} {$n < $Nverts} {incr n}
               ## This should make HEIGHTpoly non-negative.
               set HEIGHTpoly [expr {$HEIGHTpoly - $minZ}]
               if {$HEIGHTpoly < 0.0} {set HEIGHTpoly 0.0}
               ## From HEIGHTpoly we get an index into the color table. 
               set IDXcolor [expr {int( ($HEIGHTpoly/$deltaZHGTperCOLOR) + 1)}]
               if {$IDXcolor > $Ncolors} {set IDXcolor $Ncolors}
               set RGBlist $aRcolor($IDXcolor)
               set polyCOLORr [lindex $RGBlist 0]
               set polyCOLORg [lindex $RGBlist 1]
               set polyCOLORb [lindex $RGBlist 2]
            }
            ## END OF if {$deltaZHGTperCOLOR == 0.0}

            ## The poly-color of poly $polyID should be set now.
            ## per the request 'fromColorTable'.

         }
         ## END OF if {"$poly_fillsrc" == "fromColorTable"}

      }
      ## END OF if {"$poly_fillout" == "FILLonly" ||  "$poly_fillout" == "FILLoutline"}
      ## --- that is, END OF setting polyCOLORr,polyCOLORg,polyCOLORb.


      ###########################################################################
      ## DRAW-SECTION: SET-SHADED-FILL-COLORS (polyCOLORr,polyCOLORg,polyCOLORb)
      ##               for poly $polyID.
      ##
      ## The color for this polygon --- polyCOLORr,polyCOLORg,polyCOLORb ---
      ## is to be modified according to the current shading request:
      ##   'none'   OR  'origZheight'  OR  'origYheight'  OR 'origXheight'  OR
      ##   'zDepth'  OR  'byLighting'
      ##
      ## In a following section, we will use polyCOLORr,polyCOLORg,polyCOLORb
      ## to set var 'hexFILLcolor' for use, finally, in 'create polygon' commands
      ## used for the type of fill-draw that may be requested (FILLonly or FILLoutline).
      ##
      ## This section is to be executed if
      ## fill is requested (var 'poly_fillout' is 'FILLonly' or 'FILLoutline' ---
      ## i.e. if neither 'WIREhide' nor 'WIREnohide' is requested)
      ## AND
      ## shading type is not 'none'.
      ## Since we set poly_shade to 'none' if poly_fillout was 'WIREhide' nor 'WIREnohide',
      ## we execute this section if shading type is not 'none'.
      #############################################################################
      ## RECALL THAT:
      ## The possible values of the 'poly_fillout' var of the FILLOUT radiobutton:
      ##     'FILLonly'  or  'FILLoutline'  OR  'WIREhide'  OR  'WIREnohide'
      ##
      ## The possible values of the 'poly_fillsrc' var of the FILLSRC radiobutton:
      ##    'fromButton'  OR  'fromFile'  OR  'fromColorTable' OR  'random'
      ## (These are used when the 'poly_fillout' var is 'FILLonly' or 'FILLoutline'.)
      ##
      ## The possible values of the 'poly_shade' var of the SHADE radiobutton:
      ##   'none'  OR  'origZheight'  OR  'origYheight'  OR 'origXheight'  OR
      ##   'zDepth'  OR  'byLighting'
      ## (These are used when the 'poly_fillout' var is 'FILLonly' or 'FILLoutline'.)
      #############################################################################

      if {"$poly_shade" != "none"} {

         if {"$poly_shade" == "origZheight"  &&  $deltaShadeHeight != 0.0} {

            ## For the case when shading is requested by 'origZheight' and
            ## deltaShadeHeight > 0, we set the shaded color of the polygon according to
            ## its original Z-height (say, the AVERAGE of original Z-heights at the
            ## vertices of this poly), applied to the RGB 'fill' colors in var
            ## polyCOLORr,polyCOLORg,polyCOLORb.
            ##
            ## The steps are:
            ##
            ## 1) Get the average (or max, for less computing) of the ORIGINAL
            ##    Z-height at the vertices of the N-gon.
            ## 2) Compute its "ratio" in the Z (min, max) range, a float between
            ##    0 and 1.
            ## 3) Use this ratio to map the current poly-color RGB components
            ##    (polyCOLORr,polyCOLORg,polyCOLORb) into a new,shaded
            ##    fill color for the polygon, using 'shadeRatio' and 'Low0to1'
            ##    linear-interp. vars set above this poly-processing-loop.
            ##
            ## NOTE:
            ## If deltaShadeHeight is zero, we leave the polyCOLOR vars 'as-is'.

            set sumVERTheights 0
            for {set n 0} {$n < $Nverts} {incr n} {
               set idx [expr {$n + 3}]
               set vertID [lindex $aRconnect($polyID) $idx]
               foreach {dummy dummy thisX thisY thisZ}  $aRpoints($vertID) {break}
               set sumVERTheights [expr {$sumVERTheights + $thisZ}]
            }
            ## END OF n loop for poly $polyID

            set tempHeight [expr {($sumVERTheights/$Nverts) - $minZ}]

            set tempFactor [expr {($shadeRatio * $tempHeight) + $Low0to1}]

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors by 'origZheight'."
            #   puts " For poly $polyID, STARTed with:
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"

            set polyCOLORr [expr {int($tempFactor * $polyCOLORr)}]
            if {$polyCOLORr > 255} {set polyCOLORr 255}
            set polyCOLORg [expr {int($tempFactor * $polyCOLORg)}]
            if {$polyCOLORg > 255} {set polyCOLORg 255}
            set polyCOLORb [expr {int($tempFactor * $polyCOLORb)}]
            if {$polyCOLORb > 255} {set polyCOLORb 255}

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors by 'origZheight'."
            #   puts " For poly $polyID, ENDed with:
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"

         }
         ## END OF  elseif {"$poly_shade" == "origZheight"}


         if {"$poly_shade" == "origYheight"  &&  $deltaShadeHeight != 0.0} {

            ## For the case when shading is requested by 'origYheight' and
            ## deltaShadeHeight > 0, we set the shaded color of the polygon according to
            ## its original Y-height (say, the AVERAGE of original Y-heights at the
            ## vertices of this poly), applied to the RGB 'fill' colors in var
            ## polyCOLORr,polyCOLORg,polyCOLORb.
            ##
            ## The steps are:
            ##
            ## 1) Get the average (or max, for less computing) of the ORIGINAL
            ##    Y-height at the vertices of the N-gon.
            ## 2) Compute its "ratio" in the Y (min, max) range, a float between
            ##    0 and 1.
            ## 3) Use this ratio to map the current poly-color RGB components
            ##    (polyCOLORr,polyCOLORg,polyCOLORb) into a new,shaded
            ##    fill color for the polygon, using 'shadeRatio' and 'Low0to1'
            ##    linear-interp. vars set above this poly-processing-loop.
            ##
            ## NOTE:
            ## If deltaShadeHeight is zero, we leave the polyCOLOR vars 'as-is'.

            set sumVERTheights 0
            for {set n 0} {$n < $Nverts} {incr n} {
               set idx [expr {$n + 3}]
               set vertID [lindex $aRconnect($polyID) $idx]
               foreach {dummy dummy thisX thisY thisZ}  $aRpoints($vertID) {break}
               set sumVERTheights [expr {$sumVERTheights + $thisY}]
            }
            ## END OF n loop for poly $polyID

            set tempHeight [expr {($sumVERTheights/$Nverts) - $minY}]

            set tempFactor [expr {($shadeRatio * $tempHeight) + $Low0to1}]

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors by 'origYheight'."
            #   puts " For poly $polyID, STARTed with:
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"

            set polyCOLORr [expr {int($tempFactor * $polyCOLORr)}]
            if {$polyCOLORr > 255} {set polyCOLORr 255}
            set polyCOLORg [expr {int($tempFactor * $polyCOLORg)}]
            if {$polyCOLORg > 255} {set polyCOLORg 255}
            set polyCOLORb [expr {int($tempFactor * $polyCOLORb)}]
            if {$polyCOLORb > 255} {set polyCOLORb 255}

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors by 'origYheight'."
            #   puts " For poly $polyID, ENDed with:
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"

         }
         ## END OF  elseif {"$poly_shade" == "origYheight"}


         if {"$poly_shade" == "origXheight"  &&  $deltaShadeHeight != 0.0} {

            ## For the case when shading is requested by 'origXheight' and
            ## deltaShadeHeight > 0, we set the shaded color of the polygon according to
            ## its original X-height (say, the AVERAGE of original X-heights at the
            ## vertices of this poly), applied to the RGB 'fill' colors in var
            ## polyCOLORr,polyCOLORg,polyCOLORb.
            ##
            ## The steps are:
            ##
            ## 1) Get the average (or max, for less computing) of the ORIGINAL
            ##    X-height at the vertices of the N-gon.
            ## 2) Compute its "ratio" in the X (min, max) range, a float between
            ##    0 and 1.
            ## 3) Use this ratio to map the current poly-color RGB components
            ##    (polyCOLORr,polyCOLORg,polyCOLORb) into a new,shaded
            ##    fill color for the polygon, using 'shadeRatio' and 'Low0to1'
            ##    linear-interp. vars set above this poly-processing-loop.
            ##
            ## NOTE:
            ## If deltaShadeHeight is zero, we leave the polyCOLOR vars 'as-is'.

            set sumVERTheights 0
            for {set n 0} {$n < $Nverts} {incr n} {
               set idx [expr {$n + 3}]
               set vertID [lindex $aRconnect($polyID) $idx]
               foreach {dummy dummy thisX thisY thisZ}  $aRpoints($vertID) {break}
               set sumVERTheights [expr {$sumVERTheights + $thisX}]
            }
            ## END OF n loop for poly $polyID

            set tempHeight [expr {($sumVERTheights/$Nverts) - $minX}]

            set tempFactor [expr {($shadeRatio * $tempHeight) + $Low0to1}]

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors by 'origXheight'."
            #   puts " For poly $polyID, STARTed with:
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"

            set polyCOLORr [expr {int($tempFactor * $polyCOLORr)}]
            if {$polyCOLORr > 255} {set polyCOLORr 255}
            set polyCOLORg [expr {int($tempFactor * $polyCOLORg)}]
            if {$polyCOLORg > 255} {set polyCOLORg 255}
            set polyCOLORb [expr {int($tempFactor * $polyCOLORb)}]
            if {$polyCOLORb > 255} {set polyCOLORb 255}

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors by 'origXheight'."
            #   puts " For poly $polyID, ENDed with:
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"

         }
         ## END OF  elseif {"$poly_shade" == "origXheight"}


         if {"$poly_shade" == "zDepth"  &&  $deltaShadeHeight != 0.0} {

            ## For the case when shading is requested by 'zDepth' and
            ## deltaShadeHeight > 0, we set the shaded color of the polygon according to
            ## its 'biggest Z', applied to the RGB 'fill' colors in var
            ## polyCOLORr,polyCOLORg,polyCOLORb.
            ##
            ## We got the biggest Z ('bigZ') according to our view (i.e.
            ## after translation and rotation) when we accumulated
            ## the xy coords of this poly, $polyID, above.
            ## We use that for our zDepth.
            ##
            ## The steps are:
            ##
            ## 1) Use 'bigZ' as a measure of the 'depth' of this N-gon.
            ## 2) Compute its "ratio" in the 'newZ' (min, max) range, a float between
            ##    0 and 1.
            ## 3) Use this ratio to map the current poly-color RGB components
            ##    (polyCOLORr,polyCOLORg,polyCOLORb) into a new,shaded
            ##    fill color for the poly, using 'shadeRatio' and 'Low0to1'
            ##    linear-interp. vars set above this poly-processing-loop.
            ##
            ## NOTE:
            ## If deltaShadeHeight is zero, we leave the polyCOLOR vars 'as-is'.

            set tempHeight [expr {$bigZ - $minnewZ}]

            set tempFactor [expr {($shadeRatio * $tempHeight) + $Low0to1}]

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors by 'zDepth'."
            #   puts " For poly $polyID, STARTed with:
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"

            set polyCOLORr [expr {int($tempFactor * $polyCOLORr)}]
            if {$polyCOLORr > 255} {set polyCOLORr 255}
            set polyCOLORg [expr {int($tempFactor * $polyCOLORg)}]
            if {$polyCOLORg > 255} {set polyCOLORg 255}
            set polyCOLORb [expr {int($tempFactor * $polyCOLORb)}]
            if {$polyCOLORb > 255} {set polyCOLORb 255}

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors by 'zDepth'."
            #   puts " For poly $polyID, ENDed with:
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"

         }
         ## END OF  elseif {"$poly_shade" == "zDepth"}


         if {"$poly_shade" == "byLighting"} {

            ## For poly $polyID, shade its color --- polyCOLORr,polyCOLORg,
            ## polyCOLORb -- according to the angle that the polygon
            ## makes with a light source from the viewer (i.e. along
            ## the x-viewing axis).
            ## To get the angle we use a cross-product of a couple
            ## of vectors made from 3 of the vertices of the polygon.
            ##
            ## We get the vertices --- from the translated-rotated points array:
            ##     aRnew_points($k) = [list $newx $newy $newz]
            ##
            ## The steps are:
            ##
            ## 1) Get 3 vertices. Subtract 1 from 2 and 1 from 3 to get
            ##    xyz coords of 2 vectors along the side of the polygon.
            ## 2) Pass the 6 xyz coords to proc 'cross_product_normz'
            ##    which will return a float between 0 and 1.
            ## 3) Multiply that number times the current poly-color RGB components
            ##    (polyCOLORr,polyCOLORg,polyCOLORb) to get a new,shaded
            ##    fill color for the poly.
            ##
            ## Recall that the connectivity array consists of list elements of the form:
            ##    aRconnect($i) = [list $elemID $RGBcolors-list/$mtlID $numVertices $vertID1 $vertID2 ...]

            set vertID1 [lindex $aRconnect($polyID) 3]
            set vertID2 [lindex $aRconnect($polyID) 4]
            set vertID3 [lindex $aRconnect($polyID) 5]

            foreach {x1 y1 z1}  $aRnew_points($vertID1) {break}
            foreach {x2 y2 z2}  $aRnew_points($vertID2) {break}
            foreach {x3 y3 z3}  $aRnew_points($vertID3) {break}

            set x12 [expr {$x2 - $x1}]
            set y12 [expr {$y2 - $y1}]
            set z12 [expr {$z2 - $z1}]

            set x13 [expr {$x3 - $x1}]
            set y13 [expr {$y3 - $y1}]
            set z13 [expr {$z3 - $z1}]

            set tempFactor [cross_product_normz $x12 $y12 $z12 $x13 $y13 $z13]
            set tempFactor [expr {abs($tempFactor)}]

            ## Note: When tempFactor is negative, this is an indication that the
            ## polygon is tilted away from the viewer. This could be used as an
            ## indicator to skip plotting this polygon (i.e. backface culling).

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors 'byLighting'."
            #   puts " For poly $polyID, STARTed with:"
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"
            #   puts " From proc 'cross_product_normz', tempFactor: $tempFactor"

            set polyCOLORr [expr {int($tempFactor * $polyCOLORr)}]
            if {$polyCOLORr > 255} {set polyCOLORr 255}
            set polyCOLORg [expr {int($tempFactor * $polyCOLORg)}]
            if {$polyCOLORg > 255} {set polyCOLORg 255}
            set polyCOLORb [expr {int($tempFactor * $polyCOLORb)}]
            if {$polyCOLORb > 255} {set polyCOLORb 255}

            ## FOR TESTING:
            #   puts "proc 'draw_2D_pixel_polys' >  Shading the poly-colors 'byLighting'."
            #   puts " For poly $polyID, ENDed with:"
            #   puts " polyCOLORr: $polyCOLORr   polyCOLORg: $polyCOLORg   polyCOLORb: $polyCOLORb"
 
         }
         ## END OF if {"$poly_shade" == "byLighting"}

      }
      ## END OF  if {"$poly_shade" != "none"}
      ## that is, END OF the section that shades the 'polyCOLOR' RGB values.


      ############################################################################
      ## DRAW-SECTION: SET VAR 'hexFILLcolor' from polyCOLORr,polyCOLORg,polyCOLORb
      ##               (or from other considerations) for poly $polyID.
      ##
      ## If this is a wire display, we set 'hexFILLcolor' with the color from the
      ## outline-color button of the GUI. Otherwise (for non-wire = some-type-of-fill),
      ## we set 'hexFILLcolor' from polyCOLORr,polyCOLORg,polyCOLORb.
      ##
      #############################################################################

      if {"$poly_fillout" == "WIREhide" || "$poly_fillout" == "WIREnohide"} {
         set hexFILLcolor "$COLOR2hex"
      } else {
         ## If the 'polyCOLOR' vars are not set at this point and the logic is too much,
         ## we could set a default color for 'hexFILLcolor' (the color from the fill-color
         ## button of the GUI).
         set hexFILLcolor [format "#%02X%02X%02X" $polyCOLORr $polyCOLORg $polyCOLORb]
      }


      ############################################################################
      ## DRAW-SECTION: DRAW THE 'poly', $polyID, with 'create line' (when Nverts=2)
      ##               or 'create polygon' (when Nverts>2).
      ## 
      ## We take the 4 cases, 'poly_fillout' =
      ## WIREhide  OR  WIREnohide  OR  FILLout  OR  FILLoutline
      ############################################################################

      if { "$poly_fillout" == "WIREhide" } {
         if {$Nverts == 2} {
            eval .fRcanvas.canvas create line $XYlistPx \
               -fill $hexFILLcolor -tags TAGpolygon
         } else {
            eval .fRcanvas.canvas create polygon $XYlistPx \
               -outline $hexFILLcolor -fill $COLORbkGNDhex -tags TAGpolygon
         }
      }
      ## END OF if { "$poly_fillout" == "WIREhide" } 


      if { "$poly_fillout" == "WIREnohide" } {
         if {$Nverts == 2} {
            eval .fRcanvas.canvas create line $XYlistPx \
               -fill $hexFILLcolor -tags TAGpolygon
         } else {
            eval .fRcanvas.canvas create polygon $XYlistPx \
               -outline $hexFILLcolor -fill \"\" -tags TAGpolygon
            ##   (NOTE: By setting '-fill' to null instead of the background color,
            ##    we get a wireframe image with NO hiding of back polygons.)
         }
      }
      ## END OF  if { "$poly_fillout" == "WIREnohide" }


      if { "$poly_fillout" == "FILLoutline"} {
         if {$Nverts == 2} {
            eval .fRcanvas.canvas create line $XYlistPx \
               -fill $hexFILLcolor -tags TAGpolygon
         } else {
            eval .fRcanvas.canvas create polygon $XYlistPx \
               -outline $COLOR2hex -fill $hexFILLcolor -tags TAGpolygon
         }
      }
      ## END OF if { "$poly_fillout" == "FILLoutline"}


      if { "$poly_fillout" == "FILLonly"} {
         if {$Nverts == 2} {
            eval .fRcanvas.canvas create line $XYlistPx \
               -fill $hexFILLcolor -tags TAGpolygon
         } else {
            eval .fRcanvas.canvas create polygon $XYlistPx \
               -fill $hexFILLcolor -tags TAGpolygon
         }         
      }
      ## END OF if { "$poly_fillout" == "FILLonly"}


      ## We may find use for 'TAGpolygon', for example to delete
      ## all polygons but leave other canvas objects such as text
      ## (someday?) on the canvas.

      ## FOR TESTING: (slow down the drawing of the polygons)
      #  update
      #  after 50

   }
   ## END OF k loop over the z-depth-sorted polygons

}
## END OF proc 'draw_2D_pixel_polys'


##+################################################################
## proc load-translate-rotate-sort-draw
##
## PURPOSE: a proc that calls 4 procs:
##            - load_points_array
##            - translate_points_array
##            - rotate_points
##            - sort_polyIDs_list
##            - draw_2D_pixel_polys
##
## CALLED BY: see BINDINGS section above.
##+################################################################

proc load-translate-rotate-sort-draw {} {

   global t0 diam modelNOTloadedMSG

   ## Set the current time, for determining elapsed
   ## time for redrawing the 3D plot.

   set t0 [clock milliseconds]

   load_3DfileData

   ## Bail out if an indicator (that data has been read
   ## and ready for drawing) is not set.

   if {![info exists diam]} {
      popup_msgVarWithScroll_wait .fRerrmsg $modelNOTloadedMSG
      return
   }

   translate_points_array
   rotate_points
   sort_polyIDs_list
   draw_2D_pixel_polys

   update_drawtime_label

}
## END OF proc 'load-translate-rotate-sort-draw'


##+################################################################
## proc rotate-sort-draw
##
## PURPOSE: a proc that calls 2 procs:
##            - rotate_points
##            - sort_polyIDs_list
##            - draw_2D_pixel_polys
##
## CALLED BY: see BINDINGS section above.
##+################################################################

proc rotate-sort-draw {} {

   global t0 diam modelNOTloadedMSG

   ## Bail out if an indicator (that data has been read
   ## and ready for drawing) is not set.

   if {![info exists diam]} {
      popup_msgVarWithScroll_wait .fRerrmsg $modelNOTloadedMSG
      return
   }


   ## Set the current time, for determining elapsed
   ## time for redrawing the 3D plot.

   set t0 [clock milliseconds]

   rotate_points
   sort_polyIDs_list
   draw_2D_pixel_polys

   update_drawtime_label
}
## END OF proc 'rotate-sort-draw'


##+################################################################
## proc sort-draw
##
## PURPOSE: a proc that calls 2 procs:
##            - sort_polyIDs_list
##            - draw_2D_pixel_polys
##
## CALLED BY: see BINDINGS section above.
##+################################################################

proc sort-draw {} {

   global t0 diam modelNOTloadedMSG

   ## Bail out if an indicator (that data has been read
   ## and ready for drawing) is not set.

   if {![info exists diam]} {
      popup_msgVarWithScroll_wait .fRerrmsg $modelNOTloadedMSG
      return
   }

   ## Set the current time, for determining elapsed
   ## time for redrawing the 3D plot.

   set t0 [clock milliseconds]

   sort_polyIDs_list
   draw_2D_pixel_polys

   update_drawtime_label
}
## END OF proc 'sort-draw'


##+################################################################
## proc wrap_draw_2D_pixel_polys
##
## PURPOSE: a proc that calls the proc 'draw_2D_pixel_polys'
##          and shows the millisecs that the redraw took.
##            
## CALLED BY: see BINDINGS section above.
##+################################################################

proc wrap_draw_2D_pixel_polys {} {

   global t0 diam modelNOTloadedMSG

   ## Bail out if an indicator (that data has been read
   ## and ready for drawing) is not set.

   if {![info exists diam]} {
      popup_msgVarWithScroll_wait .fRerrmsg $modelNOTloadedMSG
      return
   }


   ## Set the current time, for determining elapsed
   ## time for redrawing the 3D plot.

   set t0 [clock milliseconds]

   draw_2D_pixel_polys

   update_drawtime_label
}
## END OF proc 'wrap_draw_2D_pixel_polys'


##+#####################################################################
## proc  'update_drawtime_label'
##+##################################################################### 
## PURPOSE:  Updates the draw-time (millisecs) in a label widget
##           on the GUI.
##
## ARGUMENTS: see global vars below
##
## CALLED BY: the 3 'wrapper' redraw-procs:
##             - load-translate-rotate-draw
##             - rotate-draw
##             - wrap_draw_2D_pixel_polys
##+#####################################################################

proc update_drawtime_label {} {

   global t0  COLOR1hex COLOR2hex COLORbkGNDhex

#    .fRfiletypes.labelSTATUS configure -text "\
# Use the 'Help' button to get info on the file types (supported records).
#   ** re-DRAW TIME: [expr {[clock milliseconds] - $t0}] millisecs elapsed"

   .fRbuttons.labelCOLORS configure -text "\
Poly-Fill-Color: $COLOR1hex   Poly-Outline-Color: $COLOR2hex
 Background Color: $COLORbkGNDhex
 ** re-DRAW TIME: [expr {[clock milliseconds] - $t0}] millisecs elapsed"


}
## END OF PROC  'update_drawtime_label'


##+#####################################################################
## proc 'set_polygon_color1'
##+##################################################################### 
## PURPOSE:
##
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB value to set a 'fill' color.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLOR1  button
##+#####################################################################

proc set_polygon_color1 {} {

   global COLOR1r COLOR1g COLOR1b COLOR1hex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLOR1r: $COLOR1r"
   #    puts "COLOR1g: $COLOR1g"
   #    puts "COLOR1b: $COLOR1b"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLOR1r $COLOR1g $COLOR1b]

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

   ## FOR TESTING:
   #    puts "TEMPrgb: $TEMPrgb"

   if { "$TEMPrgb" == "" } { return }
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLOR1hex "#$hexRGB"
   set COLOR1r $r255
   set COLOR1g $g255
   set COLOR1b $b255

   ## Set color of color1 button and update the colors label.

   update_colors_label

   ## Redraw the geometry in the new fill color.

   wrap_draw_2D_pixel_polys

}
## END OF proc 'set_polygon_color1'


##+#####################################################################
## proc 'set_polygon_color2'
##                          (NOT USED yet ; could be used for an outline)
##+##################################################################### 
## PURPOSE:
##
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB value to ...
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLOR2  button
##+#####################################################################

proc set_polygon_color2 {} {

   global COLOR2r COLOR2g COLOR2b COLOR2hex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLOR2r: $COLOR2r"
   #    puts "COLOR2g: $COLOR2g"
   #    puts "COLOR2b: $COLOR2b"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLOR2r $COLOR2g $COLOR2b]

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

   ## FOR TESTING:
   #    puts "TEMPrgb: $TEMPrgb"

   if { "$TEMPrgb" == "" } { return }
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLOR2hex "#$hexRGB"
   set COLOR2r $r255
   set COLOR2g $g255
   set COLOR2b $b255

   ## Set color of color2 button and update the colors label.

   update_colors_label

   ## Redraw the geometry in the new outline color.

   wrap_draw_2D_pixel_polys

}
## END OF proc 'set_polygon_color2'


##+#####################################################################
## proc 'set_background_color'
##+##################################################################### 
## PURPOSE:
##
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB value to set the color of the canvas ---
##   on which all the tagged items (lines) lie.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLORbkGND  button
##+#####################################################################

proc set_background_color {} {

   global COLORbkGNDr COLORbkGNDg COLORbkGNDb COLORbkGNDhex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLORbkGNDr: $COLORbkGNDr"
   #    puts "COLORbkGNDg: $COLORbkGNDb"
   #    puts "COLORbkGNDb: $COLORbkGNDb"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLORbkGNDr $COLORbkGNDg $COLORbkGNDb]

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

   ## FOR TESTING:
   #    puts "TEMPrgb: $TEMPrgb"

   if { "$TEMPrgb" == "" } { return }
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLORbkGNDhex "#$hexRGB"
   set COLORbkGNDr $r255
   set COLORbkGNDg $g255
   set COLORbkGNDb $b255

   ## Set color of background-color button and update the colors label.

   update_colors_label

   ## Set the color of the canvas.

   .fRcanvas.canvas config -bg $COLORbkGNDhex

}
## END OF proc 'set_background_color'


##+#####################################################################
## proc 'update_colors_label'
##+##################################################################### 
## PURPOSE:
##   This procedure is invoked to update the text in a COLORS
##   label widget, to show hex values of current color1, color2,
##   and background-color settings.
##
##   This proc also sets the background color of each of those 3 buttons
##   to its current color --- and sets foreground color to a
##   suitable black or white color, so that the label text is readable.
##
## Arguments: global color vars
##
## CALLED BY:  3 colors procs:
##            'set_polygon_color1'
##            'set_polygon_color2'
##            'set_background_color'
##             and the additional-GUI-initialization section at
##             the bottom of this script.
##+#####################################################################

proc update_colors_label {} {

   global COLOR1hex COLOR2hex COLORbkGNDhex \
      COLOR1r COLOR1g COLOR1b \
      COLOR2r COLOR2g COLOR2b \
      COLORbkGNDr COLORbkGNDg COLORbkGNDb

   .fRbuttons.labelCOLORS configure -text "\
Poly-Fill-Color: $COLOR1hex   Poly-Outline-Color: $COLOR2hex
 Background Color: $COLORbkGNDhex"

   # set COLORSUMmiddle 300
   set COLORSUMmiddle 250

   .fRbuttons.buttCOLORbkGND configure -bg $COLORbkGNDhex
   set sumCOLORbkgd [expr {$COLORbkGNDr + $COLORbkGNDg + $COLORbkGNDb}]
   if {$sumCOLORbkgd > $COLORSUMmiddle} {
      .fRbuttons.buttCOLORbkGND configure -fg "#000000"
   } else {
      .fRbuttons.buttCOLORbkGND configure -fg "#f0f0f0"
   }

   .fRbuttons.buttOUTLINE configure -bg $COLOR2hex
   set sumCOLOR2 [expr {$COLOR2r + $COLOR2g + $COLOR2b}]
   if {$sumCOLOR2 > $COLORSUMmiddle} {
      .fRbuttons.buttOUTLINE configure -fg "#000000"
   } else {
      .fRbuttons.buttOUTLINE configure -fg "#f0f0f0"
   }

   .fRbuttons.buttFILL configure -bg $COLOR1hex
   set sumCOLOR1 [expr {$COLOR1r + $COLOR1g + $COLOR1b}]
   if {$sumCOLOR1 > $COLORSUMmiddle} {
      .fRbuttons.buttFILL configure -fg "#000000"
   } else {
      .fRbuttons.buttFILL configure -fg "#f0f0f0"
   }

}
## END OF proc 'update_colors_label'


##+#########################################################################
## proc  'enable_fillsrc_radbutts'
##+#########################################################################
## PURPOSE: To enable the 'FILLSRC' radiobuttons.
##
## CALLED BY: See BINDINGS section above, some bindings on FILLOUT 
##            radiobuttons --- 'FILLonly' and 'FILLoutline'.
##+#########################################################################

proc enable_fillsrc_radbutts {} {

   .fRfillopts.radbuttFILLSRCfromFile       configure -state normal
   .fRfillopts.radbuttFILLSRCfromButton     configure -state normal
   .fRfillopts.radbuttFILLSRCrandom         configure -state normal
   .fRfillopts.radbuttFILLSRCfromColorTable configure -state normal

}
## END OF PROC  'enable_fillsrc_radbutts'

##+#########################################################################
## proc  'disable_fillsrc_radbutts'
##+#########################################################################
## PURPOSE: To disable the 'FILLSRC' radiobuttons.
##
## CALLED BY: See BINDINGS section above, some bindings some FILLOUT 
##            radiobuttons --- 'wirehidn' and 'wirehide'.
##+#########################################################################

proc disable_fillsrc_radbutts {} {

   .fRfillopts.radbuttFILLSRCfromFile       configure -state disable
   .fRfillopts.radbuttFILLSRCfromButton     configure -state disable
   .fRfillopts.radbuttFILLSRCrandom         configure -state disable
   .fRfillopts.radbuttFILLSRCfromColorTable configure -state disable

}
## END OF PROC  'disable_fillsrc_radbutts'


##+#########################################################################
## proc  'enable_shade_radbutts'
##+#########################################################################
## PURPOSE: To enable the 'SHADE' radiobuttons.
##
## CALLED BY: See BINDINGS section above, the bindings on the FILLSRC 
##            radiobuttons.
##+#########################################################################

proc enable_shade_radbutts {} {

   global poly_fillsrc poly_fillout poly_shade

   .fRshadeopts.radbuttSHADEnone         configure -state normal
   .fRshadeopts.radbuttSHADEorigZheight  configure -state normal
   .fRshadeopts.radbuttSHADEorigYheight  configure -state normal
   .fRshadeopts.radbuttSHADEorigXheight  configure -state normal
   .fRshadeopts.radbuttSHADEzDepth       configure -state normal
   .fRshadeopts.radbuttSHADEbyLighting   configure -state normal

   ## If fill-and-outline is requested, to avoid extra processing,
   ## we could discourage use of shading options
   ## by setting 'poly_shade' to 'none'.

   if {"$poly_fillout" == "FILLoutline"} {
      set poly_shade "none"
   }


   ## If fill-only is requested, to avoid a large blob of solid color,
   ## we could discourage use of no-shading
   ## by setting 'poly_shade' to an option like 'origZheight' instead
   ## of 'none'.

   if {"$poly_fillout" == "FILLonly" && "$poly_shade" == "none"} {
      set poly_shade "origZheight"
   }

}
## END OF PROC  'enable_shade_radbutts'


##+#########################################################################
## proc  'disable_shade_radbutts'
##+#########################################################################
## PURPOSE: To disable the 'SHADE' radiobuttons.
##
## CALLED BY: See BINDINGS section above, the bindings on some FILLOUT 
##            radiobuttons.
##+#########################################################################

proc disable_shade_radbutts {} {

   .fRshadeopts.radbuttSHADEorigZheight  configure -state disable
   .fRshadeopts.radbuttSHADEorigZheight  configure -state disable
   .fRshadeopts.radbuttSHADEorigYheight  configure -state disable
   .fRshadeopts.radbuttSHADEorigXheight  configure -state disable
   .fRshadeopts.radbuttSHADEzDepth       configure -state disable
   .fRshadeopts.radbuttSHADEbyLighting   configure -state disable

}
## END OF PROC  'disable_shade_radbutts'


##+########################################################################
## PROC 'cross_product_normz'
##+########################################################################
## PURPOSE: Performs the vector cross-product of 2 given 3D-vectors ---
##          p=(px,py,pz) and q=(qx,qy,qz) --- such as 2 vectors along
##          2 sides of a quadrilateral/triangular polygon of the terrain
##          surface.
##
##          The output is the normalized z-component of the cross-product
##          vector.
##
##          Since we will be assuming a light source along the viewing
##          z-axis, this component gives us the cosine of the angle
##          between this cross-product vector (normal to the polygon)
##          and a unit vector in the z-direction.
##
## CALLED BY: the 'draw' proc, to perform 'shading' of the specified
##            source of fill colors in polygons.
##
## USE:       The absolute value of the 'normz' output is
##            multiplied times the RGB values of a polygon color to give
##            the 'shaded-color' of the polygon. 
##+########################################################################

proc cross_product_normz {px py pz qx qy qz} {

   set crossx [expr { ($py * $qz) - ($qy * $pz) }]
   set crossy [expr { ($qx * $pz) - ($px * $qz) }]
   set crossz [expr { ($px * $qy) - ($qx * $py) }]

   set MAGcross [expr {sqrt(($crossx * $crossx) + ($crossy * $crossy) + ($crossz * $crossz))}]

   ## FOR TESTING:
   # if {$MAGcross == 0.0} {
   #   puts "From 'cross_product_normz' proc:"
   #   puts "px: $px   py: $py   pz: $pz    qx: $qx   qy: $qy   qz: $qz"
   #   puts "crossz: $crossz   MAGcross: $MAGcross"
   #   puts "crossx: $crossx   crossy: $crossy"
   # }

   if {$MAGcross == 0.0} {return 0.5}

   set normz [expr { $crossz / $MAGcross }]

   # set MAGp [expr {sqrt(($px * $px) + ($py * $py) + ($pz * $pz))}]
   # set MAGq [expr {sqrt(($qx * $qx) + ($qy * $qy) + ($qz * $qz))}]
   # set sinAngTWEENpANDq [expr { $MAGcross / ($MAGp * $MAGq) }]

   return $normz

}
## END OF PROC 'cross_product_normz'


##+#####################################################################
## proc  'toggle_opts'
##+##################################################################### 
## PURPOSE: To hide or show a group of 'options' frames ---
##          .fRsorts .fRfillopts .fRshadeopts --- above the
##          .fRscales and .fRcanvas frames and below other frames
##          such as .fRbuttons , .fRfile, and .fRfiletypes. 
## 
## CALLED BY:  .fRbuttons.butTOGOPTS button
##+#####################################################################

proc toggle_opts { } {

   global OPTS_shownORhidden

   if { "$OPTS_shownORhidden" == "shown" } {
      pack forget .fRsorts .fRfillopts .fRshadeopts
      set OPTS_shownORhidden "hidden"
   } else {
      pack forget .fRscales .fRcanvas
      pack  .fRsorts .fRfillopts .fRshadeopts \
         -side top \
         -anchor w \
         -fill x \
         -expand 0
      pack .fRscales \
         -side top \
         -anchor w \
         -fill none \
         -expand 0
      pack .fRcanvas \
         -side top \
         -anchor w \
         -fill both \
         -expand 1
      set OPTS_shownORhidden "shown"
   }

}
## END of proc 'toggle_opts'


##+#############################################################
## proc ReDraw_if_canvas_resized
##
## CALLED BY: bind .fRcanvas.canvas <Configure> 
##            at bottom of this script.
##+#############################################################

proc ReDraw_if_canvas_resized {} {
   global  PREVcanWidthPx PREVcanHeightPx diam

   ## Bail out if an indicator (that data has been read
   ## and ready for drawing) is not set.

   if {![info exists diam]} {return}

   set CURcanWidthPx  [winfo width  .fRcanvas.canvas]
   set CURcanHeightPx [winfo height .fRcanvas.canvas]

   if { $CURcanWidthPx  != $PREVcanWidthPx ||
        $CURcanHeightPx != $PREVcanHeightPx} {
      wrap_draw_2D_pixel_polys
      set PREVcanWidthPx  $CURcanWidthPx
      set PREVcanHeightPx $CURcanHeightPx
   }

}
## END OF ReDraw_if_canvas_resized


##+########################################################################
## PROC 'popup_msgVarWithScroll'
##+########################################################################
## PURPOSE: Report help or error conditions to the user.
##
##       We do not use focus,grab,tkwait in this proc,
##       because we use it to show help when the GUI is idle,
##       and we may want the user to be able to keep the Help
##       window open while doing some other things with the GUI
##       such as putting a filename in the filename entry field
##       or clicking on a radiobutton.
##
##       For a similar proc with focus-grab-tkwait added,
##       see the proc 'popup_msgVarWithScroll_wait'.
##
## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk',
##            4th edition, by Welch, Jones, Hobbs.
##
## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg)
##            and a variable holding text (many lines, if needed).
##
## CALLED BY: 'help' button
##+########################################################################
## To have more control over the formatting of the message (esp.
## words per line), we use this 'toplevel-text' method, 
## rather than the 'tk_dialog' method -- like on page 574 of the book 
## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications
## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor".
##+########################################################################

proc popup_msgVarWithScroll { toplevName VARtext } {

   ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global.
   ## global env

   # bell
   # bell
  
   #################################################
   ## Set VARwidth & VARheight from $VARtext.
   #################################################
   ## To get VARheight,
   ##    split at '\n' (newlines) and count 'lines'.
   #################################################
 
   set VARlist [ split $VARtext "\n" ]

   ## For testing:
   #  puts "VARlist: $VARlist"

   set VARheight [ llength $VARlist ]

   ## For testing:
   #  puts "VARheight: $VARheight"


   #################################################
   ## To get VARwidth,
   ##    loop through the 'lines' getting length
   ##     of each; save max.
   #################################################

   set VARwidth 0

   #############################################
   ## LOOK AT EACH LINE IN THE LIST.
   #############################################
   foreach line $VARlist {

      #############################################
      ## Get the length of the line.
      #############################################
      set LINEwidth [ string length $line ]

      if { $LINEwidth > $VARwidth } {
         set VARwidth $LINEwidth 
      }

   }
   ## END OF foreach line $VARlist

   ## For testing:
   #   puts "VARwidth: $VARwidth"


   ###############################################################
   ## NOTE: VARwidth works for a fixed-width font used for the
   ##       text widget ... BUT the programmer may need to be
   ##       careful that the contents of VARtext are all
   ##       countable characters by the 'string length' command.
   ###############################################################


   #####################################
   ## SETUP 'TOP LEVEL' HELP WINDOW.
   #####################################

   catch {destroy $toplevName}
   toplevel  $toplevName

   # wm geometry $toplevName 600x400+100+50

   wm geometry $toplevName +100+50

   wm title     $toplevName "Note"
   # wm title   $toplevName "Note to $env(USER)"

   wm iconname  $toplevName "Note"


   #####################################
   ## In the frame '$toplevName' -
   ## DEFINE THE TEXT WIDGET and
   ## its two scrollbars --- and
   ## DEFINE an OK BUTTON widget.
   #####################################

   text $toplevName.text \
      -wrap none \
      -font fontTEMP_varwidth \
      -width  $VARwidth \
      -height $VARheight \
      -bg "#f0f0f0" \
      -relief raised \
      -bd 2 \
      -yscrollcommand "$toplevName.scrolly set" \
      -xscrollcommand "$toplevName.scrollx set"

   scrollbar $toplevName.scrolly \
                 -orient vertical \
      -command "$toplevName.text yview"

   scrollbar $toplevName.scrollx \
                -orient horizontal \
                -command "$toplevName.text xview"

   button $toplevName.butt \
      -text "OK" \
      -font fontTEMP_varwidth \
      -command  "destroy $toplevName"

   ###############################################
   ## PACK *ALL* the widgets in frame '$toplevName'.
   ###############################################

   ## Pack the bottom button BEFORE the
   ## bottom x-scrollbar widget,

   pack  $toplevName.butt \
      -side bottom \
      -anchor center \
      -fill none \
      -expand 0

   ## Pack the scrollbars BEFORE the text widget,
   ## so that the text does not monopolize the space.

   pack $toplevName.scrolly \
      -side right \
      -anchor center \
      -fill y \
      -expand 0

   ## DO NOT USE '-expand 1' HERE on the Y-scrollbar.
   ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS
   ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA.
                
   pack $toplevName.scrollx \
      -side bottom \
      -anchor center \
      -fill x  \
      -expand 0

   ## DO NOT USE '-expand 1' HERE on the X-scrollbar.
   ## THAT KEEPS THE TEXT AREA FROM EXPANDING.

   pack $toplevName.text \
      -side top \
      -anchor center \
      -fill both \
      -expand 1


   #####################################
   ## LOAD MSG INTO TEXT WIDGET.
   #####################################

   ##  $toplevName.text delete 1.0 end
 
   $toplevName.text insert end $VARtext
   
   $toplevName.text configure -state disabled
  
}
## END OF PROC 'popup_msgVarWithScroll'


##+########################################################################
## PROC 'popup_msgVarWithScroll_wait'
##+########################################################################
## PURPOSE: Report error conditions to the user.
##          Sets 'focus' on this toplevel window, does a 'grab',
##          and does 'tkwait' so that execution stops until the
##          user responds to this window.
##
## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg)
##            and a variable holding text (many lines, if needed).
##
## CALLED BY: various procs that need to popup an error message.
##+########################################################################

proc popup_msgVarWithScroll_wait { toplevName VARtext } {

   popup_msgVarWithScroll $toplevName $VARtext
   focus $toplevName
   grab $toplevName
   tkwait window $toplevName

}
## END OF PROC 'popup_msgVarWithScroll_wait'


set HELPtext "\
\ \ \ \ \ ** HELP for this 3D-Model-Examiner Utility **

LOADING A MODEL:

When the GUI comes up, you can use the 'Browse...' button
to find a 3D model file to load.  Then set a radiobutton for
the appropriate 'data-loader' to use.

An MB1-click-release on any of the 'data-loader' radiobuttons
causes 3D model data in the specified filename to be loaded
into 'in-memory arrays' and then be displayed on the 'canvas'.
(MB = Mouse Button)

The selected 'data-loader' will read the file's 3D model data ---
data for points and polygons (and line-segments, if any).

Typically, the polygons in 3D model files are triangles and/or
quadrilaterals (N-gons where N, the number of vertices, is 3 or 4),
but some of these data-loaders will also load data for 2-gons
(line-segments) and N-gons where N is greater than 4.

ROTATING THE MODEL:

Once the model data is loaded, you can use the two rotation angle
'SCALE' (slider) widgets, to quickly change either a 'longitude'
(yaw) angle or a 'latitude' (pitch) angle. These two scales
allow for 'semi-dynamically' rotating the 3D-model by moving the
sliders of the 2 scale widgets.         

Three types of slider moves and the results:

  - 'Grab' the slider button (by clicking on it with MB1 and
    holding) and then drag MB1.  RESULT: When MB1 is released, the
    angle at that point on the scale is used to rotate the model.
or
  - MB1-click-release repeatedly on the TROUGH of the scale
    (to the RIGHT or LEFT of the slider button). RESULT:
    The model is rotated 1 degree per click-release.
or
  - Click on the TROUGH of the scale (to the RIGHT or LEFT of the
    slider button) with MB1 and hold down while the slider button
    moves, then release MB1.  RESULT:  When MB1 is released, the
    angle at that point on the scale is used to rotate the model.

Note that the 'fixed, viewing' axes of this utility are assumed to be
as follows.

- the positive y-axis is 'up' (parallel to the monitor screen)
- the positive x-axis is 'to the right' (parallel to the monitor screen)
- the positive z-axis is 'out of the screen) (perpendicular to the monitor screen)

The longitudinal (yaw) rotation is about the y-axis.
The latitudinal (pitch) rotation is about the x-axis.

---

MAPPING VERTEX-RECORD COLUMNS TO AXES:

There are 3 radiobuttons that allow for mapping the 3 coordinates
of each vertex record to the xyz axes:
123-to-xyz  OR  123-to-zxy  OR  123-to-yzx

This effectively allows the user to change the axes along which
the model was originally built. This can also help with rotating
the model with 2 rotation angles (yaw and pitch) rather than with
3 rotation angles (yaw, pitch, and roll).

---

There are several types of radiobuttons to control the type of
display of the 3D model:

FILL/OUTLINE of polygons: 
   1) fill-only
   2) fill-and-outline,
   3) wire-hidden (i.e. outline-only, with hidden outlines)
   4) wire-show-all (i.e. outline-only, none hidden)

FILL-COLOR SOURCE of polygons:
   1) Polygon-Fill color button (applied to all polygons)
   2) Model file color data (can be different for each polygon
            or for groups of polygons)
   3) random colors (for each polygon)
   4) colors from a color-table (distributed over the polygons
            according to the loaded Z-coordinates of the model;
            intended mainly for use with terrain surfaces)

Type of SHADING of polygons:
   0) none
   1) by depth, along the 'original' z-axis of the model
   2) by depth, along the 'original' y-axis of the model
   3) by depth, along the 'original' x-axis of the model
   4) by depth, along the current view direction
   5) by angle of polygon normals relative to a lighting
      direction

---

'WIRE' DISPLAY OPTIONS:

Clicking on the 'wire-hidden' or 'wire-show-all' radiobutton
causes immediate display of a 'wire-frame' image of the model
--- using the setting of the 'Polygon Outline' color button as
the color of the wire-frame lines.

When either of the 2 'wire' FILL/OUTLINE options are chosen,
the radiobuttons for FILL-COLOR-SOURCE and SHADING are disabled,
i.e. 'grayed-out'.

---

FILL DISPLAY OPTIONS:

When either of the 2 'fill' FILL/OUTLINE options are chosen,
the FILL-COLOR-SOURCE options are activated --- and the
SHADING options may be activated according to user-selection
of a FILL-COLOR-SOURCE option.

---

POLYGON 'PAINTING' (SORTING) OPTIONS:

There are several radiobuttons that allow for calling on different
sort procedures to try in case one procedure is not drawing some
polygons in a proper order --- i.e. if some polygons that should
be hidden (for the most part) are being drawn in front of polygons
that should be in front.

---

Summary of 'EVENTS' that cause a 'REDRAW' of the plot:

MB1-clicking on a 'Data Loader' radiobutton after a filename is placed
in the 3D-model-filename entry field.

MB1-clicking on the 'wire-hidden' or 'wire-nohide' radiobuttons.

MB1-clicking on one of the Shading radiobuttons --- if the 'fill-only'
or 'fill-and-outline' radiobutton is set. (The current setting of
the fill-source radiobuttons determines the color source for the 
fill of each polygon.)

Button1-release on the LONGITUDE or LATITUDE scale widget.

Button1-release on the ZOOM scale widget.

Changing color via the FILL-COLOR or OUTLINE-COLOR buttons.

ALSO: Resizing the window changes the size of the canvas,
which triggers a redraw of the plot according to the new
canvas size.

---

INPUT FILE FORMATS:

Some information on supported records of OBJ, PLY, OFF,
and STL files follows.

-----------------------------------------------------------

###################################
Wavefront 'cOBJ' FILE SUPPORT NOTES:
###################################

Example ASCII 'cOBJ' file records:

We use the abbreviation 'cOBJ' rather than 'OBJ', because this
OBJ file data-loader of this script supports a slight extension
of the Wavefront OBJ file format --- to support specifying
colors for faces --- with 'c' records in the file, rather than
with an external '.mtl' (material) file which contains groups
of color-shading parameters (ambient,diffuse,specular).

The 'c' records allow for putting the 3 RGB color decimals
(between 0.0 and 1.0) --- from the 'Kd' records of a '.mtl'
file --- into the 'c' records of the 'cOBJ' file.


Here is a complete OBJ file for an octahedron model.

v 0 1 0
v 1 0 0
v 0 0 -1
v -1 0 0
v 0 0 1
v 0 -1 0
# 6 vertices
f 6 5 4
f 5 2 1
f 1 2 3
f 1 3 4
f 5 1 4
f 2 5 6
f 3 2 6
f 4 3 6
# 8 faces

Here are records from a 'cOBJ' file for a cube model.

# This cube has a different material 
# applied to each of its 6 faces.
mtllib master.mtl
v 0.000000 2.000000 2.000000
v 0.000000 0.000000 2.000000
... five other 'v' records go here
v 2.000000 2.000000 0.000000
# 8 vertices
g front
usemtl red
c 1.0 0.0 0.0
f 1 2 3 4
g back
usemtl blue
c 0.0 0.0 1.0
f 8 7 6 5
g right
usemtl green
c 0.0 1.0 0.0
f 4 3 7 8
g top
usemtl gold
c 1.0 1.0 0.0
f 5 1 4 8
g left
usemtl orange
c 1.0 0.5 0.0
f 5 6 2 1
g bottom
usemtl purple
c 1.0 0.0 1.0
f 2 6 7 3
# 6 elements

---

Note that the counts of the vertices are based at one, not zero.

The cOBJ-file data-loader of this script supports comment records
indicated '#' in column 1. This cOBJ data-loader will skip over
records that start with '#'.

The only records that this data loader actually uses are
'v' (vertex), 'f' (face), 'l' (line), and 'c' (color) records.

This data-loader skips over other records such as 'vt' (vertex
texture), 'vn' (vertex normal)', 'mtllib' (material library),
'usemtl' (references to groups of material records in an external
material library file), 'g' (group), 'o' (object', 's' (shininess),
and other non-v-f-l-c records.

As this cOBJ data-loader reads the 'v' records, it keeps a count
of the 'points' records (based at one, not zero) and loads the xyz
coordinate data, along with the vertex count, into list-records
of an 'aRpoints' array.

As this cOBJ data-loader reads the 'f' records, it keeps a count
of the 'connectivity' records (based at one, not zero) and loads
the integers from the 'f' records, along with the 'connectivity-count' and
color info (a list of 3 RGB integers or a single material-property-ID
integer), into list-records of an 'aRconnect' array.

As this cOBJ data-loader reads the 'l' records, it increments the count
of the 'connectivity' records (based at one, not zero) and loads
the 2 integers from the 'l' records, along with the integer 2 and
the connectivity-count and color info (a list of 3 RGB integers or
a single material-property-ID integer), into list-records of an
'aRconnect' array.

Any time this data-loader encounters a 'c' record, it uses the
3 decimals between 0 and 1 to compute 3 RGB-255 integers.
Those values are used as the 3-element color-list to put in
any subsequent 'aRpoints' or 'aRconnect' array entries.



-----------------------------------------------------------

##################################
Cyberware 'PLY' FILE SUPPORT NOTES:
##################################

Example ASCII 'PLY' file records:

This is a complete file for a tetrahedron model.

ply
format ascii 1.0
comment created by platoply
element vertex 4
property float32 x
property float32 y
property float32 z
element face 4
property list uint8 int32 vertex_indices
end_header
-1 -1 -1 
1 1 -1 
1 -1 1 
-1.0 1.0 1.0 
3 1 2 3 
3 1 0 2 
3 3 2 0 
3 0 1 3

NOTE: These face records are for triangles and start with
the integer 3 followed by 3 integer vertex IDs (base 0).
Face records starting with 4 (for quads), 5, 6, etc. are
also occasionally seen.

---

Note that there are a variable number of header records
whose end is indicated by a 'end_header' record.

The 'element vertex' record gives the number of vertex
records in the PLY file.

The 'element face' record gives the number of face
records in the PLY file.

Comment header records start with 'comment'. The PLY file
data loader of this script also supports comment records
indicated '#' in column 1. This PLY data-loader will skip
over records that start with '#'.

The integer, say NV, from the 'element vertex' record is
used to read the NV non-comment records following the
'end_header' record and load their xyz coordinates into
list-records of an 'aRpoints' array. Then ...

The integer, say NP, from the 'element face' record is
used to read the NP non-comment records following the
vertex records and load their integers into
list-records of an 'aRconnect' array.

In summary, 4 main types of PLY records are used by this
OFF data loader:
  - an 'element vertex' record (with 1 integer - for number of vertices)
  - an 'element face' record (with 1 integer - for number of polygons)
  - then vertex records
  - then polygon/face records.

If it ever proves necessary to support the 'property' records,
the PLY data loader may be enhanced to use those records.

-----------------------------------------------------------

#################################
Geomview 'OFF' FILE SUPPORT NOTES:
#################################

Example ASCII 'OFF' file records:

In this example there are 3 header records followed by
vertex records and then by face/polygon records.

OFF
#created by dirichlet domain computation.
36   20        54
-0.586233        0.192482        -0.019732
-0.344233        0.301871        0.358301
... more of these vertex records, for a total of 36
0.248355        -0.095223        0.535651
7        3        23        26        13        14        7        8        6
5        27        6        5        1        0        8 
... more of these face/polygon records, for a total of 20
 4        33        32        35        34        14

---

The polygon (N-gon) records of an OFF file can be of essentially
any length. The polygon records look like N+1 white-space-separated
integers --- comprised of the integer N followed by vertex-ID
integers (base zero). Examples:

3  14 51 54             (for a triangle)
4  8 7 10 9             (for a quadrangle)
5  27 6 5 1 0           (for a pentagon)
6         29 20 21 9 10 28    (for a hexagon)
7         32        15        18        25        24        2        35 (for a 7-gon)
...
10   128 48 49 45 46 47 125 126 129 127  (for a 10-gon)

In some types of OFF files, the polygon records may have 
additional entries (integers) after the N integers indicated
by the first integer in the record. Those entries may be
for color or other information. This data loader could be
enhanced to use some of that additional data in polygon records.

Comment records are indicated '#' in column 1. This OFF
data-loader will skip over records that start with '#'.

The OFF dataloader of this script looks for an 'OFF' string
in the first record of the file.

Some OFF files may have the string 'NOFF' or '4OFF' or 'NFF'
in the 1st record of the file.

You can edit an OFF file to change a string like 'NFF' to 'OFF'.
And if there are comment (#) lines above the 'OFF' line, you can
edit the file to move the 'OFF' line above the comments.

The first non-comment record after the 'OFF' record should
be a record containing 3 integers --- for number of vertices,
polygons, and edges in the model. 

The first integer, say NV, is used to read the following
NV non-comment records and load their xyz coordinates into
list-records of an 'aRpoints' array. Then ...

The second integer, say NP, is used to read the following
NP non-comment records and load their integers into
list-records of an 'aRconnect' array.

In summary, 3 main types of OFF records are used by this
OFF data loader:
  - a counts record (with 3 integers - for vertices,polygons,edges)
  - then vertex records
  - then polygon records.

-----------------------------------------------------------

##########################################
Stereolithography 'STL' FILE SUPPORT NOTES:
##########################################

Example ASCII 'STL' file records:

facet normal  0.0   0.0  -1.0    
  outer loop
    vertex    0.0   0.0   0.0    
    vertex    1.0   1.0   0.0    
    vertex    1.0   0.0   0.0    
  endloop
endfacet

---

These are usually the only types of data in an ASCII STL file,
except for a record like 'solid MYSOLID' at the top of the file
and a record like 'endsolid MYSOLID' at the bottom of the file.

The STL dataloader of this script puts each of the 3 vertex records
in an 'aRpoints' data array and assigns each vertex an ID number,
the current vertex count.

Then the STL dataloader makes a polygon record for the 'aRconnect'
array that contains the 3 vertex ID numbers.

Typically, STL files do not contain comment records but this
data-loader will skip over records that start with '#'. Such
records can be used for comments.

In summary, the only STL data records that are actually used
by this STL data loader are the 'vertex' records.

-------------------------------------------------------------

POSSIBLE ENHANCEMENTS:

Eventually some other features may be added to this utility:

- a checkbutton to allow for turning on/off a 'triad' display,
  that indicates the current direction of the original, 'local'
  xyz coordinate axes of the model.

- the ability to pan the model, as well as rotate and zoom it

- the ability to use mouse-motions on the canvas area to
  move the model. Say:  MB1 to rotate, MB2 to zoom, and MB3
  to pan the model.

- the ability to specify a different, arbitrary direction of
  the light source --- for use with the 'polyLighting' radiobutton.

- Depth clipping may be added --- so that the user
  can essentially get section views of the model.

- The 'NASshort' DATA-LOADER radiobutton is currently 'grayed-out'.
  That option may be activated when its data loader proc is tested
  adequately.

- More 'data-loaders' may be added --- to support more
  3D model file types --- ASCII and perhaps some binary.
  Examples: NASTRAN 'free field' format records,
  NASTRAN 'long field' format records, IGES, STEP,
  some types of terrain-elevation files.

- More elaborate shading models may eventually be
  implemented --- to get smoother shading effects
  across polygon edges, and perhaps to get glossy
  effects. (These effects may be easiest to implement
  by using colors assigned to points rather than
  colors assigned to polygons.)

- the list may go on."
   


##+#####################################################
## Additional GUI initialization, if needed (or wanted).
##+#####################################################

## Some mathematical constants/parameters are here.
##
## TOLfactor (times the diameter 'diam' of a model)
## is used as a 'zero-tolerance' check. Numbers less than
## the  'zero-tolerance' may be set to 0.0.

set TOLfactor 0.0001


##########################################################################
## If an argument is passed on the command line, assume it is a filename
## and put it in the ENTRYfilename var as a default.
##########################################################################

set argc [llength $argv]

set FILEname ""

if {$argc == 0} {
   set ENTRYfilename ""
} else {
   set ENTRYfilename [lindex $argv 0]
   # set ENTRYfilename "/tmp/$ENTRYfilename"
}


## Set a default file type.
## (Note: The user must click on one of the 'filetype'
## radiobuttons to load the selected/entered filename.)

  set VARfiletype "cOBJ"
# set VARfiletype "PLY"
# set VARfiletype "OFF"
# set VARfiletype "STL"
# set VARfiletype "NASshort"

## Set a default vertCols-to-xyzAxes mapping.

  set VAR123toxyz "x1y2z3"
# set VAR123toxyz "x2y3z1"
# set VAR123toxyz "x3y1z2"


## Set a default 'z-depth' sort.

  set poly_sort  "avePolyDepth"
# set poly_sort  "maxPolyDepth"
# set poly_sort  "minPolyDepth"


## Set initial FILLOUT radiobutton value.

# set poly_fillout "FILLonly"
  set poly_fillout "FILLoutline"

# disable_fillsrc_radbutts ; disable_shade_radbutts
# set poly_fillout "WIREhide"
# set poly_fillout "WIREnohide"


## Set initial FILLSRC radiobutton value.

  set poly_fillsrc "fromButton"
# set poly_fillsrc "fromFile"
# set poly_fillsrc "fromColorTable"
# set poly_fillsrc "random"

## Set initial SHADE radiobutton value.

  set poly_shade "none"
# set poly_shade "origZheight"
# set poly_shade "origYheight"
# set poly_shade "origXheight"
# set poly_shade "zDepth"
# set poly_shade "byLighting"


## Set a variable for use in the 'toggle_opts' proc.

set OPTS_shownORhidden "shown"


## Set the initial values for the 2 scale widgets that
## set the rotation angles (longitude/yaw and latitude/pitch).
##
## longitude between 0 and 360  (in the zx plane).
## latitude  between -90 and 90 (from zx plane to y-axis).
##
## NOTE: Using the '-variable' option of the
## 'scale' widget may cause unwanted 'auto-repeat'
## behavior of the widget, so we do NOT specify
## variables. We use 'set' and 'get' instead.

if {0} {
## Start out looking at a FRONT VIEW of the model.
.fRscales.scaleLON set 0
.fRscales.scaleLAT set 0
}

if {0} {
## Start out looking at a BACK VIEW of the model.
.fRscales.scaleLON set 180
.fRscales.scaleLAT set 0
}

if {1} {
## Start out looking at an 'ISOMETRIC' VIEW of the model.
.fRscales.scaleLON set 45
.fRscales.scaleLAT set 45
}

if {0} {
## Start out looking at a RIGHT-SIDE VIEW of the model.
.fRscales.scaleLON set 90
.fRscales.scaleLAT set 0
}

if {0} {
## Start out looking at a LEFT-SIDE VIEW of the model.
.fRscales.scaleLON set 270
.fRscales.scaleLAT set 0
}

if {0} {
## Start out looking at a TOP VIEW of the model.
.fRscales.scaleLON set 0
.fRscales.scaleLAT set 90
}

if {0} {
## Start out looking at a BOTTOM VIEW of the model.
.fRscales.scaleLON set 0
.fRscales.scaleLAT set -90
}


## We set the initial value for the 'scaleZOOM'.
## (We can set this so that there is a nice margin
##  around the initial plot.)

  set curZOOM 0.6
# set curZOOM 0.8
# set curZOOM 0.9
# set curZOOM 1.0


## Apply the background color to the canvas.

.fRcanvas.canvas configure -bg "$COLORbkGNDhex"


## We need following command because none of the
## procs in this additional-GUI-initialization section
## call the 'update_colors_label' proc to
## set the color of the color buttons and put
## the hex color values in the colors label. Only the
## color button procs call the 'update_colors_label' proc.

update_colors_label


## From now on, if the canvas is resized, we do an automatic
## redraw via the 'wrap_draw_2D_pixel_polys' proc within the
## 'ReDraw_if_canvas_resized' proc.

set PREVcanWidthPx  [winfo width  .fRcanvas.canvas]
set PREVcanHeightPx [winfo height .fRcanvas.canvas]
## We can COMMENT the following bind TEMPORARILY, to avoid
## canvas-reconfigure issues in testing.
bind .fRcanvas.canvas <Configure> "ReDraw_if_canvas_resized"


SHADING ISSUES

The following 2 images show that when you choose 'fill-only' for the polygons (no outlines), you can sometimes get pretty nice color shading of the models.

3DmodelExaminerGUI_OBJ_roseVase_3360faces_redShadedOnBlack_1024x713.jpg

3DmodelExaminerGUI_PLY_turbineBlades_6618faces_redShaded_716x500.jpg

However, I could not get good shading on the 'gear' model at the top of this page (and the 'dome' image several paragraphs below) --- with any of the 4 height/depth shading options.

There was a groove on the surface of the gear, around the center of the gear. With any of the four height/depth shading options, the groove did not show up very well. However, on taking the OBJ 'gear' model into the 'g3dviewer' program that I have installed on my computer running Ubuntu 9.10 Linux, I was able to see the groove quite nicely. That is because the 'g3dviewer' program is doing the shading by using lighting, rather than height or depth of polygons, to do the shading.

In the 'dome' model, the protuberances on the dome did not shade distinctly from the shading on the dome, with any of the four height/depth shading options. And the rectangular opening in the dome did not shade distinctly from the shading on the dome. This is another example where shading by using lighting would yield better results.

For the initial release of this script, the SHADING option called 'byLighting' is grayed-out (not implemented yet). I plan to enhance this script by using a 'cross_product' proc to take into account the angle that polygon normals make with a light direction --- for simplicity, assuming the light is down the z viewing axis, i. e. coming from the user/viewer.

Then I should be able to get nice shading of the gear model --- as well as nice shading on other models such as car, airplane, boat, and StarWars/StarTrek cruiser models, for which I was unable to get pleasing shading via the height/depth shading options.

Probably the computations involved in computing the cross-product for thousands of polygons will increase the draw time considerably. That is why I did not try implementing that shading method first.


GUI LAYOUT ISSUES

Putting all the non-canvas frames at the top of the GUI, strung out across the GUI, works out well for long horizontal objects --- like cars, airplanes, boats, cows, and the following dome.

3DmodelExaminerGUI_OBJ_dome_3492faces_grayWithWhiteOutlinesOnBlack_718x500.jpg

But for tall objects, like the following lamp-post, it might be better to put many of the frames (like several of the radiobutton frames) into Tk 'labelframes' on the left or right of the GUI.

3DmodelExaminerGUI_OBJ_lamppost_4404faces_grayWithGrayOutlinesOnBlack_1024x713.jpg

For example, here is a layout for Stucky's 3D viewer, in which most of the non-canvas items are on the left of the canvas.

3Dviewer_Stucky_screenshot_839x657.png

(I changed the packing to put controls on the left instead of the top --- and the canvas on the right instead of the bottom --- to get more room for the canvas and waste less space on area around the dials.)

In a future version of my script (above), when I implement and enable several 'grayed-out' options, I may also change the layout of the GUI, so that I can 'square up' the canvas.

If I do that, I will probably put 'left-layout' and 'right-layout' radiobuttons on the GUI that will allow the user to quickly move the left frame to the right, and back. I can do this by using the Tk 'pack forget' command --- as I have done in several other scripts that I have contributed to this wiki.

For example, I used the 'pack forget' technique to switch the color-swatch from right to left, and back, in the color-selector script that I mentioned above --- A non-obfuscated color selector GUI.

Other scripts in which I have used the 'pack forget' technique --- to switch the position of a frame or to show/hide a frame:


THE RANDOM COLOR OPTION

The following two images help illustrate the effect achieved by using the 'random' radiobutton from among the 'fill-color-source' radiobuttons.

The first image shows a bust of Beethoven, in profile, using the fill-and-outline display option --- with color taken from the 'polygon-fill-color' button of the GUI.

3DmodelExaminerGUI_PLY_beethovenBust_4994faces_redFillOutlined_1024x715.jpg

The following image shows what happened when I switched the 'fill-color-source' radiobuttons setting from 'fromButton' to 'random'.

3DmodelExaminerGUI_PLY_beetovenBust_4994faces_randomColor_1024x715.jpg

Actually, this image is almost 'too busy' with colors. The random-colors option may work more nicely on models with fewer polygons.


THE Z-DEPTH POLYGON SORT for PAINTING THE POLYGONS

The following two images show that when there are a lot of polygons in the model and the polygons are not too elongated, the 'painting' of the polygons according to the z-depth sort works out quite nicely --- no significant 'artifacts'.

3DmodelExaminerGUI_PLY_knot_5760faces_547x616.jpg

3DmodelExaminerGUI_PLY_torus_2048faces_wireONgray_539x624.jpg

However, the following crudely-modeled 'head of Dilbert' --- which involves lots of long, 'sliver' polygons --- can lead to poorly hidden polygons.

3DmodelExaminerGUI_OBJ_dilbert_220faces_modelColors_1024x713.jpg

Notice how one of the ear-pieces of Dilbert's glasses disappears under one of the long, sliver-polygons that runs down the side of his face.

One solution to this problem would be to take the OBJ file of this model into a modeler (like Blender or Wings3D) and re-mesh it with more polygons, none of which are highly elongated.

In fact, here are a couple of images that show what nice results you can get when you mesh a model nicely.

3DmodelExaminerGUI_OBJ_soccerBall_1992faces_magentaWithWhiteOutlinesOnBlack_1019x715.jpg

3DmodelExaminerGUI_OBJ_trumpet_11362faces_yellowWithWhiteOutlinesOnBlack_1024x713.jpg

If you look at the meshes of 3D models in a magazine like '3D World', you will see that most modeling professionals use lots of quadrilateral polygons in their models --- especially models of human faces.

Note that there are lots of quadrilaterals in the two models above.


SOME POTENTIAL ENHANCEMENTS:

Eventually some other features may be added to this utility:

** A checkbutton may be added to allow for turning on/off a 'triad' display, that indicates the current direction of the 'local' xyz coordinate axes of the model.

** Add the ability to pan the model, as well as rotate and zoom it

** Add the ability to use mouse-motions on the canvas area to move the model. Say: MB1 to rotate, MB2 to zoom, and MB3 to pan the model.

** Depth clipping may be added --- so that the user can essentially get section views of the model.

** A check-button may be added to allow for 'backface culling', to speed up the plots when lots of polygons are not facing the viewer and perhaps would be hidden --- for example, polygons on the back-side of a sphere or torus or cylinder.

** A check-button may be added to allow for switching to a 'perspective' view, from the current 'parallel projection'.

** We may add the ability to specify a different, arbitrary direction of the light source, for use with the 'polyLighting' radiobutton option.

** The 'NASshort' DATA-LOADER radiobutton is currently 'grayed-out'. That data loader proc may be completed.

** More 'data-loaders' may be added --- to support more 3D model file types --- ASCII and perhaps some binary. Examples: NASTRAN 'free field' format records, NASTRAN 'long field' format records, IGES, STEP, some types of terrain-elevation files.

** More elaborate shading techniques may eventually be implemented --- Gouraud-like (color interpolation) --- to get smoother shading effects across polygon edges, and perhaps to get glossy effects. (These effects may be easiest to implement by using colors assigned to polygon vertex points rather than colors assigned to polygons.)

This would really slow each plot down, but having the option might be worth it, to get a higher quality image --- if it can be done within 10 to 30 seconds for a 2,000 face model, say.

(Unfortunately, there is no color-interpolation-across-polygons option built into the Tk 'create polygon' command for the canvas. I would like to suggest this for Tk 9.0 --- at least in the case when the polygons are triangles --- allow for specifying a different color at each of the 3 vertices --- and interpolate the 3 colors across the triangle, perhaps by using barycentric coordinates. In C code, this might proceed at a usable speed.)

** No doubt, there are some ways to change the calculations and/or procedures to get a significant speed up of the redraws and thus allow for smooth rotation of fairly large models.

If this script is enhanced to allow for 'immediate' rotation according to mouse motion on the canvas, then the rotation will definitely be a bit 'jumpy' --- even for 'small' models on the order of a few thousand polygons. So it behooves one to find ways to speed up the procs in the 'graphics pipeline'.

Note that in magazines like '3D World', you can see models of human faces, where the author is modeling whiskers and pores on the face, where the models contain on the order of 1 million to 100 million polygons (typically quadrilaterals).

It would probably take this script on the order of 4 minutes to load a 1 million polygon model. If Moore's law and the pace of computer-power-increase holds up, maybe we can load a million-facet model in under a minute in a couple of decades (around 2030), with a script like this.

Or maybe we can use the multi-threading features of Tcl-Tk, and computers with at least 8 cores, to load 1 million polygon models in under 30 seconds, now.

Note that, for models containing a million polygons or more, you will probably have to zoom in on the model to see the polygons as more than single pixels --- even if you have a 'retina-display' monitor.

** This enhancements list may be extended.

I may add a few more ideas for enhancements to this script in coming months, as I tackle other 3D utilities.


SCALES RESPONSE

'-repeatdelay 500' and '-repeatinterval 50' parameters are present on all three scales. If the responsiveness of the sliderbar movements is not to your liking (when you press-and-hold mouse-button-1 over a scale's trough --- to rapidly make changes of about 5 to 10 degrees in a view angle), you can change these milliseconds values.


SOME SOURCES OF 3D MODELS

3D model files can get quite large. I assume it is not appropriate to post some examples at this site unless they are quite small.

So here are some sites (available in 2013 Jan) from which you can get a variety of model sizes and types for use with this 3D model examiner.

OBJ files:

  • free ; for software testing ; Burkardt, FSU [L1 ]
  • free ; many different kinds [L2 ]
  • free ; mostly inanimate objects around the home [L3 ]
  • some free and many low-cost ; many different kinds [L4 ]

PLY files:

  • free ; for software testing ; Burkardt, FSU [L5 ]

OFF files:

  • free ; for software testing ; Burkardt, FSU [L6 ]

STL files:

  • free ; for software testing ; Burkardt, FSU [L7 ]

OBJ,PLY,OFF,STL files:

  • meant for testing in HTML links with web browser helper apps [L8 ]

Note that this script has the potential of being a web browser helper app for OBJ, PLY, OFF, STL, and FEA/CAD files.

---

For descriptions of these file formats, here are some links.

OBJ format: [L9 ] [L10 ] [L11 ] [L12 ]

PLY format: [L13 ] [L14 ] [L15 ]

OFF format: [L16 ] [L17 ] [L18 ]

STL format: [L19 ] [L20 ] [L21 ]

Other 3D formats: [L22 ] [L23 ]


NEXT 3D PROJECTS

The three 3D-viewer scripts that I have developed so far have given me a wealth of procs and techniques that will be very useful to me.

The procs of these 3 scripts will be useful for other 3D Tk-script projects --- such as generation-and-viewing of terrain surfaces --- the next 3D project on my to-do list.

In that project, I will be reading in an image file (GIF or PNG), instead of a 3D model file (OBJ, PLY, OFF, STL, etc.).

I will probably offer an option to write out an OBJ file containing the generated terrain elevation data.


IN CONCLUSION

The script on this page may not be as capable or fast as some commercial 3D model file viewers --- but there's plenty to like about a utility that is 'free freedom' --- that is, no-cost and open-source so that you can modify/enhance/fix it without having to wait for someone else to do it for you (which may be never). So ...

A BIG THANK YOU to Tcl-Tk developers and maintainers.


uniquename 2013feb07 UPDATE

I have implemented at least 4 of the items that I had listed as possible enhancements above:

1) Implemented a shade 'byLighting' radiobutton. This gives better color shading than the shading by height/depth options.

2) Added 3 radiobuttons to map vertex-record columns 1,2,3 to x,y,z axes.

This could probably use some explanation. The three radiobuttons provide mappings:

   123-to-xyz  OR   231-to-xyz   OR   312-to-xyz

to provide the ability to specify how the 3 coordinates (columns) of the vertex records will be assigned to the 'local' x,y,z axes of the model.

In the first implementation of this script, if a model like a car or airplane or boat or cow were built with its longest dimension parallel to the y-axis, and if the two viewing angles are set to zero, the model would load with its 'nose' pointing up or down instead of to the right or left --- or front or rear.

To simplify the handling of the internal data arrays, this script always goes back to the original data (translated to a point in the middle of the cloud of points) and applies the two rotation angles to that 'original' data, rather than to the current orientation of the points.

When you rotate a model, say the cow, that was built with its spine parallel to the 'local' y-axis of the model, then it is difficult to get such a model in a good viewing direction (on its feet and facing right, for example) by rotating around the y and x viewing axes. We mainly end up spinning the cow around its spine.

There are several ways to handle this. (1) These 3 mappings, or (2) by rotating the 'current' point coordinates rather than the 'original', or (3) adding a 3rd 'scale' widget for rotation about z ('roll').

I chose option (1) in order to keep the internal data-array handling relatively simple and to avoid adding another scale widget to the GUI (and to avoid adding 'roll' to the rotation mix).

Or, one could make three versions of this script --- :-).

Note that option (1) gives the user a rather unique capability that you do not see in most model viewers --- the capability to change the coordinate axes that were used to originally build the model.

In any case, by allowing the user to load a model by the '231-to-xyz' OR '312-to-xyz' method, we could more easily get a cow (or car or airplane or boat or StarWars/StarTrek cruiser) into a good viewing position.

For example, a cow that loads nose-up in the '123-to-xyz' mapping, would load facing right in the '231-to-xyz' mapping, and would load facing the user in the '312-to-xyz' mapping.

3) Added sort radiobuttons to allow for choice of several sort methods. (The 'compare' procs that implement these sorts could use some additional logic, to handle cases when there is a 'tie' in the comparison of 2 polygons.)

4) Implemented fill-source-color via a 'ColorTable' radiobutton, with colors assigned according to z-height of polygons --- mainly for use with terrain surfaces.

5) Added an option to pass a filename on the command line. This is to facilitate the use of this script as a 'helper application' in a web browser, for viewing 3D model files (OBJ, PLY, OFF, STL).

6) Updated the Help text, for the Help button.

7) Added a 'ToggleOpts' button to the GUI. This hides and re-shows a group of several options frames --- between the data-loader radiobuttons and the longitude-latitude scales --- so that more canvas area can be made available, in the vertical direction, to display the model. The Tk 'pack forget' command is used to implement this rapid hide/show option.

---

After making these changes, I re-tested with OBJ, PLY, OFF, and STL files. In particular, I tested the 'polyLighting' shading option and here are some sample results for an OBJ file (bunny), PLY file (Porsche), and STL file (half of the classic teapot model).

3DmodelExaminerGUI_OBJ_bunny_4968faces_magentaFillOnly_shadeByLighting_788x550.jpg

3DmodelExaminerGUI_PLY_porsche_10474faces_redFillOnly_shadeByLighting_790x550.jpg

3DmodelExaminerGUI_STL_teapot_2016trias_magentaFillOnly_shadeByLighting_787x550.jpg

I still have not implemented the read-NASTRAN (FEA) input option. I have 90% of the code in place. Need to test it and touch it up.

More importantly, I should add the option to show a 'triad', so that it is clearer what is going on with the axes of the model during rotations.

I will probably implement a triad option in another 3D model utility and 'backport' it into this utility.


DRB 2013-04-26 "can't read "maxX": no such variable

    while executing

"expr {$maxX - $minX}"

    (procedure "load_3DfileData_cOBJ" line 385)
    invoked from within

"load_3DfileData_cOBJ"

    (procedure "load_3DfileData" line 6)
    invoked from within

"load_3DfileData"

    (procedure "load-translate-rotate-sort-draw" line 10)
    invoked from within

"load-translate-rotate-sort-draw"

    (command bound to event)"

uniquename 2013aug24

To DRB: You do not say what you used for input. OBJ or PLY or OFF or STL??? And from where??? As I mentioned above, I have tested on many files --- especially OBJ and PLY files --- many from the source sites listed above. Although the trace indicates you were trying an OBJ file, you may have forgotten to specify the proper type with the radiobuttons on the GUI.

Since I tested on so many files (see the note just below the turbine blades STL image above), I would first suspect that you are using a 'corrupt' input file --- one not conforming to the file format specifications. Were you trying a file from one of the source sites above? --- so I could get the one you were using, for a test.


rattleCAD 2013oct18 hi, rattleCAD is tcl but 2D, Do you have an idea how to support rattleCAD with your great work? http://rattlecad.sourceforge.net/ chears Manfred