Macro functionality

  • Pass the required functionality:
    call "SomeMacro" parameters all function1 = 3, function2 = 4
    ! instead of the identification of the caller object:
    call "SomeMacro" parameters all iCallingObject = 3
  • Never trust the default parameter values of a macro, set each relevant parameter.
  • Unused return parameters should be identified with a comment. If no returned parameters are used then the returned_parameters block can be omitted.
  • Always return arrays as arrays embedded in a dict.
  • Return closely related parameters in a dict.

Numeric types – precision

Before ARCHICAD 9, all numeric values were stored internally as floating point values resulting in imprecise values. This meant that integer values were – a little – imprecisely stored. From ARCHICAD 9, integers – and hence GDL parameter types that are best described with integers – are correctly stored internally as integers.
Parameter types internally stored as an integer:

  • Integer
  • Boolean
  • Surface
  • Line Type
  • Fill Type
  • Pen
  • Intensity (Light)
  • Light Switch (Light)
  • Building Material
  • Profile
  • Dictionary / Integer

Parameter types internally stored as a floating-point number:

  • Length
  • Angle
  • Real Number
  • RGB Color component (Light)
  • Dictionary / RealNum

GDL variables still do not require type definition, the type is determined during the interpretation from the value to be loaded into the variable. The output of numeric operators now have a type. You should consult the GDL Manual for this information.

In fact, from ARCHICAD 9 warnings are now issued, if the code tries to directly compare floating point values with integer values using the equality operators.

_nValue = 1 * 2
if _nValue = 2 then
    ! valid comparison, it is true, these statements will be executed

_value = 1.5 + 0.5
if _value # 2 then
    ! you never know if it is true, do not trust such comparisons

_value = 1.1 * 2
if _value = 2.2 then
    ! you never know if it is true, do not trust such comparisons

Comparing int and float values

For comparisons of floating-point numbers use a small epsilon value meaning the precision of the comparison. Using <, >, <=, or >= operators could give misleading results without a warning.

It is safe to compare integer types with the relational operators (do not use an epsilon value in this case).

For comparisons of a floating-point number and an integer use the round_int function only when the floating-point value cannot be other than an integer. This can happen in a mixed-typed array, or after using operators with mixed types.

_test = 1E-18 * 1E18       ! mathematically 1, binary almost 1
print _test,               ! prints 1.000000
      _test > 1,           ! prints 1, incorrect
      _test >= 1,          ! prints 1, correct
      _test < 1,           ! prints 0, correct
      _test <= 1           ! prints 0, incorrect

EPS = 0.00001 ! 1/100th mm
if abs(_value) < EPS then ...               ! use instead _value = 0
if abs(_value - 1.5) < EPS then ...         ! use instead _value = 1.5
if _value < 1.5 - EPS then ...              ! use instead _value < 1.5
if not(_value < 1.5 - EPS) then ...         ! use instead _value >= 1.5
if round_int(mixedArray[i][3]) = 3 then ...
if round_int(1.5 * 2) = 3 then ...

Mixed-type arrays

Arrays can contain mixed integer and float values. The type of such array values is not known at compile time. Using a =, # operator on them would result in a warning during script check: “Use of real types can result in precision problems”. Prevent the situation of having to use round_int by keeping the integer values in a separate array or a dictionary.

if iPointType[i] = 3 then hotspot2 point[i][1], point[i][2]      ! use instead of
if round_int(pointInfo[i][3]) = 3) then hotspot2 pointInfo[1][1], pointInfo[1][2]

Arrays in dictionaries behave differently, they cannot contain mixed types, resulting in a run-time error.

Do not use strings for controlling functionality

Because library parts are identified by GUID, not name, many localized versions might exist with the same GUID. If you have string-type controlling parameters, the relevant values will differ between national versions.

For example: if you are unaware of the problem, loading a German plan file with the Danish sibling library will change the generated elements since some control parameters have meaningless values.

The solution is to create an integer type control parameter that shows a string on the UI for each integer value (see VALUES{2}).

The integer values are determinant, and the string descriptions are just an input method for them (therefore localizations can be different, but the true meaning will stay the same). When writing a script, the integer parameter values should be used. The following example code features a detail level integer parameter acting as a string:

! Master script:
dim _stDetlevelDescription[3]
_stDetlevelDescription[1] = _(`Detailed`)
_stDetlevelDescription[2] = _(`Simple`)
_stDetlevelDescription[3] = _(`Off`)

! iDetlevel3D constants
! Parameter script:
values{2} "iDetlevel3D" DETLEVEL3D_DETAILED, _stDetlevelDescription[1],
                        DETLEVEL3D_SIMPLE,   _stDetlevelDescription[2],
                        DETLEVEL3D_OFF,      _stDetlevelDescription[3]

Decimal separators in functions

