├── pyno ├── nodes │ ├── __init__.py │ ├── identity.py │ └── timeSinus.py ├── imgs │ ├── corner.png │ ├── save_load_32.png │ └── corner_w_trans.png ├── examples │ ├── blank.pn │ ├── sub_pass.pn │ ├── sub_add2.pn │ ├── sub_add2-2.pn │ ├── test-sub_pass.pn │ ├── test-sub_add2.pn │ ├── test-sub_add2-2.pn │ ├── old-style │ │ └── many_batch_graphics.pn │ ├── serial_monitor.pn │ ├── simple_graphics.pn │ ├── flow_control_delay.pn │ ├── particles.pn │ ├── mouse_keyboard.pn │ ├── welcome.pn │ └── ping-pong.pn ├── __init__.py ├── __main__.py ├── initialCode.py ├── runner.py ├── utils.py ├── process.py ├── fileOperator.py ├── menu.py ├── processor.py ├── node.py ├── draw.py ├── serializer.py ├── sub.py ├── field.py ├── element.py ├── codeEditor.py └── window.py ├── tests ├── test_element.py ├── conftest.py └── test_window.py ├── docs └── screenshots │ ├── edit.png │ ├── error.png │ ├── fibo.png │ ├── plus.PNG │ ├── start.png │ ├── render.png │ ├── big_patch.png │ ├── fibonacci.png │ ├── particles.png │ ├── wiki │ ├── get.PNG │ ├── save.PNG │ ├── set.PNG │ ├── change.PNG │ ├── old_change.PNG │ ├── old_save.PNG │ └── old_framediff.PNG │ └── mass_render.png ├── .travis.yml ├── .gitignore ├── LICENSE ├── utils ├── resave.py ├── oldFile.pn └── newFile.pn ├── setup.py └── README.md /pyno/nodes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_element.py: -------------------------------------------------------------------------------- 1 | """Tests for pyno.element""" 2 | 3 | pass 4 | -------------------------------------------------------------------------------- /pyno/imgs/corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/pyno/imgs/corner.png -------------------------------------------------------------------------------- /pyno/nodes/identity.py: -------------------------------------------------------------------------------- 1 | def identity(x=None): 2 | return x 3 | 4 | call = identity 5 | -------------------------------------------------------------------------------- /docs/screenshots/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/edit.png -------------------------------------------------------------------------------- /docs/screenshots/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/error.png -------------------------------------------------------------------------------- /docs/screenshots/fibo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/fibo.png -------------------------------------------------------------------------------- /docs/screenshots/plus.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/plus.PNG -------------------------------------------------------------------------------- /docs/screenshots/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/start.png -------------------------------------------------------------------------------- /pyno/imgs/save_load_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/pyno/imgs/save_load_32.png -------------------------------------------------------------------------------- /docs/screenshots/render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/render.png -------------------------------------------------------------------------------- /pyno/imgs/corner_w_trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/pyno/imgs/corner_w_trans.png -------------------------------------------------------------------------------- /docs/screenshots/big_patch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/big_patch.png -------------------------------------------------------------------------------- /docs/screenshots/fibonacci.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/fibonacci.png -------------------------------------------------------------------------------- /docs/screenshots/particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/particles.png -------------------------------------------------------------------------------- /docs/screenshots/wiki/get.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/wiki/get.PNG -------------------------------------------------------------------------------- /docs/screenshots/wiki/save.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/wiki/save.PNG -------------------------------------------------------------------------------- /docs/screenshots/wiki/set.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/wiki/set.PNG -------------------------------------------------------------------------------- /docs/screenshots/mass_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/mass_render.png -------------------------------------------------------------------------------- /docs/screenshots/wiki/change.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/wiki/change.PNG -------------------------------------------------------------------------------- /docs/screenshots/wiki/old_change.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/wiki/old_change.PNG -------------------------------------------------------------------------------- /docs/screenshots/wiki/old_save.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/wiki/old_save.PNG -------------------------------------------------------------------------------- /docs/screenshots/wiki/old_framediff.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honix/Pyno/HEAD/docs/screenshots/wiki/old_framediff.PNG -------------------------------------------------------------------------------- /pyno/examples/blank.pn: -------------------------------------------------------------------------------- 1 | [{'x': 122, 'parent': 47, 'type': 'field', 'code': "'blank'", 'y': 511, 'connects': [], 'size': (93, 30)}] -------------------------------------------------------------------------------- /pyno/examples/sub_pass.pn: -------------------------------------------------------------------------------- 1 | [{'connects': [], 'x': 122, 'y': 511, 'parent': 17, 'code': "'blank'", 'type': 'field', 'size': (93, 30)}] -------------------------------------------------------------------------------- /pyno/__init__.py: -------------------------------------------------------------------------------- 1 | """Pyno: Python-based data-flow visual programming.""" 2 | 3 | from .runner import run 4 | 5 | __version__ = '1.2.0' 6 | -------------------------------------------------------------------------------- /pyno/__main__.py: -------------------------------------------------------------------------------- 1 | """Provide a way to run pyno using the Python interpreter directly.""" 2 | 3 | from .runner import run 4 | 5 | run() 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | install: 5 | - sudo apt-get install freeglut3-dev 6 | - pip install . 7 | script: 8 | - xvfb-run --server-args="-screen 0 1024x768x24" pytest 9 | -------------------------------------------------------------------------------- /pyno/nodes/timeSinus.py: -------------------------------------------------------------------------------- 1 | S['timer'] = 0 2 | 3 | def timeSinus(): 4 | import math 5 | 6 | S['timer'] += G['dt'] 7 | result = math.sin(S['timer']) 8 | return result 9 | 10 | call = timeSinus 11 | -------------------------------------------------------------------------------- /pyno/initialCode.py: -------------------------------------------------------------------------------- 1 | node = '''def newNode(a=0, b=0): 2 | result = a + b 3 | return result 4 | 5 | call = newNode''' 6 | 7 | open_node = '''path = 'nodes/identity.py' 8 | 9 | with open(path) as file: 10 | exec(file.read())''' -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """This contains e.g. fixtures that supply reusable elements.""" 2 | 3 | import pytest 4 | 5 | import pyno.runner 6 | 7 | 8 | @pytest.fixture 9 | def window(): 10 | """Supply a window, as that will probably be useful quite often.""" 11 | return pyno.runner.create_window() 12 | -------------------------------------------------------------------------------- /tests/test_window.py: -------------------------------------------------------------------------------- 1 | """Tests for pyno.window""" 2 | import pyglet 3 | 4 | from pyno import runner 5 | 6 | 7 | def test_app_run(): 8 | pyglet.clock.schedule_once(lambda x: pyglet.app.exit(), 0.1) 9 | runner.run() 10 | 11 | 12 | def test_get_window(window): 13 | # Check some meaningful things 14 | assert window.caption == 'Pyno' 15 | 16 | 17 | def test_saving_consistency(window): 18 | # Load some file n times then save and check equality 19 | pass 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | bin/ 29 | *.spec 30 | .pytest_cache/ 31 | 32 | screens/ 33 | graphics/ 34 | 35 | *.nja 36 | .auto-saved.pn 37 | 38 | .vscode/ 39 | .idea/ 40 | -------------------------------------------------------------------------------- /pyno/runner.py: -------------------------------------------------------------------------------- 1 | import pyglet 2 | 3 | from .window import PynoWindow 4 | 5 | 6 | def run(): 7 | print('Loading...') 8 | create_window() 9 | pyglet.app.run() 10 | 11 | 12 | def create_window(): 13 | config = pyglet.gl.Config(double_buffer=True, depth_size=0, 14 | stencil_size=0, aux_buffers=0, 15 | samples=1) 16 | 17 | try: 18 | window = PynoWindow(config, filename='.auto-saved.pn') 19 | except: 20 | # if config is crashed run more default one 21 | print('Runnig using default config...') 22 | window = PynoWindow(pyglet.gl.Config(), filename='.auto-saved.pn') 23 | 24 | return window -------------------------------------------------------------------------------- /pyno/examples/sub_add2.pn: -------------------------------------------------------------------------------- 1 | [{'connects': [{'input': {'put': {'name': 'b'}}, 'output': {'put': {'name': 'output'}, 'node': 22}}, {'input': {'put': {'name': 'a'}}, 'output': {'put': {'name': 'output'}, 'node': 21}}], 'color': (100, 130, 117), 'parent': 20, 'type': 'node', 'code': 'def add(a=0, b=0):\n result = a + b\n return result\n\ncall = add', 'x': 556, 'size': (300, 150), 'y': 411}, {'connects': [], 'size': (70, 30), 'parent': 21, 'type': 'field', 'x': 499, 'code': '29', 'y': 520}, {'connects': [], 'size': (70, 30), 'parent': 22, 'type': 'field', 'x': 618, 'code': '9', 'y': 521}, {'connects': [{'input': {'put': {'name': 'input'}}, 'output': {'put': {'name': 'result'}, 'node': 20}}], 'size': (70, 30), 'parent': 23, 'type': 'field', 'x': 556, 'code': '38', 'y': 310}] -------------------------------------------------------------------------------- /pyno/utils.py: -------------------------------------------------------------------------------- 1 | import platform 2 | from random import randint 3 | 4 | win = platform.system() == 'Windows' 5 | font = 'Consolas' if win else 'DejaVu Sans Mono' 6 | 7 | def random_node_color(): 8 | return (randint(80, 130), 9 | randint(80, 130), 10 | randint(80, 130)) 11 | 12 | 13 | def x_y_pan_scale(x, y, pan_scale, wscale): 14 | s = pan_scale 15 | 16 | x = int((-wscale[0] / 2 + x) / s[1] - (-wscale[0] / 2 + s[0][0])) 17 | y = int((-wscale[1] / 2 + y) / s[1] - (-wscale[1] / 2 + s[0][1])) 18 | return x, y 19 | 20 | 21 | def centered(init, width, count, number): 22 | if count > 1: 23 | c = count - 1 24 | return (init - width / 2) + width * number / c 25 | else: 26 | return init 27 | 28 | 29 | def point_intersect_quad(point, rect): 30 | if (min(rect[0], rect[2]) < point[0] < max(rect[0], rect[2]) and 31 | min(rect[1], rect[3]) < point[1] < max(rect[1], rect[3])): 32 | return True 33 | return False 34 | -------------------------------------------------------------------------------- /pyno/examples/sub_add2-2.pn: -------------------------------------------------------------------------------- 1 | [{'type': 'node', 'x': 556, 'y': 411, 'size': (341, 185), 'color': (100, 130, 117), 'code': 'from typing import *\n\ndef add(a=0, b=0) -> Tuple[Any, Any]:\n result = a + b\n result2 = a - b\n return (result, result2)\n\ncall = add', 'connects': [{'output': {'node': 23, 'put': {'name': 'output'}}, 'input': {'put': {'name': 'a'}}}, {'output': {'node': 24, 'put': {'name': 'output'}}, 'input': {'put': {'name': 'b'}}}], 'parent': 22}, {'type': 'field', 'x': 493, 'y': 520, 'size': (70, 30), 'code': '5', 'connects': [], 'parent': 23}, {'type': 'field', 'x': 618, 'y': 521, 'size': (70, 30), 'code': '8', 'connects': [], 'parent': 24}, {'type': 'field', 'x': 480, 'y': 292, 'size': (70, 30), 'code': '13', 'connects': [{'output': {'node': 22, 'put': {'name': 'result 0'}}, 'input': {'put': {'name': 'input'}}}], 'parent': 25}, {'type': 'field', 'x': 631, 'y': 297, 'size': (70, 30), 'code': '-3', 'connects': [{'output': {'node': 22, 'put': {'name': 'result 1'}}, 'input': {'put': {'name': 'input'}}}], 'parent': 26}] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Fyodor Shchukin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyno/process.py: -------------------------------------------------------------------------------- 1 | from .serializer import Serializer 2 | from .fileOperator import FileOperator 3 | 4 | 5 | class Process(): 6 | ''' 7 | Abstract process 8 | ''' 9 | 10 | def __init__(self): 11 | self.running = -1 # -1: run continously, 0: pause/stop, n: do n steps 12 | 13 | self.serializer = Serializer(self) 14 | self.file_operator = FileOperator() 15 | 16 | self.nodes = [] 17 | 18 | self.global_scope = {} # local space for in-pyno programs 19 | self.global_scope['G'] = self.global_scope # to get global stuff 20 | 21 | def nodes_update(self): 22 | if not self.running: 23 | return 24 | if self.running > 0: 25 | self.running -= 1 26 | 27 | for node in self.nodes: 28 | node.reset_proc() 29 | 30 | for node in self.nodes: 31 | node.processor() 32 | 33 | def new_pyno(self): 34 | for node in self.nodes: 35 | node.delete(fully=True) 36 | del node 37 | self.nodes = [] 38 | print('New pyno!') 39 | 40 | def save_pyno(self, filepath=None): 41 | data = self.serializer.serialize(self.nodes) 42 | return self.file_operator.save(data, filepath=filepath, initialfile=self.filename) 43 | 44 | def load_pyno(self, filepath=None): 45 | data, self.filename = self.file_operator.load(filepath) 46 | if data is None: # Loading data failed 47 | return None 48 | elif data: 49 | self.new_pyno() 50 | return self.load_data(data) 51 | 52 | def load_data(self, data, anchor=(0, 0)): 53 | nodes = self.serializer.deserialize(data, anchor) 54 | for node in nodes: 55 | self.nodes.append(node) 56 | return nodes 57 | -------------------------------------------------------------------------------- /pyno/fileOperator.py: -------------------------------------------------------------------------------- 1 | from tkinter import Tk, filedialog 2 | 3 | class FileOperator(): 4 | ''' 5 | File operations using filedialog if no filepath given 6 | ''' 7 | 8 | def load(self, filepath=None): 9 | if filepath: 10 | path = filepath 11 | else: 12 | root = Tk() 13 | root.withdraw() 14 | path = filedialog.askopenfilename(filetypes=( 15 | ('Pyno files', '*.pn'), 16 | ('All files', '*.*'))) 17 | root.destroy() 18 | try: 19 | filepath = open(path, 'r') 20 | except Exception as ex: 21 | print('Can\'t load file:', ex) 22 | return None, path 23 | data = filepath.read() 24 | filepath.close() 25 | print('File', path, 'loaded!') 26 | return data, path 27 | 28 | def save(self, data, filepath=None, initialfile='pyno_file.pn'): 29 | if filepath: 30 | path = filepath 31 | else: 32 | root = Tk() 33 | root.withdraw() 34 | path = filedialog.asksaveasfilename(defaultextension='pn', 35 | initialfile=initialfile, 36 | filetypes=( 37 | ('Pyno files', '*.pn'), 38 | ('All files', '*.*'))) 39 | root.destroy() 40 | try: 41 | file = open(path, 'w') 42 | except Exception as ex: 43 | print('Can\'t save file:', ex) 44 | return False 45 | file.write(data) 46 | file.close() 47 | print('File', path, 'saved!') 48 | return True -------------------------------------------------------------------------------- /pyno/examples/test-sub_pass.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": 0.3 4 | }, 5 | { 6 | "type": "field", 7 | "x": 499, 8 | "y": 520, 9 | "size": [ 10 | 70, 11 | 30 12 | ], 13 | "code": [ 14 | "10" 15 | ], 16 | "connects": [], 17 | "parent": 44 18 | }, 19 | { 20 | "type": "field", 21 | "x": 481, 22 | "y": 291, 23 | "size": [ 24 | 93, 25 | 30 26 | ], 27 | "code": [ 28 | "10" 29 | ], 30 | "connects": [ 31 | { 32 | "output": { 33 | "node": 46, 34 | "put": { 35 | "name": "output_0" 36 | } 37 | }, 38 | "input": { 39 | "put": { 40 | "name": "input" 41 | } 42 | } 43 | } 44 | ], 45 | "parent": 45 46 | }, 47 | { 48 | "type": "sub", 49 | "x": 475, 50 | "y": 414, 51 | "size": [ 52 | 300, 53 | 150 54 | ], 55 | "color": [ 56 | 109, 57 | 118, 58 | 126 59 | ], 60 | "code": "pyno/examples/sub_pass.pn", 61 | "connects": [ 62 | { 63 | "output": { 64 | "node": 44, 65 | "put": { 66 | "name": "output" 67 | } 68 | }, 69 | "input": { 70 | "put": { 71 | "name": "input_0" 72 | } 73 | } 74 | } 75 | ], 76 | "parent": 46 77 | } 78 | ] -------------------------------------------------------------------------------- /pyno/menu.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | import pyglet 3 | 4 | from .draw import uiGroup 5 | 6 | 7 | class Menu: 8 | ''' 9 | Patch controls (time control, save-load) 10 | ''' 11 | 12 | offset = 10 13 | 14 | def __init__(self, window): 15 | self.window = window 16 | img = pkg_resources.resource_stream('pyno', 'imgs/save_load_32.png') 17 | save_load_img = pyglet.image.load('dummyname', file=img) 18 | self.save_load = pyglet.sprite.Sprite( 19 | save_load_img, 20 | x=800-save_load_img.width-self.offset, y=self.offset, 21 | batch=window.batch, group=uiGroup) 22 | 23 | self.save_load.opacity = 100 24 | 25 | def click(self, x, y, button=1): 26 | if self.update(): 27 | s = self.save_load 28 | # RUN/PAUSE 29 | if x < s.x + (s.width / 3): 30 | if button == 1: 31 | if not self.window.running: 32 | self.window.running = -1 # -1: run continously 33 | else: 34 | self.window.running = 0 # 0: pause/stop 35 | elif (button == 4) and not self.window.running: 36 | self.window.running = 1 # n: do n steps 37 | self.window.nodes_update() 38 | return True 39 | # SAVE 40 | if x < s.x + (s.width * 2 / 3): 41 | self.window.save_pyno() 42 | # LOAD 43 | else: 44 | self.window.load_pyno() 45 | return True 46 | 47 | def update(self): 48 | self.save_load.x = self.window.width - self.save_load.width - self.offset 49 | s = self.save_load 50 | if s.x < self.window.mouse[0] < s.x + s.width and \ 51 | s.y < self.window.mouse[1] < s.y + s.height: 52 | s.opacity = 255 53 | return True 54 | s.opacity = 100 55 | return False 56 | -------------------------------------------------------------------------------- /utils/resave.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This script will convert old *.pn file to new one. 3 | 4 | New versions will have 'call' binding, and return type annotations. 5 | This script will fill this gaps automatically, using 'parser' from old Pyno snapshot. 6 | 7 | Usage: 8 | resave.py < oldFile.pn > newFile.pn 9 | 10 | ''' 11 | 12 | import sys 13 | import json 14 | 15 | importTyping = 'from typing import *' 16 | imports = importTyping 17 | 18 | # You can add additional imports if necessary 19 | 20 | # importPyglet = 'import pyglet' 21 | # imports = importPyglet + '\n' + importTyping 22 | 23 | def returnAnnotation(names): 24 | return 'Tuple[' + ', '.join(map(lambda x: '"' + x + '"', names)) + ']' 25 | 26 | def main(): 27 | data = sys.stdin.read() 28 | 29 | try: 30 | nodes = json.loads(data) 31 | except ValueError: 32 | nodes = eval(data) 33 | 34 | for node in nodes: 35 | code = node['code'] 36 | 37 | name, outputs = None, None 38 | 39 | # https://github.com/honix/Pyno/blob/4fa27a4335c6841e6c4a6bc29e69d399625e36d5/node.py#L43 40 | def_pos = code.find('def') 41 | if def_pos > -1: 42 | bracket = code[def_pos:].find('(') 43 | if bracket > -1: 44 | name = code[def_pos + 3:def_pos + bracket].strip() 45 | 46 | column = code[def_pos:].find(':') 47 | 48 | ret_pos = code.rfind('return') 49 | if ret_pos > -1: 50 | outputs = tuple(x.strip() 51 | for x in code[ret_pos + 6:].split(',')) 52 | 53 | if outputs: 54 | code = (code[:def_pos + column] + ' -> ' + returnAnnotation(outputs) + ' ' + 55 | code[def_pos + column:]) 56 | 57 | if name: 58 | code = (imports + '\n\n' + 59 | code + '\n\n' + 60 | 'call = ' + name) 61 | 62 | node['code'] = code 63 | 64 | print(json.dumps(nodes, indent=4)) 65 | 66 | 67 | if __name__ == '__main__': 68 | main() -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from os import path 3 | 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | 7 | # Get the long description from the README file 8 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 9 | long_description = f.read() 10 | 11 | # Keep version string only in one place to avoid duplication 12 | # Read the string from code without importing to avoid problems at setup 13 | with open(path.join('pyno', '__init__.py')) as init: 14 | for l in init.readlines(): 15 | if l.startswith('__version__'): 16 | version = l.split('=')[1].strip(" '\r\n") 17 | 18 | setup( 19 | name='Pyno', 20 | # TODO: "pyno" already exists on PyPI, maybe rename? 21 | version=version, 22 | license='MIT', 23 | description='Python-based data-flow visual programming', 24 | long_description=long_description, 25 | long_description_content_type='text/markdown', # Optional (see note above) 26 | url='https://github.com/honix/Pyno', 27 | author='Fyodor Shchukin', 28 | author_email='ted888@ya.ru', 29 | # For a list of valid classifiers, see https://pypi.org/classifiers/ 30 | classifiers=[ 31 | # How mature is this project? Common values are 32 | # 3 - Alpha 33 | # 4 - Beta 34 | # 5 - Production/Stable 35 | 'Development Status :: 3 - Alpha', 36 | 'Intended Audience :: End Users/Desktop', 37 | 'License :: OSI Approved :: MIT License', 38 | 'Programming Language :: Python :: 3', 39 | 'Programming Language :: Python :: 3.4', 40 | 'Programming Language :: Python :: 3.5', 41 | 'Programming Language :: Python :: 3.6', 42 | 'Programming Language :: Python :: 3.7', 43 | ], 44 | packages=find_packages(exclude=['docs', 'tests']), 45 | install_requires=['pyglet', 'pyperclip'], 46 | extras_require={ 47 | 'test': ['pytest'], 48 | }, 49 | package_data={ 50 | 'pyno': ['imgs/*.png', 'examples/*.pn'], 51 | }, 52 | entry_points={ 53 | 'console_scripts': [ 54 | 'pyno=pyno.runner:run', 55 | ], 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /pyno/processor.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | from .element import Element 4 | 5 | 6 | class Processor(): 7 | ''' 8 | Processor is a engine of Pyno, 9 | there functions defines and outputs calculates 10 | ''' 11 | 12 | def init_processor(self, global_scope): 13 | self.proc_result = None 14 | 15 | self.call_func = None 16 | self.cleanup_func = None 17 | 18 | self.need_update = True 19 | self.problem = False 20 | self.problem_desc = '' 21 | 22 | self.local_scope = {} 23 | 24 | def reset_proc(self): 25 | self.proc_result = None 26 | 27 | def processor(self, connected_to, outputs): 28 | # Called every frame 29 | 30 | if self.proc_result and not self.need_update: 31 | return self.proc_result 32 | 33 | # check all in-connections, get results and gave names of in-puts 34 | gen_inputs = {} 35 | for connection in connected_to: 36 | try: 37 | inputs = connection['output']['node'].processor() 38 | data = inputs[connection['output']['put']['name']] 39 | except: 40 | self.problem = True 41 | self.problem_desc = 'Can\'t read input' 42 | continue 43 | gen_inputs[connection['input']['put']['name']] = data 44 | 45 | # run-time mode: just get inputs and put in function 46 | get_outputs = {} 47 | try: 48 | result = self.call_func(**gen_inputs) 49 | except Exception as ex: 50 | if not self.problem: 51 | self.problem_desc = "Runtime error: " + str(ex) 52 | self.problem = True 53 | else: 54 | if isinstance(result, Tuple) and len(outputs) > 1: 55 | for output in outputs: 56 | item = result[outputs.index(output)] 57 | get_outputs[output] = item # tuple output 58 | else: 59 | get_outputs[outputs[0]] = result # one output 60 | # build output 61 | self.proc_result = get_outputs 62 | self.problem = False 63 | 64 | return self.proc_result 65 | 66 | def cleanup(self): 67 | try: 68 | self.cleanup_func() 69 | except Exception as ex: 70 | if not self.problem: 71 | self.problem_desc = "Cleanup error: " + str(ex) 72 | self.problem = True 73 | -------------------------------------------------------------------------------- /pyno/examples/test-sub_add2.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": 0.3 4 | }, 5 | { 6 | "type": "field", 7 | "x": 342, 8 | "y": 531, 9 | "size": [ 10 | 70, 11 | 30 12 | ], 13 | "code": [ 14 | "5" 15 | ], 16 | "connects": [], 17 | "parent": 21 18 | }, 19 | { 20 | "type": "field", 21 | "x": 474, 22 | "y": 283, 23 | "size": [ 24 | 93, 25 | 30 26 | ], 27 | "code": [ 28 | "37" 29 | ], 30 | "connects": [ 31 | { 32 | "output": { 33 | "node": 23, 34 | "put": { 35 | "name": "output_3" 36 | } 37 | }, 38 | "input": { 39 | "put": { 40 | "name": "input" 41 | } 42 | } 43 | } 44 | ], 45 | "parent": 22 46 | }, 47 | { 48 | "type": "sub", 49 | "x": 475, 50 | "y": 398, 51 | "size": [ 52 | 300, 53 | 150 54 | ], 55 | "color": [ 56 | 86, 57 | 108, 58 | 106 59 | ], 60 | "code": "pyno/examples/sub_add2.pn", 61 | "connects": [ 62 | { 63 | "output": { 64 | "node": 24, 65 | "put": { 66 | "name": "output" 67 | } 68 | }, 69 | "input": { 70 | "put": { 71 | "name": "input_2" 72 | } 73 | } 74 | }, 75 | { 76 | "output": { 77 | "node": 21, 78 | "put": { 79 | "name": "output" 80 | } 81 | }, 82 | "input": { 83 | "put": { 84 | "name": "input_1" 85 | } 86 | } 87 | } 88 | ], 89 | "parent": 23 90 | }, 91 | { 92 | "type": "field", 93 | "x": 634, 94 | "y": 523, 95 | "size": [ 96 | 70, 97 | 30 98 | ], 99 | "code": [ 100 | "32" 101 | ], 102 | "connects": [], 103 | "parent": 24 104 | } 105 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### (This repository is archived and no longer under development!) 2 | # Pyno — *Python-based data-flow visual programming* [![Build Status](https://travis-ci.com/honix/Pyno.svg?branch=master)](https://travis-ci.com/honix/Pyno) 3 | ![Pyno](docs/screenshots/particles.png) 4 | 5 | **So, what you can?** 6 | - real-time interactive development 7 | - no predefined nodes - you will code yours 8 | - use python as you like (any libs and tips) 9 | - crash-less errors 10 | - perpetuum mobile (~60 fps bacchanalia) 11 | 12 | *Pyno is an experiment. Real-world scenarios is confusing.* 13 | 14 | # How to use 15 | 16 | [Check wiki for advanced tutorials!](https://github.com/honix/Pyno/wiki) 17 | 18 | **Basics:** 19 | 20 | There are only three elements: 21 | 22 | | Element | Description | Key on keyboard to spawn | 23 | |-|-|:-:| 24 | | **node** | is a function | **N** | 25 | | **node from file** | exact same as node but loads from file | **O** | 26 | | **field** | is a object, value or lambda function | **F** | 27 | | **subpatch** | is a link to pyno-file, allows you to control complexity of your patches | **S** | 28 | 29 | Controls: 30 | - Use right-mouse-button to panning 31 | - Save and load pyno-file using bottom-right buttons (S-save, L-load) 32 | - Move and select elements by mouse, selected elements can be deleted by **Delete** key 33 | - Selected elements can be **ctrl-c** and **ctrl-v** 34 | - Nodes has a code inside, edit code just by pressing on node and hover code editor 35 | - Last, you want to transfer data from element to element, just press and hold on pin and drop connection to other pin 36 | 37 | ![Pyno](docs/screenshots/edit.png) 38 | 39 | # How to run 40 | Make sure you have **Python 3.4** or better on your computer. 41 | 42 | To install pyno you must run ```pip install .``` from the repository root directory. 43 | Pyno's dependencies `pyglet` and `pyperclip` are going to be installed automatically. 44 | 45 | In detail: 46 | ``` 47 | $ git clone https://github.com/honix/Pyno.git 48 | $ cd Pyno 49 | $ pip install . 50 | ``` 51 | Or alternatively as one-liner: 52 | ``` 53 | $ pip install git+https://github.com/honix/Pyno.git 54 | ``` 55 | 56 | Then you can run ```pyno``` in a console from anywhere. 57 | 58 | If you want to work with just the project repository, i.e. without installing the package (this is not recommended!), run `python -m pyno` in a console, or open a python console in the repository root directory, and run 59 | ``` 60 | import pyno 61 | pyno.app_run() 62 | ``` 63 | 64 | # How to modify 65 | 66 | To develop/modify code, you can install Pyno in "developer mode" by running `pip install -e .` in the Pyno directory. 67 | This runs setup.py and installs a link to the `pyno` directory, as if it were normally installed. 68 | 69 | To run the tests, you need `pytest` installed. 70 | You run `pytest` in the root directory of the repository. 71 | It will discover the tests in the `tests` directory and run them on the installed package. 72 | You will see a couple of flashing windows when they get instantiated. 73 | 74 | If you have (on Linux) `xvfb` and the pytest plugin `pytest-xvfb` installed, the tests should automatically be run on the virtual framebuffer, so no windows should become visible. 75 | If this for some reason does not work, invoking pytest via `xvfb-run pytest` should do the trick. 76 | -------------------------------------------------------------------------------- /pyno/examples/test-sub_add2-2.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": 0.3 4 | }, 5 | { 6 | "type": "field", 7 | "x": 353, 8 | "y": 528, 9 | "size": [ 10 | 70, 11 | 30 12 | ], 13 | "code": [ 14 | "5" 15 | ], 16 | "connects": [], 17 | "parent": 34 18 | }, 19 | { 20 | "type": "field", 21 | "x": 350, 22 | "y": 267, 23 | "size": [ 24 | 93, 25 | 30 26 | ], 27 | "code": [ 28 | "13" 29 | ], 30 | "connects": [ 31 | { 32 | "output": { 33 | "node": 36, 34 | "put": { 35 | "name": "output_3" 36 | } 37 | }, 38 | "input": { 39 | "put": { 40 | "name": "input" 41 | } 42 | } 43 | } 44 | ], 45 | "parent": 35 46 | }, 47 | { 48 | "type": "sub", 49 | "x": 475, 50 | "y": 398, 51 | "size": [ 52 | 300, 53 | 150 54 | ], 55 | "color": [ 56 | 86, 57 | 108, 58 | 106 59 | ], 60 | "code": "pyno/examples/sub_add2-2.pn", 61 | "connects": [ 62 | { 63 | "output": { 64 | "node": 37, 65 | "put": { 66 | "name": "output" 67 | } 68 | }, 69 | "input": { 70 | "put": { 71 | "name": "input_2" 72 | } 73 | } 74 | }, 75 | { 76 | "output": { 77 | "node": 34, 78 | "put": { 79 | "name": "output" 80 | } 81 | }, 82 | "input": { 83 | "put": { 84 | "name": "input_1" 85 | } 86 | } 87 | } 88 | ], 89 | "parent": 36 90 | }, 91 | { 92 | "type": "field", 93 | "x": 602, 94 | "y": 535, 95 | "size": [ 96 | 70, 97 | 30 98 | ], 99 | "code": [ 100 | "8" 101 | ], 102 | "connects": [], 103 | "parent": 37 104 | }, 105 | { 106 | "type": "field", 107 | "x": 600, 108 | "y": 259, 109 | "size": [ 110 | 70, 111 | 30 112 | ], 113 | "code": [ 114 | "-3" 115 | ], 116 | "connects": [ 117 | { 118 | "output": { 119 | "node": 36, 120 | "put": { 121 | "name": "output_4" 122 | } 123 | }, 124 | "input": { 125 | "put": { 126 | "name": "input" 127 | } 128 | } 129 | } 130 | ], 131 | "parent": 38 132 | } 133 | ] -------------------------------------------------------------------------------- /pyno/node.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pyglet 3 | import types 4 | import typing 5 | import inspect 6 | from inspect import getargspec 7 | 8 | from .element import Element 9 | from .processor import Processor 10 | from .draw import labelsGroup 11 | from .utils import font 12 | 13 | 14 | # Fix compatibility in typing module for Python < 3.7 15 | if sys.hexversion < 0x030700F0: 16 | try: 17 | typing.ForwardRef = typing._ForwardRef 18 | except: 19 | pass 20 | 21 | 22 | class Node(Element, Processor): 23 | ''' 24 | Node is a main Pyno element, in fact it is a function with in/outputs 25 | ''' 26 | 27 | def __init__(self, window, x, y, batch, color=(200, 200, 200), code="", 28 | connects=None, size=(300, 150), id=None): 29 | Element.__init__(self, x, y, color, batch, id) 30 | Processor.init_processor(self, window.global_scope) # node has a processor for calculation 31 | 32 | self.window = window 33 | self.editor_size = size 34 | self.code = code 35 | 36 | if connects: 37 | self.connected_to = connects 38 | 39 | self.env = {} 40 | 41 | self.name = '' 42 | self.label = pyglet.text.Label(self.name, font_name=font, 43 | bold=True, font_size=11, 44 | anchor_x='center', anchor_y='center', 45 | batch=batch, group=labelsGroup, 46 | color=(255, 255, 255, 230)) 47 | self.reload() 48 | 49 | def processor(self): 50 | return Processor.processor(self, self.connected_to, self.outputs) 51 | 52 | def new_code(self, code): 53 | self.cleanup() 54 | # New code, search for in/outputs 55 | 56 | self.code = code 57 | self.problem = False 58 | 59 | self.call_func = None 60 | self.cleanup_func = None 61 | try: 62 | self.env = {'S': self.local_scope, 'G': self.window.global_scope} 63 | exec(code, self.env) 64 | 65 | self.call_func = self.env['call'] 66 | if not isinstance(self.call_func, types.FunctionType): 67 | raise Exception('Call value is not callable!') 68 | 69 | if 'cleanup' in self.env: 70 | self.cleanup_func = self.env['cleanup'] 71 | except Exception as ex: 72 | self.problem = True 73 | self.er_label.text = "Reader error: " + str(ex) 74 | else: 75 | self.label.text = self.name = self.call_func.__name__ 76 | 77 | signature = inspect.signature(self.call_func) 78 | inputs = tuple(map(lambda x: x.name, signature.parameters.values())) 79 | 80 | if (tuple in signature.return_annotation.mro()): 81 | out = [] 82 | i = 0 83 | for arg in list(signature.return_annotation.__args__): 84 | is_string = isinstance(arg, typing.ForwardRef) and isinstance(arg.__forward_arg__, str) 85 | out.append(arg.__forward_arg__ if is_string else 'result ' + str(i)) 86 | i += 1 87 | outputs = tuple(out) 88 | else: 89 | outputs = ('result',) 90 | 91 | self.resize_to_name(self.name) 92 | self.insert_inouts({'inputs': inputs, 93 | 'outputs': outputs}) 94 | 95 | def reload(self): 96 | self.new_code(self.code) 97 | self.make_active() 98 | 99 | def render_base(self): 100 | if Element.render_base(self): 101 | self.label.x, self.label.y = self.x, self.y 102 | 103 | def delete(self, fully=False): 104 | self.cleanup() 105 | Element.delete(self, fully) 106 | self.label.delete() 107 | -------------------------------------------------------------------------------- /pyno/examples/old-style/many_batch_graphics.pn: -------------------------------------------------------------------------------- 1 | [{'code': "S['quads'] = []\nS['batch'] = None\n\ndef quad(window=None, xx=[], yy=[], width=10):\n count = len(xx) * len(yy)\n need_update = len(S['quads']) != count\n if need_update:\n S['quads'] = []\n S['batch'] = pyglet.graphics.Batch()\n for x in xx:\n for y in yy:\n S['quads'].append(S['batch'].add_indexed(4, \n pyglet.gl.GL_TRIANGLES,\n None, [0, 1, 2, 0, 2, 3],\n ('v2f', (0,0, 0,0, 0,0, 0,0)),\n ('c3B', (y // 2, x // 2, 150) * 4)))\n\n window.switch_to()\n window.clear()\n c = 0\n for x in xx:\n for y in yy:\n S['quads'][c].vertices = (x, y,\n x + width, y,\n x + width, y + width,\n x, y + width)\n c += 1\n\n S['batch'].draw()\n\n return need_update, count", 'parent': 31, 'color': (99, 114, 116), 'connects': [{'output': {'node': 46, 'put': {'name': "S['window']"}}, 'input': {'put': {'name': 'window'}}}, {'output': {'node': 39, 'put': {'name': 'result'}}, 'input': {'put': {'name': 'xx'}}}, {'output': {'node': 39, 'put': {'name': 'result'}}, 'input': {'put': {'name': 'yy'}}}, {'output': {'node': 32, 'put': {'name': 'cos'}}, 'input': {'put': {'name': 'width'}}}], 'y': 190, 'x': 401, 'type': 'node', 'size': (601, 566)}, {'code': 'def sincos(a=0, m=1):\n cos = math.cos(a)*m\n return cos', 'parent': 32, 'color': (80, 123, 85), 'connects': [{'output': {'node': 33, 'put': {'name': 'output'}}, 'input': {'put': {'name': 'm'}}}, {'output': {'node': 36, 'put': {'name': 'result'}}, 'input': {'put': {'name': 'a'}}}], 'y': 332, 'x': 671, 'type': 'node', 'size': (300, 150)}, {'code': '14', 'parent': 33, 'connects': [], 'y': 416, 'x': 736, 'type': 'field', 'size': (70, 30)}, {'code': "S['t'] = 0\n\ndef time():\n S['t'] += G['dt']\n return S['t']", 'parent': 34, 'color': (124, 125, 123), 'connects': [], 'y': 482, 'x': 578, 'type': 'node', 'size': (300, 150)}, {'code': '\n#There is example of some heavy graphics\n\nimport pyglet\nimport math\n', 'parent': 35, 'connects': [], 'y': 521, 'x': 221, 'type': 'field', 'size': (414, 124)}, {'code': 'def mul(a=0, b=1):\n result = a * b\n return result', 'parent': 36, 'color': (84, 85, 94), 'connects': [{'output': {'node': 34, 'put': {'name': "S['t']"}}, 'input': {'put': {'name': 'a'}}}, {'output': {'node': 37, 'put': {'name': 'output'}}, 'input': {'put': {'name': 'b'}}}], 'y': 407, 'x': 623, 'type': 'node', 'size': (300, 150)}, {'code': '3.0\n', 'parent': 37, 'connects': [], 'y': 482, 'x': 664, 'type': 'field', 'size': (81, 30)}, {'code': 'def rangef(f=0, t=100, s=10):\n result = list(range(f,t,s))\n return result', 'parent': 39, 'color': (117, 90, 80), 'connects': [{'output': {'node': 41, 'put': {'name': 'output'}}, 'input': {'put': {'name': 't'}}}, {'output': {'node': 42, 'put': {'name': 'output'}}, 'input': {'put': {'name': 's'}}}, {'output': {'node': 40, 'put': {'name': 'output'}}, 'input': {'put': {'name': 'f'}}}], 'y': 292, 'x': 401, 'type': 'node', 'size': (342, 151)}, {'code': '45', 'parent': 40, 'connects': [], 'y': 394, 'x': 304, 'type': 'field', 'size': (70, 30)}, {'code': '520', 'parent': 41, 'connects': [], 'y': 393, 'x': 401, 'type': 'field', 'size': (81, 30)}, {'code': '15', 'parent': 42, 'connects': [], 'y': 393, 'x': 501, 'type': 'field', 'size': (70, 30)}, {'code': 'False', 'parent': 44, 'connects': [{'output': {'node': 31, 'put': {'name': 'need_update'}}, 'input': {'put': {'name': 'input'}}}], 'y': 92, 'x': 369, 'type': 'field', 'size': (100, 30)}, {'code': '1024', 'parent': 45, 'connects': [{'output': {'node': 31, 'put': {'name': 'count'}}, 'input': {'put': {'name': 'input'}}}], 'y': 91, 'x': 501, 'type': 'field', 'size': (70, 30)}, {'code': "# make config for anti-aliasing\n\nS['config']=pyglet.gl.Config(samples=1)\nS['window']=pyglet.window.Window(540,540,\n config=S['config'])\n\ndef window():\n return S['window']", 'parent': 46, 'color': (101, 103, 106), 'connects': [], 'y': 297, 'x': 250, 'type': 'node', 'size': (572, 274)}] -------------------------------------------------------------------------------- /pyno/draw.py: -------------------------------------------------------------------------------- 1 | from math import atan2, sin, cos 2 | 3 | import pyglet 4 | from pyglet import gl 5 | 6 | 7 | class UIGroup(pyglet.graphics.OrderedGroup): 8 | def __init__(self, order): 9 | super().__init__(order) 10 | 11 | def set_state(self): 12 | gl.glPushMatrix() 13 | gl.glLoadIdentity() 14 | 15 | def unset_state(self): 16 | gl.glPopMatrix() 17 | 18 | 19 | class LinesGroup(pyglet.graphics.OrderedGroup): 20 | def __init__(self, order): 21 | super().__init__(order) 22 | 23 | def set_state(self): 24 | # Toggle smooth lines 25 | gl.glEnable(gl.GL_POLYGON_SMOOTH) 26 | gl.glEnable(gl.GL_BLEND) 27 | 28 | def unset_state(self): 29 | gl.glDisable(gl.GL_POLYGON_SMOOTH) 30 | gl.glDisable(gl.GL_BLEND) 31 | 32 | uiGroup = UIGroup(-1) 33 | linesGroup = LinesGroup(0) 34 | baseGroup = pyglet.graphics.OrderedGroup(1) 35 | labelsGroup = pyglet.graphics.OrderedGroup(2) 36 | 37 | 38 | class Line: 39 | def __init__(self, batch): 40 | self.id = batch.add_indexed( 41 | 4, gl.GL_TRIANGLES, linesGroup, 42 | [0, 1, 2, 2, 3, 0], 43 | ('v2f', (0.0, 0.0, 44 | 0.0, 0.0, 45 | 0.0, 0.0, 46 | 0.0, 0.0)), 47 | ('c3B', (100, 100, 100) * 4) 48 | ) 49 | 50 | def redraw(self, p1, p2): 51 | angle = atan2(p2[1] - p1[1], p2[0] - p1[0]) 52 | width = 2.4 53 | sina = width / 2 * sin(angle) 54 | cosa = width / 2 * cos(angle) 55 | self.id.vertices = (p1[0] - sina, p1[1] + cosa, 56 | p1[0] + sina, p1[1] - cosa, 57 | p2[0] + sina, p2[1] - cosa, 58 | p2[0] - sina, p2[1] + cosa) 59 | 60 | def delete(self, fully=False): 61 | self.id.vertices = (0, 0, 62 | 0, 0, 63 | 0, 0, 64 | 0, 0) 65 | if fully: 66 | self.id.delete() 67 | del self.id 68 | 69 | 70 | class Quad: 71 | def __init__(self, batch, backdrop=False, frontdrop=False): 72 | group = baseGroup 73 | if backdrop: 74 | group = linesGroup 75 | elif frontdrop: 76 | group = labelsGroup 77 | 78 | self.id = batch.add_indexed( 79 | 4, gl.GL_TRIANGLES, group, 80 | [0, 1, 2, 2, 3, 0], 81 | ('v2i', (0, 0, 82 | 0, 0, 83 | 0, 0, 84 | 0, 0)), 85 | ('c3B', (0, 0, 0) * 4)) 86 | 87 | def redraw(self, x, y, cw, ch, color): 88 | self.id.vertices = (x - cw, y - ch, 89 | x + cw, y - ch, 90 | x + cw, y + ch, 91 | x - cw, y + ch) 92 | self.id.colors = color * 4 93 | 94 | def delete(self, fully=False): 95 | self.id.vertices = (0, 0, 96 | 0, 0, 97 | 0, 0, 98 | 0, 0) 99 | if fully: 100 | self.id.delete() 101 | del self.id 102 | 103 | 104 | def quad_aligned(x, y, w, h, color): 105 | quad_data = pyglet.graphics.vertex_list_indexed( 106 | 4, 107 | [0, 1, 2, 2, 3, 0], 108 | ('v2i', (x, y, 109 | x + w, y, 110 | x + w, y + h, 111 | x, y + h)), 112 | ('c4B', color * 4)) 113 | 114 | gl.glEnable(gl.GL_BLEND) 115 | quad_data.draw(pyglet.gl.GL_TRIANGLES) 116 | gl.glDisable(gl.GL_BLEND) 117 | 118 | 119 | def selector(init, corner): 120 | '''Selection tool representation''' 121 | quad_data = pyglet.graphics.vertex_list_indexed( 122 | 4, 123 | [0, 1, 2, 2, 3, 0], 124 | ('v2i', (init[0], init[1], 125 | corner[0], init[1], 126 | corner[0], corner[1], 127 | init[0], corner[1])), 128 | ('c4B', (120, 200, 255, 50) * 4)) 129 | 130 | # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 131 | gl.glEnable(gl.GL_BLEND) 132 | quad_data.draw(gl.GL_TRIANGLES) 133 | gl.glDisable(gl.GL_BLEND) 134 | -------------------------------------------------------------------------------- /pyno/serializer.py: -------------------------------------------------------------------------------- 1 | import json 2 | from collections import OrderedDict 3 | 4 | from .node import Node 5 | from .field import Field 6 | from .sub import Sub 7 | 8 | class Serializer(): 9 | ''' 10 | Serialize nodes to data or deserialize data to nodes. 11 | Uses json format 12 | ''' 13 | 14 | def __init__(self, window): 15 | Serializer.version = 0.4 16 | self.window = window 17 | 18 | def serialize(self, nodes, anchor=(0, 0)): 19 | buff = [] 20 | buff.append({'version': Serializer.version}) 21 | for node in nodes: 22 | if isinstance(node, Node): 23 | buff.append(OrderedDict({'type': 'node', 24 | 'x': node.x - anchor[0], 25 | 'y': node.y - anchor[1], 26 | 'size': node.editor_size, 27 | 'color': node.color, 28 | 'code': node.code.split('\n'), 29 | 'connects': node.get_con_id(), 30 | 'id': node.id})) 31 | elif isinstance(node, Field): 32 | buff.append(OrderedDict({'type': 'field', 33 | 'x': node.x - anchor[0], 34 | 'y': node.y - anchor[1], 35 | 'size': (node.w, node.h), 36 | 'code': node.document.text.split('\n'), 37 | 'connects': node.get_con_id(), 38 | 'id': node.id})) 39 | elif isinstance(node, Sub): 40 | buff.append(OrderedDict({'type': 'sub', 41 | 'x': node.x - anchor[0], 42 | 'y': node.y - anchor[1], 43 | 'size': node.editor_size, 44 | 'color': node.color, 45 | 'code': node.code, 46 | 'connects': node.get_con_id(), 47 | 'id': node.id})) 48 | return json.dumps(buff, indent=4) 49 | 50 | def deserialize(self, data, anchor=(0, 0)): 51 | try: 52 | paste = json.loads(data) 53 | #paste = json.loads(data, object_pairs_hook=OrderedDict) 54 | except ValueError: 55 | print("Attention: pyno-file still uses the old save format, please re-save soon!") 56 | paste = eval(data) 57 | 58 | try: 59 | version = paste[0]['version'] 60 | except Exception as ex: 61 | print('Can\'t fetch format version. Probably format is < 0.3.') 62 | version = 0.0 63 | 64 | nodes = paste[1:] if version >= 0.3 else paste 65 | 66 | buff = [] 67 | try: 68 | for node in nodes: 69 | if node['type'] == 'node': 70 | buff.append(Node(self.window, 71 | node['x'] + anchor[0], 72 | node['y'] + anchor[1], 73 | self.window.batch, 74 | tuple(node['color']), 75 | '\n'.join(node['code']) if version >= 0.3 else node['code'], 76 | node['connects'], 77 | node['size'], 78 | node['id' if version >= 0.4 else 'parent'])) 79 | elif node['type'] == 'field': 80 | buff.append(Field(self.window, 81 | node['x'] + anchor[0], 82 | node['y'] + anchor[1], 83 | self.window.batch, 84 | '\n'.join(node['code']) if version >= 0.3 else node['code'], 85 | node['connects'], 86 | node['size'], 87 | node['id' if version >= 0.4 else 'parent'])) 88 | elif node['type'] == 'sub': 89 | buff.append(Sub(self.window, 90 | node['x'] + anchor[0], 91 | node['y'] + anchor[1], 92 | self.window.batch, 93 | tuple(node['color']), 94 | node['code'], 95 | node['connects'], 96 | node['size'], 97 | node['id' if version >= 0.4 else 'parent'])) 98 | except Exception as ex: 99 | print('Wrong data:', ex) 100 | return None 101 | finally: 102 | for node in buff: 103 | node.reconnect(buff) 104 | return buff 105 | -------------------------------------------------------------------------------- /pyno/examples/serial_monitor.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "color": [ 4 | 81, 5 | 102, 6 | 105 7 | ], 8 | "type": "node", 9 | "x": 293, 10 | "parent": 34, 11 | "y": 390, 12 | "connects": [ 13 | { 14 | "output": { 15 | "node": 35, 16 | "put": { 17 | "name": "output" 18 | } 19 | }, 20 | "input": { 21 | "put": { 22 | "name": "port" 23 | } 24 | } 25 | }, 26 | { 27 | "output": { 28 | "node": 42, 29 | "put": { 30 | "name": "output" 31 | } 32 | }, 33 | "input": { 34 | "put": { 35 | "name": "baud" 36 | } 37 | } 38 | } 39 | ], 40 | "code": "import serial # sudo pip3 install pyserial\n#import time\nfrom typing import *\n\ndef open_port(port='', baud=9600) -> Tuple[Any, Any, Any]:\n ser = serial.Serial(port, baudrate=baud) # open first serial port\n return ( ser, ser.portstr, ser.baudrate )\n\ncall = open_port", 41 | "size": [ 42 | 300, 43 | 150 44 | ] 45 | }, 46 | { 47 | "type": "field", 48 | "x": 185, 49 | "parent": 35, 50 | "y": 459, 51 | "connects": [], 52 | "code": "'/dev/ttyACM0'", 53 | "size": [ 54 | 208, 55 | 30 56 | ] 57 | }, 58 | { 59 | "type": "field", 60 | "x": 401, 61 | "parent": 36, 62 | "y": 301, 63 | "connects": [ 64 | { 65 | "output": { 66 | "node": 34, 67 | "put": { 68 | "name": "result 1" 69 | } 70 | }, 71 | "input": { 72 | "put": { 73 | "name": "input" 74 | } 75 | } 76 | } 77 | ], 78 | "code": "'/dev/ttyACM0'", 79 | "size": [ 80 | 150, 81 | 30 82 | ] 83 | }, 84 | { 85 | "type": "field", 86 | "x": 545, 87 | "parent": 37, 88 | "y": 301, 89 | "connects": [ 90 | { 91 | "output": { 92 | "node": 34, 93 | "put": { 94 | "name": "result 2" 95 | } 96 | }, 97 | "input": { 98 | "put": { 99 | "name": "input" 100 | } 101 | } 102 | } 103 | ], 104 | "code": "115200", 105 | "size": [ 106 | 94, 107 | 30 108 | ] 109 | }, 110 | { 111 | "color": [ 112 | 98, 113 | 96, 114 | 91 115 | ], 116 | "type": "node", 117 | "x": 248, 118 | "parent": 38, 119 | "y": 275, 120 | "connects": [ 121 | { 122 | "output": { 123 | "node": 34, 124 | "put": { 125 | "name": "result 0" 126 | } 127 | }, 128 | "input": { 129 | "put": { 130 | "name": "ser" 131 | } 132 | } 133 | } 134 | ], 135 | "code": "from typing import *\ndef read_port(ser=None) -> Tuple[Any, Any, Any]:\n #while True:\n string = ser.readline()\n #s = ser.read()\n length = len(string)\n hex = ['0x%02x ' % b for b in string]\n# time.sleep(1.)\n return ( length, string, hex )\n\ncall = read_port", 136 | "size": [ 137 | 300, 138 | 150 139 | ] 140 | }, 141 | { 142 | "type": "field", 143 | "x": 81, 144 | "parent": 39, 145 | "y": 184, 146 | "connects": [ 147 | { 148 | "output": { 149 | "node": 38, 150 | "put": { 151 | "name": "result 0" 152 | } 153 | }, 154 | "input": { 155 | "put": { 156 | "name": "input" 157 | } 158 | } 159 | } 160 | ], 161 | "code": "10", 162 | "size": [ 163 | 70, 164 | 30 165 | ] 166 | }, 167 | { 168 | "type": "field", 169 | "x": 291, 170 | "parent": 40, 171 | "y": 134, 172 | "connects": [ 173 | { 174 | "output": { 175 | "node": 38, 176 | "put": { 177 | "name": "result 1" 178 | } 179 | }, 180 | "input": { 181 | "put": { 182 | "name": "input" 183 | } 184 | } 185 | } 186 | ], 187 | "code": "b'900,0.00\\r\\n'", 188 | "size": [ 189 | 288, 190 | 130 191 | ] 192 | }, 193 | { 194 | "type": "field", 195 | "x": 629, 196 | "parent": 41, 197 | "y": 134, 198 | "connects": [ 199 | { 200 | "output": { 201 | "node": 38, 202 | "put": { 203 | "name": "result 2" 204 | } 205 | }, 206 | "input": { 207 | "put": { 208 | "name": "input" 209 | } 210 | } 211 | } 212 | ], 213 | "code": "['0x39 ', '0x30 ', '0x30 ', '0x2c ', '0x30 ', '0x2e ', '0x30 ', '0x30 ', '0x0d ', '0x0a ']", 214 | "size": [ 215 | 300, 216 | 128 217 | ] 218 | }, 219 | { 220 | "type": "field", 221 | "x": 357, 222 | "parent": 42, 223 | "y": 459, 224 | "connects": [], 225 | "code": "115200\n", 226 | "size": [ 227 | 108, 228 | 30 229 | ] 230 | } 231 | ] -------------------------------------------------------------------------------- /utils/oldFile.pn: -------------------------------------------------------------------------------- 1 | [{'code': 'import pyglet', 'type': 'field', 'size': (180, 30), 'connects': [], 'x': 103, 'y': 368, 'parent': 32}, {'code': "S['box'] = pyglet.graphics.vertex_list_indexed(\n 4, [0, 1, 2, 0, 2, 3],\n ('v2i', (0, 0, 10, 0, 10, 10, 0, 10)),\n ('c3B', (255,255,255)*4) )\n\ndef draw_rect(pos=(0,0), size=(10,10)):\n x, y = int(pos[0]), int(pos[1])\n w, h = int(size[0]), int(size[1])\n S['box'].vertices = (x, y,\n x + w, y,\n x + w, y + h,\n x, y + h)\n return S['box']", 'type': 'node', 'size': (473, 316), 'connects': [{'output': {'node': 37, 'put': {'name': "S['size']"}}, 'input': {'put': {'name': 'size'}}}, {'output': {'node': 37, 'put': {'name': "S['position']"}}, 'input': {'put': {'name': 'pos'}}}], 'x': 509, 'y': 96, 'color': (99, 114, 116), 'parent': 33}, {'code': "'''\nLittle ping-pong game\nKeys:\n upper pad:\n A D\n bottom pad:\n LEFT RIGHT\n'''", 'type': 'field', 'size': (256, 169), 'connects': [], 'x': 139, 'y': 504, 'parent': 34}, {'code': "G['key'] = []\n\ndef keyboard(window=0):\n\n @window.event\n def on_key_press(symbol, mod):\n G['key'].append(symbol)\n\n @window.event\n def on_key_release(symbol, mod):\n if symbol in G['key']:\n G['key'].pop(G['key'].index(symbol))\n\n @window.event\n def on_deactivate():\n G['key'] = []\n\n return G['key']", 'type': 'node', 'size': (408, 395), 'connects': [{'output': {'node': 39, 'put': {'name': "G['window']"}}, 'input': {'put': {'name': 'window'}}}], 'x': 482, 'y': 459, 'color': (93, 93, 89), 'parent': 35}, {'code': '[]', 'type': 'field', 'size': (110, 118), 'connects': [{'output': {'node': 42, 'put': {'name': 'keys'}}, 'input': {'put': {'name': 'input'}}}], 'x': 184, 'y': 223, 'parent': 36}, {'code': "w = G['window']\nS['size'] = [70, 10]\nS['position'] = [w.width/2 - S['size'][0]/2, 20]\n\ndef pad_bottom(key=[]):\n speed = G['dt'] * 200\n\n if 'RIGHT' in key:\n if S['position'][0] < w.width - S['size'][0]:\n S['position'][0] += speed\n if 'LEFT' in key:\n if S['position'][0] > 0:\n S['position'][0] -= speed\n return S['position'], S['size'], S", 'type': 'node', 'size': (477, 301), 'connects': [{'output': {'node': 42, 'put': {'name': 'keys'}}, 'input': {'put': {'name': 'key'}}}], 'x': 526, 'y': 280, 'color': (101, 91, 127), 'parent': 37}, {'code': "def render(meshes=None):\n window = G['window']\n window.switch_to()\n window.clear()\n for mesh in meshes:\n mesh.draw(pyglet.gl.GL_TRIANGLES)\n ", 'type': 'node', 'size': (376, 172), 'connects': [{'output': {'node': 46, 'put': {'name': 'result'}}, 'input': {'put': {'name': 'meshes'}}}], 'x': 509, 'y': -83, 'color': (127, 96, 112), 'parent': 38}, {'code': "G['window'] = pyglet.window.Window(300, 450)\n\ndef create_window():\n return G['window']", 'type': 'node', 'size': (433, 162), 'connects': [], 'x': 439, 'y': 545, 'color': (129, 105, 124), 'parent': 39}, {'code': "w = G['window']\nS['size'] = [70, 10]\nS['position'] = [w.width/2 - S['size'][0]/2, w.height - 30]\n\ndef pad_upper(key=[]):\n speed = G['dt'] * 200\n\n if 'D' in key:\n if S['position'][0] < w.width - S['size'][0]:\n S['position'][0] += speed\n if 'A' in key:\n if S['position'][0] > 0:\n S['position'][0] -= speed\n return S['position'], S['size'], S", 'type': 'node', 'size': (560, 279), 'connects': [{'output': {'node': 42, 'put': {'name': 'keys'}}, 'input': {'put': {'name': 'key'}}}], 'x': 371, 'y': 278, 'color': (101, 91, 127), 'parent': 41}, {'code': 'def keys_to_names(keys=[]):\n keys = list(map(lambda x: pyglet.window.key.symbol_string(x), keys))\n return keys', 'type': 'node', 'size': (715, 151), 'connects': [{'output': {'node': 35, 'put': {'name': "G['key']"}}, 'input': {'put': {'name': 'keys'}}}], 'x': 453, 'y': 375, 'color': (81, 121, 111), 'parent': 42}, {'code': "w = G['window']\nS['position'] = [w.width // 2, w.height // 2]\nS['size'] = [10, 10]\nS['acc'] = [2, 1]\n\ndef ball(pad_u={}, pad_b={}):\n speed = G['dt'] * 60\n pos = S['position']\n\n # OUT OF GAME\n if (pos[1] > w.height) or (pos[1] < -S['size'][0]):\n S['position'] = [w.width // 2, w.height // 2]\n S['acc'][1] *= -1\n\n # WALLS COLLISION\n if pos[0] < w.width - S['size'][0]:\n S['acc'][0] *= -1\n if pos[0] > 0:\n S['acc'][0] *= -1\n\n # PADS COLLISION \n pu, su = pad_u['position'], pad_u['size']\n if ((pu[1] > pos[1] > pu[1] - su[1]) and\n (pu[0] < pos[0] < pu[0] + su[0])):\n S['acc'][1] *= -1\n\n pu, su = pad_b['position'], pad_b['size']\n if ((pu[1] < pos[1] < pu[1] + su[1]) and\n (pu[0] < pos[0] < pu[0] + su[0])):\n S['acc'][1] *= -1\n\n # MOVEMENT\n S['position'][0] += S['acc'][0] * speed\n S['position'][1] += S['acc'][1] * speed\n\n return S['position'], S['size']", 'type': 'node', 'size': (562, 680), 'connects': [{'output': {'node': 41, 'put': {'name': 'S'}}, 'input': {'put': {'name': 'pad_u'}}}, {'output': {'node': 37, 'put': {'name': 'S'}}, 'input': {'put': {'name': 'pad_b'}}}], 'x': 654, 'y': 170, 'color': (101, 91, 127), 'parent': 44}, {'code': 'def join3(a=None, b=None, c=None):\n result = a, b, c\n return result', 'type': 'node', 'size': (336, 150), 'connects': [{'output': {'node': 33, 'put': {'name': "S['box']"}}, 'input': {'put': {'name': 'b'}}}, {'output': {'node': 51, 'put': {'name': "S['box']"}}, 'input': {'put': {'name': 'a'}}}, {'output': {'node': 52, 'put': {'name': "S['box']"}}, 'input': {'put': {'name': 'c'}}}], 'x': 509, 'y': -4, 'color': (81, 90, 127), 'parent': 46}, {'code': "S['box'] = pyglet.graphics.vertex_list_indexed(\n 4, [0, 1, 2, 0, 2, 3],\n ('v2i', (0, 0, 10, 0, 10, 10, 0, 10)),\n ('c3B', (255,255,255)*4) )\n\ndef draw_rect(pos=(0,0), size=(10,10)):\n x, y = int(pos[0]), int(pos[1])\n w, h = int(size[0]), int(size[1])\n S['box'].vertices = (x, y,\n x + w, y,\n x + w, y + h,\n x, y + h)\n return S['box']", 'type': 'node', 'size': (473, 316), 'connects': [{'output': {'node': 41, 'put': {'name': "S['size']"}}, 'input': {'put': {'name': 'size'}}}, {'output': {'node': 41, 'put': {'name': "S['position']"}}, 'input': {'put': {'name': 'pos'}}}], 'x': 376, 'y': 96, 'color': (99, 114, 116), 'parent': 51}, {'code': "S['box'] = pyglet.graphics.vertex_list_indexed(\n 4, [0, 1, 2, 0, 2, 3],\n ('v2i', (0, 0, 10, 0, 10, 10, 0, 10)),\n ('c3B', (255,100,20)*4) )\n\ndef draw_rect(pos=(0,0), size=(10,10)):\n x, y = int(pos[0]), int(pos[1])\n w, h = int(size[0]), int(size[1])\n S['box'].vertices = (x, y,\n x + w, y,\n x + w, y + h,\n x, y + h)\n return S['box']", 'type': 'node', 'size': (473, 316), 'connects': [{'output': {'node': 44, 'put': {'name': "S['position']"}}, 'input': {'put': {'name': 'pos'}}}, {'output': {'node': 44, 'put': {'name': "S['size']"}}, 'input': {'put': {'name': 'size'}}}], 'x': 653, 'y': 95, 'color': (99, 114, 116), 'parent': 52}] -------------------------------------------------------------------------------- /pyno/examples/simple_graphics.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": 0.4 4 | }, 5 | { 6 | "type": "field", 7 | "x": 579, 8 | "y": 506, 9 | "size": [ 10 | 286, 11 | 50 12 | ], 13 | "code": [ 14 | "import pyglet" 15 | ], 16 | "connects": [], 17 | "id": 28 18 | }, 19 | { 20 | "type": "field", 21 | "x": 467, 22 | "y": 416, 23 | "size": [ 24 | 640, 25 | 30 26 | ], 27 | "code": [ 28 | "pyglet.window.Window(300,300,config=pyglet.gl.Config(double_buffer=False))" 29 | ], 30 | "connects": [], 31 | "id": 29 32 | }, 33 | { 34 | "type": "node", 35 | "x": 491, 36 | "y": 83, 37 | "size": [ 38 | 467, 39 | 338 40 | ], 41 | "color": [ 42 | 99, 43 | 114, 44 | 116 45 | ], 46 | "code": [ 47 | "import pyglet", 48 | "", 49 | "def draw(window=None, x=0, y=0):", 50 | " window.switch_to()", 51 | " window.clear()", 52 | " x, y = int(x), int(y)", 53 | " pyglet.graphics.draw_indexed(4, ", 54 | " pyglet.gl.GL_TRIANGLES,\r", 55 | " [0, 1, 2, 0, 2, 3],\r", 56 | " ('v2i', (100, 100,\r", 57 | " 150, 100,\r", 58 | " 150 + x, 150 + y,\r", 59 | " 100, 150))\r", 60 | " )", 61 | "", 62 | "call = draw" 63 | ], 64 | "connects": [ 65 | { 66 | "output": { 67 | "node": 31, 68 | "put": { 69 | "name": "result 0" 70 | } 71 | }, 72 | "input": { 73 | "put": { 74 | "name": "x" 75 | } 76 | } 77 | }, 78 | { 79 | "output": { 80 | "node": 31, 81 | "put": { 82 | "name": "result 1" 83 | } 84 | }, 85 | "input": { 86 | "put": { 87 | "name": "y" 88 | } 89 | } 90 | }, 91 | { 92 | "output": { 93 | "node": 29, 94 | "put": { 95 | "name": "output" 96 | } 97 | }, 98 | "input": { 99 | "put": { 100 | "name": "window" 101 | } 102 | } 103 | } 104 | ], 105 | "id": 30 106 | }, 107 | { 108 | "type": "node", 109 | "x": 615, 110 | "y": 193, 111 | "size": [ 112 | 387, 113 | 231 114 | ], 115 | "color": [ 116 | 80, 117 | 123, 118 | 85 119 | ], 120 | "code": [ 121 | "from typing import *", 122 | "from math import sin, cos", 123 | "", 124 | "def sincos(a=0, m=1) -> Tuple[Any, Any]:", 125 | " si = sin(a)*m", 126 | " ci = cos(a)*m", 127 | " return si, ci", 128 | "", 129 | "call = sincos" 130 | ], 131 | "connects": [ 132 | { 133 | "output": { 134 | "node": 32, 135 | "put": { 136 | "name": "output" 137 | } 138 | }, 139 | "input": { 140 | "put": { 141 | "name": "m" 142 | } 143 | } 144 | }, 145 | { 146 | "output": { 147 | "node": 35, 148 | "put": { 149 | "name": "result" 150 | } 151 | }, 152 | "input": { 153 | "put": { 154 | "name": "a" 155 | } 156 | } 157 | } 158 | ], 159 | "id": 31 160 | }, 161 | { 162 | "type": "field", 163 | "x": 680, 164 | "y": 289, 165 | "size": [ 166 | 70, 167 | 30 168 | ], 169 | "code": [ 170 | "28" 171 | ], 172 | "connects": [], 173 | "id": 32 174 | }, 175 | { 176 | "type": "node", 177 | "x": 521, 178 | "y": 354, 179 | "size": [ 180 | 300, 181 | 150 182 | ], 183 | "color": [ 184 | 124, 185 | 125, 186 | 123 187 | ], 188 | "code": [ 189 | "S['t'] = 0", 190 | "", 191 | "def time():", 192 | " S['t'] += G['dt']", 193 | " return S['t']", 194 | "", 195 | "call = time" 196 | ], 197 | "connects": [], 198 | "id": 33 199 | }, 200 | { 201 | "type": "field", 202 | "x": 142, 203 | "y": 506, 204 | "size": [ 205 | 212, 206 | 102 207 | ], 208 | "code": [ 209 | "'''", 210 | "There is example of some graphics stuff", 211 | "", 212 | "'''" 213 | ], 214 | "connects": [], 215 | "id": 34 216 | }, 217 | { 218 | "type": "node", 219 | "x": 565, 220 | "y": 276, 221 | "size": [ 222 | 300, 223 | 150 224 | ], 225 | "color": [ 226 | 84, 227 | 85, 228 | 94 229 | ], 230 | "code": [ 231 | "def mul(a=0, b=1):", 232 | " result = a * b", 233 | " return result", 234 | "", 235 | "call = mul" 236 | ], 237 | "connects": [ 238 | { 239 | "output": { 240 | "node": 33, 241 | "put": { 242 | "name": "result" 243 | } 244 | }, 245 | "input": { 246 | "put": { 247 | "name": "a" 248 | } 249 | } 250 | }, 251 | { 252 | "output": { 253 | "node": 36, 254 | "put": { 255 | "name": "output" 256 | } 257 | }, 258 | "input": { 259 | "put": { 260 | "name": "b" 261 | } 262 | } 263 | } 264 | ], 265 | "id": 35 266 | }, 267 | { 268 | "type": "field", 269 | "x": 608, 270 | "y": 355, 271 | "size": [ 272 | 85, 273 | 30 274 | ], 275 | "code": [ 276 | "3.00" 277 | ], 278 | "connects": [], 279 | "id": 36 280 | } 281 | ] -------------------------------------------------------------------------------- /pyno/examples/flow_control_delay.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "field", 4 | "parent": 12, 5 | "code": "# TODO: from code/schematic add\n# * flow control [done]\n# * timing [done]\n# * scheduling [open]\n# add dedicated functions/nodes \n\n", 6 | "connects": [], 7 | "size": [ 8 | 312, 9 | 334 10 | ], 11 | "x": 190, 12 | "y": 400 13 | }, 14 | { 15 | "type": "node", 16 | "parent": 13, 17 | "code": "import time\ndef newNode(a=0, b=0):\n result = time.time()\n return result\ncall = newNode", 18 | "color": [ 19 | 91, 20 | 109, 21 | 117 22 | ], 23 | "connects": [], 24 | "size": [ 25 | 300, 26 | 150 27 | ], 28 | "x": 470, 29 | "y": 482 30 | }, 31 | { 32 | "type": "field", 33 | "parent": 14, 34 | "code": "1541784055.7765543", 35 | "connects": [ 36 | { 37 | "input": { 38 | "put": { 39 | "name": "input" 40 | } 41 | }, 42 | "output": { 43 | "node": 13, 44 | "put": { 45 | "name": "result" 46 | } 47 | } 48 | } 49 | ], 50 | "size": [ 51 | 214, 52 | 34 53 | ], 54 | "x": 632, 55 | "y": 401 56 | }, 57 | { 58 | "type": "node", 59 | "parent": 15, 60 | "code": "import time\ndef newNode(a=0, b=0):\n result = time.time() - a\n return result\ncall = newNode", 61 | "color": [ 62 | 91, 63 | 109, 64 | 117 65 | ], 66 | "connects": [ 67 | { 68 | "input": { 69 | "put": { 70 | "name": "a" 71 | } 72 | }, 73 | "output": { 74 | "node": 13, 75 | "put": { 76 | "name": "result" 77 | } 78 | } 79 | } 80 | ], 81 | "size": [ 82 | 300, 83 | 150 84 | ], 85 | "x": 399, 86 | "y": 156 87 | }, 88 | { 89 | "type": "node", 90 | "parent": 16, 91 | "code": "import time\ndef delay(variable=0, delay=0):\n time.sleep(delay)\n return variable\ncall = delay", 92 | "color": [ 93 | 85, 94 | 121, 95 | 85 96 | ], 97 | "connects": [ 98 | { 99 | "input": { 100 | "put": { 101 | "name": "variable" 102 | } 103 | }, 104 | "output": { 105 | "node": 13, 106 | "put": { 107 | "name": "result" 108 | } 109 | } 110 | }, 111 | { 112 | "input": { 113 | "put": { 114 | "name": "delay" 115 | } 116 | }, 117 | "output": { 118 | "node": 20, 119 | "put": { 120 | "name": "output" 121 | } 122 | } 123 | } 124 | ], 125 | "size": [ 126 | 300, 127 | 150 128 | ], 129 | "x": 529, 130 | "y": 246 131 | }, 132 | { 133 | "type": "node", 134 | "parent": 17, 135 | "code": "import time\ndef newNode(a=0, b=0):\n result = time.time() - a\n return result\ncall = newNode", 136 | "color": [ 137 | 91, 138 | 109, 139 | 117 140 | ], 141 | "connects": [ 142 | { 143 | "input": { 144 | "put": { 145 | "name": "a" 146 | } 147 | }, 148 | "output": { 149 | "node": 16, 150 | "put": { 151 | "name": "result" 152 | } 153 | } 154 | } 155 | ], 156 | "size": [ 157 | 300, 158 | 150 159 | ], 160 | "x": 565, 161 | "y": 158 162 | }, 163 | { 164 | "type": "field", 165 | "parent": 18, 166 | "code": "0.10026955604553223", 167 | "connects": [ 168 | { 169 | "input": { 170 | "put": { 171 | "name": "input" 172 | } 173 | }, 174 | "output": { 175 | "node": 17, 176 | "put": { 177 | "name": "result" 178 | } 179 | } 180 | } 181 | ], 182 | "size": [ 183 | 186, 184 | 30 185 | ], 186 | "x": 574, 187 | "y": 65 188 | }, 189 | { 190 | "type": "field", 191 | "parent": 19, 192 | "code": "0.00087738037109375", 193 | "connects": [ 194 | { 195 | "input": { 196 | "put": { 197 | "name": "input" 198 | } 199 | }, 200 | "output": { 201 | "node": 15, 202 | "put": { 203 | "name": "result" 204 | } 205 | } 206 | } 207 | ], 208 | "size": [ 209 | 236, 210 | 30 211 | ], 212 | "x": 347, 213 | "y": 64 214 | }, 215 | { 216 | "type": "field", 217 | "parent": 20, 218 | "code": "0.1", 219 | "connects": [], 220 | "size": [ 221 | 72, 222 | 30 223 | ], 224 | "x": 544, 225 | "y": 323 226 | }, 227 | { 228 | "type": "node", 229 | "parent": 21, 230 | "code": "import time\ndef newNode(a=0, b=0):\n result = time.time() - a\n return result\ncall = newNode", 231 | "color": [ 232 | 91, 233 | 109, 234 | 117 235 | ], 236 | "connects": [ 237 | { 238 | "input": { 239 | "put": { 240 | "name": "a" 241 | } 242 | }, 243 | "output": { 244 | "node": 13, 245 | "put": { 246 | "name": "result" 247 | } 248 | } 249 | } 250 | ], 251 | "size": [ 252 | 300, 253 | 150 254 | ], 255 | "x": 783, 256 | "y": 160 257 | }, 258 | { 259 | "type": "field", 260 | "parent": 22, 261 | "code": "2.6226043701171875e-06", 262 | "connects": [ 263 | { 264 | "input": { 265 | "put": { 266 | "name": "input" 267 | } 268 | }, 269 | "output": { 270 | "node": 21, 271 | "put": { 272 | "name": "result" 273 | } 274 | } 275 | } 276 | ], 277 | "size": [ 278 | 232, 279 | 30 280 | ], 281 | "x": 794, 282 | "y": 65 283 | } 284 | ] -------------------------------------------------------------------------------- /pyno/sub.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | import pyglet 3 | from pyno import window 4 | 5 | from .element import Element 6 | from .processor import Processor 7 | from .draw import labelsGroup 8 | from .utils import font 9 | from .field import Field 10 | 11 | 12 | class Sub(Processor, Element): 13 | ''' 14 | Sub is a main pyno element, in fact it is a pyno file with in/outputs 15 | ''' 16 | 17 | def __init__(self, window, x, y, batch, color=(200, 200, 200), code=None, 18 | connects=None, size=(300, 150), id=None): 19 | Element.__init__(self, x, y, color, batch, id) 20 | Processor.init_processor(self, window.global_scope) # node has a processor for calculation 21 | 22 | self.editor_size = size 23 | 24 | if connects: 25 | self.connected_to = connects 26 | 27 | self.code = None 28 | if not code: 29 | sub_pass = pkg_resources.resource_filename('pyno', 'examples/sub_pass.pn') 30 | code = sub_pass # identical to '''examples/blank.pn''' 31 | 32 | self.name = '' 33 | self.label = pyglet.text.Label(self.name, font_name=font, 34 | bold=True, font_size=11, 35 | anchor_x='center', anchor_y='center', 36 | batch=batch, group=labelsGroup, 37 | color=(255, 255, 255, 230)) 38 | self.pwindow = None 39 | self.input_nodes = {} 40 | self.output_nodes = {} 41 | self.new_code(code) 42 | 43 | def new_code(self, code): 44 | # New code, search for in/outputs 45 | 46 | if self.code == code: 47 | return 48 | 49 | self.problem = False 50 | 51 | self.code = code 52 | self.name = code.strip() 53 | self.label.text = self.name + ' →' 54 | 55 | try: 56 | pwin = window.PynoWindow(pyglet.gl.Config(), 57 | caption='→ ' + self.name) 58 | pwin.load_pyno(self.code) 59 | if self.pwindow: 60 | self.pwindow.close() 61 | del self.pwindow 62 | self.pwindow = pwin 63 | except Exception as ex: 64 | self.problem = ex # abusing since this resolves in most cases like "True" 65 | self.er_label.text = repr(ex) 66 | return 67 | 68 | # window visibility handeled in window.py 69 | 70 | self.pwindow.nodes_update() 71 | 72 | inputs, outputs = [], [] 73 | self.input_nodes, self.output_nodes = {}, {} 74 | for nid, node in enumerate(self.pwindow.nodes): 75 | if not isinstance(node, Field): 76 | continue 77 | # print(node.inputs, node.outputs, node.connected_to, node.child, node.name) 78 | # print(node, node.inputs, node.outputs, node.connected_to, node.child, node.code, node.gen_output) 79 | # nid = node.id 80 | # nid = hex(id(node)) 81 | if not node.connected_to: # fields with free inputs 82 | for inp in node.inputs: 83 | name = "%s_%i" % (inp, nid) 84 | inputs.append(name) 85 | self.input_nodes[name] = node # from self.pwindow.nodes 86 | if not node.child: # fields with free outputs 87 | for outp in node.outputs: 88 | name = "%s_%i" % (outp, nid) 89 | outputs.append(name) 90 | self.output_nodes[name] = node # from self.pwindow.nodes 91 | 92 | self.insert_inouts({'inputs': inputs, 93 | 'outputs': outputs}) 94 | 95 | self.resize_to_name(self.name) 96 | 97 | # processor copying values to and from fields, has error handling 98 | # works with: sub_pass.pn (see test-sub_pass.pn) 99 | # works with: sub_add2.pn (see test-sub_add2.pn) 100 | # works with: sub_add2-2.pn (see test-sub_add2-2.pn) 101 | def processor(self): 102 | # Called every frame 103 | 104 | if (self.proc_result and not self.need_update) \ 105 | or not self.pwindow \ 106 | or not isinstance(self.problem, bool): 107 | return self.proc_result 108 | 109 | self.problem = False 110 | 111 | # check all in-connections, get results and gave names of in-puts 112 | gen_inputs = {} 113 | for connection in self.connected_to: 114 | try: 115 | inputs = connection['output']['node'].processor() 116 | data = inputs[connection['output']['put']['name']] 117 | 118 | # send data to sub part 119 | node = self.input_nodes[connection['input']['put']['name']] # from self.pwindow.nodes 120 | node.gen_output = {'output': data} 121 | node.document.text = repr(data) 122 | # node.need_update = True 123 | except: 124 | self.er_label.text = 'Cant read input' 125 | self.problem = True 126 | continue 127 | gen_inputs[connection['input']['put']['name']] = data 128 | 129 | # exec process of sub part 130 | self.pwindow.nodes_update() 131 | # self.pwindow.update() 132 | 133 | # check for errors 134 | errors = [] 135 | for node in self.pwindow.nodes: 136 | if node.problem: 137 | errors.append(node) 138 | if errors: 139 | if not self.problem: 140 | self.er_label.text = "Errors in %i node(s)." % len(errors) 141 | self.problem = True 142 | 143 | # run-time mode: just get inputs and put in function 144 | try: 145 | # get data back and return them 146 | self.gen_output = {} 147 | for outp in self.outputs: 148 | node = self.output_nodes[outp] # from self.pwindow.nodes 149 | self.gen_output[outp] = node.gen_output['output'] 150 | except Exception as ex: 151 | if not self.problem: 152 | self.er_label.text = str(ex) 153 | self.problem = True 154 | self.proc_result = self.gen_output 155 | 156 | self.render_base() # update error display 157 | 158 | return self.gen_output 159 | 160 | # processor connecting inputs and outputs of fields, does not have error handling 161 | # works with: sub_pass.pn 162 | def _processor(self, space): 163 | # Called every frame 164 | 165 | # directly connect inputs and outputs to fields in sub 166 | # hackish should not be in processor() since it bypasses and disables it also 167 | # elegant since it uses processor() mechanics optimal but draws strange wires 168 | # consider using connect_out_to_in() and connect_in_to_out() 169 | 170 | # connect sub inputs 171 | # self.pwindow.nodes[0].connected_to[0]['output'] = self.connected_to[0]['output'] 172 | self.pwindow.nodes[0].connected_to = self.connected_to 173 | 174 | # connect sub outputs 175 | # print(self.child) 176 | # self.pwindow.nodes[0].add_child(self.child[0]) 177 | # self.pwindow.nodes[0].child = self.child 178 | if self.child: 179 | # print(self.child[0].connected_to) 180 | self.child[0].connected_to[0]['output'] = {'put': {'name': 'output'}, 'node': self.pwindow.nodes[0]} 181 | 182 | return {'output': None} 183 | 184 | def render_base(self): 185 | if Element.render_base(self): 186 | self.label.x, self.label.y = self.x, self.y 187 | 188 | def delete(self, fully=False): 189 | if self.pwindow: 190 | self.pwindow.close() 191 | del self.pwindow 192 | Element.delete(self, fully) 193 | self.label.delete() 194 | -------------------------------------------------------------------------------- /pyno/field.py: -------------------------------------------------------------------------------- 1 | import pyglet 2 | import pyperclip 3 | 4 | from pyno.element import Element 5 | from pyno.utils import x_y_pan_scale, font 6 | from pyno.draw import Quad, labelsGroup 7 | 8 | 9 | class Field(Element): 10 | ''' 11 | Field is a white box where you can put values 12 | ''' 13 | 14 | def __init__(self, window, x, y, batch, code='0', connects=None, size=None, id=None): 15 | Element.__init__(self, x, y, (230, 230, 230), batch, id) 16 | 17 | if size: 18 | self.w, self.h = size 19 | 20 | if connects: 21 | self.connected_to = connects 22 | 23 | self.window = window 24 | self.code = code 25 | 26 | self.document = pyglet.text.document.FormattedDocument(self.code) 27 | self.document.set_style(0, len(self.document.text), 28 | dict(font_name=font, 29 | font_size=11, 30 | color=(0, 0, 0, 230))) 31 | self.layout = pyglet.text.layout.IncrementalTextLayout( 32 | self.document, 33 | self.w - 30, self.h - 3, 34 | multiline=True, batch=batch, 35 | group=labelsGroup) 36 | self.caret = pyglet.text.caret.Caret(self.layout) 37 | self.caret.color = (0, 0, 0) 38 | self.caret.visible = False 39 | 40 | self.incr = False 41 | self.resize = False 42 | 43 | self.inputs = ('input',) 44 | self.outputs = ('output',) 45 | self.insert_inouts({'inputs': self.inputs, 'outputs': self.outputs}) 46 | 47 | self.pan_scale = [[0.0, 0.0], 1] 48 | self.screen_size = (800, 600) 49 | 50 | self.graphics['scroll'] = None 51 | self.graphics['corner'] = None 52 | 53 | # Processor variables 54 | self.proc_result = None 55 | 56 | self.value = None 57 | self.is_number = True 58 | self.need_update = True 59 | self.problem = False 60 | self.gen_output = {'output': None} 61 | 62 | def reset_proc(self): 63 | self.proc_result = None 64 | 65 | def processor(self): 66 | # Processor called every frame 67 | 68 | if self.proc_result: 69 | return self.proc_result 70 | 71 | self.problem = False 72 | 73 | # if field is a child 74 | if self.connected_to: 75 | connection = self.connected_to[0] 76 | try: 77 | inputs = connection['output']['node'].processor() 78 | data = inputs[connection['output']['put']['name']] 79 | except: 80 | self.er_label.text = 'Cant read input' 81 | self.problem = True 82 | else: 83 | self.is_number = False 84 | self.gen_output['output'] = data 85 | self.document.text = repr(data) 86 | 87 | # update value carefully and check some stuff 88 | elif self.need_update: 89 | try: 90 | self.value = eval(self.document.text, self.window.global_scope) 91 | except Exception as ex: 92 | self.problem = True 93 | self.er_label.text = str(ex) 94 | self.is_number = False 95 | try: 96 | exec(self.document.text, self.window.global_scope) 97 | except Exception as ex: 98 | self.problem = True 99 | self.er_label.text = str(ex) 100 | else: 101 | self.problem = False 102 | else: 103 | self.is_number = (isinstance(self.value, (int, float)) 104 | and not self.connected_to) 105 | 106 | self.document.set_style(0, len(self.document.text), 107 | {'align': 'right' if self.is_number 108 | else 'left'}) 109 | 110 | self.gen_output['output'] = self.value 111 | self.need_update = False 112 | 113 | self.proc_result = self.gen_output 114 | return self.gen_output 115 | 116 | def render_base(self): 117 | if Element.render_base(self): 118 | self.style() 119 | 120 | if self.is_number: 121 | if not self.graphics['scroll']: 122 | self.graphics['scroll'] = Quad(self.batch, frontdrop=True) 123 | 124 | self.graphics['scroll'].redraw(self.x - self.cw + 10, 125 | self.y, 10, self.ch, 126 | (172, 150, 83)) 127 | 128 | elif self.graphics['scroll']: 129 | self.graphics['scroll'].delete() 130 | self.graphics['scroll'] = None 131 | 132 | if self.hover: 133 | if not self.graphics['corner']: 134 | self.graphics['corner'] = Quad(self.batch, frontdrop=True) 135 | 136 | self.graphics['corner'].redraw(self.x + self.cw - 5, 137 | self.y - self.ch + 5, 138 | 5, 5, (50, 50, 50)) 139 | 140 | elif self.graphics['corner']: 141 | self.graphics['corner'].delete() 142 | self.graphics['corner'] = None 143 | 144 | def style(self): 145 | # Vary how represent value, for numbers there is inc/decrement slider 146 | l = self.layout 147 | 148 | if self.is_number: 149 | l.x = self.x - self.cw + 30 150 | l.y = self.y - self.ch + 5 151 | l.width = self.w - 45 152 | l.height = self.h - 10 153 | 154 | else: 155 | l.x = self.x - self.cw + 15 156 | l.y = self.y - self.ch + 5 157 | l.width = self.w - 30 158 | l.height = self.h - 10 159 | 160 | def resize_field(self): 161 | self.w += self.layout.view_x 162 | self.h -= self.layout.view_y 163 | self.cw, self.ch = self.w // 2, self.h // 2 164 | 165 | def intersect_point(self, point, visual=True): 166 | inter = super().intersect_point(point, visual) 167 | if inter: 168 | if self.is_number: 169 | self.intersect_incr(point) 170 | self.intersect_corner(point) 171 | return inter 172 | 173 | def intersect_incr(self, point): 174 | # Intersect with inc/decrement slider, to fast value change 175 | intersect = (0 < point[0] - (self.x - self.cw) < 20 and 176 | 0 < point[1] - (self.y - self.ch) < self.h) 177 | self.incr = intersect 178 | return intersect 179 | 180 | def intersect_corner(self, point): 181 | # Intersect bottom right corner to resize 182 | intersect = (0 < point[0] - (self.x + self.cw - 10) < 10 and 183 | 0 < point[1] - (self.y - self.ch) < 10) 184 | self.resize = intersect 185 | return intersect 186 | 187 | def delete(self, fully=False): 188 | Element.delete(self, fully) 189 | self.layout.delete() 190 | 191 | # --- Input events --- 192 | 193 | def on_mouse_press(self, x, y, button, modifiers): 194 | x, y = x_y_pan_scale(x, y, self.pan_scale, self.screen_size) 195 | 196 | if button == 1: 197 | if self.hover: 198 | self.set_focus() 199 | self.caret.on_mouse_press(x, y, button, modifiers) 200 | else: 201 | self.lost_focus() 202 | 203 | def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): 204 | x, y = x_y_pan_scale(x, y, self.pan_scale, self.screen_size) 205 | dx, dy = int(dx / self.pan_scale[1]), int(dy / self.pan_scale[1]) 206 | 207 | if buttons == 1: 208 | if self.incr: 209 | t = self.document.text 210 | n = float(t) if '.' in t else int(t) 211 | i = dy / 10 if (modifiers == 1 or modifiers == 17) else dy 212 | r = n + i 213 | s = str(r) if isinstance(r, int) else "%.2f" % r 214 | self.document.text = s 215 | self.caret.visible = False 216 | self.caret.position = self.caret.mark = len(self.document.text) 217 | self.resize_field() 218 | self.x -= dx 219 | self.y -= dy 220 | self.need_update = True 221 | elif self.resize: 222 | self.w = max(self.w + dx * 2, 70) 223 | self.h = max(self.h - dy * 2, 30) 224 | self.x -= dx 225 | self.y -= dy 226 | elif self.hover and self.caret.visible: 227 | self.caret.on_mouse_drag(x, y, dx, dy, buttons, modifiers) 228 | self.x -= dx 229 | self.y -= dy 230 | self.style() 231 | 232 | def on_text(self, text): 233 | self.caret.on_text(text) 234 | self.resize_field() 235 | 236 | def on_text_motion(self, motion): 237 | self.caret.on_text_motion(motion) 238 | 239 | def on_text_motion_select(self, motion): 240 | self.caret.on_text_motion_select(motion) 241 | 242 | def on_key_press(self, symbol, modifiers): 243 | self.make_active() 244 | self.style() 245 | self.problem = False 246 | self.need_update = True 247 | key = pyglet.window.key 248 | 249 | if symbol == key.TAB: 250 | self.document.insert_text(self.caret.position, ' ') 251 | self.caret.position += 4 252 | 253 | elif modifiers & key.MOD_CTRL: 254 | if symbol == key.C: 255 | start = min(self.caret.position, self.caret.mark) 256 | end = max(self.caret.position, self.caret.mark) 257 | text = self.document.text[start:end] 258 | pyperclip.copy(text) 259 | elif symbol == key.V: 260 | text = pyperclip.paste() 261 | self.document.insert_text(self.caret.position, text) 262 | self.caret.position += len(text) 263 | 264 | def set_focus(self): 265 | self.caret.visible = True 266 | self.caret.mark = 0 267 | self.caret.position = len(self.document.text) 268 | 269 | def lost_focus(self): 270 | self.caret.visible = False 271 | try: 272 | self.caret.mark = self.caret.position = 0 273 | except AttributeError: 274 | pass # work-a-round for https://github.com/honix/Pyno/issues/12 275 | -------------------------------------------------------------------------------- /pyno/examples/particles.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": 0.4 4 | }, 5 | { 6 | "type": "field", 7 | "x": 197, 8 | "y": 188, 9 | "size": [ 10 | 308, 11 | 89 12 | ], 13 | "code": [ 14 | "# Patricles test", 15 | "", 16 | "# use mouse right and left", 17 | "# buttons to interact", 18 | "" 19 | ], 20 | "connects": [], 21 | "id": 9 22 | }, 23 | { 24 | "type": "node", 25 | "x": 260, 26 | "y": 513, 27 | "size": [ 28 | 460, 29 | 230 30 | ], 31 | "color": [ 32 | 129, 33 | 105, 34 | 124 35 | ], 36 | "code": [ 37 | "import pyglet", 38 | "", 39 | "config = pyglet.gl.Config(double_buffer=False)", 40 | "S['window'] = pyglet.window.Window(450, 450, config=config)", 41 | "", 42 | "def create_window():", 43 | " return S['window']", 44 | "", 45 | "call = create_window", 46 | "", 47 | "cleanup = lambda: S['window'].close()" 48 | ], 49 | "connects": [], 50 | "id": 10 51 | }, 52 | { 53 | "type": "node", 54 | "x": 542, 55 | "y": 224, 56 | "size": [ 57 | 509, 58 | 487 59 | ], 60 | "color": [ 61 | 99, 62 | 114, 63 | 116 64 | ], 65 | "code": [ 66 | "import pyglet", 67 | "", 68 | "S['rects'] = []", 69 | "S['batch'] = None", 70 | "", 71 | "def draw_rects(poses=[(0,0),(10,10),(30,30)]):", 72 | "", 73 | " # make new array of rects if count not match", 74 | " count = len(poses)", 75 | " if len(S['rects']) != count:", 76 | " S['rects'] = []", 77 | " S['batch'] = pyglet.graphics.Batch()", 78 | " for i in range(count):", 79 | " S['rects'].append(S['batch'].add(", 80 | " 1, pyglet.gl.GL_POINTS, None,", 81 | " ('v2i', (0, 0)),", 82 | " ('c4B', (255,255,255,10))))", 83 | "", 84 | " # new poses and sizes", 85 | " for rect, pos in zip(S['rects'], poses):", 86 | " x, y = int(pos[0]), int(pos[1]) ", 87 | " rect.vertices = (x, y)", 88 | "", 89 | " return S['batch']", 90 | "", 91 | "call = draw_rects" 92 | ], 93 | "connects": [ 94 | { 95 | "output": { 96 | "node": 12, 97 | "put": { 98 | "name": "result" 99 | } 100 | }, 101 | "input": { 102 | "put": { 103 | "name": "poses" 104 | } 105 | } 106 | } 107 | ], 108 | "id": 11 109 | }, 110 | { 111 | "type": "node", 112 | "x": 542, 113 | "y": 310, 114 | "size": [ 115 | 528, 116 | 680 117 | ], 118 | "color": [ 119 | 86, 120 | 111, 121 | 98 122 | ], 123 | "code": [ 124 | "import math", 125 | "from random import random", 126 | "", 127 | "S['p'] = []", 128 | "S['a'] = []", 129 | "", 130 | "def particles(inv=False, magnet=(0,0), count=10):", 131 | " if count != len(S['p']):", 132 | " S['p'] = []", 133 | " S['a'] = []", 134 | " for i in range(count):", 135 | " S['p'].append([225,225])", 136 | " S['a'].append([random()*200-100,", 137 | " random()*200-100])", 138 | "", 139 | " for pos, acc in zip(S['p'], S['a']):", 140 | " dist = math.sqrt((pos[0] - magnet[0])**2 +", 141 | " (pos[1] - magnet[1])**2)", 142 | " acc[0] *= 0.6", 143 | " acc[1] *= 0.6", 144 | " if not inv:", 145 | " acc[0] += (pos[0] - magnet[0]) / dist * 2", 146 | " acc[1] += (pos[1] - magnet[1]) / dist * 2", 147 | " else:", 148 | " acc[0] -= (pos[0] - magnet[0]) / dist * 2", 149 | " acc[1] -= (pos[1] - magnet[1]) / dist * 2", 150 | " pos[0] += acc[0] / 5", 151 | " pos[1] += acc[1] / 5", 152 | " if not (0 < pos[0] < 450):", 153 | " acc[0] *= -20", 154 | " if not (0 < pos[1] < 450):", 155 | " acc[1] *= -20", 156 | " ", 157 | " return S['p']", 158 | "", 159 | "call = particles" 160 | ], 161 | "connects": [ 162 | { 163 | "output": { 164 | "node": 13, 165 | "put": { 166 | "name": "output" 167 | } 168 | }, 169 | "input": { 170 | "put": { 171 | "name": "count" 172 | } 173 | } 174 | }, 175 | { 176 | "output": { 177 | "node": 15, 178 | "put": { 179 | "name": "mouse_pos" 180 | } 181 | }, 182 | "input": { 183 | "put": { 184 | "name": "magnet" 185 | } 186 | } 187 | }, 188 | { 189 | "output": { 190 | "node": 15, 191 | "put": { 192 | "name": "click" 193 | } 194 | }, 195 | "input": { 196 | "put": { 197 | "name": "inv" 198 | } 199 | } 200 | } 201 | ], 202 | "id": 12 203 | }, 204 | { 205 | "type": "field", 206 | "x": 500, 207 | "y": 506, 208 | "size": [ 209 | 98, 210 | 30 211 | ], 212 | "code": [ 213 | "512" 214 | ], 215 | "connects": [], 216 | "id": 13 217 | }, 218 | { 219 | "type": "node", 220 | "x": 494, 221 | "y": 140, 222 | "size": [ 223 | 317, 224 | 178 225 | ], 226 | "color": [ 227 | 103, 228 | 92, 229 | 84 230 | ], 231 | "code": [ 232 | "def draw_batch(window=None,", 233 | " trail=False,", 234 | " batch=None):", 235 | " window.switch_to()", 236 | " if not trail:", 237 | " window.clear()", 238 | " batch.draw()", 239 | "", 240 | "call = draw_batch" 241 | ], 242 | "connects": [ 243 | { 244 | "output": { 245 | "node": 10, 246 | "put": { 247 | "name": "result" 248 | } 249 | }, 250 | "input": { 251 | "put": { 252 | "name": "window" 253 | } 254 | } 255 | }, 256 | { 257 | "output": { 258 | "node": 11, 259 | "put": { 260 | "name": "result" 261 | } 262 | }, 263 | "input": { 264 | "put": { 265 | "name": "batch" 266 | } 267 | } 268 | }, 269 | { 270 | "output": { 271 | "node": 15, 272 | "put": { 273 | "name": "rclick" 274 | } 275 | }, 276 | "input": { 277 | "put": { 278 | "name": "trail" 279 | } 280 | } 281 | } 282 | ], 283 | "id": 14 284 | }, 285 | { 286 | "type": "node", 287 | "x": 373, 288 | "y": 439, 289 | "size": [ 290 | 568, 291 | 697 292 | ], 293 | "color": [ 294 | 80, 295 | 81, 296 | 92 297 | ], 298 | "code": [ 299 | "from typing import *", 300 | "from pyglet.window.mouse import *", 301 | "", 302 | "G['mouse_pos'] = [0,0]", 303 | "G['click'] = False", 304 | "G['rclick'] = False", 305 | "", 306 | "def mouse(window=0) -> Tuple['rclick', 'click', 'mouse_pos']:", 307 | " @window.event", 308 | " def on_mouse_motion(x, y, ", 309 | " dx, dy):", 310 | " G['mouse_pos'] = x, y", 311 | "", 312 | " @window.event", 313 | " def on_mouse_drag(x, y, dx, dy, ", 314 | " buttons, modifiers):", 315 | " G['mouse_pos'] = x, y", 316 | "", 317 | " @window.event", 318 | " def on_mouse_press(x, y, ", 319 | " button, modifiers):", 320 | " if button & LEFT:", 321 | " G['click'] = True", 322 | " elif button & RIGHT:", 323 | " G['rclick'] = True", 324 | "", 325 | " @window.event", 326 | " def on_mouse_release(x, y, ", 327 | " button, modifiers):", 328 | " if button & LEFT:", 329 | " G['click'] = False", 330 | " elif button & RIGHT:", 331 | " G['rclick'] = False", 332 | "", 333 | " return G['rclick'], G['click'], G['mouse_pos']", 334 | "", 335 | "call = mouse" 336 | ], 337 | "connects": [ 338 | { 339 | "output": { 340 | "node": 10, 341 | "put": { 342 | "name": "result" 343 | } 344 | }, 345 | "input": { 346 | "put": { 347 | "name": "window" 348 | } 349 | } 350 | } 351 | ], 352 | "id": 15 353 | }, 354 | { 355 | "type": "field", 356 | "x": 672, 357 | "y": 505, 358 | "size": [ 359 | 221, 360 | 30 361 | ], 362 | "code": [ 363 | "# number of particles" 364 | ], 365 | "connects": [], 366 | "id": 16 367 | } 368 | ] -------------------------------------------------------------------------------- /pyno/element.py: -------------------------------------------------------------------------------- 1 | import pyglet 2 | from pyglet import gl 3 | 4 | from pyno.draw import Line, Quad 5 | from pyno.utils import font, centered 6 | 7 | 8 | # some colors functions 9 | def color_select(color): 10 | '''Color for hover''' 11 | return tuple(map(lambda c: int(c * 0.65), color)) 12 | 13 | 14 | def color_inverse(color): 15 | '''Color for selected''' 16 | return tuple(map(lambda c: int(c * -0.8), color)) 17 | 18 | 19 | class Element(): 20 | ''' 21 | Element is a base class of pyno objects 22 | ''' 23 | 24 | id_counter = 0 # count of all elements 25 | 26 | def __init__(self, x, y, color, batch, id): 27 | if id: 28 | self.id_counter = max(self.id_counter, id) 29 | self.id = self.id_counter 30 | else: 31 | Element.id_counter += 1 32 | self.id = self.id_counter 33 | 34 | self.x, self.y = x, y 35 | self.w, self.h = 70, 30 36 | self.cw, self.ch = self.w // 2, self.h // 2 37 | self.offset = 20 38 | self.put_size = 5 39 | self.pin_color = color_select(color) 40 | self.color = color 41 | self.draw_color = color 42 | self.er_color = (230, 20, 20) 43 | 44 | self.active = True 45 | self.batch = batch 46 | self.graphics = dict(inputs=dict(), outputs=dict(), connections=list(), 47 | error=None, base=Quad(self.batch)) 48 | 49 | self.er_label = pyglet.text.Label('error', font_name=font, 50 | bold=True, font_size=12, 51 | color=self.er_color + (255,), 52 | anchor_x='right', anchor_y='center') 53 | self.inputs = () 54 | self.outputs = () 55 | self.in_labels = [] 56 | self.connected_to = [] 57 | self.out_labels = [] 58 | self.child = [] 59 | self.selected = False # 714848 60 | self.selectedInput = {'name': 'none', 'pos': 0} 61 | self.selectedOutput = {'name': 'none', 'pos': 0} 62 | self.hover = False 63 | 64 | self.problem = False 65 | 66 | def intersect_point(self, point, visual=True): 67 | # Intersection with whole element, also check pins intersection 68 | 69 | if self.x + self.cw > point[0] > self.x - self.cw and \ 70 | self.y + self.ch + self.put_size * 2 > point[1] > \ 71 | self.y - self.ch - self.put_size * 2: 72 | self.selectedInput = {'name': 'none', 'pos': 0} 73 | self.selectedOutput = {'name': 'none', 'pos': 0} 74 | 75 | if visual: 76 | self.draw_color = color_select(self.color) 77 | self.hover = True 78 | 79 | if point[1] > self.y + self.ch: 80 | for put in self.put_pos(self.inputs): 81 | if put['pos'] + self.put_size * 2 > point[0] > \ 82 | put['pos'] - self.put_size * 2: 83 | self.selectedInput = ({'name': put['name']}) 84 | 85 | elif point[1] < self.y - self.ch: 86 | for put in self.put_pos(self.outputs): 87 | if put['pos'] + self.put_size * 2 > point[0] > \ 88 | put['pos'] - self.put_size * 2: 89 | self.selectedOutput = ({'name': put['name']}) 90 | self.active = True 91 | return True 92 | 93 | elif self.hover: 94 | self.active = True 95 | 96 | self.selectedInput = {'name': 'none', 'pos': 0} 97 | self.selectedOutput = {'name': 'none', 'pos': 0} 98 | self.hover = False 99 | if visual: 100 | self.draw_color = self.color 101 | return False 102 | 103 | def render_base(self): 104 | # Render for base 105 | 106 | if not self.active and not self.hover: 107 | return False 108 | 109 | gr = self.graphics 110 | self.cw, self.ch = self.w // 2, self.h // 2 111 | 112 | if self.problem: 113 | if not gr['error']: 114 | gr['error'] = Quad(self.batch, True) 115 | 116 | gr['error'].redraw(self.x, self.y, self.cw + self.put_size, 117 | self.ch + self.put_size, 118 | (190, 20, 20)) 119 | 120 | elif gr['error']: 121 | gr['error'].id.delete() 122 | gr['error'] = None 123 | 124 | gr['base'].redraw(self.x, self.y, self.cw, self.ch, 125 | self.draw_color) 126 | 127 | self.pin_color = color_select(self.draw_color) 128 | 129 | for input in self.put_pos(self.inputs): 130 | put_name = self.selectedInput['name'] 131 | if input['name'] == put_name: 132 | c = color_inverse(self.pin_color) 133 | else: 134 | c = self.pin_color 135 | gr['inputs'][input['name']].redraw( 136 | input['pos'], 137 | self.y + self.ch + self.put_size, 138 | self.put_size, self.put_size, c) 139 | 140 | for output in self.put_pos(self.outputs): 141 | put_name = self.selectedOutput['name'] 142 | if output['name'] == put_name: 143 | c = color_inverse(self.pin_color) 144 | else: 145 | c = self.pin_color 146 | gr['outputs'][output['name']].redraw( 147 | output['pos'], 148 | self.y - self.ch - self.put_size, 149 | self.put_size, self.put_size, c) 150 | 151 | con = gr['connections'] 152 | while len(con) < len(self.connected_to): 153 | con.append([Line(self.batch), Line(self.batch), Line(self.batch)]) 154 | 155 | i = -1 156 | for node in self.connected_to: 157 | i += 1 158 | n = node['output']['node'] 159 | 160 | try: 161 | iputx = self.put_pos_by_name(node['input']['put']['name'], 162 | 'inputs') 163 | oputx = n.put_pos_by_name(node['output']['put']['name'], 164 | 'outputs') 165 | if not iputx or not oputx: 166 | raise Exception() 167 | except Exception as ex: 168 | for lines in con[i]: 169 | lines.delete() 170 | con.remove(con[i]) 171 | del self.connected_to[self.connected_to.index(node)] 172 | print('Connection is broken:', ex) 173 | break 174 | 175 | from_in = self.y + self.ch + self.offset // 2 176 | from_out = self.y + self.ch + self.offset 177 | to_out = n.y - n.ch - n.offset 178 | to_in = n.y - n.ch - n.offset // 2 179 | 180 | con[i][0].redraw((iputx, from_in), (iputx, from_out)) 181 | con[i][1].redraw((iputx, from_out), (oputx, to_out)) 182 | con[i][2].redraw((oputx, to_out), (oputx, to_in)) 183 | 184 | return True 185 | 186 | def render_labels(self): 187 | # Render for errors and labels of pins 188 | 189 | if not self.active and not self.hover: 190 | return 191 | 192 | if self.problem: 193 | self.er_label.x = self.x - self.cw - self.offset 194 | self.er_label.y = self.y 195 | self.er_label.draw() 196 | 197 | if self.hover: 198 | for label, put in zip(self.in_labels, self.put_pos(self.inputs)): 199 | gl.glPushMatrix() 200 | gl.glTranslatef(put['pos'], self.y + self.ch + 15, 0.0) 201 | gl.glRotatef(45.0, 0.0, 0.0, 1.0) 202 | label.draw() 203 | gl.glPopMatrix() 204 | 205 | for label, put in zip(self.out_labels, self.put_pos(self.outputs)): 206 | gl.glPushMatrix() 207 | gl.glTranslatef(put['pos'], self.y - self.ch - 20, 0.0) 208 | gl.glRotatef(45.0, 0.0, 0.0, 1.0) 209 | label.draw() 210 | gl.glPopMatrix() 211 | 212 | def deactive(self): 213 | if self.active: 214 | self.active = False 215 | 216 | def insert_inouts(self, data): 217 | # New inputs and output was created. 218 | # We need create labels for each 219 | 220 | gr = self.graphics 221 | self.inputs = data['inputs'] 222 | self.outputs = data['outputs'] 223 | [put.delete() for put in gr['inputs'].values()] 224 | [put.delete() for put in gr['outputs'].values()] 225 | 226 | self.in_labels = [] 227 | gr['inputs'] = dict() 228 | for input in self.inputs: 229 | gr['inputs'][input] = Quad(self.batch) 230 | self.in_labels.append(pyglet.text.Label(input, x=0, y=0, 231 | font_name=font, 232 | bold=True, 233 | color=(255,255,255,200), 234 | font_size=12)) 235 | self.out_labels = [] 236 | gr['outputs'] = dict() 237 | for output in self.outputs: 238 | gr['outputs'][output] = Quad(self.batch) 239 | self.out_labels.append(pyglet.text.Label(output, x=0, y=0, 240 | font_name=font, 241 | bold=True, 242 | color=(255,255,255,200), 243 | font_size=12, 244 | anchor_x='right')) 245 | 246 | def put_pos(self, puts): 247 | # Calculate pos for pins 248 | for put in puts: 249 | yield {'name': put, 250 | 'pos': int(centered(self.x, self.w * 0.8, 251 | len(puts), 252 | puts.index(put)))} 253 | 254 | def put_pos_by_name(self, name, mode): 255 | # Return pose x of pin by name 256 | if mode == 'outputs': 257 | for put in self.outputs: 258 | if put == name: 259 | return int(centered(self.x, self.w * 0.8, 260 | len(self.outputs), 261 | self.outputs.index(put))) 262 | elif mode == 'inputs': 263 | for put in self.inputs: 264 | if put == name: 265 | return int(centered(self.x, self.w * 0.8, 266 | len(self.inputs), 267 | self.inputs.index(put))) 268 | 269 | def resize_to_name(self, name): 270 | self.w = max(len(name) * 10 + 20, 271 | len(self.inputs) * 20, len(self.outputs) * 20, 64) 272 | self.cw = self.w // 2 273 | 274 | def make_active(self): 275 | self.active = True 276 | 277 | def make_child_active(self): 278 | self.make_active() 279 | [c.make_active() for c in self.child] 280 | 281 | def add_child(self, child): 282 | if child not in self.child: 283 | self.child.append(child) 284 | 285 | def get_con_id(self): 286 | new_connectedto = [] 287 | for connect in self.connected_to: 288 | new_connect = {'output': {'node': connect['output']['node'].id, 289 | 'put': connect['output']['put']}, 290 | 'input': {'put': connect['input']['put']}} 291 | new_connectedto.append(new_connect) 292 | return new_connectedto 293 | 294 | def reconnect(self, buff): 295 | # Find parent node when paste 296 | for connect in self.connected_to: 297 | for o in buff: 298 | if connect['output']['node'] == o.id: 299 | connect['output']['node'] = o 300 | o.add_child(self) 301 | 302 | def delete(self, fully=False): 303 | for value in self.graphics.values(): 304 | if value: 305 | if isinstance(value, dict): 306 | [v.delete(fully) for v in value.values()] 307 | elif isinstance(value, list): 308 | for l in value: 309 | for lines in l: 310 | lines.delete(fully) 311 | else: 312 | value.delete(fully) 313 | -------------------------------------------------------------------------------- /pyno/examples/mouse_keyboard.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": 0.4 4 | }, 5 | { 6 | "type": "node", 7 | "x": 494, 8 | "y": 116, 9 | "size": [ 10 | 436, 11 | 293 12 | ], 13 | "color": [ 14 | 99, 15 | 114, 16 | 116 17 | ], 18 | "code": [ 19 | "import pyglet", 20 | "", 21 | "def box(pos=[0, 0]):", 22 | " x, y = int(pos[0]), int(pos[1])", 23 | " box = pyglet.graphics.vertex_list_indexed(4,", 24 | " [0, 1, 2, 0, 2, 3],\r", 25 | " ('v2i', (x, y,\r", 26 | " x + 50, y,\r", 27 | " x + 50, y +50,\r", 28 | " x, y + 50))\r,", 29 | " ('c3B', (255, 100, 0)*4)", 30 | " )", 31 | " return box", 32 | "", 33 | "call = box" 34 | ], 35 | "connects": [ 36 | { 37 | "output": { 38 | "node": 14, 39 | "put": { 40 | "name": "result" 41 | } 42 | }, 43 | "input": { 44 | "put": { 45 | "name": "pos" 46 | } 47 | } 48 | } 49 | ], 50 | "id": 10 51 | }, 52 | { 53 | "type": "field", 54 | "x": 143, 55 | "y": 523, 56 | "size": [ 57 | 250, 58 | 107 59 | ], 60 | "code": [ 61 | "'''", 62 | "Getting keyboard and mouse events from window", 63 | "", 64 | "'''" 65 | ], 66 | "connects": [], 67 | "id": 11 68 | }, 69 | { 70 | "type": "node", 71 | "x": 495, 72 | "y": 435, 73 | "size": [ 74 | 447, 75 | 398 76 | ], 77 | "color": [ 78 | 93, 79 | 93, 80 | 89 81 | ], 82 | "code": [ 83 | "G['key'] = []", 84 | "", 85 | "def keyboard(window=0):", 86 | "", 87 | " @window.event", 88 | " def on_key_press(symbol, mod):", 89 | " G['key'].append(symbol)", 90 | "", 91 | " @window.event", 92 | " def on_key_release(symbol, mod):", 93 | " if symbol in G['key']:", 94 | " G['key'].pop(G['key'].index(symbol))", 95 | "", 96 | " @window.event", 97 | " def on_deactivate():", 98 | " G['key'] = []", 99 | "", 100 | " return G['key']", 101 | "", 102 | "call = keyboard" 103 | ], 104 | "connects": [ 105 | { 106 | "output": { 107 | "node": 20, 108 | "put": { 109 | "name": "result" 110 | } 111 | }, 112 | "input": { 113 | "put": { 114 | "name": "window" 115 | } 116 | } 117 | } 118 | ], 119 | "id": 12 120 | }, 121 | { 122 | "type": "field", 123 | "x": 631, 124 | "y": 357, 125 | "size": [ 126 | 140, 127 | 30 128 | ], 129 | "code": [ 130 | "[]" 131 | ], 132 | "connects": [ 133 | { 134 | "output": { 135 | "node": 12, 136 | "put": { 137 | "name": "result" 138 | } 139 | }, 140 | "input": { 141 | "put": { 142 | "name": "input" 143 | } 144 | } 145 | } 146 | ], 147 | "id": 13 148 | }, 149 | { 150 | "type": "node", 151 | "x": 495, 152 | "y": 297, 153 | "size": [ 154 | 631, 155 | 398 156 | ], 157 | "color": [ 158 | 101, 159 | 91, 160 | 127 161 | ], 162 | "code": [ 163 | "import pyglet", 164 | "", 165 | "S['position'] = [20, 20]", 166 | "", 167 | "def position(key=[]):", 168 | "", 169 | " key = list(map(lambda x: pyglet.window.key.symbol_string(x), key))", 170 | "", 171 | " if 'UP' in key:", 172 | " S['position'][1] += 1", 173 | " if 'DOWN' in key:", 174 | " S['position'][1] -= 1", 175 | " if 'RIGHT' in key:", 176 | " S['position'][0] += 1", 177 | " if 'LEFT' in key:", 178 | " S['position'][0] -= 1", 179 | " return S['position']", 180 | "", 181 | "call = position" 182 | ], 183 | "connects": [ 184 | { 185 | "output": { 186 | "node": 12, 187 | "put": { 188 | "name": "result" 189 | } 190 | }, 191 | "input": { 192 | "put": { 193 | "name": "key" 194 | } 195 | } 196 | } 197 | ], 198 | "id": 14 199 | }, 200 | { 201 | "type": "field", 202 | "x": 634, 203 | "y": 215, 204 | "size": [ 205 | 132, 206 | 30 207 | ], 208 | "code": [ 209 | "[84, 132]" 210 | ], 211 | "connects": [ 212 | { 213 | "output": { 214 | "node": 14, 215 | "put": { 216 | "name": "result" 217 | } 218 | }, 219 | "input": { 220 | "put": { 221 | "name": "input" 222 | } 223 | } 224 | } 225 | ], 226 | "id": 15 227 | }, 228 | { 229 | "type": "node", 230 | "x": 337, 231 | "y": 331, 232 | "size": [ 233 | 326, 234 | 214 235 | ], 236 | "color": [ 237 | 80, 238 | 81, 239 | 92 240 | ], 241 | "code": [ 242 | "import pyglet", 243 | "", 244 | "G['mouse_pos'] = [0,0]", 245 | "", 246 | "def mouse(window=0):", 247 | " @window.event", 248 | " def on_mouse_motion(x, y, ", 249 | " dx, dy):", 250 | " G['mouse_pos'] = [x, y]", 251 | " return G['mouse_pos']", 252 | "", 253 | "call = mouse" 254 | ], 255 | "connects": [ 256 | { 257 | "output": { 258 | "node": 20, 259 | "put": { 260 | "name": "result" 261 | } 262 | }, 263 | "input": { 264 | "put": { 265 | "name": "window" 266 | } 267 | } 268 | } 269 | ], 270 | "id": 16 271 | }, 272 | { 273 | "type": "field", 274 | "x": 210, 275 | "y": 242, 276 | "size": [ 277 | 124, 278 | 30 279 | ], 280 | "code": [ 281 | "[272, 300]" 282 | ], 283 | "connects": [ 284 | { 285 | "output": { 286 | "node": 16, 287 | "put": { 288 | "name": "result" 289 | } 290 | }, 291 | "input": { 292 | "put": { 293 | "name": "input" 294 | } 295 | } 296 | } 297 | ], 298 | "id": 17 299 | }, 300 | { 301 | "type": "node", 302 | "x": 336, 303 | "y": 112, 304 | "size": [ 305 | 434, 306 | 298 307 | ], 308 | "color": [ 309 | 99, 310 | 114, 311 | 116 312 | ], 313 | "code": [ 314 | "import pyglet", 315 | "", 316 | "def box(pos=[0, 0]):", 317 | " x, y = int(pos[0]), int(pos[1])", 318 | " box = pyglet.graphics.vertex_list_indexed(4,", 319 | " [0, 1, 2, 0, 2, 3],\r", 320 | " ('v2i', (x, y,\r", 321 | " x + 50, y,\r", 322 | " x + 50, y +50,\r", 323 | " x, y + 50))\r,", 324 | " ('c3B', (100, 255, 0)*4)", 325 | " )", 326 | " return box", 327 | "", 328 | "call = box" 329 | ], 330 | "connects": [ 331 | { 332 | "output": { 333 | "node": 16, 334 | "put": { 335 | "name": "result" 336 | } 337 | }, 338 | "input": { 339 | "put": { 340 | "name": "pos" 341 | } 342 | } 343 | } 344 | ], 345 | "id": 18 346 | }, 347 | { 348 | "type": "node", 349 | "x": 495, 350 | "y": -28, 351 | "size": [ 352 | 379, 353 | 243 354 | ], 355 | "color": [ 356 | 127, 357 | 96, 358 | 112 359 | ], 360 | "code": [ 361 | "import pyglet", 362 | "", 363 | "def render(a=0, b=0, clear=True):", 364 | " window = G['window']", 365 | " window.switch_to()", 366 | " if clear:", 367 | " window.clear()", 368 | " a.draw(pyglet.gl.GL_TRIANGLES)", 369 | " b.draw(pyglet.gl.GL_TRIANGLES)", 370 | "", 371 | "call = render" 372 | ], 373 | "connects": [ 374 | { 375 | "output": { 376 | "node": 10, 377 | "put": { 378 | "name": "result" 379 | } 380 | }, 381 | "input": { 382 | "put": { 383 | "name": "b" 384 | } 385 | } 386 | }, 387 | { 388 | "output": { 389 | "node": 18, 390 | "put": { 391 | "name": "result" 392 | } 393 | }, 394 | "input": { 395 | "put": { 396 | "name": "a" 397 | } 398 | } 399 | }, 400 | { 401 | "output": { 402 | "node": 21, 403 | "put": { 404 | "name": "output" 405 | } 406 | }, 407 | "input": { 408 | "put": { 409 | "name": "clear" 410 | } 411 | } 412 | } 413 | ], 414 | "id": 19 415 | }, 416 | { 417 | "type": "node", 418 | "x": 439, 419 | "y": 545, 420 | "size": [ 421 | 406, 422 | 194 423 | ], 424 | "color": [ 425 | 129, 426 | 105, 427 | 124 428 | ], 429 | "code": [ 430 | "import pyglet", 431 | "", 432 | "config = pyglet.gl.Config(double_buffer=False)", 433 | "G['window'] = pyglet.window.Window(300,300, config=config)", 434 | "", 435 | "def create_window():", 436 | " return G['window']", 437 | "", 438 | "call = create_window" 439 | ], 440 | "connects": [], 441 | "id": 20 442 | }, 443 | { 444 | "type": "field", 445 | "x": 629, 446 | "y": 87, 447 | "size": [ 448 | 70, 449 | 30 450 | ], 451 | "code": [ 452 | "1" 453 | ], 454 | "connects": [], 455 | "id": 21 456 | } 457 | ] -------------------------------------------------------------------------------- /pyno/codeEditor.py: -------------------------------------------------------------------------------- 1 | import pyglet 2 | import pyperclip 3 | import keyword 4 | import tokenize 5 | import io 6 | import os 7 | 8 | from pyno.utils import x_y_pan_scale, font 9 | from pyno.draw import quad_aligned 10 | 11 | 12 | highlight = set(list(__builtins__.keys()) + 13 | list(keyword.__dict__.keys()) + 14 | keyword.kwlist + 15 | ['call', 'cleanup']) 16 | 17 | x_shift = 40 18 | y_shift = 20 19 | layout_padding = 5 20 | resize_button = 10 21 | nline_width = 30 22 | nline_padding = 2 23 | help_offset = 5 24 | 25 | 26 | class CodeEditor(): 27 | ''' 28 | Code editor is the window you define nodes function 29 | ''' 30 | 31 | def __init__(self, node, highlighting=1): 32 | self.node = node # node-owner of this codeEditor 33 | self.document = pyglet.text.document.FormattedDocument(node.code) 34 | 35 | self.highlighting = highlighting # 0: off, 1: python (node), 2: file (sub) 36 | 37 | @self.document.event 38 | def on_insert_text(start, end): 39 | self.update_highlighting() 40 | 41 | @self.document.event 42 | def on_delete_text(start, end): 43 | self.update_highlighting() 44 | 45 | self.document.set_style(0, len(node.code), 46 | dict(font_name=font, 47 | font_size=11, color=(255, 255, 255, 230))) 48 | 49 | self.layout = pyglet.text.layout.IncrementalTextLayout( 50 | self.document, 51 | *node.editor_size, 52 | multiline=True, wrap_lines=False) 53 | 54 | self.update_label = pyglet.text.Label('CTRL+ENTER to save and execute', 55 | font_name=font, 56 | anchor_y='top', 57 | font_size=9) 58 | 59 | self.line_numbering = pyglet.text.Label('', 60 | font_name=font, 61 | font_size=11, 62 | color=(255, 255, 255, 127), 63 | align='right', 64 | anchor_y='top', 65 | width=nline_width, 66 | multiline=True) 67 | 68 | self.autocomplete = pyglet.text.Label('', 69 | font_name=font, 70 | font_size=9, 71 | color=(125, 255, 125, 127)) 72 | 73 | self.caret = pyglet.text.caret.Caret(self.layout) 74 | self.caret.color = (255, 255, 255) 75 | self.caret.visible = False 76 | self.hover = False 77 | self.hovered = True 78 | self.resize = False 79 | 80 | self.change = False 81 | 82 | self.pan_scale = [[0.0, 0.0], 1] 83 | self.screen_size = (800, 600) 84 | 85 | def update_node(self): 86 | # Push code to node 87 | self.node.new_code(self.document.text) 88 | self.node.need_update = True 89 | self.change = False 90 | 91 | def intersect_point(self, point): 92 | # Intersection with whole codeEditor 93 | l = self.layout 94 | if 0 < point[0] - l.x + 20 < l.width + 20 and \ 95 | 0 < point[1] - l.y < l.height + 10: 96 | self.node.hover = True 97 | return True 98 | return False 99 | 100 | def intersect_corner(self, point): 101 | # Intersection with bottom right corner to resize 102 | l = self.layout 103 | return (0 < point[0] - (l.x + l.width - resize_button) < resize_button and 104 | 0 < point[1] - l.y < resize_button) 105 | 106 | def render(self): 107 | self.node.make_child_active() 108 | 109 | l = self.layout 110 | l.x = self.node.x + self.node.cw + x_shift 111 | l.y = self.node.y - l.height + self.node.ch + y_shift 112 | 113 | if self.change: 114 | self.update_label.x = l.x 115 | self.update_label.y = l.y - help_offset 116 | self.update_label.draw() 117 | 118 | if self.hover: 119 | if self.document.text and not self.hovered: 120 | self.hovered = True 121 | self.update_highlighting() 122 | 123 | color = self.node.color if not self.change else (255, 100, 10) 124 | 125 | # codeEditor background 126 | quad_aligned(l.x - layout_padding, l.y, 127 | l.width + layout_padding, l.height, 128 | ((0, 0, 0) if not self.change 129 | else (20, 10, 5)) + (230,)) 130 | 131 | if self.resize: 132 | quad_aligned(l.x - layout_padding, l.y + l.height, 133 | self.node.editor_size[0] + layout_padding, 134 | -self.node.editor_size[1], 135 | color + (100,)) 136 | 137 | # codeEditor resize corner 138 | quad_aligned(l.x + l.width - resize_button, l.y, 139 | resize_button, resize_button, color + (255,)) 140 | 141 | # codeEditor left line 142 | quad_aligned(l.x - layout_padding - nline_width, l.y, 143 | nline_width, l.height, color + (255,)) 144 | 145 | # codeEditor left line numbering 146 | font_height = self.layout.content_height / self.layout.get_line_count() 147 | line_offset = (-self.layout.view_y) % font_height 148 | first_line = int(-self.layout.view_y / font_height) 149 | count_line = min(int((self.layout.height + line_offset) / font_height), self.layout.get_line_count()) 150 | self.line_numbering.x = l.x - layout_padding - nline_width - nline_padding 151 | self.line_numbering.y = l.y + l.height + line_offset 152 | self.line_numbering.text = '\n'.join(['%02i'%i for i in range(first_line + 1, first_line + count_line + 1)]) 153 | self.line_numbering.draw() 154 | 155 | # codeEditor autocomplete hint 156 | self.autocomplete.x = l.x 157 | self.autocomplete.y = l.y + l.height + help_offset 158 | self.autocomplete.draw() 159 | else: 160 | if self.document.text and self.hovered: 161 | self.hovered = False 162 | self.document.set_style(0, len(self.node.code), 163 | dict(color=(255, 255, 255, 50))) 164 | 165 | self.layout.draw() 166 | 167 | def update_highlighting(self): 168 | if len(self.document.text) == 0: 169 | return 170 | 171 | # reset highlighting and hint 172 | self.document.set_style(0, len(self.document.text), 173 | dict(color=(255, 255, 255, 255))) 174 | self.autocomplete.text = "" 175 | 176 | if self.highlighting == 0: # 0: off 177 | return 178 | elif self.highlighting == 1: # 1: python 179 | # rudimentary syntax highlighting and autocomplete hint 180 | newline_offset = ([-1] + 181 | [i for i, ch in enumerate(self.document.text) if ch == '\n'] + 182 | [len(self.document.text)]) 183 | try: 184 | obj_string = "" 185 | for item in tokenize.tokenize(io.BytesIO(self.document.text.encode('utf-8')).readline): 186 | start = newline_offset[item.start[0] - 1] + item.start[1] + 1 187 | stopp = newline_offset[item.end[0] - 1] + item.end[1] + 1 188 | 189 | # rudimentary autocomplete hint 190 | if (item.type == tokenize.NAME) or (item.string == "."): 191 | obj_string += item.string 192 | else: 193 | obj_string = "" 194 | if (start <= self.caret.position <= stopp): 195 | if not obj_string: 196 | obj_string = item.string 197 | try: 198 | obj = eval(obj_string.strip(), self.node.env) 199 | #print("Code hint:\n", obj.__doc__) 200 | self.autocomplete.text = obj.__doc__.split("\n")[0] 201 | except: 202 | pass 203 | 204 | # syntax highlighting 205 | if (item.type == tokenize.NAME) and (item.string in highlight): 206 | pass 207 | elif (item.type in [tokenize.COMMENT, tokenize.OP, tokenize.NUMBER, tokenize.STRING]): 208 | pass # (we could e.g. set another color here...) 209 | else: 210 | continue # do not highlight this token 211 | self.document.set_style(start, stopp, 212 | dict(color=(255, 200, 100, 255))) 213 | except tokenize.TokenError: 214 | pass 215 | elif self.highlighting == 2: # 2: file 216 | if os.path.exists(self.document.text): 217 | self.document.set_style(0, len(self.node.code), 218 | dict(color=(255, 200, 100, 255))) 219 | 220 | # --- Input events --- 221 | 222 | def on_mouse_press(self, x, y, button, modifiers): 223 | x, y = x_y_pan_scale(x, y, self.pan_scale, self.screen_size) 224 | 225 | if self.intersect_corner((x, y)): 226 | self.resize = True 227 | elif button == 1 and self.hover: 228 | self.set_focus() 229 | self.caret.on_mouse_press(x, y, button, modifiers) 230 | self.update_highlighting() 231 | 232 | def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): 233 | x, y = x_y_pan_scale(x, y, self.pan_scale, self.screen_size) 234 | dx, dy = int(dx / self.pan_scale[1]), int(dy / self.pan_scale[1]) 235 | 236 | if buttons == 1 and self.resize: 237 | width = max(self.node.editor_size[0] + dx, 300) 238 | height = max(self.node.editor_size[1] - dy, 150) 239 | self.node.editor_size = width, height 240 | elif buttons == 1 and self.hover: 241 | self.caret.on_mouse_drag(x, y, dx, dy, buttons, modifiers) 242 | 243 | def on_mouse_release(self, x, y, button, modifiers): 244 | if self.resize: 245 | self.layout.width, self.layout.height = self.node.editor_size 246 | self.resize = False 247 | 248 | def on_text(self, text): 249 | if self.hover: 250 | self.change = True 251 | self.caret.on_text(text) 252 | 253 | def on_text_motion(self, motion): 254 | if self.hover: 255 | self.caret.on_text_motion(motion) 256 | 257 | def on_text_motion_select(self, motion): 258 | if self.hover: 259 | self.caret.on_text_motion_select(motion) 260 | 261 | def on_key_press(self, symbol, modifiers): 262 | key = pyglet.window.key 263 | 264 | if symbol == key.TAB: 265 | self.change = True 266 | self.document.insert_text(self.caret.position, ' ') 267 | self.caret.position += 2 268 | 269 | elif modifiers & key.MOD_CTRL and symbol == key.ENTER: 270 | print('Reload code') 271 | self.update_node() 272 | 273 | elif modifiers & key.MOD_CTRL: 274 | if symbol == key.C and self.caret.mark: 275 | self.copy_text() 276 | elif symbol == key.V: 277 | start = min(self.caret.position, self.caret.mark or self.caret.position) 278 | end = max(self.caret.position, self.caret.mark or self.caret.position) 279 | text = pyperclip.paste() 280 | self.document.delete_text(start, end) 281 | self.document.insert_text(self.caret.position, text) 282 | self.caret.position += len(text) 283 | self.caret.mark = self.caret.position 284 | elif symbol == key.X and self.caret.mark: 285 | start, end = self.copy_text() 286 | self.document.delete_text(start, end) 287 | self.caret.mark = self.caret.position 288 | 289 | elif symbol == key.BACKSPACE or symbol == key.DELETE: 290 | self.change = True 291 | 292 | def copy_text(self): 293 | start = min(self.caret.position, self.caret.mark) 294 | end = max(self.caret.position, self.caret.mark) 295 | text = self.document.text[start:end] 296 | pyperclip.copy(text) 297 | return (start, end) 298 | 299 | def set_focus(self): 300 | self.caret.visible = True 301 | self.caret.mark = 0 302 | self.caret.position = len(self.document.text) 303 | 304 | def __del__(self): 305 | self.layout.delete() 306 | self.update_label.delete() 307 | self.caret.delete() 308 | -------------------------------------------------------------------------------- /pyno/examples/welcome.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": 0.4 4 | }, 5 | { 6 | "type": "node", 7 | "x": 557, 8 | "y": 411, 9 | "size": [ 10 | 300, 11 | 150 12 | ], 13 | "color": [ 14 | 100, 15 | 130, 16 | 117 17 | ], 18 | "code": [ 19 | "def add(a=0, b=0):", 20 | " result = a + b", 21 | " return result", 22 | "", 23 | "call = add" 24 | ], 25 | "connects": [ 26 | { 27 | "output": { 28 | "node": 38, 29 | "put": { 30 | "name": "output" 31 | } 32 | }, 33 | "input": { 34 | "put": { 35 | "name": "b" 36 | } 37 | } 38 | }, 39 | { 40 | "output": { 41 | "node": 36, 42 | "put": { 43 | "name": "output" 44 | } 45 | }, 46 | "input": { 47 | "put": { 48 | "name": "a" 49 | } 50 | } 51 | } 52 | ], 53 | "id": 35 54 | }, 55 | { 56 | "type": "field", 57 | "x": 499, 58 | "y": 520, 59 | "size": [ 60 | 70, 61 | 30 62 | ], 63 | "code": [ 64 | "5" 65 | ], 66 | "connects": [], 67 | "id": 36 68 | }, 69 | { 70 | "type": "field", 71 | "x": 202, 72 | "y": 327, 73 | "size": [ 74 | 350, 75 | 496 76 | ], 77 | "code": [ 78 | "#", 79 | "# Welcome to Pyno!", 80 | "#", 81 | "# Pyno is a visual programming env", 82 | "# based on Python", 83 | "#", 84 | "# You can find info and tutorials", 85 | "# at https://github.com/honix/Pyno", 86 | "#", 87 | "#", 88 | "# Basic controls", 89 | "#", 90 | "# Create - Node press N or O", 91 | "# - Field press F", 92 | "# - Sub press S", 93 | "#", 94 | "# Pan - right-mouse-button drag", 95 | "#", 96 | "# Zoom - mouse wheel", 97 | "#", 98 | "# ____", 99 | "# | _ \\ _ _ ____ ___", 100 | "# | (_) ) | | || _ \\ / _ \\", 101 | "# | __/| |_| || | | | (_) )", 102 | "# |_| \\___ ||_| |_|\\___/", 103 | "# (___/", 104 | "#" 105 | ], 106 | "connects": [], 107 | "id": 37 108 | }, 109 | { 110 | "type": "field", 111 | "x": 618, 112 | "y": 521, 113 | "size": [ 114 | 70, 115 | 30 116 | ], 117 | "code": [ 118 | "20" 119 | ], 120 | "connects": [], 121 | "id": 38 122 | }, 123 | { 124 | "type": "field", 125 | "x": 481, 126 | "y": 291, 127 | "size": [ 128 | 70, 129 | 30 130 | ], 131 | "code": [ 132 | "25" 133 | ], 134 | "connects": [ 135 | { 136 | "output": { 137 | "node": 35, 138 | "put": { 139 | "name": "result" 140 | } 141 | }, 142 | "input": { 143 | "put": { 144 | "name": "input" 145 | } 146 | } 147 | } 148 | ], 149 | "id": 39 150 | }, 151 | { 152 | "type": "node", 153 | "x": 640, 154 | "y": 286, 155 | "size": [ 156 | 300, 157 | 150 158 | ], 159 | "color": [ 160 | 116, 161 | 127, 162 | 128 163 | ], 164 | "code": [ 165 | "def devide(a=0, b=1):", 166 | " result = a // b", 167 | " return result", 168 | "", 169 | "call = devide" 170 | ], 171 | "connects": [ 172 | { 173 | "output": { 174 | "node": 41, 175 | "put": { 176 | "name": "output" 177 | } 178 | }, 179 | "input": { 180 | "put": { 181 | "name": "b" 182 | } 183 | } 184 | }, 185 | { 186 | "output": { 187 | "node": 35, 188 | "put": { 189 | "name": "result" 190 | } 191 | }, 192 | "input": { 193 | "put": { 194 | "name": "a" 195 | } 196 | } 197 | } 198 | ], 199 | "id": 40 200 | }, 201 | { 202 | "type": "field", 203 | "x": 696, 204 | "y": 409, 205 | "size": [ 206 | 70, 207 | 30 208 | ], 209 | "code": [ 210 | "5" 211 | ], 212 | "connects": [], 213 | "id": 41 214 | }, 215 | { 216 | "type": "field", 217 | "x": 640, 218 | "y": 168, 219 | "size": [ 220 | 70, 221 | 30 222 | ], 223 | "code": [ 224 | "5" 225 | ], 226 | "connects": [ 227 | { 228 | "output": { 229 | "node": 40, 230 | "put": { 231 | "name": "result" 232 | } 233 | }, 234 | "input": { 235 | "put": { 236 | "name": "input" 237 | } 238 | } 239 | } 240 | ], 241 | "id": 42 242 | }, 243 | { 244 | "type": "field", 245 | "x": 1038, 246 | "y": 485, 247 | "size": [ 248 | 170, 249 | 30 250 | ], 251 | "code": [ 252 | "lambda x: x - 5" 253 | ], 254 | "connects": [], 255 | "id": 43 256 | }, 257 | { 258 | "type": "field", 259 | "x": 1336, 260 | "y": 481, 261 | "size": [ 262 | 129, 263 | 30 264 | ], 265 | "code": [ 266 | "range(1,10)" 267 | ], 268 | "connects": [], 269 | "id": 44 270 | }, 271 | { 272 | "type": "node", 273 | "x": 1193, 274 | "y": 343, 275 | "size": [ 276 | 300, 277 | 150 278 | ], 279 | "color": [ 280 | 84, 281 | 114, 282 | 81 283 | ], 284 | "code": [ 285 | "def outOfFunction(a=None, b=()):", 286 | " result = tuple(map(a,b))", 287 | " return result", 288 | "", 289 | "call = outOfFunction" 290 | ], 291 | "connects": [ 292 | { 293 | "output": { 294 | "node": 44, 295 | "put": { 296 | "name": "output" 297 | } 298 | }, 299 | "input": { 300 | "put": { 301 | "name": "b" 302 | } 303 | } 304 | }, 305 | { 306 | "output": { 307 | "node": 43, 308 | "put": { 309 | "name": "output" 310 | } 311 | }, 312 | "input": { 313 | "put": { 314 | "name": "a" 315 | } 316 | } 317 | } 318 | ], 319 | "id": 45 320 | }, 321 | { 322 | "type": "field", 323 | "x": 1159, 324 | "y": 246, 325 | "size": [ 326 | 328, 327 | 30 328 | ], 329 | "code": [ 330 | "(-4, -3, -2, -1, 0, 1, 2, 3, 4)" 331 | ], 332 | "connects": [ 333 | { 334 | "output": { 335 | "node": 45, 336 | "put": { 337 | "name": "result" 338 | } 339 | }, 340 | "input": { 341 | "put": { 342 | "name": "input" 343 | } 344 | } 345 | } 346 | ], 347 | "id": 46 348 | }, 349 | { 350 | "type": "node", 351 | "x": 842, 352 | "y": 155, 353 | "size": [ 354 | 300, 355 | 219 356 | ], 357 | "color": [ 358 | 126, 359 | 120, 360 | 86 361 | ], 362 | "code": [ 363 | "S['timer'] = 0", 364 | "", 365 | "def timeSinus():", 366 | " import math", 367 | "", 368 | " S['timer'] += G['dt']", 369 | " result = math.sin(S['timer'])", 370 | " return result", 371 | "", 372 | "call = timeSinus", 373 | "" 374 | ], 375 | "connects": [], 376 | "id": 47 377 | }, 378 | { 379 | "type": "field", 380 | "x": 890, 381 | "y": 43, 382 | "size": [ 383 | 224, 384 | 30 385 | ], 386 | "code": [ 387 | "0.07546959274639231" 388 | ], 389 | "connects": [ 390 | { 391 | "output": { 392 | "node": 47, 393 | "put": { 394 | "name": "result" 395 | } 396 | }, 397 | "input": { 398 | "put": { 399 | "name": "input" 400 | } 401 | } 402 | } 403 | ], 404 | "id": 48 405 | }, 406 | { 407 | "type": "field", 408 | "x": 1664, 409 | "y": 293, 410 | "size": [ 411 | 326, 412 | 52 413 | ], 414 | "code": [ 415 | "# Check examples folder for more" 416 | ], 417 | "connects": [], 418 | "id": 49 419 | }, 420 | { 421 | "type": "node", 422 | "x": 1674, 423 | "y": 506, 424 | "size": [ 425 | 300, 426 | 150 427 | ], 428 | "color": [ 429 | 121, 430 | 91, 431 | 120 432 | ], 433 | "code": [ 434 | "call = lambda x: x" 435 | ], 436 | "connects": [ 437 | { 438 | "output": { 439 | "node": 51, 440 | "put": { 441 | "name": "output" 442 | } 443 | }, 444 | "input": { 445 | "put": { 446 | "name": "x" 447 | } 448 | } 449 | } 450 | ], 451 | "id": 50 452 | }, 453 | { 454 | "type": "field", 455 | "x": 1662, 456 | "y": 584, 457 | "size": [ 458 | 70, 459 | 30 460 | ], 461 | "code": [ 462 | "None", 463 | "" 464 | ], 465 | "connects": [], 466 | "id": 51 467 | }, 468 | { 469 | "type": "field", 470 | "x": 1657, 471 | "y": 427, 472 | "size": [ 473 | 70, 474 | 30 475 | ], 476 | "code": [ 477 | "None" 478 | ], 479 | "connects": [ 480 | { 481 | "output": { 482 | "node": 50, 483 | "put": { 484 | "name": "result" 485 | } 486 | }, 487 | "input": { 488 | "put": { 489 | "name": "input" 490 | } 491 | } 492 | } 493 | ], 494 | "id": 52 495 | }, 496 | { 497 | "type": "node", 498 | "x": 1119, 499 | "y": 159, 500 | "size": [ 501 | 300, 502 | 150 503 | ], 504 | "color": [ 505 | 92, 506 | 113, 507 | 125 508 | ], 509 | "code": [ 510 | "path = 'pyno/nodes/timeSinus.py'", 511 | "", 512 | "with open(path) as file:", 513 | " exec(file.read())", 514 | "" 515 | ], 516 | "connects": [], 517 | "id": 53 518 | }, 519 | { 520 | "type": "field", 521 | "x": 1170, 522 | "y": 46, 523 | "size": [ 524 | 218, 525 | 30 526 | ], 527 | "code": [ 528 | "0.07546959274639231" 529 | ], 530 | "connects": [ 531 | { 532 | "output": { 533 | "node": 53, 534 | "put": { 535 | "name": "result" 536 | } 537 | }, 538 | "input": { 539 | "put": { 540 | "name": "input" 541 | } 542 | } 543 | } 544 | ], 545 | "id": 54 546 | } 547 | ] -------------------------------------------------------------------------------- /utils/newFile.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "code": "import pyglet", 4 | "type": "field", 5 | "size": [ 6 | 180, 7 | 30 8 | ], 9 | "connects": [], 10 | "x": 103, 11 | "y": 368, 12 | "parent": 32 13 | }, 14 | { 15 | "code": "import pyglet\nfrom typing import *\n\nS['box'] = pyglet.graphics.vertex_list_indexed(\n 4, [0, 1, 2, 0, 2, 3],\n ('v2i', (0, 0, 10, 0, 10, 10, 0, 10)),\n ('c3B', (255,255,255)*4) )\n\ndef draw_rect(pos=(0,0), size=(10,10)) -> Tuple[\"S['box']\"] :\n x, y = int(pos[0]), int(pos[1])\n w, h = int(size[0]), int(size[1])\n S['box'].vertices = (x, y,\n x + w, y,\n x + w, y + h,\n x, y + h)\n return S['box']\n\ncall = draw_rect", 16 | "type": "node", 17 | "size": [ 18 | 473, 19 | 316 20 | ], 21 | "connects": [ 22 | { 23 | "output": { 24 | "node": 37, 25 | "put": { 26 | "name": "S['size']" 27 | } 28 | }, 29 | "input": { 30 | "put": { 31 | "name": "size" 32 | } 33 | } 34 | }, 35 | { 36 | "output": { 37 | "node": 37, 38 | "put": { 39 | "name": "S['position']" 40 | } 41 | }, 42 | "input": { 43 | "put": { 44 | "name": "pos" 45 | } 46 | } 47 | } 48 | ], 49 | "x": 509, 50 | "y": 96, 51 | "color": [ 52 | 99, 53 | 114, 54 | 116 55 | ], 56 | "parent": 33 57 | }, 58 | { 59 | "code": "'''\nLittle ping-pong game\nKeys:\n upper pad:\n A D\n bottom pad:\n LEFT RIGHT\n'''", 60 | "type": "field", 61 | "size": [ 62 | 256, 63 | 169 64 | ], 65 | "connects": [], 66 | "x": 139, 67 | "y": 504, 68 | "parent": 34 69 | }, 70 | { 71 | "code": "import pyglet\nfrom typing import *\n\nG['key'] = []\n\ndef keyboard(window=0) -> Tuple[\"G['key']\"] :\n\n @window.event\n def on_key_press(symbol, mod):\n G['key'].append(symbol)\n\n @window.event\n def on_key_release(symbol, mod):\n if symbol in G['key']:\n G['key'].pop(G['key'].index(symbol))\n\n @window.event\n def on_deactivate():\n G['key'] = []\n\n return G['key']\n\ncall = keyboard", 72 | "type": "node", 73 | "size": [ 74 | 408, 75 | 395 76 | ], 77 | "connects": [ 78 | { 79 | "output": { 80 | "node": 39, 81 | "put": { 82 | "name": "G['window']" 83 | } 84 | }, 85 | "input": { 86 | "put": { 87 | "name": "window" 88 | } 89 | } 90 | } 91 | ], 92 | "x": 482, 93 | "y": 459, 94 | "color": [ 95 | 93, 96 | 93, 97 | 89 98 | ], 99 | "parent": 35 100 | }, 101 | { 102 | "code": "[]", 103 | "type": "field", 104 | "size": [ 105 | 110, 106 | 118 107 | ], 108 | "connects": [ 109 | { 110 | "output": { 111 | "node": 42, 112 | "put": { 113 | "name": "keys" 114 | } 115 | }, 116 | "input": { 117 | "put": { 118 | "name": "input" 119 | } 120 | } 121 | } 122 | ], 123 | "x": 184, 124 | "y": 223, 125 | "parent": 36 126 | }, 127 | { 128 | "code": "import pyglet\nfrom typing import *\n\nw = G['window']\nS['size'] = [70, 10]\nS['position'] = [w.width/2 - S['size'][0]/2, 20]\n\ndef pad_bottom(key=[]) -> Tuple[\"S['position']\", \"S['size']\", \"S\"] :\n speed = G['dt'] * 200\n\n if 'RIGHT' in key:\n if S['position'][0] < w.width - S['size'][0]:\n S['position'][0] += speed\n if 'LEFT' in key:\n if S['position'][0] > 0:\n S['position'][0] -= speed\n return S['position'], S['size'], S\n\ncall = pad_bottom", 129 | "type": "node", 130 | "size": [ 131 | 477, 132 | 301 133 | ], 134 | "connects": [ 135 | { 136 | "output": { 137 | "node": 42, 138 | "put": { 139 | "name": "keys" 140 | } 141 | }, 142 | "input": { 143 | "put": { 144 | "name": "key" 145 | } 146 | } 147 | } 148 | ], 149 | "x": 526, 150 | "y": 280, 151 | "color": [ 152 | 101, 153 | 91, 154 | 127 155 | ], 156 | "parent": 37 157 | }, 158 | { 159 | "code": "import pyglet\nfrom typing import *\n\ndef render(meshes=None):\n window = G['window']\n window.switch_to()\n window.clear()\n for mesh in meshes:\n mesh.draw(pyglet.gl.GL_TRIANGLES)\n \n\ncall = render", 160 | "type": "node", 161 | "size": [ 162 | 376, 163 | 172 164 | ], 165 | "connects": [ 166 | { 167 | "output": { 168 | "node": 46, 169 | "put": { 170 | "name": "result" 171 | } 172 | }, 173 | "input": { 174 | "put": { 175 | "name": "meshes" 176 | } 177 | } 178 | } 179 | ], 180 | "x": 509, 181 | "y": -83, 182 | "color": [ 183 | 127, 184 | 96, 185 | 112 186 | ], 187 | "parent": 38 188 | }, 189 | { 190 | "code": "import pyglet\nfrom typing import *\n\nG['window'] = pyglet.window.Window(300, 450)\n\ndef create_window() -> Tuple[\"G['window']\"] :\n return G['window']\n\ncall = create_window", 191 | "type": "node", 192 | "size": [ 193 | 433, 194 | 162 195 | ], 196 | "connects": [], 197 | "x": 439, 198 | "y": 545, 199 | "color": [ 200 | 129, 201 | 105, 202 | 124 203 | ], 204 | "parent": 39 205 | }, 206 | { 207 | "code": "import pyglet\nfrom typing import *\n\nw = G['window']\nS['size'] = [70, 10]\nS['position'] = [w.width/2 - S['size'][0]/2, w.height - 30]\n\ndef pad_upper(key=[]) -> Tuple[\"S['position']\", \"S['size']\", \"S\"] :\n speed = G['dt'] * 200\n\n if 'D' in key:\n if S['position'][0] < w.width - S['size'][0]:\n S['position'][0] += speed\n if 'A' in key:\n if S['position'][0] > 0:\n S['position'][0] -= speed\n return S['position'], S['size'], S\n\ncall = pad_upper", 208 | "type": "node", 209 | "size": [ 210 | 560, 211 | 279 212 | ], 213 | "connects": [ 214 | { 215 | "output": { 216 | "node": 42, 217 | "put": { 218 | "name": "keys" 219 | } 220 | }, 221 | "input": { 222 | "put": { 223 | "name": "key" 224 | } 225 | } 226 | } 227 | ], 228 | "x": 371, 229 | "y": 278, 230 | "color": [ 231 | 101, 232 | 91, 233 | 127 234 | ], 235 | "parent": 41 236 | }, 237 | { 238 | "code": "import pyglet\nfrom typing import *\n\ndef keys_to_names(keys=[]) -> Tuple[\"keys\"] :\n keys = list(map(lambda x: pyglet.window.key.symbol_string(x), keys))\n return keys\n\ncall = keys_to_names", 239 | "type": "node", 240 | "size": [ 241 | 715, 242 | 151 243 | ], 244 | "connects": [ 245 | { 246 | "output": { 247 | "node": 35, 248 | "put": { 249 | "name": "G['key']" 250 | } 251 | }, 252 | "input": { 253 | "put": { 254 | "name": "keys" 255 | } 256 | } 257 | } 258 | ], 259 | "x": 453, 260 | "y": 375, 261 | "color": [ 262 | 81, 263 | 121, 264 | 111 265 | ], 266 | "parent": 42 267 | }, 268 | { 269 | "code": "import pyglet\nfrom typing import *\n\nw = G['window']\nS['position'] = [w.width // 2, w.height // 2]\nS['size'] = [10, 10]\nS['acc'] = [2, 1]\n\ndef ball(pad_u={}, pad_b={}) -> Tuple[\"S['position']\", \"S['size']\"] :\n speed = G['dt'] * 60\n pos = S['position']\n\n # OUT OF GAME\n if (pos[1] > w.height) or (pos[1] < -S['size'][0]):\n S['position'] = [w.width // 2, w.height // 2]\n S['acc'][1] *= -1\n\n # WALLS COLLISION\n if pos[0] < w.width - S['size'][0]:\n S['acc'][0] *= -1\n if pos[0] > 0:\n S['acc'][0] *= -1\n\n # PADS COLLISION \n pu, su = pad_u['position'], pad_u['size']\n if ((pu[1] > pos[1] > pu[1] - su[1]) and\n (pu[0] < pos[0] < pu[0] + su[0])):\n S['acc'][1] *= -1\n\n pu, su = pad_b['position'], pad_b['size']\n if ((pu[1] < pos[1] < pu[1] + su[1]) and\n (pu[0] < pos[0] < pu[0] + su[0])):\n S['acc'][1] *= -1\n\n # MOVEMENT\n S['position'][0] += S['acc'][0] * speed\n S['position'][1] += S['acc'][1] * speed\n\n return S['position'], S['size']\n\ncall = ball", 270 | "type": "node", 271 | "size": [ 272 | 562, 273 | 680 274 | ], 275 | "connects": [ 276 | { 277 | "output": { 278 | "node": 41, 279 | "put": { 280 | "name": "S" 281 | } 282 | }, 283 | "input": { 284 | "put": { 285 | "name": "pad_u" 286 | } 287 | } 288 | }, 289 | { 290 | "output": { 291 | "node": 37, 292 | "put": { 293 | "name": "S" 294 | } 295 | }, 296 | "input": { 297 | "put": { 298 | "name": "pad_b" 299 | } 300 | } 301 | } 302 | ], 303 | "x": 654, 304 | "y": 170, 305 | "color": [ 306 | 101, 307 | 91, 308 | 127 309 | ], 310 | "parent": 44 311 | }, 312 | { 313 | "code": "import pyglet\nfrom typing import *\n\ndef join3(a=None, b=None, c=None) -> Tuple[\"result\"] :\n result = a, b, c\n return result\n\ncall = join3", 314 | "type": "node", 315 | "size": [ 316 | 336, 317 | 150 318 | ], 319 | "connects": [ 320 | { 321 | "output": { 322 | "node": 33, 323 | "put": { 324 | "name": "S['box']" 325 | } 326 | }, 327 | "input": { 328 | "put": { 329 | "name": "b" 330 | } 331 | } 332 | }, 333 | { 334 | "output": { 335 | "node": 51, 336 | "put": { 337 | "name": "S['box']" 338 | } 339 | }, 340 | "input": { 341 | "put": { 342 | "name": "a" 343 | } 344 | } 345 | }, 346 | { 347 | "output": { 348 | "node": 52, 349 | "put": { 350 | "name": "S['box']" 351 | } 352 | }, 353 | "input": { 354 | "put": { 355 | "name": "c" 356 | } 357 | } 358 | } 359 | ], 360 | "x": 509, 361 | "y": -4, 362 | "color": [ 363 | 81, 364 | 90, 365 | 127 366 | ], 367 | "parent": 46 368 | }, 369 | { 370 | "code": "import pyglet\nfrom typing import *\n\nS['box'] = pyglet.graphics.vertex_list_indexed(\n 4, [0, 1, 2, 0, 2, 3],\n ('v2i', (0, 0, 10, 0, 10, 10, 0, 10)),\n ('c3B', (255,255,255)*4) )\n\ndef draw_rect(pos=(0,0), size=(10,10)) -> Tuple[\"S['box']\"] :\n x, y = int(pos[0]), int(pos[1])\n w, h = int(size[0]), int(size[1])\n S['box'].vertices = (x, y,\n x + w, y,\n x + w, y + h,\n x, y + h)\n return S['box']\n\ncall = draw_rect", 371 | "type": "node", 372 | "size": [ 373 | 473, 374 | 316 375 | ], 376 | "connects": [ 377 | { 378 | "output": { 379 | "node": 41, 380 | "put": { 381 | "name": "S['size']" 382 | } 383 | }, 384 | "input": { 385 | "put": { 386 | "name": "size" 387 | } 388 | } 389 | }, 390 | { 391 | "output": { 392 | "node": 41, 393 | "put": { 394 | "name": "S['position']" 395 | } 396 | }, 397 | "input": { 398 | "put": { 399 | "name": "pos" 400 | } 401 | } 402 | } 403 | ], 404 | "x": 376, 405 | "y": 96, 406 | "color": [ 407 | 99, 408 | 114, 409 | 116 410 | ], 411 | "parent": 51 412 | }, 413 | { 414 | "code": "import pyglet\nfrom typing import *\n\nS['box'] = pyglet.graphics.vertex_list_indexed(\n 4, [0, 1, 2, 0, 2, 3],\n ('v2i', (0, 0, 10, 0, 10, 10, 0, 10)),\n ('c3B', (255,100,20)*4) )\n\ndef draw_rect(pos=(0,0), size=(10,10)) -> Tuple[\"S['box']\"] :\n x, y = int(pos[0]), int(pos[1])\n w, h = int(size[0]), int(size[1])\n S['box'].vertices = (x, y,\n x + w, y,\n x + w, y + h,\n x, y + h)\n return S['box']\n\ncall = draw_rect", 415 | "type": "node", 416 | "size": [ 417 | 473, 418 | 316 419 | ], 420 | "connects": [ 421 | { 422 | "output": { 423 | "node": 44, 424 | "put": { 425 | "name": "S['position']" 426 | } 427 | }, 428 | "input": { 429 | "put": { 430 | "name": "pos" 431 | } 432 | } 433 | }, 434 | { 435 | "output": { 436 | "node": 44, 437 | "put": { 438 | "name": "S['size']" 439 | } 440 | }, 441 | "input": { 442 | "put": { 443 | "name": "size" 444 | } 445 | } 446 | } 447 | ], 448 | "x": 653, 449 | "y": 95, 450 | "color": [ 451 | 99, 452 | 114, 453 | 116 454 | ], 455 | "parent": 52 456 | } 457 | ] 458 | -------------------------------------------------------------------------------- /pyno/window.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | import pyglet 3 | import pyperclip 4 | from pyglet.window import Window 5 | from pyglet import gl 6 | 7 | from . import draw, initialCode, menu 8 | from .process import Process 9 | from .node import Node 10 | from .field import Field 11 | from .sub import Sub 12 | from .codeEditor import CodeEditor 13 | from .element import color_inverse 14 | from .utils import font, x_y_pan_scale, point_intersect_quad, random_node_color 15 | 16 | 17 | class PynoWindow(Window, Process): 18 | ''' 19 | Visual interface for Process 20 | ''' 21 | 22 | def __init__(self, config, filename=None, caption='Pyno', style=Window.WINDOW_STYLE_DEFAULT): 23 | Window.__init__(self, resizable=True, caption=caption, config=config, style=style) 24 | Process.__init__(self) 25 | self.set_minimum_size(320, 200) 26 | self.set_size(800, 600) 27 | 28 | # set window position to center 29 | screen = self.display.get_default_screen() 30 | self.set_location(screen.width // 2 - 400, screen.height // 2 - 300) 31 | 32 | pyglet.clock.schedule_interval(self.update, 1 / 30) # fps 33 | pyglet.clock.schedule_interval(lambda x: self.info(), 1) # drop time arg 34 | pyglet.clock.schedule_interval(lambda x: self.autosave(), 30) 35 | 36 | self.selected_nodes = [] 37 | 38 | self.code_editor = None 39 | self.field = None 40 | self.node_drag = False 41 | self.select = False 42 | self.connection = False 43 | self.connecting_node = None 44 | self.nodes_check = 0 45 | 46 | self.w, self.c = (0, 0), (0, 0) 47 | self.mouse = (0, 0) 48 | self.pointer = (0, 0) 49 | self.line = () 50 | self.pan_scale = [[0.0, 0.0], 1] 51 | 52 | self.batch = None 53 | self.info_label, self.pyno_logo, self.menu = None, None, None 54 | 55 | self.new_batch() 56 | 57 | if filename: 58 | # open auto-save or welcome-file 59 | welcome = pkg_resources.resource_filename('pyno', 'examples/welcome.pn') 60 | (self.load_pyno(filename) or self.load_pyno(welcome)) 61 | 62 | def new_batch(self): 63 | self.batch = pyglet.graphics.Batch() 64 | self.info_label = pyglet.text.Label('BOOM!!', font_name=font, 65 | font_size=9, batch=self.batch, 66 | color=(200, 200, 255, 100), 67 | x=160, y=10, group=draw.uiGroup) 68 | # load pyno-logo in left bottom 69 | corner = pkg_resources.resource_stream('pyno', 'imgs/corner.png') 70 | pyno_logo_img = pyglet.image.load('dummyname', file=corner) 71 | self.pyno_logo = pyglet.sprite.Sprite(pyno_logo_img, 72 | batch=self.batch, 73 | group=draw.uiGroup) 74 | self.menu = menu.Menu(self) 75 | # line place-holder 76 | self.line = (draw.Line(self.batch), draw.Line(self.batch)) 77 | 78 | def info(self, message=None): 79 | self.info_label.text = message or 'run:' + str(self.running) 80 | 81 | def update(self, dt): 82 | self.global_scope['dt'] = dt 83 | 84 | # ---- Calculations ---- 85 | 86 | self.nodes_update() 87 | 88 | if self.selected_nodes: 89 | for node in self.selected_nodes: 90 | node.make_active() 91 | else: 92 | # pointer over nodes 93 | nc = self.nodes_check 94 | self.nodes_check = nc + 1 if nc < len(self.nodes)-25 else 0 95 | for node in self.nodes[self.nodes_check:self.nodes_check+25]: 96 | node.intersect_point(self.pointer) 97 | 98 | if self.code_editor: 99 | if self.code_editor.intersect_point(self.pointer): 100 | self.code_editor.node.hover = True 101 | 102 | self.menu.update() 103 | 104 | def on_draw(self): 105 | # ---- BG ---- 106 | 107 | pyglet.gl.glClearColor(0.14, 0.14, 0.14, 0) 108 | self.clear() 109 | 110 | # ---- PYNORAMA ---- 111 | 112 | ps = self.pan_scale 113 | gl.glTranslatef(self.width / 2, self.height / 2, 0) 114 | gl.glScalef(ps[1], ps[1], ps[1]) 115 | gl.glTranslatef(self.width / -2 + ps[0][0], 116 | self.height / -2 + ps[0][1], 0.0) 117 | 118 | # ---- NODES ---- 119 | 120 | for node in self.nodes: 121 | node.render_base() 122 | 123 | self.batch.draw() 124 | 125 | for node in self.nodes: 126 | node.render_labels() 127 | 128 | for node in self.nodes: 129 | node.deactive() 130 | 131 | if self.code_editor: 132 | self.code_editor.render() 133 | 134 | if self.select: 135 | draw.selector(self.w, self.c) 136 | 137 | # ---- GUI ---- 138 | 139 | # reset camera position 140 | gl.glLoadIdentity() 141 | 142 | def on_mouse_motion(self, x, y, dx, dy): 143 | self.mouse = (x, y) 144 | 145 | x, y = x_y_pan_scale(x, y, self.pan_scale, self.get_size()) 146 | self.pointer = (x, y) 147 | 148 | if not self.selected_nodes: 149 | if self.code_editor: 150 | if self.code_editor.intersect_point(self.pointer): 151 | if not self.code_editor.hover and not self.field: 152 | self.push_handlers(self.code_editor) 153 | self.code_editor.pan_scale = self.pan_scale 154 | self.code_editor.screen_size = self.get_size() 155 | self.code_editor.hover = True 156 | self.code_editor.node.hover = True 157 | return 158 | elif self.field: 159 | if self.field.intersect_point((x, y)): 160 | self.field.pan_scale = self.pan_scale 161 | self.field.screen_size = self.get_size() 162 | 163 | def on_mouse_press(self, x, y, button, modifiers): 164 | if self.menu.click(x, y, button): 165 | return 166 | 167 | x, y = x_y_pan_scale(x, y, self.pan_scale, self.get_size()) 168 | 169 | if button == 1: 170 | if self.field: 171 | self.pop_handlers() 172 | self.push_handlers() 173 | self.field = None 174 | if self.code_editor: 175 | if self.code_editor.intersect_point((x, y)): 176 | return 177 | else: 178 | if self.code_editor.hover: 179 | self.pop_handlers() 180 | self.push_handlers() 181 | self.code_editor = None 182 | for node in self.nodes: 183 | if node.intersect_point((x, y)): 184 | if (node in self.selected_nodes and 185 | len(self.selected_nodes) > 1): 186 | self.node_drag = True 187 | return 188 | else: 189 | si = node.selectedInput['name'] != 'none' 190 | so = node.selectedOutput['name'] != 'none' 191 | if si or so: 192 | self.pointer = (x, y) 193 | self.connection = True 194 | if si: 195 | self.disconnect_node(node) 196 | elif so: 197 | self.connecting_node = \ 198 | {'node': node, 199 | 'put': node.selectedOutput, 200 | 'mode': 'output'} 201 | return 202 | if isinstance(node, Node): 203 | self.code_editor = CodeEditor(node) 204 | elif isinstance(node, Field) and not self.field: 205 | self.push_handlers(node) 206 | self.field = node 207 | elif isinstance(node, Sub): 208 | self.code_editor = CodeEditor(node, highlighting=2) 209 | if node.pwindow: 210 | node.pwindow.set_visible(not node.pwindow.visible) 211 | self.selected_nodes = [node] 212 | self.node_drag = True 213 | return 214 | self.select = True 215 | self.selectPoint = (x, y) 216 | 217 | def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): 218 | x, y = x_y_pan_scale(x, y, self.pan_scale, self.get_size()) 219 | dx, dy = int(dx / self.pan_scale[1]), int(dy / self.pan_scale[1]) 220 | 221 | if self.node_drag and buttons != 5: 222 | for node in self.selected_nodes: 223 | node.x += dx 224 | node.y += dy 225 | node.make_child_active() 226 | elif self.select: 227 | self.w = self.selectPoint 228 | self.c = (x, y) 229 | elif self.connection: 230 | self.pointer = (x, y) 231 | for node in self.nodes: 232 | node.intersect_point((x, y)) 233 | 234 | p = self.pointer 235 | cn = self.connecting_node 236 | n = self.connecting_node['node'] 237 | 238 | if self.connecting_node['mode'] == 'input': 239 | start = n.put_pos_by_name(cn['put']['name'], 'inputs') 240 | self.line[0].redraw((start, n.y + n.ch + n.offset // 2), 241 | (start, n.y + n.ch + n.offset)) 242 | self.line[1].redraw((start, n.y + n.ch + n.offset), p) 243 | 244 | elif self.connecting_node['mode'] == 'output': 245 | start = n.put_pos_by_name(cn['put']['name'], 'outputs') 246 | self.line[0].redraw((start, n.y - n.ch - n.offset // 2), 247 | (start, n.y - n.ch - n.offset)) 248 | self.line[1].redraw((start, n.y - n.ch - n.offset), p) 249 | 250 | if buttons == 4 or buttons == 5: 251 | self.pan_scale[0][0] += dx 252 | self.pan_scale[0][1] += dy 253 | 254 | def on_mouse_release(self, x, y, button, modifiers): 255 | x, y = x_y_pan_scale(x, y, self.pan_scale, self.get_size()) 256 | 257 | if button == 1: 258 | self.node_drag = False 259 | self.connection = False 260 | 261 | self.line[0].redraw((-9000, 9000), (-9000, 9010)) 262 | self.line[1].redraw((-9000, 9010), (-9010, 9000)) 263 | 264 | if self.select: 265 | select = [] 266 | for node in self.nodes: 267 | if point_intersect_quad((node.x, node.y), 268 | (self.c + self.w)): 269 | select.append(node) 270 | node.draw_color = color_inverse(node.color) 271 | self.selected_nodes = select 272 | self.w, self.c = (0, 0), (0, 0) 273 | self.select = False 274 | else: 275 | self.selected_nodes = [] 276 | 277 | cn = self.connecting_node 278 | if cn: 279 | for node in self.nodes: 280 | if node.intersect_point((x, y)): 281 | if node != cn['node']: 282 | if (node.selectedInput['name'] != 'none' and 283 | cn['mode'] == 'output'): 284 | self.connect_out_to_in(node) 285 | elif (node.selectedOutput['name'] != 'none' and 286 | cn['mode'] == 'input'): 287 | self.connect_in_to_out(node) 288 | cn = self.connecting_node 289 | 290 | def on_mouse_scroll(self, x, y, scroll_x, scroll_y): 291 | di = 10 292 | max_zoom = 1 293 | 294 | ps = self.pan_scale 295 | zoom = scroll_y / di * ps[1] 296 | 297 | if ps[1] + zoom < max_zoom: 298 | ps[1] += zoom 299 | ps[0][0] -= ((self.width / -2 + x) / ps[1] * scroll_y) // di 300 | ps[0][1] -= ((self.height / -2 + y) / ps[1] * scroll_y) // di 301 | elif ps[1] < max_zoom: 302 | ps[1] = max_zoom 303 | ps[0][0] -= ((self.width / -2 + x) / 2 * scroll_y) // di 304 | ps[0][1] -= ((self.height / -2 + y) / 2 * scroll_y) // di 305 | else: 306 | ps[1] = max_zoom 307 | 308 | def on_key_press(self, symbol, modifiers): 309 | key = pyglet.window.key 310 | 311 | if modifiers == 2 and symbol == key.EQUAL: 312 | # double zoom 313 | self.pan_scale[1] = 2 314 | 315 | if not (self.code_editor or self.field): 316 | if symbol == key.N: 317 | self.nodes.append(Node(self, self.pointer[0], self.pointer[1], self.batch, 318 | random_node_color(), code=initialCode.node)) 319 | 320 | elif symbol == key.O: 321 | self.nodes.append(Node(self, self.pointer[0], self.pointer[1], self.batch, 322 | random_node_color(), code=initialCode.open_node)) 323 | 324 | elif symbol == key.F: 325 | self.nodes.append(Field(self, self.pointer[0], self.pointer[1], self.batch)) 326 | 327 | elif symbol == key.S: 328 | self.nodes.append(Sub(self, self.pointer[0], self.pointer[1], self.batch, 329 | random_node_color())) 330 | 331 | elif symbol == key.R: 332 | for node in self.nodes: 333 | if isinstance(node, Node): 334 | node.reload() 335 | 336 | if modifiers & key.MOD_CTRL: 337 | if symbol == key.C: 338 | self.copy_nodes() 339 | 340 | elif symbol == key.V: 341 | self.paste_nodes() 342 | 343 | if symbol == key.DELETE: 344 | for node in self.selected_nodes: 345 | node.make_child_active() 346 | node.delete() 347 | node.outputs = () 348 | del self.nodes[self.nodes.index(node)] 349 | del node 350 | print('Delete node') 351 | self.selected_nodes = [] 352 | 353 | elif symbol == key.HOME: 354 | self.pan_scale = [[0.0, 0.0], 1] 355 | 356 | elif symbol == key.END: 357 | print(len(self.nodes)) 358 | for node in self.selected_nodes: 359 | print(str(node)) 360 | 361 | def on_close(self, force=False): 362 | if (not self.caption == "Pyno") and (not force): # is this the root/main window? 363 | return True 364 | for node in self.nodes: 365 | if isinstance(node, Sub) and node.pwindow: 366 | node.pwindow.on_close(force=True) 367 | self.autosave() 368 | self.close() 369 | 370 | def disconnect_node(self, node): 371 | n = node.connected_to 372 | for c in n: 373 | a = c['output'] 374 | # if user clicked on input connection destroyed 375 | if c['input']['put']['name'] == node.selectedInput['name']: 376 | self.connecting_node = {'node': a['node'], 377 | 'put': {'name': a['put']['name']}, 378 | 'mode': 'output'} 379 | del n[n.index(c)] 380 | for line in node.graphics['connections']: 381 | for segment in line: 382 | segment.delete() 383 | node.graphics['connections'] = list() 384 | return 385 | # if user clicked on output connecting wire created 386 | self.connecting_node = {'node': node, 387 | 'put': node.selectedInput, 388 | 'mode': 'input'} 389 | 390 | def connect_out_to_in(self, node): 391 | del self.connecting_node['mode'] 392 | another = {'node': node, 393 | 'put': node.selectedInput} 394 | insert = {'output': self.connecting_node, 395 | 'input': another} 396 | 397 | # check if something already connected to put 398 | i, n = node.selectedInput, node.connected_to 399 | for inp in n: 400 | if inp['input']['put']['name'] == i['name']: 401 | del n[n.index(inp)] 402 | break 403 | 404 | if insert not in node.connected_to: 405 | node.connected_to.append(insert) 406 | self.connecting_node['node'].add_child(node) 407 | # print('Connect output to input') 408 | 409 | def connect_in_to_out(self, node): 410 | del self.connecting_node['mode'] 411 | another = {'node': node, 412 | 'put': node.selectedOutput} 413 | insert = {'output': another, 414 | 'input': self.connecting_node} 415 | 416 | n = self.connecting_node['node'] 417 | if insert not in n.connected_to: 418 | n.connected_to.append(insert) 419 | node.add_child(n) 420 | n.make_active() 421 | # print('Connect input to output') 422 | 423 | def new_pyno(self): 424 | Process.new_pyno(self) 425 | self.switch_to() 426 | self.code_editor = None 427 | self.new_batch() 428 | self.pan_scale = [[0.0, 0.0], 1] 429 | print('New window!') 430 | 431 | def autosave(self): 432 | if self.save_pyno(filepath='.auto-saved.pn'): 433 | self.info('auto-saved') 434 | 435 | def copy_nodes(self): 436 | data = self.serializer.serialize(self.selected_nodes, anchor=self.pointer) 437 | pyperclip.copy(data) 438 | 439 | def paste_nodes(self): 440 | data = pyperclip.paste() 441 | self.load_data(data, anchor=self.pointer) 442 | -------------------------------------------------------------------------------- /pyno/examples/ping-pong.pn: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": 0.4 4 | }, 5 | { 6 | "type": "node", 7 | "x": 509, 8 | "y": 96, 9 | "size": [ 10 | 473, 11 | 316 12 | ], 13 | "color": [ 14 | 99, 15 | 114, 16 | 116 17 | ], 18 | "code": [ 19 | "import pyglet", 20 | "", 21 | "S['box'] = pyglet.graphics.vertex_list_indexed(", 22 | " 4, [0, 1, 2, 0, 2, 3],", 23 | " ('v2i', (0, 0, 10, 0, 10, 10, 0, 10)),", 24 | " ('c3B', (255,255,255)*4) )", 25 | "", 26 | "def draw_rect(pos=(0,0), size=(10,10)):", 27 | " x, y = int(pos[0]), int(pos[1])", 28 | " w, h = int(size[0]), int(size[1])", 29 | " S['box'].vertices = (x, y,", 30 | " x + w, y,", 31 | " x + w, y + h,", 32 | " x, y + h)", 33 | " return S['box']", 34 | "", 35 | "call = draw_rect" 36 | ], 37 | "connects": [ 38 | { 39 | "output": { 40 | "node": 55, 41 | "put": { 42 | "name": "result 1" 43 | } 44 | }, 45 | "input": { 46 | "put": { 47 | "name": "size" 48 | } 49 | } 50 | }, 51 | { 52 | "output": { 53 | "node": 55, 54 | "put": { 55 | "name": "result 0" 56 | } 57 | }, 58 | "input": { 59 | "put": { 60 | "name": "pos" 61 | } 62 | } 63 | } 64 | ], 65 | "id": 43 66 | }, 67 | { 68 | "type": "field", 69 | "x": 139, 70 | "y": 504, 71 | "size": [ 72 | 256, 73 | 169 74 | ], 75 | "code": [ 76 | "'''", 77 | "Little ping-pong game", 78 | "Keys:", 79 | " upper pad:", 80 | " A D", 81 | " bottom pad:", 82 | " LEFT RIGHT", 83 | "'''" 84 | ], 85 | "connects": [], 86 | "id": 44 87 | }, 88 | { 89 | "type": "node", 90 | "x": 482, 91 | "y": 459, 92 | "size": [ 93 | 422, 94 | 482 95 | ], 96 | "color": [ 97 | 93, 98 | 93, 99 | 89 100 | ], 101 | "code": [ 102 | "import pyglet", 103 | "", 104 | "G['key'] = []", 105 | "", 106 | "def keyboard(window=0):", 107 | "", 108 | " @window.event", 109 | " def on_key_press(symbol, mod):", 110 | " G['key'].append(symbol)", 111 | "", 112 | " @window.event", 113 | " def on_key_release(symbol, mod):", 114 | " if symbol in G['key']:", 115 | " G['key'].pop(G['key'].index(symbol))", 116 | "", 117 | " @window.event", 118 | " def on_deactivate():", 119 | " G['key'] = []", 120 | "", 121 | " return G['key']", 122 | "", 123 | "call = keyboard" 124 | ], 125 | "connects": [ 126 | { 127 | "output": { 128 | "node": 48, 129 | "put": { 130 | "name": "result" 131 | } 132 | }, 133 | "input": { 134 | "put": { 135 | "name": "window" 136 | } 137 | } 138 | } 139 | ], 140 | "id": 45 141 | }, 142 | { 143 | "type": "field", 144 | "x": 184, 145 | "y": 223, 146 | "size": [ 147 | 110, 148 | 118 149 | ], 150 | "code": [ 151 | "[]" 152 | ], 153 | "connects": [ 154 | { 155 | "output": { 156 | "node": 50, 157 | "put": { 158 | "name": "result" 159 | } 160 | }, 161 | "input": { 162 | "put": { 163 | "name": "input" 164 | } 165 | } 166 | } 167 | ], 168 | "id": 46 169 | }, 170 | { 171 | "type": "node", 172 | "x": 509, 173 | "y": -83, 174 | "size": [ 175 | 379, 176 | 207 177 | ], 178 | "color": [ 179 | 127, 180 | 96, 181 | 112 182 | ], 183 | "code": [ 184 | "import pyglet", 185 | "", 186 | "def render(meshes=None):", 187 | " window = G['window']", 188 | " window.switch_to()", 189 | " window.clear()", 190 | " for mesh in meshes:", 191 | " mesh.draw(pyglet.gl.GL_TRIANGLES)", 192 | " ", 193 | "call = render" 194 | ], 195 | "connects": [ 196 | { 197 | "output": { 198 | "node": 52, 199 | "put": { 200 | "name": "result" 201 | } 202 | }, 203 | "input": { 204 | "put": { 205 | "name": "meshes" 206 | } 207 | } 208 | } 209 | ], 210 | "id": 47 211 | }, 212 | { 213 | "type": "node", 214 | "x": 430, 215 | "y": 553, 216 | "size": [ 217 | 461, 218 | 208 219 | ], 220 | "color": [ 221 | 129, 222 | 105, 223 | 124 224 | ], 225 | "code": [ 226 | "import pyglet", 227 | "", 228 | "config = pyglet.gl.Config(double_buffer=False)", 229 | "G['window'] = pyglet.window.Window(300, 450, config=config)", 230 | "", 231 | "def create_window():", 232 | " return G['window']", 233 | "", 234 | "call = create_window", 235 | "", 236 | "cleanup = lambda: G['window'].close()" 237 | ], 238 | "connects": [], 239 | "id": 48 240 | }, 241 | { 242 | "type": "node", 243 | "x": 371, 244 | "y": 278, 245 | "size": [ 246 | 561, 247 | 370 248 | ], 249 | "color": [ 250 | 101, 251 | 91, 252 | 127 253 | ], 254 | "code": [ 255 | "from typing import *", 256 | "", 257 | "w = G['window']", 258 | "S['size'] = [70, 10]", 259 | "S['position'] = [w.width/2 - S['size'][0]/2, w.height - 30]", 260 | "", 261 | "def pad_upper(key=[]) -> Tuple[Any, Any, Any]:", 262 | " speed = G['dt'] * 20", 263 | "", 264 | " if 'D' in key:", 265 | " if S['position'][0] < w.width - S['size'][0]:", 266 | " S['position'][0] += speed", 267 | " if 'A' in key:", 268 | " if S['position'][0] > 0:", 269 | " S['position'][0] -= speed", 270 | " return S['position'], S['size'], S", 271 | "", 272 | "call = pad_upper" 273 | ], 274 | "connects": [ 275 | { 276 | "output": { 277 | "node": 50, 278 | "put": { 279 | "name": "result" 280 | } 281 | }, 282 | "input": { 283 | "put": { 284 | "name": "key" 285 | } 286 | } 287 | } 288 | ], 289 | "id": 49 290 | }, 291 | { 292 | "type": "node", 293 | "x": 453, 294 | "y": 375, 295 | "size": [ 296 | 715, 297 | 151 298 | ], 299 | "color": [ 300 | 81, 301 | 121, 302 | 111 303 | ], 304 | "code": [ 305 | "import pyglet", 306 | "", 307 | "def keys_to_names(keys=[]):", 308 | " keys = list(map(lambda x: pyglet.window.key.symbol_string(x), keys))", 309 | " return keys", 310 | "", 311 | "call = keys_to_names" 312 | ], 313 | "connects": [ 314 | { 315 | "output": { 316 | "node": 45, 317 | "put": { 318 | "name": "result" 319 | } 320 | }, 321 | "input": { 322 | "put": { 323 | "name": "keys" 324 | } 325 | } 326 | } 327 | ], 328 | "id": 50 329 | }, 330 | { 331 | "type": "node", 332 | "x": 654, 333 | "y": 170, 334 | "size": [ 335 | 586, 336 | 752 337 | ], 338 | "color": [ 339 | 101, 340 | 91, 341 | 127 342 | ], 343 | "code": [ 344 | "from typing import *", 345 | "", 346 | "w = G['window']", 347 | "S['position'] = [w.width // 2, w.height // 2]", 348 | "S['size'] = [10, 10]", 349 | "S['acc'] = [2, 1]", 350 | "", 351 | "def ball(pad_u={}, pad_b={}) -> Tuple[Any, Any]:", 352 | " speed = G['dt'] * 6", 353 | " pos = S['position']", 354 | "", 355 | " # OUT OF GAME", 356 | " if (pos[1] > w.height) or (pos[1] < -S['size'][0]):", 357 | " S['position'] = [w.width // 2, w.height // 2]", 358 | " S['acc'][1] *= -1", 359 | "", 360 | " # WALLS COLLISION", 361 | " if pos[0] < w.width - S['size'][0]:", 362 | " S['acc'][0] *= -1", 363 | " if pos[0] > 0:", 364 | " S['acc'][0] *= -1", 365 | "", 366 | " # PADS COLLISION ", 367 | " pu, su = pad_u['position'], pad_u['size']", 368 | " if ((pu[1] > pos[1] > pu[1] - su[1]) and", 369 | " (pu[0] < pos[0] < pu[0] + su[0])):", 370 | " S['acc'][1] *= -1", 371 | "", 372 | " pu, su = pad_b['position'], pad_b['size']", 373 | " if ((pu[1] < pos[1] < pu[1] + su[1]) and", 374 | " (pu[0] < pos[0] < pu[0] + su[0])):", 375 | " S['acc'][1] *= -1", 376 | "", 377 | " # MOVEMENT", 378 | " S['position'][0] += S['acc'][0] * speed", 379 | " S['position'][1] += S['acc'][1] * speed", 380 | "", 381 | " return S['position'], S['size']", 382 | "", 383 | "call = ball" 384 | ], 385 | "connects": [ 386 | { 387 | "output": { 388 | "node": 49, 389 | "put": { 390 | "name": "result 2" 391 | } 392 | }, 393 | "input": { 394 | "put": { 395 | "name": "pad_u" 396 | } 397 | } 398 | }, 399 | { 400 | "output": { 401 | "node": 55, 402 | "put": { 403 | "name": "result 2" 404 | } 405 | }, 406 | "input": { 407 | "put": { 408 | "name": "pad_b" 409 | } 410 | } 411 | } 412 | ], 413 | "id": 51 414 | }, 415 | { 416 | "type": "node", 417 | "x": 509, 418 | "y": -4, 419 | "size": [ 420 | 336, 421 | 150 422 | ], 423 | "color": [ 424 | 81, 425 | 90, 426 | 127 427 | ], 428 | "code": [ 429 | "def join3(a=None, b=None, c=None):", 430 | " result = a, b, c", 431 | " return result", 432 | "", 433 | "call = join3" 434 | ], 435 | "connects": [ 436 | { 437 | "output": { 438 | "node": 54, 439 | "put": { 440 | "name": "result" 441 | } 442 | }, 443 | "input": { 444 | "put": { 445 | "name": "c" 446 | } 447 | } 448 | }, 449 | { 450 | "output": { 451 | "node": 43, 452 | "put": { 453 | "name": "result" 454 | } 455 | }, 456 | "input": { 457 | "put": { 458 | "name": "b" 459 | } 460 | } 461 | }, 462 | { 463 | "output": { 464 | "node": 53, 465 | "put": { 466 | "name": "result" 467 | } 468 | }, 469 | "input": { 470 | "put": { 471 | "name": "a" 472 | } 473 | } 474 | } 475 | ], 476 | "id": 52 477 | }, 478 | { 479 | "type": "node", 480 | "x": 376, 481 | "y": 96, 482 | "size": [ 483 | 473, 484 | 316 485 | ], 486 | "color": [ 487 | 99, 488 | 114, 489 | 116 490 | ], 491 | "code": [ 492 | "import pyglet", 493 | "", 494 | "S['box'] = pyglet.graphics.vertex_list_indexed(", 495 | " 4, [0, 1, 2, 0, 2, 3],", 496 | " ('v2i', (0, 0, 10, 0, 10, 10, 0, 10)),", 497 | " ('c3B', (255,255,255)*4) )", 498 | "", 499 | "def draw_rect(pos=(0,0), size=(10,10)):", 500 | " x, y = int(pos[0]), int(pos[1])", 501 | " w, h = int(size[0]), int(size[1])", 502 | " S['box'].vertices = (x, y,", 503 | " x + w, y,", 504 | " x + w, y + h,", 505 | " x, y + h)", 506 | " return S['box']", 507 | "", 508 | "call = draw_rect" 509 | ], 510 | "connects": [ 511 | { 512 | "output": { 513 | "node": 49, 514 | "put": { 515 | "name": "result 0" 516 | } 517 | }, 518 | "input": { 519 | "put": { 520 | "name": "pos" 521 | } 522 | } 523 | }, 524 | { 525 | "output": { 526 | "node": 49, 527 | "put": { 528 | "name": "result 1" 529 | } 530 | }, 531 | "input": { 532 | "put": { 533 | "name": "size" 534 | } 535 | } 536 | } 537 | ], 538 | "id": 53 539 | }, 540 | { 541 | "type": "node", 542 | "x": 653, 543 | "y": 95, 544 | "size": [ 545 | 473, 546 | 316 547 | ], 548 | "color": [ 549 | 99, 550 | 114, 551 | 116 552 | ], 553 | "code": [ 554 | "import pyglet", 555 | "", 556 | "S['box'] = pyglet.graphics.vertex_list_indexed(", 557 | " 4, [0, 1, 2, 0, 2, 3],", 558 | " ('v2i', (0, 0, 10, 0, 10, 10, 0, 10)),", 559 | " ('c3B', (255,100,20)*4) )", 560 | "", 561 | "def draw_rect(pos=(0,0), size=(10,10)):", 562 | " x, y = int(pos[0]), int(pos[1])", 563 | " w, h = int(size[0]), int(size[1])", 564 | " S['box'].vertices = (x, y,", 565 | " x + w, y,", 566 | " x + w, y + h,", 567 | " x, y + h)", 568 | " return S['box']", 569 | "", 570 | "call = draw_rect" 571 | ], 572 | "connects": [ 573 | { 574 | "output": { 575 | "node": 51, 576 | "put": { 577 | "name": "result 1" 578 | } 579 | }, 580 | "input": { 581 | "put": { 582 | "name": "size" 583 | } 584 | } 585 | }, 586 | { 587 | "output": { 588 | "node": 51, 589 | "put": { 590 | "name": "result 0" 591 | } 592 | }, 593 | "input": { 594 | "put": { 595 | "name": "pos" 596 | } 597 | } 598 | } 599 | ], 600 | "id": 54 601 | }, 602 | { 603 | "type": "node", 604 | "x": 534, 605 | "y": 280, 606 | "size": [ 607 | 490, 608 | 370 609 | ], 610 | "color": [ 611 | 101, 612 | 91, 613 | 127 614 | ], 615 | "code": [ 616 | "from typing import *", 617 | "", 618 | "w = G['window']", 619 | "S['size'] = [70, 10]", 620 | "S['position'] = [w.width/2 - S['size'][0]/2, 20]", 621 | "", 622 | "def pad_bottom(key=[]) -> Tuple[Any, Any, Any]:", 623 | " speed = G['dt'] * 20", 624 | "", 625 | " if 'RIGHT' in key:", 626 | " if S['position'][0] < w.width - S['size'][0]:", 627 | " S['position'][0] += speed", 628 | " if 'LEFT' in key:", 629 | " if S['position'][0] > 0:", 630 | " S['position'][0] -= speed", 631 | " return S['position'], S['size'], S", 632 | "", 633 | "call = pad_bottom" 634 | ], 635 | "connects": [ 636 | { 637 | "output": { 638 | "node": 50, 639 | "put": { 640 | "name": "result" 641 | } 642 | }, 643 | "input": { 644 | "put": { 645 | "name": "key" 646 | } 647 | } 648 | } 649 | ], 650 | "id": 55 651 | } 652 | ] --------------------------------------------------------------------------------