# BLpython3
This is a Python 3 interface with the Blockland engine. Many of the C++ functions available with TSFuncs are available to Python in addition to anything inside the wide array of default Python libraries.

This acts as a sort of opposite to BlLua: you are given the full power of Python without restriction. Python is in control here, rather than TorqueScript.

There are two goals: speed and power.
1. Carefully-written Python code can be faster than TorqueScript.
2. You can use any library provided in the embedded Python distribution: threads, image processing, date and time, SQLite3, JSON, HTTP(S) client, network sockets, compression, and so on...

This is a beta project so expect some Python errors to be unhandled. Any uncaught exceptions will kill the entire Python engine.

## Installing
Install RedBlocklandLoader (Eagle517) here: https://gitlab.com/Eagle517/redblocklandloader

Create a "modules" folder in your Blockland folder if you don't have one yet.

Copy "BLpython3.dll" and the "python" folder (located in the release folder of this project) to the "modules" folder.

## Re-installing
Delete `modules\BLpytohn\bootstrap.py` because an improved loader was written.

## Security notice
Python scripts are given full permissions - treat any modules as if you were installing another program or DLL.

## Installing modules
Drop the module ZIP file in the folder "modules/BLpython". This folder is not created until BLpython3 successfully runs for the first time.

No modules have been created at the time of writing except for the HelloWorld and FullFeatureTest packages.

## Build dependencies (for developers)
TSfuncs - Now included without minhook code, which is not used by this project - https://gitlab.com/Eagle517/tsfuncs
xxd - converts bootstrap.py and test.py into C arrays for using C++ includes to directly store Python code

## Runtime dependencies (for developers)
Python stable 3 Windows embeddable package (32-bit) - https://www.python.org/downloads/release/python-3107/

Size `7615330 bytes`

MD5 `python-3.10.7-embed-win32.zip 7e4de22bfe1e6d333b2c691ec2c1fcee`

SHA256 `python-3.10.7-embed-win32.zip d5eb57d67c8c74c1362e671933720151a62eaae4437e71dad7b06ddbcdbad0ff`

## Writing modules (for developers)
Note the structure has changed and has been simplified.

A fully-working package is provided in `examples/HelloWorld.zip`

Example ZIP package structure called "packageName":
```
packageName.zip
	packageName/
		__init__.py (executes during DLL load)
			Import modules in the same folder with "from . import <module>"
			"from . import ImportThisClass"
		ImportThisClass.py
			Another module
	README.txt - optional README
```

Modules are executed during Sim::init. Eval does not work and there are no objects. Try to execute code at a later time.

## The API (for developers)
Object functions work based on object IDs. An object ID of zero means an object does not exist.


BLpython module
```
TSgetvar("$Favorite::Brick1_0")
	Get a TorqueScript global variable. Dollar sign is optional.

TSgetvar_int("$Game::argc")
	Get a TorqueScript global variable as an integer. Dollar sign is optional.

TSfindObj("rootGroup")
	Find an object ID by its name. Does not raise TorqueObjectNotFoundError, but will return 0 if object wasn't found.

BlPrint("print this")
	Print through Blockland's streams. Accepts one string argument.

TSregisterFunc()
	Register a TorqueScript function. First argument to the called Python function is the TorqueScript function name.
	Usage: (string)package name, (string)class name, (string)torque function, (callable)python function,
	None|str|int|float|bool, min args, max args
	Can raise TorqueRegisterFunctionError.

TSeval("echo(\"hello from python\");")
	Eval a string

TScall((string)name, arg0... arg19)
	Call a function up to 20 arguments.

TScallSimObj((int)object id, (string)name, arg0... arg18)
	Call a function on an object. Note there can only be 19 arguments beyond the object ID. Can raise TorqueObjectNotFoundError.

TSgetDataField(TSfindObj('MainMenuGUI'), 'vertSizing', None)
	Get a field from an object. The field name is in two parts: slot and array. Can raise TorqueObjectNotFoundError.

TSgetDataField_int(TSfindObj('MainMenuGUI'), 'visible', None)
	Get a field from an object as an integer. The field name is in two parts: slot and array. Can raise TorqueObjectNotFoundError.

TSsetDataField(TSfindObj('MainMenuGUI'), 'visible', None, 0)
	Set a field on an object. The field name is in two parts: slot and array. Can raise TorqueObjectNotFoundError.

```


BLpython module exceptions
```
TorqueObjectNotFoundError
TorqueRegisterFunctionError
```


SimSetLooper module (contains object class of same name, child of BLpython module)
- In the constructor, takes a single integer: the object ID of a SimSet or SimGroup (anything that implements getObject() and getCount() in TorqueScript)
- Constructor can raise TorqueObjectNotFoundError
- Implements iter and iternext (`__iter__` and `__next__`) making this iterable
- The logic now directly works off a SimSet's mElementCount and mArray variables, as if the engine itself were looping through the SimSet

## Examples (for developers)
The example package FullFeatureTest.zip uses most if not all of the features of this program. After installing, it can be initiated with `BLP_FFT_test_all();` in the console.

Create multiple functions accesible through TorqueScript and mess with the different return types:

```
import BLpython

#Keyword arguments "obj" and "args" are mandatory
#obj keyword is provided as a Torque object ID - only relevant in class calls

def PYreturn(obj=None, args=None):
	return

def PYreturnString(obj=None, args=None):
	return "string from python"

def PYreturnInt(obj=None, args=None):
	return 3

def PYreturnFloat(obj=None, args=None):
	return float(5.0)

def PYreturnBool(obj=None, args=None):
	return True

#Make functions callable from TorqueScript (no package and no class, so regular functions)

BLpython.TSregisterFunc(None, None, "PYreturn", PYreturn, None, "help text", 1, 1)
BLpython.TSregisterFunc(None, None, "PYreturnString", PYreturnString, str, "help text", 1, 1)
BLpython.TSregisterFunc(None, None, "PYreturnInt", PYreturnInt, int, "help text", 1, 1)
BLpython.TSregisterFunc(None, None, "PYreturnFloat", PYreturnFloat, float, "help text", 1, 1)
BLpython.TSregisterFunc(None, None, "PYreturnBool", PYreturnBool, bool, "help text", 1, 1)
```

Loop through rootGroup and print each object name:

```
import BLpython

try:
	iterator = BLpython.SimSetLooper(BLpython.TSfindObj("rootGroup"))
	BLpython.BlPrint("rootGroup children:")
	for obj in iterator:
		name=BLpython.TScallSimObj(obj, 'getName')
		name="("+ str(name) +")"
		BLpython.BlPrint("    "+ str(obj) +" = "+ name)
except:
	type, value, traceback = sys.exc_info()
	BLpython.BlPrint('Exception: '+ str(value))

```