By default, most versions of STR function output use the decimal separator defined by the operating system (“,” “.”, etc.). This makes the result unpredictable in some cases, which may cause problems if the result is the input for some other function (requests, for example), which will only work correctly with a specified “.” separator (in GDL, all numeric types use “.” as a separator by default).

To make sure you get the desired decimal separator no matter what, use the “^” flag of the STR{2} command in conversions.

Condition grouping

Organize conditions in a way that reflects the purpose of the code, not the parameters in the condition. This might lead to having to repeat certain conditions. We prefer readability over the slight speed increase. (It can still be optimized by storing conditions in variables if speed is critical).
The logical rules will be easier to understand this way, it is harder to write code that misses some cases (especially when parameters depend on each other, typically in GLOB_MODPAR_NAME conditions in a parameter script).

if GLOB_MODPAR_NAME = "foo" | GLOB_MODPAR_NAME = "bar" then
    A = B
    parameters A = B
    B = A
    parameters B = A

if GLOB_MODPAR_NAME = "foo" then
    C = D
    parameters C = D
    D = C
    parameters D = C

! instead of
if GLOB_MODPAR_NAME = "foo" then
    A = B
    C = D
    parameters A = B, C = D
    if GLOB_MODPAR_NAME = "bar" then
        A = B
        D = C
        parameters A = B, D = C
        B = A
        D = C
        parameters B = A, D = C

Connected parameters

Often multiple parameters are connected in a way that the last changed one can modify the others. Express the changed parameters based on the one that initiated the change, not others affected.

if GLOB_MODPAR_NAME = "B" then
    A = B
    halfA = B / 2
    parameters A = A, halfA = halfA
    if GLOB_MODPAR_NAME = "halfA" then
        A = 2 * halfA
        B = 2 * halfA
        parameters A = A, B = B
        B = A
        halfA = A / 2
        parameters B = B, halfA = halfA

! instead of
if GLOB_MODPAR_NAME = "B" then
    A = B
    halfA = B / 2
    parameters A = A, halfA = halfA
    if GLOB_MODPAR_NAME = "halfA" then
        A = 2 * halfA
        parameters A = A
        halfA = A / 2
        parameters halfA = halfA
    B = A
    parameters B = B

Keep parameters unchanged

Do not change parameters or global variables directly, copy them to a variable and change the variable.

_depth = depth
if bSquare then _depth = width
call "rectangle" parameters all,
                 depth = _depth
! instead of
if bSquare then depth = width
                call "rectangle" parameters all

Do not make unnecessary copies

If a variable / parameter is copied but is not changed anywhere in the script, use the original instead of the copy. Subroutine variables mimicking function parameters are an exception.

Do not re-use variables for different purposes

A variable should have only one purpose throughout the script. Do not modify it just to save memory.

Clean up code after functionality changes

When functionality is changed, sometimes conditions are deleted / changed in a way that variables / parameters can be deleted.

Do not use deprecated GDL

Deprecated globals, ac_ parameters and commands are marked in the reference guide. Usually they have some bugs and do not work as expected under all circumstances.

Do not use them in new code and refactor them when modifying old code.

Calling .gdl files is considered deprecated, use macro objects instead. Specify macro parameters by name, not order.

Bittest or array

Packing/unpacking parameters into/from an integer is hard to read, avoid using bittest/bitset and use arrays instead.

dim bTypeEnabled[]
if ... then bTypeEnabled[2] = 1

! instead of
if ... then iTypeEnabled = bitset(iTypeEnabled, 2, 1)

One justified use case is when a macro or subroutine is called often with different flag-like parameters, writing one-line code of constant additions keeps the call compact.

stText = "line1"
gosub "TypeText" _iStyle = 1 + 4 ! bold underline
_stText = "line2"
gosub "TypeText" _iStyle = 2     ! italic

Trigonometry functions

While GDL scripting, you may need various trigonometry functions. The following functions are directly available from GDL: COS, SIN, TAN, ACS, ASN, ATN.

