Data Driven Nodes

Pyjea Nodes are stored in a simple json file format that you can parse from another script, or generate from a script. Generating graphs from data using Python is simple. Each node must be given a unique ID, so in theory if you make a generator script that also reads and parses the json file, you can then stay compatible with hand made graphs (by keeping track of the generated node IDs in your own database).

File Format

Below is a quick glance at the file format.

Scene

			SCENE {
				nodes : [ NODES ]
				edges : [ EDGES ]
			}
			

Node

			NODE {
				id      : unique_id
				inputs  : [ SOCKETS ]
				outputs : [ SOCKET  ]
				content : {}
				op_code : INT
			}
			

Edge

			EDGE {
				start  : unique_id
				end    : unique_id
			}
			

Socket

			SOCKET {
				id     : unique_id
				index  : INT
			}
			

Example 1


https://gitlab.com/hartsantler/pyjea/-/blob/master/lesson9.py
			CUBE_OP_CODE = 83
			INSTANCE_OP_CODE = 82
			ID = 0
			def genid():
				global ID
				ID += 1
				return ID
			Scene = {
				'id' : genid(),
				"scene_width" : 64000, "scene_height": 64000, 
				"nodes": [],
				"edges": [],
			}
			Colors = {
				'R':[1,0,0],
				'H':[0.3,0.3,0.1],
				'B':[0,0,1],
				'Y':[1,1,0],
				'0':[0,0,0],
				'-':[0.8,0.7,0.5]
			}
			Cubes = {}
			y = 0
			for symbol in 'RHBY0-':
				color = Colors[ symbol ]
				cubeid = genid()
				outid  = genid()
				y += 200
				cube = {
					'id':cubeid,
					"title": "Make Cube", 
					"pos_x": -100, 
					"pos_y": y, 
					'inputs':[],
					'outputs':[
						{
							"id": outid, 
							"index": 0, "multi_edges": True, "position": 5, "socket_type": 6
						}
					],
					"content": {
						"object_name": symbol, 
						"object_clr": color, 
					}, 
					"op_code": CUBE_OP_CODE
				}
				Cubes[ symbol ] = outid
				Scene['nodes'].append( cube )
			MyData = [
				'     RRRRR      ',
				'    RRRRRRRRR   ',
				'    HHH--0-     ',
				'   H-H---0---   ',
				'   H-HH---0---- ',
				'   HH----00000  ',
				'     --------   ',
				'  RRRRBBRRR     ',
				'--RRRRBBBRRRR---',
				'--- RRBYBBBBRR--',
				'-- BBBBBBBBBB   ',
				'  BBBBBBBBBBBB  ',
				' BBBBB    BBBB  ',
				'HHBBB      BBB  ',
				'HHHH       HHHH ',
				' HHHHH     HHHHH',
			]
			y = 0
			for ln in MyData:
				x = 0
				y += 200
				for char in ln:
					x += 150
					if not char.strip():
						continue
					instance_id = genid()
					instance_input = genid()
					node = {
						"id": instance_id, 
						"title": "Instance", 
						"pos_x": x, 
						"pos_y": y, 
						"inputs": [
							{
								"id": instance_input, 
								"index": 7, "multi_edges": False, "position": 2, "socket_type": 6
							}
						], 
						"outputs": [
							{
								"id": genid(), 
								"index": 0, "multi_edges": True, "position": 5, "socket_type": 6
							}
						], 
						"content": {
							"object_name": '%s_%s' %(char,instance_id), 
						}, 
						"op_code": INSTANCE_OP_CODE
					}
					Scene['nodes'].append( node )
					edge = {
						"id": genid(), 
						"edge_type": 1, 
						"start": Cubes[char], 
						"end": instance_input
					}
					Scene['edges'].append( edge )
			import sys, os, json, subprocess
			data = json.dumps( Scene )
			open('/tmp/mario.json', 'wb').write(data)
			if '--blender27' in sys.argv:
				subprocess.check_call([os.path.expanduser('~/panjeaplugin/panjeaplugin.py'), '/tmp/mario.json', '--blender27'])
			else:
				subprocess.check_call([os.path.expanduser('~/panjeaplugin/panjeaplugin.py'), '/tmp/mario.json'])
			

Example 2

Generating so many instance nodes like in Example1 is generally a bad idea, because we do not need to expose all of those options for each cube. It is better to use the Tile Node for this case.


https://gitlab.com/hartsantler/pyjea/-/blob/master/lesson10.py
			TILE_OP_CODE = 100
			rtf = []
			for ln in MyData:
				new_line = u''
				for char in ln:
					if char in Colors:
						r,g,b = Colors[char]
						r = int(r*255)
						g = int(g*255)
						b = int(b*255)
						new_line += u'<span style="color:rgb(%s,%s,%s)">█</span>' %(r,g,b)
					else:
						new_line += u'░'
				rtf.append( new_line )
			tile = {
				"id": 2, 
				"title": "Tile", 
				"pos_x": 5.0, 
				"pos_y": -122.0, 
				"inputs": [],
				"outputs":[],
				"content": {
					"value": '<br/>'.join(rtf),
					"name" : 'mario.tile'
				},
				"op_code" : TILE_OP_CODE
			}
			Scene['nodes'].append( tile )
			

The code above takes the same ASCII art drawing and converts it to Rich Text Format (RTF), replacing each letter in the Colors mapping with the full block symbol █. The RTF format is HTML compatible, and can be copy and pasted into a word processor like LibreOffice, where it can be edited, and then copy-pasted back into the Tile Node inside Pyjea.

RTF Mario

░░░░░█████░░░░░░
░░░░█████████░░░
░░░░█████░░░░░
░░░██████░░░
░░░█████████
░░░███████████░░
░░░░░████████░░░
░░█████████░░░░░
████████████████
█████████████
████████████░░░
░░████████████░░
█████░░░░████░░
█████░░░░░░███░░
████░░░░░░░████
█████░░░░░█████

Above, the RTF version of Mario, note: this is copy and paste compatible with word processors. Below, Pyjea will parse the RTF and convert it to 3D objects in Blender, note: the full block symbol █ will create a cube.

Example 3

Above in Example2, the full block symbol █ is hardcoded into the script, this is bad design. Instead it would be better to pass the unicode character as a command line argument to the script, this will allow us to quickly test all the symbols supported by the Tile Node.

			SYMBOL = sys.argv[-1].decode('utf-8')
			assert len(SYMBOL)==1
			
https://gitlab.com/hartsantler/pyjea/-/blob/master/lesson11.py

python lesson.py ⭖


The ⭖ symbol creates a wide metaball. Metaballs can also be made with: ⦿⦾⭗

python lesson.py 🐵