Go to QuArK Web Site
Introducing QuArK development
Updated 27 Feb 2022
Upper levels:
QuArK Information Base
3. Advanced customization

 3.1. Introducing QuArK development

 [ Prev - Up - Next ] 

 Index


 Introduction

Tiglari - 27 Feb 2022   [ Top ] 

QuArK development can be carried out at three levels:

  • the *.qrk configuration files
  • the Python code
  • the Delphi code.

The first two require just a text-editor, since a functioning QuArK installation includes the Python interpreter, while the last requires Delphi (Borland's version of Pascal, enhanced with visual development tools). The current developers are using versions 7; version 6 will also work, and with minor modifications (mainly replacing references to the `StringUtils' module to `SysUtils') Delphi 5 can also work. Free versions can sometimes be acquired from computer magazine CD's. The Delphi code is written in a mixture of French and English, and the French is slowly being converted to English (so there is a possibility that some references to things in the Delphi here will be outdated).

Total conversions can typically be supported by creating new *.qrk files in the form of `addons', while supporting a new game usually requires some Delphi coding (mostly because each game tends to introduce some quirks into the texture and map formats, and the read/write code for these needs to be reasonably fast). Interface ideas on the other hand can often be supported just in Python, with maybe a little bit of Delphi to support a new `specific type' in a form (if you have a bright idea and think you need a new specific type, some Delphi coder might well be able to provide it).

Central to everything is the concept of `QObject', QuArK's universal format for the storage and management of structured information. Maps, game information supplied in add-ons, and the descriptions of most of the dialog boxes and other data-entry windows in QuArK are all described as QOjects. The .Qrk files are just QObjects presented in a written format for QObjects, and understanding them is essential for following the Python and Delphi as well.

A QObject is a thing (technically an instance of a class in Delphi) with two kinds of properties:

  • A list of attributes called `Specifics', each with a value, which might be a string, a number, a sequence of numbers or a few other things.
  • A list of `subelements' (SubElements in the Delphi, .subitems in the Python), each of which is just another QObject.

So a face is a QObject with a `v' specific of 9 numbers indicating the face and texture position information (the location of the corner and two ends of the Cyan L), and a `tex' specific giving the name of the texture. You can see the specifics of a map object by hitting the second button over the multi-page panel (it's usually only interesting for entities).

And a poly (brush) is a QObject whose list of subitems are the non-shared faces of that poly (shared faces appear in the subitem list of the group containing that poly). And so on.

There's one special specific that should always be there, and maybe shouldn't be thought of as a specific at all, and that is `name'. A QObject's name is divided into two parts, shortname and extension. The shortname is usually just an identifying label (tho for entities it's the classname), but the extension, separated by a dot or colon, specifies the kind of object. The dot separator is used for types of QObjects that can plausibly be stored in a file of their own (so they are in effect a file-type), while the colons are for smaller types of objects, such as the different components of a map.

Here are some of the more important extensions:

  • .qctx - Quake Context. Contains info that modifies how things are done
  • .qtx - Toolbox. Collection of objects that can be chosen from and inserted into something.
  • .qtxfolder - folder in .qtx
  • :form - specifies a format for a window whereby info about something is entered into QuArK. To support a mod, you'll have to get familiar with the :form object associated with each entity; these are found in the entity forms.qctx of the .qrk
  • :f - face
  • :p - poly (brush; faces in subitems thereof, shortname arbitrary label)
  • :g - group (contained faces, brushes & entities in subitems, shortname arbitrary label)
  • :b - brush entity (attached brushes in subitems, entity attributes in specifics, shortname = classname)
  • :b2 - (quadratic) bezier patch or quilt
  • :e - point entity (entity attributes in specifics, shortname = classname)

Because each specific has a unique identifying name, the order of the specifics list doesn't matter, but the subelements don't have labels; for map objects, they are displayed in the tree-view, and the order is as given. In principle you could make something depend on it, tho I don't think anyone ever has).

It might be useful to note that QObjects are sort of like a limited form of XML, with no character data interspersed between subelements.

With QObjects in hand, we push on to .qrk's.


 .Qrk files

Tiglari - 03 Mar 2019   [ Top ] 
ToDo: *add a rundown of the major components of a game-support file and an addon file.

.qrk files are just QObjects stored in files. There are actually two kinds of .qrk files, text and binary (both unfortunately with the extension .qrk). The text files can be read an edited with a text-editor (although the format is initially forbidding), whereas the binaries are much more compact, and allow `delay loading' which helps QuArK manage big ones with less chugging.

To get a text version of a binary *.qrk, just open it, and save then save it (as `structured text', with the extension .txt to keep the file system happy). The `technical info' zips for the older QuArK versions, and the current development snapshots, contain the text versions of the *.qrk files, which furthermore tend to contain useful information such as comments on the syntax, etc.

A (text format) .Qrk begins with the line `QQRKSRC1', which says that it's a QuArK text format file. Then there can be comment lines beginning with `//' (everything after `//' on a line is ignored), whitespace and blank lines etc. until we get down to business with a line whose first nonblank character is:

'{'.

'{' opens a QObject. We already know that QObjects have two `Specifics' (name-value pairs), and `Subitems', which are QObjects which will have a `name' specific. So it's not too surprising that the rest of a QObject will consist of a series of pairs:

  <Name> = <Value>

(where <Value> can be as large an expanse of text as you like, if it's a great big QObject)

Until we fetch up at the end with the closing `}', after which the file must end.

So let's look at the beginning of the Portal of Praevus add-on (I started as a Hexen II entity guy, so we gotta have some Hx2 content here ...)

QQRKSRC1
// This file has been written by QuArK Version 5.0.b5
// It's the text version of file: Praevus.qrk
// Portal of Praevus add-on by tiglari@hexenworld.com
//   to be added on to datah2.qrk (data file for Hexen II)
//
// The Textures need to be further subsorted into wall, floor/ceiling,
//  button, etc; if you want to do this please do, it's not rocket
//  science!

{
  QuArKProtected = "1"
  Description = "Portal of Praevus Data"

    praevus directory infos.qctx =
    {
        Game = "Hexen II"
        SourceDir = "PORTALS"
//      GameDir = "fortress"
    }

After the opening guff, we see that the first specific is `QuArKProtected'. This will cause a warning to pop up whenever QuArK tries to save/overwrite this file (only use this if the file is considered part of QuArK). 'QuArKProtected''s value is a string, enclosed in quotes. There are several other schemes for representing values, such as Hex codes, which we'll get into shortly.

Then comes our first subelement. Its Name specific will be `praevus directory infos.qctx'. Its shortname is `praevus directory infos' and its extension is .qctx, so it's a Quake Context, and modifies how things are done (here where to look for info about the game). This particular QObject has two ordinary string-specifics, and then ends. So it goes on with more subelements until the final '}' on line 6108.

So that's the basic idea, but there is a bit more to it than that.

First, in addition to strings, there are several additional types of specific-values. Binary data is introduced with `$'. So for example the line from the Defaults.qrk file prescribing the red default color for tags is:

  Tag        = $FF0000

(a standard RGB color code, for those new to such things)

Multiline strings can be specified as sequences of ordinary "-bounded strings separated by `$0D':

  Hint = "This ought to work"
      $0D"  if it doesn't, why not try extreme skiing?"

And sequences of numbers are bounded by ', a bounding box for example might be specified as:

     bbox = '-8 -8 -8 8 8 8'

All this and some more stuff that I haven't seen exemplified is prescribed in the function ConstructObjsFromText in the file QkFileObject.

* ToDo: sections on the major components of the major kinds of .qrk file would go nicely here *

Forms: For game supporters, one of the most important kinds of components of .qrk files are :forms, since these control how the specifics of entities are entered into maps. :forms are also important in Python coding, since they are used to control in making dialog boxes, etc.

To get a sense of how it goes, here's the beginning of the entity forms specifications from DATAH2.QRK:

   Entity forms.qctx =
   {  // ENTFRM
     Ring_Flight:form =
     {
      Help = "Ring of Flight"
      bbox = '-8 -8 -44 8 8 20'
      mdl = "models/ringft.mdl"
     more: = {
       Typ="B"
       Txt="triggering"
       Cap="Push"
       Form="item_triggers:form"
       Hint="specifics for triggering when item is taken"
     }
      spawnflags: =
        {Txt="&"
         Typ="X1"
         Cap="FLOAT"
         Hint="item doesn't drop to floor when spawned  "}
     }
     Ring_Regeneration:form =
     { ...
     ....

Entity Forms is a Quake Context, its subitems are a (big) list of :form objects. These have some specifics (all QObjects, regardless of type, can have specifics freely added to them), and then comes a list of subitems. Each subitem is a little window, `control' or `widget' (GUI geekspeak) whereby some attribute of the entity can be set by the editor. I need to call them something, so I'll call them `fields'.

The way fields work is a bit devious. They have names like `more:', `spawnflags:', etc. (colon, null extension). Then inside the brackets are listed further specifics, of which the most important is `Typ'. Typ determines what kind of control is used; X1 for example is a checkbox that sets the first bit position (flag) in an integer. Usually the name determines what attribute of the entity is being set, so the `spawnflags' subitem sets the the first bit position of the `spawnflags' attribute of the entity. But not always; `more:' above is just a label.

Another important specific of fields is `Txt'. This is the `label' that appears to the left of the data-entry widget itself. If it's "&", then the name of the field is used as the label, so if you look at entity specifics in the map editor, you'll see the name of the various entity attributes on the left, their values on the right (in the data-entry widgets). Their are various further field specifics, "Cap" for additional text (appearing on the right) and "Hint", for flyover hints, and whatever more the Delphi code for that kind of Typ happens to support. So the :more button calls up an additional window for entering further specifics, its form is given as the value of the Form specific.

Next is a large list of all the different Typs (programmers might like to know that they're defined in a big cascade of Case statements in prog/FormCfg.pas:TFormCfg..wmMessageInterne; once you've got the hang of it, it's not too hard to add new ones, without fully understanding everything that's going on). Browsing in the text versions of the game support files, especially dataq2.qrk, is a way to get started learning them.


 Guide to Typ's

cdunde - 13 Aug 2021   [ Top ] 

Typ's are used in all kinds of forms but the most common are Dialog Boxes. These are placed within triple double quotes  """  and ends with them also, because it's a multi-line string.
A very important thing to know and remember about this is how to use a Python defined variable within this triple double quotes area.

First the variable, which can also be a stored setting, must be defined before and outside of the above area like this:

    NbrOfLines = "10"

Then that variable can be used within the triple double quotes area like this:

    mesh_shader: = {Typ="M"
                    Rows = """ + chr(34) + NbrOfLines + chr(34) + """
                    Scrollbars="1"
                    Txt = "mesh shader"
                    Hint="Contains the full text of this skin texture's shader, if any."$0D
                         "This can be copied to a text file, changed and saved."
                   }

The  chr(34)  items, which is for a single double quote, must be used like this to avoid confusion with the triple double quotes.

Here is a list of the most common used Typ's in .QRK :form definitions.

Almost all components have some standard specifics you can use:

Hint - displays a help message when the cursor hovers over the item. You can have spread it on several lines.
A valid text can look like this:

Hint = "Help text"$0D0D"How to spr"
       "ead text on several lines."

The dollar sign is used to enter one or more special/invisible characters. In this case two new-line characters.
It is also valid to cut the text off and begin a new line if it becomes too long.
The above Hint will display:

Help text

How to spread text on several lines.

Txt - defines the label. Labels are special in QuArK. If you set their value to "&" they will display the name of the component.
So if you have defined a component like this:

name: = { Typ="E" Txt="&" }

you will get 'name' as caption for this entry field. This is often used for the labels of entity specifics.

SelectMe - set this to one and the component will become selected when its window/dialog is displayed.
This should be used on the first component on a dialog only, as it may be confusing to have input directed to a component in the middle from the beginning.

Note: The first character of Typ works as a flag. If you use a lower-case character the button/entry field will fill all available space its caption (Txt) does not occupy.

Typ'sEntry fields
description
Examples and
Comments
E

E R
Entry field.
This is the default type, if Typ="..." is not given.
Hint: Use "E R" to make it read-only. Use it for simple text entries for read-only text displays.
targetname: = { Typ="E" Txt="&" }
target: = { Txt="&" }
readonly: = {
    Typ="ER"
    Txt="Output:"
    Hint="Displays the current memory usage."
    }
Specifics: 
only common specifics (see above)
EN

ENR
Entry name field.
The last "R" (at third position) means Read/Only.
Contains the name of the object.
classname: = { Typ="ENR" Txt="classname" }
Specifics: 
only common specifics (see above)
EF

EFR

EF[0..0]

[x]
Entry field containing Float(s).
The last "R" (at third position) means Read/Only.
EF is special in the way that you can choose how many values you have to enter and how many digits they should have. To set the number of digits after the decimal dot add zeros after EF. To make the field accept more than one value add the number of values at the end.

NOTE:
Do NOT use this for entities, it will not be exported to the .map file. Use Typ="E" above instead.
three: = {
    Typ="EF003" 
    Txt="The three values:"
    Hint="You must enter three values here. They have"
         "an accuracy of two digits."
    }
greater_than_zero: = { Typ="EF00" Txt="Value:" Min="0.01" Hint="Enter positive value here." }
Important:  To have a dialog round values to the desired accuracy you have to use the LiveEditDlg class. Look at some Python scripts to find out how it works.

Specifics:  Min/Max - set minimum or maximum values with this.
ED

EDL
Entry field containing Directory-path (with browse button).
The last "L" (at third position) means only the last foldername of the path is to be used.
SourceDir: = {
    Typ="ED"
    Hint="Full directory-path"
    CheckFile="Game.exe"
    }

GameMod: = {
    Typ="EDL"
    Hint="Modification folder"
    }
Specifics:  CheckFile - set this and the dialog will only enable the Ok button if it finds this file in the chosen directory.
ET Entry field containing Texture-name (with browse button).
Opens the texture-browser, if the browse button is pressed.
Texture: = {
    Typ="ET"
    Hint="Choose texture"
    }
QuakeTexture: = { Typ="ET" Hint="Choose a Quake texture" GameCfg="Quake 1" }
Specifics:  GameCfg - set one of the short game names here to make QuArK switch to this game when the browse button is pressed. You can find these names in the Defaults.qrk.
EP Entry field containing Path and File-name (with browse button).
Opens a file-dialog, so the user can choose a filename.
QBSP1: = { Typ="EP" Txt="Path to QBSP" DefExt="exe" }
LIGHT1: = { Typ="EP" Txt="Path to QRAD3" DefExt="exe" }
soundfile: = {
    Typ=&quot;EP&quot;
    Txt=&quot;Select a file&quot;
    DefExt=&quot;WAV&quot;
    BasePath=&quot;$Game\basedata\sound&quot;
    CutPath=&quot;$Game\?\sound&quot;
    AugPath=&quot;sound&quot;
    }
EP
specifics
This is a list of specifics that effect the path handling for the Typ="EP" above.
1) Typ="EP" opens the file browser window.
2) Txt="Path to QBSP" gives the title at the top of the window.
3) DefExt="md2; *.map; *.pcx" sets the type of file/files to accept.
                          Set it to "*" if file type does't matter.
4) BasePath="$Game" sets the Directory that the browser window
                           will start with.
                      $Game will be converted to the game
                           directory... for example "C:\Quake2".
5) CutPath="$Game\?\" will be cut off the beginning of the
                           full path string.
                      $Game is the same as above.
                      ? means that there HAS to be a directory.
                      * means that there may or may not be a
                           directory and only stands for ONE
                           directory. For example
               "$Game\?\sprites\*\" accepts all of the following:
                           C:\Quake2\baseq2\sprites\abc.spr
                           C:\Quake2\xyz\sprites\xyz\abc.spr
                      but not:
                           C:\Quake2\baseq2\sprites\a\b\c\abc.spr
6) DirSep="/" Quake games use '/' as path separator. Using this
                   specific will convert all the "\" to "/".
                   Many games don't use the Windows directory
                   separator '\'. Instead they use '/'.