Secant: Sec(X)                    = 1 / cos(X)
Cosecant: Cosec(X)                = 1 / sin(X)
Cotangent: Cotan(X)               = 1 / tan(X)
Inv. Sine: Arcsin(X)              = atn(X / sqr(-X * X + 1))
Inv. Cosine: Arccos(X)            = atn(-X / sqr(-X * X + 1)) + 90
Inv. Secant: Arcsec(X)            = atn(X / sqr(X * X - 1)) + sgn(X - 1) * 90
Inv. Cosecant: Arccosec(X)        = atn(X / sqr(X * X - 1)) + (sgn(X) - 1) * 90
Inv. Cotangent: Arccotan(X)       = atn(X) + 90
Hyp. Sine: HSin(X)                = (exp(X) - exp(-X)) / 2
Hyp. Cosine: HCos(X)              = (exp(X) + exp(-X)) / 2
Hyp. Tangent: HTan(X)             = (exp(X) - exp(-X)) / (exp(X) + exp(-X))
Hyp. Secant: HSec(X)              = 2 / (exp(X) + exp(-X))
Hyp. Cosecant: HCosec(X)          = 2 / (exp(X) - exp(-X))
Hyp. Cotangent: HCotan(X)         = (exp(X) + exp(-X)) / (exp(X) - exp(-X))
Inv. Hyp. Sine: HArcsin(X)        = log(X + sqr(X * X + 1))
Inv. Hyp. Cosine: HArccos(X)      = log(X + sqr(X * X - 1))
Inv. Hyp. Tangent: HArctan(X)     = log((1 + X) / (1 - X)) / 2
Inv. Hyp. Secant: HArcsec(X)      = log((sqr(-X * X + 1) + 1) / X)
Inv. Hyp. Cosecant: HArccosec(X)  = log((sgn(X) * sqr(X * X + 1) + 1) / X)
Inv. Hyp. Cotangent: HArccotan(X) = log((X + 1) / (X - 1)) / 2

Logarithm to base N: LogN(X)      = log(X) / log(N)

Speed Relations

Memory size and processing speed is generally not an issue for GDL scripts, computationally intensive algorithms are rarely needed. The amount of code optimization that can be done in the script is of minimal effect compared to drawing/modeling speeds.

Slow GDL code due to high polygonal count

  • Consider creating a simplified symbol/model for graphical editing with the use of GLOB_FEEDBACK_MODE.
  • Consider creating a simplified symbol/model for the settings/favorite preview with the use of GLOB_PREVIEW_MODE.
  • Views generated from 3D (section, elevation and 2D view with PROJECT2 command) may be slow based on how complex the 3D model is. Consider creating an alternate model for 2D and section/elevation usage with GLOB_VIEW_TYPE.
  • Reduce the number of surfaces to the minimum to make 3D regeneration faster. Use RESOL, TOLER, and RADIUS commands to control the segmentation of curved surfaces. Make sure that hand-made segmentation reacts to resolution settings as well (e.g. circular path for TUBE command, manually given points for COONS, RULED, and other surfaces).
  • High polygon count negatively affects rendering time. Consider using images instead of modeling (e.g. trees, fire).
  • High polygon count also affects the speed of Solid Element Operations negatively. Consider creating an alternate model for SEO context with the use of GLOB_SEO_TOOL_MODE.
  • Try to create closed bodies instead of open ones. Open bodies have double the polygonal count and are also slower in Solid Element Operations

Slow PDF due to GDL-defined vectorial fills

Vectorial fills published to PDF result in a large file size. When defining custom fills, use DEFINE SYMBOL_FILL and place it in a MASTER_GDL_… object so it doesn’t affect background processing. This results in a much smaller PDF file and quicker redraw/pan/zoom.

Slow GDL code due to slow and superfluous script running

  • 3D views are generated in the background when possible, write code that doesn’t disable this optimization.
  • Keep in mind that the Master script runs with each script type, so do not put script-type specific calculations here. If parts of the master script is needed in multiple scripts, but not all of them, use GLOB_SCRIPT_TYPE.
  • When scripting doors and windows, avoid making unnecessary cuts (WALLHOLE and WALLNICHE). This also applies to cutting in general: try to achieve the desired shape with the least number of cuts.
  • When the same complex piece of model has to be placed multiple times, consider creating a group for them and only place the group instead of recalculating the model every time. This is however not advised for simpler models.
  • Try to minimize the usage of string operations. For choices use integer parameters with descriptions (VALUES{2}).
  • Try to avoid transferring string type parameters in macro calls. Use numeric types where possible.
  • Do not add superfluous parameters into macros. They do not have to have all the parameters that the caller object has.
  • Write linear parameter scripts where possible, and set the object to run the parameter script only once (GDL editor/Details/Compatibility Options/Run the parameter script only once).
    • Slow UI regeneration can be caused by multiple reasons:
    • Slow parameter script
    • Reading/writing external files
    • Combined size of the images is too large
    • External images loaded from the library (embedded images are faster as they do not have to be searched for in the folders)
    • Calling subroutines without conditions (this way the whole UI script runs first, then only the required UI page and items get drawn. This means that all of the UI script is interpreted each time). Consider using if conditions before subroutine calls so only the current tabpage’s script is run.
               _stTabTitles[1], _iTabIconID[1] ! uiTab_Content_hl(1)
      if gs_ui_current_page = TABID_CONTENT then gosub "ContentSettingsPage"
      instead of:
              _stTabTitles[1], _iTabIconID[1] ! uiTab_Content_hl(1)
      gosub "ContentSettingsPage"
    • Large number of UI items on tabpage