import zmq from collections import namedtuple from . import serial, exc, expr Property = namedtuple("Property", [ "name", "type", "subtype", #"defaults", "ui_name", "desc", "is_null_allowed", "is_expr_allowed", "array_len", "min_val", "max_val", "enum_items", "depends" ]) class Command: HELLO = 0 ADD_NODE = 1 REMOVE_NODE = 9 RENAME_NODE = 23 SCAN_DANGLING = 24 LOOKUP_NODE_ID = 12 ADD_PROPERTY = 11 LINK_NODE = 2 UNLINK_NODE = 8 GET_INFO = 19 GET_CHILDREN = 4 GET_PARENTS = 5 GET_PROPERTIES = 3 GET_PROPERTY_VALUE = 6 SET_PROPERTY_VALUE = 7 GET_SIGNALS = 14 REGISTER_SLOT = 15 UNREGISTER_SLOT = 16 LOOKUP_SLOT_ID = 17 GET_SLOTS = 18 GET_METHODS = 20 GET_METHOD = 21 CALL_METHOD = 22 class SceneNodeType: NULL = 0 ROOT = 1 WINDOW = 2 WINDOW_INPUT = 3 KEYBOARD = 4 MOUSE = 5 LAYER = 6 OBJECT = 7 VECTOR_ART = 8 TEXT = 9 TEXTURE = 10 FONTS = 11 FONT = 12 CHAT_VIEW = 13 EDIT = 14 IMAGE = 15 BUTTON = 16 SHORTCUT = 17 GESTURE = 18 EMOJI_PICKER = 19 SETTING_ROOT = 20 SETTING = 21 PLUGINS = 100 PLUGIN = 101 class PropertyType: NULL = 0 BOOL = 1 UINT32 = 2 FLOAT32 = 3 STR = 4 ENUM = 5 BUFFER = 6 SCENE_NODE_ID = 7 SEXPR = 8 @staticmethod def to_str(prop_type): match prop_type: case PropertyType.NULL: return "null" case PropertyType.BOOL: return "bool" case PropertyType.UINT32: return "uint32" case PropertyType.FLOAT32: return "float32" case PropertyType.STR: return "str" case PropertyType.ENUM: return "enum" case PropertyType.BUFFER: return "buffer" case PropertyType.SCENE_NODE_ID: return "scene_node_id" case PropertyType.SEXPR: return "sexpr" class PropertySubType: NULL = 0 COLOR = 1 PIXEL = 2 RESOURCE_ID = 3 @staticmethod def to_str(prop_type): match prop_type: case PropertySubType.NULL: return "null" case PropertySubType.Color: return "color" case PropertySubType.PIXEL: return "pixel" case PropertySubType.RESOURCE_ID: return "resource_id" class PropertyStatus: OK = 0 UNSET = 1 NULL = 2 EXPR = 3 class ErrorCode: INVALID_SCENE_PATH = 1 NODE_NOT_FOUND = 2 CHILD_NODE_NOT_FOUND = 3 PARENT_NODE_NOT_FOUND = 4 PROPERTY_ALREADY_EXISTS = 5 PROPERTY_NOT_FOUND = 6 PROPERTY_WRONG_TYPE = 7 PROPERTY_WRONG_SUB_TYPE = 8 PROPERTY_WRONG_LEN = 9 PROPERTY_WRONG_INDEX = 10 PROPERTY_OUT_OF_RANGE = 11 PROPERTY_NULL_NOT_ALLOWED = 12 PROPERTY_SEXPR_NOT_ALLOWED = 13 PROPERTY_IS_BOUNDED = 14 PROPERTY_WRONG_ENUM_ITEM = 15 SIGNAL_ALREADY_EXISTS = 16 SIGNAL_NOT_FOUND = 17 SLOT_NOT_FOUND = 18 METHOD_ALREADY_EXISTS = 19 METHOD_NOT_FOUND = 20 NODES_ARE_LINKED = 21 NODES_NOT_LINKED = 22 NODE_HAS_PARENTS = 23 NODE_HAS_CHILDREN = 24 NODE_PARENT_NAME_CONFLICT = 25 NODE_CHILD_NAME_CONFLICT = 26 NODE_SIBLING_NAME_CONFLICT = 27 FILE_NOT_FOUND = 28 RESOURCE_NOT_FOUND = 29 PY_EVAL_ERR = 30 SEXPR_EMPTY = 31 SEXPR_GLOBAL_NOT_FOUND = 32 CHANNEL_CLOSED = 36 @staticmethod def to_str(errc): match errc: case ErrorCode.INVALID_SCENE_PATH: return "invalid_scene_path" case ErrorCode.NODE_NOT_FOUND: return "node_not_found" case ErrorCode.CHILD_NODE_NOT_FOUND: return "child_node_not_found" case ErrorCode.PARENT_NODE_NOT_FOUND: return "parent_node_not_found" case ErrorCode.PROPERTY_ALREADY_EXISTS: return "property_already_exists" case ErrorCode.PROPERTY_NOT_FOUND: return "property_not_found" case ErrorCode.PROPERTY_WRONG_TYPE: return "property_wrong_type" case ErrorCode.PROPERTY_WRONG_LEN: return "property_wrong_len" case ErrorCode.PROPERTY_WRONG_INDEX: return "property_wrong_index" case ErrorCode.PROPERTY_OUT_OF_RANGE: return "property_out_of_range" case ErrorCode.PROPERTY_NULL_NOT_ALLOWED: return "property_null_not_allowed" case ErrorCode.PROPERTY_SEXPR_NOT_ALLOWED: return "property_sexpr_not_allowed" case ErrorCode.PROPERTY_IS_BOUNDED: return "property_is_bounded" case ErrorCode.PROPERTY_WRONG_ENUM_ITEM: return "property_wrong_enum_item" case ErrorCode.SIGNAL_ALREADY_EXISTS: return "signal_already_exists" case ErrorCode.SIGNAL_NOT_FOUND: return "signal_not_found" case ErrorCode.SLOT_NOT_FOUND: return "slot_not_found" case ErrorCode.METHOD_ALREADY_EXISTS: return "method_already_exists" case ErrorCode.METHOD_NOT_FOUND: return "method_not_found" case ErrorCode.NODES_ARE_LINKED: return "nodes_are_linked" case ErrorCode.NODES_NOT_LINKED: return "nodes_not_linked" case ErrorCode.NODE_HAS_PARENTS: return "node_has_parents" case ErrorCode.NODE_HAS_CHILDREN: return "node_has_children" case ErrorCode.NODE_PARENT_NAME_CONFLICT: return "node_parent_name_conflict" case ErrorCode.NODE_CHILD_NAME_CONFLICT: return "node_child_name_conflict" case ErrorCode.NODE_SIBLING_NAME_CONFLICT: return "node_sibling_name_conflict" case ErrorCode.FILE_NOT_FOUND: return "file_not_found" case ErrorCode.RESOURCE_NOT_FOUND: return "resource_not_found" case ErrorCode.PY_EVAL_ERR: return "py_eval_err" case ErrorCode.SEXPR_EMPTY: return "sexpr_empty" case ErrorCode.SEXPR_GLOBAL_NOT_FOUND: return "sexpr_global_not_found" case ErrorCode.CHANNEL_CLOSED: return "channel_closed" def vertex(x, y, r, g, b, a, u, v): buf = bytearray() serial.write_f32(buf, x) serial.write_f32(buf, y) serial.write_f32(buf, r) serial.write_f32(buf, g) serial.write_f32(buf, b) serial.write_f32(buf, a) serial.write_f32(buf, u) serial.write_f32(buf, v) return buf def face(idx1, idx2, idx3): buf = bytearray() serial.write_u32(buf, idx1) serial.write_u32(buf, idx2) serial.write_u32(buf, idx3) return buf class Api: def __init__(self, addr="127.0.0.1", port=9484): context = zmq.Context() self.socket = context.socket(zmq.REQ) #self.socket.setsockopt(zmq.IPV6, True) self.socket.connect(f"tcp://{addr}:{port}") def _make_request(self, cmd, payload): req_cmd = bytearray() serial.write_u8(req_cmd, cmd) self.socket.send_multipart([req_cmd, payload]) errc, reply = self.socket.recv_multipart() errc = int.from_bytes(errc, "little") cursor = serial.Cursor(reply) match errc: case 1: raise exc.InvalidScenePath case 2: raise exc.NodeNotFound case 3: raise exc.ChildNodeNotFound case 4: raise exc.ParentNodeNotFound case 5: raise exc.PropertyAlreadyExists case 6: raise exc.PropertyNotFound case 7: raise exc.PropertyWrongType case 8: raise exc.PropertyWrongSubType case 9: raise exc.PropertyWrongLen case 10: raise exc.PropertyWrongIndex case 11: raise exc.PropertyOutOfRange case 12: raise exc.PropertyNullNotAllowed case 12: raise exc.PropertySExprNotAllowed case 14: raise exc.PropertyIsBounded case 15: raise exc.PropertyWrongEnumItem case 16: raise exc.SignalAlreadyExists case 17: raise exc.SignalNotFound case 18: raise exc.SlotNotFound case 19: raise exc.MethodAlreadyExists case 20: raise exc.MethodNotFound case 21: raise exc.NodesAreLinked case 22: raise exc.NodesNotLinked case 23: raise exc.NodeHasParents case 24: raise exc.NodeHasChildren case 25: raise exc.NodeParentNameConflict case 26: raise exc.NodeChildNameConflict case 27: raise exc.NodeSiblingNameConflict case 28: raise exc.FileNotFound case 29: raise exc.ResourceNotFound case 30: raise exc.PyEvalErr case 31: raise exc.SExprEmpty case 32: raise exc.SExprGlobalNotFound case 36: raise exc.ChannelClosed return cursor def hello(self): response = self._make_request(Command.HELLO, bytearray()) return serial.decode_str(response) def get_info(self, node_id): req = bytearray() serial.write_u32(req, node_id) cur = self._make_request(Command.GET_INFO, req) name = serial.decode_str(cur) type = serial.read_u8(cur) return (name, type) def get_children(self, node_path): req = bytearray() serial.encode_str(req, node_path) cur = self._make_request(Command.GET_CHILDREN, req) children_len = serial.decode_varint(cur) children = [] for _ in range(children_len): child_name = serial.decode_str(cur) child_id = serial.read_u32(cur) child_type = serial.read_u8(cur) children.append((child_name, child_id, child_type)) return children def get_parents(self, node_id): req = bytearray() serial.write_u32(req, node_id) cur = self._make_request(Command.GET_PARENTS, req) parents_len = serial.decode_varint(cur) parents = [] for _ in range(parents_len): parent_name = serial.decode_str(cur) parent_id = serial.read_u32(cur) parent_type = serial.read_u8(cur) parents.append((parent_name, parent_id, parent_type)) return parents def get_properties(self, node_path): req = bytearray() serial.encode_str(req, node_path) cur = self._make_request(Command.GET_PROPERTIES, req) props_len = serial.decode_varint(cur) props = [] enum_read_fn = lambda cur: serial.decode_arr(cur, serial.decode_str) def depend_read_fn(cur): i = serial.read_u32(cur) local_name = serial.decode_str(cur) return (i, local_name) for _ in range(props_len): prop_name = serial.decode_str(cur) # We need prop_type below prop_type = serial.read_u8(cur) prop_read_fn = lambda cur: Api.read_prop_val(cur, prop_type) prop = Property( prop_name, prop_type, # subtype serial.read_u8(cur), # defaults #serial.decode_arr(cur, prop_read_fn), # ui_name serial.decode_str(cur), # desc serial.decode_str(cur), # is_null_allowed bool(serial.read_u8(cur)), # is_expr_allowed bool(serial.read_u8(cur)), # array_len serial.read_u32(cur), # min_val serial.decode_opt(cur, prop_read_fn), # max_val serial.decode_opt(cur, prop_read_fn), # enum_items serial.decode_opt(cur, enum_read_fn), # depends serial.decode_arr(cur, depend_read_fn) ) props.append(prop) return props @staticmethod def read_prop_val(cur, prop_type): match prop_type: case PropertyType.NULL: return None case PropertyType.BOOL: return bool(serial.read_u8(cur)) case PropertyType.UINT32: return serial.read_u32(cur) case PropertyType.FLOAT32: return serial.read_f32(cur) case PropertyType.STR: return serial.decode_str(cur) case PropertyType.ENUM: return serial.decode_str(cur) case PropertyType.BUFFER: pass case PropertyType.SCENE_NODE_ID: return serial.read_u32(cur) case _: raise Exception("unknown property type returned") def get_property_value(self, node_path, prop_name): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, prop_name) cur = self._make_request(Command.GET_PROPERTY_VALUE, req) prop_type = serial.read_u8(cur) def prop_read_fn(cur): prop_status = serial.read_u8(cur) match prop_status: case PropertyStatus.NULL: return None case PropertyStatus.EXPR: return None case PropertyStatus.UNSET | PropertyStatus.OK: return Api.read_prop_val(cur, prop_type) vals = serial.decode_arr(cur, prop_read_fn) return vals def add_node(self, node_name, node_type): req = bytearray() serial.encode_str(req, node_name) serial.write_u8(req, int(node_type)) cur = self._make_request(Command.ADD_NODE, req) node_id = serial.read_u32(cur) return node_id def remove_node(self, node_id): req = bytearray() serial.write_u32(req, node_id) self._make_request(Command.REMOVE_NODE, req) def rename_node(self, node_id, node_name): req = bytearray() serial.write_u32(req, node_id) serial.encode_str(req, node_name) self._make_request(Command.RENAME_NODE, req) def scan_dangling(self): cur = self._make_request(Command.SCAN_DANGLING, bytearray()) dangling_len = serial.decode_varint(cur) dangling = [] for _ in range(dangling_len): node_id = serial.read_u32(cur) dangling.append(node_id) return dangling def lookup_node_id(self, node_path): req = bytearray() serial.encode_str(req, node_path) try: cur = self._make_request(Command.LOOKUP_NODE_ID, req) except exc.NodeNotFound: return None return serial.read_u32(cur) def add_property(self, node_id, prop): req = bytearray() serial.write_u32(req, node_id) serial.encode_str(req, prop.name) serial.write_u8(req, int(prop.type)) serial.write_u8(req, int(prop.subtype)) serial.write_u32(req, int(prop.array_len)) def write_defaults(by): assert prop.defaults is not None defaults_len = len(prop.defaults) serial.encode_varint(req, defaults_len) for default in prop.defaults: match prop.type: case PropertyType.UINT32: serial.write_u32(req, default) case PropertyType.FLOAT32: serial.write_f32(req, default) case PropertyType.STR: serial.encode_str(req, default) case _: raise exc.PropertyWrongType serial.encode_opt(req, prop.defaults, write_defaults) serial.encode_str(req, prop.ui_name) serial.encode_str(req, prop.desc) serial.write_u8(req, int(prop.is_null_allowed)) serial.write_u8(req, int(prop.is_expr_allowed)) def write_mxx(v, by): assert v is not None match prop.type: case PropertyType.UINT32: serial.write_u32(req, v) case PropertyType.FLOAT32: serial.write_f32(req, v) case _: raise exc.PropertyWrongType write_min = lambda by: write_mxx(prop.min_val, by) write_max = lambda by: write_mxx(prop.max_val, by) serial.encode_opt(req, prop.min_val, write_min) serial.encode_opt(req, prop.max_val, write_max) serial.encode_varint(req, len(prop.enum_items)) for enum_item in prop.enum_items: if prop.type != PropertyType.ENUM: raise exc.PropertyWrongType serial.encode_str(req, enum_item) self._make_request(Command.ADD_PROPERTY, req) def link_node(self, child_id, parent_id): req = bytearray() serial.write_u32(req, child_id) serial.write_u32(req, parent_id) self._make_request(Command.LINK_NODE, req) def unlink_node(self, child_id, parent_id): req = bytearray() serial.write_u32(req, child_id) serial.write_u32(req, parent_id) self._make_request(Command.UNLINK_NODE, req) def set_property_null(self, node_path, prop_name, i): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, prop_name) serial.write_u32(req, i) serial.write_u8(req, PropertyType.NULL) self._make_request(Command.SET_PROPERTY_VALUE, req) def set_property_bool(self, node_path, prop_name, i, val): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, prop_name) serial.write_u32(req, i) serial.write_u8(req, PropertyType.BOOL) serial.write_u8(req, int(val)) self._make_request(Command.SET_PROPERTY_VALUE, req) def set_property_u32(self, node_path, prop_name, i, val): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, prop_name) serial.write_u32(req, i) serial.write_u8(req, PropertyType.UINT32) serial.write_u32(req, val) self._make_request(Command.SET_PROPERTY_VALUE, req) def set_property_f32(self, node_path, prop_name, i, val): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, prop_name) serial.write_u32(req, i) serial.write_u8(req, PropertyType.FLOAT32) serial.write_f32(req, val) self._make_request(Command.SET_PROPERTY_VALUE, req) def set_property_str(self, node_path, prop_name, i, val): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, prop_name) serial.write_u32(req, i) serial.write_u8(req, PropertyType.STR) serial.encode_str(req, val) self._make_request(Command.SET_PROPERTY_VALUE, req) def set_property_enum(self, node_path, prop_name, i, val): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, prop_name) serial.write_u32(req, i) serial.write_u8(req, PropertyType.ENUM) serial.encode_str(req, val) self._make_request(Command.SET_PROPERTY_VALUE, req) def set_property_buf(self, node_path, prop_name, i, buf): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, prop_name) serial.write_u32(req, i) serial.write_u8(req, PropertyType.BUFFER) serial.encode_buf(req, buf) self._make_request(Command.SET_PROPERTY_VALUE, req) def set_property_expr(self, node_path, prop_name, i, code): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, prop_name) serial.write_u32(req, i) serial.write_u8(req, PropertyType.SEXPR) serial.encode_varint(req, len(code)) for sexpr in code: expr.encode_expr(req, sexpr) self._make_request(Command.SET_PROPERTY_VALUE, req) def get_signals(self, node_path): req = bytearray() serial.encode_str(req, node_path) cur = self._make_request(Command.GET_SIGNALS, req) sigs_len = serial.decode_varint(cur) sigs = [] for _ in range(sigs_len): sigs.append(serial.decode_str(cur)) return sigs def register_slot(self, node_path, sig_name, slot_name, user_data): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, sig_name) serial.encode_str(req, slot_name) serial.encode_varint(req, len(user_data)) req += user_data cur = self._make_request(Command.REGISTER_SLOT, req) slot_id = serial.read_u32(cur) return slot_id def unregister_slot(self, node_id, sig_name, slot_id): req = bytearray() serial.write_u32(req, node_id) serial.encode_str(req, sig_name) serial.write_u32(req, slot_id) self._make_request(Command.UNREGISTER_SLOT, req) def lookup_slot_id(self, node_id, sig_name, slot_name): req = bytearray() serial.write_u32(req, node_id) serial.encode_str(req, sig_name) serial.encode_str(req, slot_name) try: cur = self._make_request(Command.LOOKUP_SLOT_ID, req) except exc.RequestSlotNotFound: return None return serial.read_u32(cur) def get_slots(self, node_path, sig_name): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, sig_name) cur = self._make_request(Command.GET_SLOTS, req) def read_slot(cur): slot_name = serial.decode_str(cur) slot_id = serial.read_u32(cur) return (slot_name, slot_id) slots = serial.decode_arr(cur, read_slot) return slots def get_methods(self, node_path): req = bytearray() serial.encode_str(req, node_path) cur = self._make_request(Command.GET_METHODS, req) def read_method(cur): method_name = serial.decode_str(cur) return method_name methods = serial.decode_arr(cur, read_method) return methods def get_method(self, node_path, method_name): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, method_name) cur = self._make_request(Command.GET_METHOD, req) def read_arg(cur): arg_name = serial.decode_str(cur) arg_desc = serial.decode_str(cur) arg_type = serial.read_u8(cur) return (arg_name, arg_desc, arg_type) args = serial.decode_arr(cur, read_arg) results = serial.decode_arr(cur, read_arg) return (args, results) def call_method(self, node_path, method_name, arg_data): req = bytearray() serial.encode_str(req, node_path) serial.encode_str(req, method_name) serial.encode_buf(req, arg_data) cur = self._make_request(Command.CALL_METHOD, req) result = serial.decode_opt(cur, serial.decode_buf) return result