7) AugPath="../ "  will augment the modified path and add ../ to
                           it at the beginning. The DirSep specific
                           above does not effect this specific,
                           so proper coding should be used.
EQ Entry field with direction button.
Displays entry field for two floating point entries and 4-way directional button as pressed gives floating point entries.


offset: = { Txt = "Offset" Typ="EQ" Hint="x, y offsets" }
EU Entry field with increase/decrease button.
Displays entry field for single digit entry and increase/decrease button as pressed gives single digit entry.


tilt: = { Txt = "Tilt" Typ="EU" Hint="`tilt' angle, in degrees." }
K Key entry field.
Use this if you want to define keyboard shortcuts or otherwise need info in form of a key.
KeyLeft: = {Typ="K" Txt=" turn left"}
T

TF
Slider bar with sliding button. (only top portions shown)
T returns integer, TF returns float value when slider released.

Continuous values can be applied by example code below:
def macro_slider(self, value):
    editor.Root.currentcomponent['control_slider'] = str(value)
def macro_slider2(self, value):
    editor.Root.currentcomponent['control_slider2'] = str(value)

quarkpy.qmacro.MACRO_slider = macro_slider
quarkpy.qmacro.MACRO_slider2 = macro_slider2
 Specifics: 
 Txt = "control slider"
 Hint = "Contains text" # optional.
 Min = "-1000" # shown if 'Labels' below is used.
 Max = "1000" # shown if 'Labels' below is used.
 Steps = "50" # increment of values between Min & Max.
 TickFreq = "5" # number of 'Steps' between tick marks.
 Labels = "1" # To display Min & Max settings above.
 ShowValue = "1" # To display value below slider bar.
 Macro="slider" # To use macro code (shown on left) for continuous value use as slider is moved.
---- Choices Examples and Comments
X
[value]
Checkbox,
which sets the bits according to <value>.
The same specificname may appear, with different <values>, so each bit in the specific can be controlled individually.
Usually used together with a Cap="...", to tell what the bit controls.
spawnflags: = { Typ="X1" Cap="Ambush" Txt="&" }
spawnflags: = { Typ="X2" Cap="On Trigger" Txt="&" }
spawnflags: = { Typ="X128" Cap="X Axis" Txt="&" }
option: = { Typ="X" Cap="on/off" Txt="An option:" }
Specifics:  Cap - Like Txt, but to the right of the checkbox. Clicking on the text will toggle the option.
C

CL
Combo box.
A predefined list of choices and their values. To use this type, the items  and values  must also be set, as can be seen in the example.
Each choice in the list will be indicated by a newline ($0D), except for the last choice!

"CL" will create a list only without an entry field. You have to set items here and you should also use values.
mylist: = { Typ="C" Txt="&"
    items =
        "Choice 1" $0D
        "Choice 2" $0D
        "Choice 3"
    values =
        "1" $0D
        "2" $0D
        "3"
    }
Specifics: 
items - The list will be filled with these items. Separate them with a new line character ($0D).
values - In many cases a program can not work with the displayed items. To have a more 'computer friendly' version you can set this specific. This list must be as long as the items list. Most used are numbers or names of variables.
Rows - This specifies the maximum number of items to show in the dropdown list.
---- Buttons & misc. Examples and Comments
B Pushbutton.
Usually used to activate a second spec/arg view (a :form), which contains other spec/args thats more specialized for some purpose.
more: = { Typ="B" Txt="Specialize"
    Cap = "Push..."
    Form = "specialize_this:form"
    }
Specifics: 
The Cap="..." controls the text in the pushbutton.
L
[4]

LI
[4]

LN
[4]

LP
[4]
Color-picker pushbutton.
A pushbutton which shows the choosen color, and when activated, will bring up a color dialog.
If only Typ="L" is given, the resulting RGB values will be in range 0 - 255.
If Typ="LI" is given, the resulting RGB values will be in range 0 - 255 and will be packed into a single integer.
If Typ="LN" is given, the RGB values will be normalized to range 0.0 - 1.0.
If Typ="LP" is given, you will only be able to pick from the game's palette (i.e. Quake palette)
_color: = { Typ="L" Txt="&" }
_light: = { Typ="LN" Txt="&" }
light: = { Typ="L4" Txt="&" }
_lightbright: = { Typ="LN4" Txt="&" }

//To view the exact values, create a
//second spec/arg pair
color: = { Txt="&" Typ="L" } //Pushbutton
color: = { Txt="&" } //Entryfield

Typ = "LI" 
The "LI" type works like "L", except that the values are stored in a single integer that can be directly used for canvas.pencolor functions and similars.
Specifics: 
If the additional 4 is given, for example Typ="LN4" or Typ="L 4", there will be created a fourth value, which controls the brightness of the light. This however, must be changed using a second spec/arg pair, with Typ="E".
M Message button.
extedit:tbbtn = {
    Typ = "M"
    Hint = "call external editor"
    Msg = "EXTE"
    Icon = $6677777777777776600000000000007660FFFFFFFFFFF07660
               FFFFFFFFFFF07660F7
         $F33FFFFFF07660FFF3333FF7F07660F7F333118FF07660FFFF
             331118807660FFFF91
         $1111807760FF7FF91111107760FFFFFF9111117660FF877FF9
             11111660FFFFFFFF91
         $111160F777787F79111660FFFFFFFF7F911660FFFFFFFF7069
             666000000000066666
    Rows = "10"
    Scrollbars = "V"
    }
Specifics: 
Msg - Message to send.
Cap - Caption of the button/menu.
Icon - Hex raw-data of a 16*16 pixel 16 colors icon (same as for Typ "I").
Rows - Set this to the string number value of rows of text to display at once (max:35, default: 3).
Scrollbars - Set this to add scrollbars to the control.
                      Three values are available: (default: No scrollbars)
    -"H": The control will have a horizontal scrollbar.
    -"V": The control will have a vertical scrollbar.
    -"1": The control will have both a horizontal AND a vertical scrollbar.
Any other setting will result in no scrollbars.
P Python macro button. generates a button to fire a python-macro.
centering: = {
    Txt = "&"
    Typ = "P"
    Macro = "usercenter"
    Cap = "push"
    Hint = "Push to add a user center"
    }
Specifics: 
Cap - button caption
Macro - macro to be fired. Most of the items called in the macro are pre-coded in the source code.
But using the quarkx.externaledit(obj) function, any object, such as text or a texture image,
can be passed to it causing that object to be opened in an external editor.
PM Button array,
same as above, for multiple buttons. Each button gets one character out of Cap as its caption.

While all buttons will fire the same script they will also send their index so the script can identify the exact button.
buttons: = {
    Typ = "PM"
    Num = "2"
    Macro = "fixview"
    Caps = "IF"
    Txt = "Actions:"
    Hint1 = "Inspect the chosen one"
    Hint2 = "Fix the chosen one"
    }
Specifics: 
Macro - macro to be fired
Count - number of buttons
Caps - enter on character for each button here
Hint1 - What button 1 does
Hint2 - What button 2 does etc...
F Font dialog button
with preview
.
Font: = {
    Typ = "F"
    Txt = "Plain text"
    Cap = "Plain text sample"
    }
Specifics: 
Cap - button caption (will be displayed with the selected font)
I Icon display.
Displays an icon.
planes: = {
    Txt = &quot;Brush icon&quot;
    Typ = "I"
    Icon = $66000000000000066687777777775506668FFFFFFFFFF
               506668FF557FFFFF706
           $668FFF55577FF706668FFFF500F77706668FFFFF00FFF
               706668FF002200FF706
           $668F5990AA00F706668F5F90AA000706668FF55AAA200
               506668FFF2BAA270006
           $6685FF222227000666855FFFFFF7860066888888888866
               056666666666666666
    }
Specifics: 
Icon - 16x16 pixel icon. As in the example each character represents the hex. code of a pixel (16 colors). This is raw data, don't include any file header!
S Separator.
sep: = { Typ="S" }

 Understanding OOP

Tiglari/cdunde - 05 Apr 2018   [ Top ] 

1. Intro

OOP, or object-oriented programming, is a programming methodology developed in the 70s that became popular in the 80s for building large and complex programs, such as especially GUI applications, of which QuArK is a fine instance. Popular OOP languages include C++, Java, Object Pascal/Delphi and Python. There are both subtle and obvious differences between the approaches taken by all of these languages, but there is also a lot of commonality, such that someone already familiar with OOP from the first three mentioned languages can probably pick up what they need to know about Python OOP from the documentation and QuArK code.

2. Basic Objects

An object is basically a chunk of the computer's memory that has been organized to provide storage for some data, and methods for changing it and reading it. So it's like a 'struct' in C, or a 'record' in Pascal, but more powerful. For a simple example that's not totally irrelevant to a geometry-managing program such as QuArK, suppose we have circles to manage. A circle can be characterized by the three coordinates of its center, and by its radius:

  class Circle:
      x = 1
      y = 0
      z = 5
      r = 1.5

(Important: watch your letters, they are case sensitive.)

Or you can just create the object by writing:

  class Circle:
      pass

and add the data later:

  Circle.x = 1
  Circle.y = 0
  Circle.z = 5
  Circle.r = 1.5
The '.' separates the name of the class itself from its 'attributes'.

Whichever you've done, you can get the data back later (Delphi users take note: there's no 'with' statement, since Guido van Rossum, Python's creator, finds them harmful to programs' maintainability). So if either of the above is in a file 'circles.py', in the Python interpreter you can write:

>>> import plugins.circles         [you type]
>>> plugins.circles.Circle.r       [ditto]
1.5                        [Python answers]

Note that Python is much more liberal than C++, Object Pascal/Delphi, or Java, since in these languages you'd have to declare your object as belonging to a 'type', and say what kinds of 'data members' it could have, in advance of using it.

This rather minimalistic sort of class has its uses (for example as a package for passing bundles of data round in the 'live edit dialog' facility (LiveEditDlg in quarkpy.dlgclasses), but to make classes really worthwhile several closely related new ideas are needed, first instances and methods, and later, inheritance.

2. Methods and Instances

A method is basically a function, closely associated with an object, for doing something involving its data (adding data, reading it, performing some calculation involving it and returning the result, or whatever). For example having created a circle, we might want to access its circumference and area; the idea is that we'll write things like

answer =  plugins.circle.circumference()
(plugins because that is the folder in which the circles.py file will be located.)

(Parentheses because this is going to be produced by a kind of function, and functions are marked in Python by parentheses.)

But to do this in a sensible way we need to distinguish two slightly different notions, 'circle' as a particular object, and 'circle' as a template for making objects. Different circles will have different centers and radii, but use the same method to make them. So we need a way to define both a template and a method for making things that fit it. Templates for objects are what 'classes' are really supposed to be, and the methods for making particular objects, 'instances', are called 'constructors'.

In Python, constructors all have the same name, '__init__': the double underscores on either side are a signal that something with special meaning for Python is involved. All methods, including constructors, are defined as functions internal to the class, so their definitions start with 'def'. And these functions have 'self' as their first argument, which always refers to the object (being created, if the function is a constructor). So the revised definition for the circle class might look like this:

import math

class Circle:
    def __init__(self, x, y, z, r):
        self.x = x
        self.y = y
        self.z = z
        self.radius = r

    def circumference(self):
        return self.radius*math.pi

    def radius(self):
        return self.radius
The constructor attaches the three coordinates and the radius as 'data members' of the object self it creates, and there are two methods to write this; the second is included to help people who can't remember whether to write circle.radius or circle.radius() (pretty much anybody who's focused on content rather than punctuation). We've used uppercase for the name of our class, in accord with a sensible but not always obeyed convention to use uppercase to start 'template' names, lowercase for 'instance names'.

At this point, we have completed the creation of our class. Because we write this code in a text editor we need to save it as a Python file, in this case as 'circles.py', and include 'import math' (because we have used its pi function in our 'def circumference') in front of the 'circles' definition, before we save it. We then need to move the 'circles.py' file to the plugins folder. As a matter of good coding practice, the 'import' and 'from' statements are usually grouped together at the beginning of the Python file.

At this point we could continue to add more code, to this file, to apply (or use) the 'class Circle' and its definitions we just created, or we can create another Python 'plugin' file and 'import circles' to there, which is what we will do now just to demonstrate how files can pass data and functions.

So we will now write another simple 'plugin' Python file (and save that to the plugins folder as well) with the following code to see the effect of the Circle class in the circles.py file:

import quarkpy.qmacro   # to bring in qmacro functions we need
import quarkx           # to bring in quarkx functions we need
from circles import *   # to bring in all of our circle class

def printcir(self):
    circle = Circle(1,0,3,1.5)  # assigns values to the attributes
    circle.circumference()      # passes the values to circle class
    answer = circle.circumference()  # collects the 'return' data
    print answer                # prints the answer to the console
This will pass the three coordinates of 1,0,3 (for x,y,z) and the radius of 1.5 to the Circle class which will process it then 'return' the 'answer' of 4.71238898038 that will be printed to the console.

But before we can save this file, we need to add some code to create a menu item, that we can use to activate (or call) the 'printcir' function:

def WindowClick(self):
    import quarkpy.mapoptions   # brings in the 'Options' menu
    printcir(self)              # calls our function
    quarkx.console(1)           # brings the console into view

quarkpy.mapoptions.items.append(quarkpy.qmenu.item("&Print Cir.", 
WindowClick)) # adds it to the 'Options' menu
Now we can save this 'plugin' file as well, calling it something like ' mapcircles.py ', to the plugins folder. You can either cut-n-paste these scripts to a text editor to create your own files as we have discussed, or use the ones provided here, OOP_examples.zip, of working examples for this tutorial (and others) in the 'zips' sub-folder of your QuArK-help folder on your hard drive.

For a concrete example of some rather basic classes, look at the two kinds of menu items defined in quarkpy.qmenu, 'item', and 'popup'. These classes have only constructors, and no other methods, and all the constructors do is attach the same parameters as data members, and add in some extra default stuff, such as 'normal' as a value of 'state'. Note that functions, such as 'onclick', can be attached to objects by constructors (but these aren't methods).

3. Inheritance

The final thing we need for a real OOP system is inheritance, which is basing classes on other classes. In a real geometry-managing program we'd have lots of different shapes, with various features. For example points wouldn't have radius or circumference (perhaps), but they would have a 'center' or 'origin', like all other figures. So we might define:

class Figure:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def origin(self):
        return self.x, self.y, self.z
Now we might want at least two 'derived classes', or 'subclasses', Point and Circle, so we might write something like this:
class Point(Figure):
    pass

class Circle(Figure):
    def __init__(self, x, y, z, r):
        Figure.__init__(self, x, y, z)
        self.radius = r

    def circumference(self):
        return self.radius*math.pi

    def radius(self):
        return self.radius
The Point class is strictly cosmetic: there's a sensible opinion to the effect that subclasses ought to be more specialized than their parents, not just different (golden lab is a good subclass of dog, but not of cat). Circles aren't kinds of points, but the point class doesn't add anything to our Figure concept (at least yet). So we define it as a subclass of Figure with no new content; note that the 'base class' (Figure) is put in parentheses.

A derived class 'inherits' all methods and data members from its parent, so we can write something like:

>>>from Figures import *
>>>point = Point(1,1,1)
>>>point.origin()
and get
(1,1,1)
in reply.

In addition to being inherited, methods (and data members) can be 'overridden': if the derived class defines a method or data member with the same name as one of the parent class, then the definition in the derived class is normally used rather than in the parent, for instances of the derived class. This is illustrated with the second constructor __init__() of class Circle with the code line of Figure.__init__(self, x, y, z) as shown above in the script.

However, sometimes we need to get at the parent method, which is also illustrated in this constructor: when one name is put in front of another with '.', we say that the first name is a qualifier of the second. So to get at the parent's definition, we qualify the method name with the name of the parent class, but when we do this we have to provide the 'self' argument explicitly. Indeed, any method of any class can be used in this way, qualifying it with the class name and providing the first argument.

So what is the difference between a method and a function that just happens to have been attached to an object, such as the onclick function for qmenu.item? First, only methods participate in inheritance, and second, only methods can be called via the shortcut technique with an instance as qualifier, and automatically supplied as first argument (but you could write something like Circle.circumference(mycircle) instead of mycircle.circumference(), if you wanted to).

Unfortunately I haven't been able to find any really simple example of a base class and some subclasses in the QuArK code. Some important ones are the dialog boxes, base class dialogbox in quarkpy.qmacro, and the handles, base class GenericHandle in qhandles.py. But there's lots of stuff in them. An example of a subclass of dialogbox is SimpleCancelDlgBox defined in quarkpy.qeditor; you can find further subclasses of SimpleCancelDlgBox by searching for 'CancelDlgBox)' (the final parenthesis picks up that it's being used as a base class in a class statement). There is much more to Python classes than this; it wouldn't be a bad idea to look over the classes section in the tutorial, although there's a fair amount of stuff that wouldn't make much sense to beginners (but also some reasonably straightforward examples.

4. Classes, Builtin types and Extension types.

There's one more basic topic that really has to be covered, which is the difference between classes, builtin types, and extension types. Classes are the things we've just covered, whereas builtin types are the basic types such as int, string etc. provided by Python. A basic difference between them is that you can't add random new stuff to a builtin type. For example, if you write:

myinteger = 2
myinteger.tag = 'this cool one I just invented'
you'll get an error, whereas:
mycircle = Circle(0,0,0,3)
mycircle.tag= 'this cool one I just invented'
will work fine (assuming you've imported the definition of Circle, etc.).

Extension types are types which have been defined in extensions to Python, which in the case of QuArK Python will have been written in Delphi. Some important extension types for QuArK Python are vector and qobject, created by calls such as:

myvector = quarkx.vect(1, 0, 10)
face = quarkx.newobj("top:f")
Like classes, extension types have methods (followed by ()) and data members (not followed by ()), but unlike classes, and like builtin types, you can't attach random stuff to them. (Note: in recent versions of Python, these differences have been largely eliminated, but rewriting QuArK to take advantage of this would be a major enterprise). The various extension classes that Delphi provides to QuArK Python are described in the quarkx section of the infobase.

An especially noteworthy feature is, that the QObject type in particular is defined by means of a rather large Delphi class hierarchy; Delphi classes are also OOP, but different in various ways from Python. The 'suffixes' to the string argument passed to quarkx.newobj (":f", ":b", etc.) determine what Delphi type is used to make the instance of the extension class, and this then determines what data members and methods are available from Python (e.g. .normal is available for faces (suffix ":f"), but not polys (suffix ":p").



Copyright (c) 2022, GNU General Public License by The QuArK (Quake Army Knife) Community - https://quark.sourceforge.io/

 [ Prev - Top - Next ]