From 91baa3dd507ab822a66996c712f914f7f01e3935 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 22 Mar 2020 13:22:12 -0700 Subject: [PATCH 01/18] move math funcs into class header --- zsc.py | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/zsc.py b/zsc.py index 22243a3..4fdd901 100644 --- a/zsc.py +++ b/zsc.py @@ -14,28 +14,7 @@ VERSION = '0.1.0' -# these could be imported from 'zbrush' -# or math. this is not a 1:1 match with -# zbrush's list, some of those are handled -# by python constucts, such as "-x -> [NEG, #x]" -KNOWN_MATH_FUNCS = { - "sin": "SIN", - "cos": "COS", - "tan": "TAN", - "asin": "ASIN", - "acos": "ACOS", - "atan": "ATAN", - "atan2": "ATAN2", - "log": "LOG", - "log10": "LOG10", - "sqrt": "SQRT", - "abs": "ABS", - "random": "RAND", - "randint": "IRAND", - "bool": "BOOL", - "int": "INT", - "frac": "FRAC", -} + class Analyzer(ast.NodeVisitor): @@ -46,15 +25,36 @@ def __init__(self, indent=0, input_file='', context=None): self.context = context self.stack = [] self.defined = [] - self.zbrush = [] + + # these are functions which we recognize in input calls and convert to their + # zbrush equivalents. Not all have exact equivalents -- some zbrush + # functions are represents by python syntac instead + + # todo: As written we only check these against the name of a call, ie, + # it could be 'zrush.sin()` or `math.sin()` in the python side. We should + # probably regularize that for consistency & readability + self.funcs = { - 'array': 'VarDef', + "sin": "SIN", + "cos": "COS", + "tan": "TAN", + "asin": "ASIN", + "acos": "ACOS", + "atan": "ATAN", + "atan2": "ATAN2", + "log": "LOG", + "log10": "LOG10", + "sqrt": "SQRT", + "abs": "ABS", + "random": "RAND", + "randint": "IRAND", + "bool": "BOOL", + "int": "INT", + "frac": "FRAC", 'min': 'MIN', 'max': 'MAX' } - self.funcs.update(**KNOWN_MATH_FUNCS) - self.top_level_defs = [] if self.context: @@ -688,7 +688,7 @@ def compile(filename, out_filename=''): tp = (f'transpiled with zsc {VERSION}') orig = f'from: {filename}' - output.write(f"/*\n{tp}\n{orig}\n*/\n\n") + output.write(f"// {tp}\n// {orig}\n") output.write(analyzer.format()) return out_filename, analyzer.format() From 049853c023ccfb8792e8f4029af9fe3ba6cc84ac Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 22 Mar 2020 17:18:07 -0700 Subject: [PATCH 02/18] this is a crusd working version that uses context managers to do callbacks. Probably not better to just use callbacks instead: --- zbrush.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/zbrush.py b/zbrush.py index 694dd39..a449ce4 100644 --- a/zbrush.py +++ b/zbrush.py @@ -238,7 +238,7 @@ def FileDelete(FileName: str) -> int: pass -def FileExecute(FileName:str, MethodName: str, TextInputMem : MemBlock = None, Number: float= 0, InOutMem1: MemBlock = None, InOutMem2: MemBlock: None) -> int: +def FileExecute(FileName: str, MethodName: str, TextInputMem: MemBlock = None, Number: float = 0, InOutMem1: MemBlock = None, InOutMem2: MemBlock: None) -> int: """ Executes the specified plugin file (DLL). @@ -256,12 +256,12 @@ def FileExists(FileName: str) -> int: pass -def FileGetInfo(FileName: str, InfoIndex: int) ->float: +def FileGetInfo(FileName: str, InfoIndex: int) -> float: """ Retrieve information about a specified file. - + InfoIndex values: - + 1: file size (in mb) 2 -7: Creation date: year, month(1-12), day, hour, minutes, seconds 8 -13: Modified date: year, month(1-12), day, hour, minutes, seconds @@ -283,7 +283,7 @@ def FileNameAdvance(FileNameBase: str, NumDigits: int, AddCopyTag: int) -> str: pass -def FileNameAsk(Extensions: str, DefaultName: str = None, DialogTitle: str = None ) -> str: +def FileNameAsk(Extensions: str, DefaultName: str = None, DialogTitle: str = None) -> str: """ Asks user for a file name If DefaultName is omitted, it's an Open dialog @@ -296,7 +296,7 @@ def FileNameAsk(Extensions: str, DefaultName: str = None, DialogTitle: str = Non def FileNameExtract(FileName: str, Component: int) -> str: """ Extracts filename components. - + Components: 1: path 2: name @@ -309,7 +309,6 @@ def FileNameExtract(FileName: str, Component: int) -> str: pass # todo: should we use path.splitext, basename, etc? - def FileNameGetLastTyped() -> str: """ @@ -356,7 +355,7 @@ def FileNameMake(BaseFileName: str, Index: int, NumDigits: int) -> str: pass -def FileNameResolvePath(LocalFileName: str) ->str: +def FileNameResolvePath(LocalFileName: str) -> str: """ Resolves local path to full path @@ -365,7 +364,7 @@ def FileNameResolvePath(LocalFileName: str) ->str: pass -def FileNameSetNext(FileName: str, TemplatePath: str=None) -> None: +def FileNameSetNext(FileName: str, TemplatePath: str = None) -> None: """ Pre-sets the file name that will be used in the next Save/Load action """ @@ -454,6 +453,7 @@ def GetActiveToolPath() -> str: pass ''' + def HotKeyText(InterfacePath: str) -> None: """ Displays a hot-key for the specified interface item @@ -463,7 +463,7 @@ def HotKeyText(InterfacePath: str) -> None: #------------- interface -def IButton(ButtonText: str, PopupText: str = None, Commands: ZBrushCommandList : None , Disabled: int = 0, Width: int = 0, Hotkey: str ='', Icon: str ='', Height: int =0) -> None: +def IButton(ButtonText: str, PopupText: str = None, Commands: ZBrushCommandList = ..., Disabled: int = 0, Width: int = 0, Hotkey: str = '', Icon: str = '', Height: int = 0) -> None: """ Creates an interactive push button """ @@ -471,6 +471,7 @@ def IButton(ButtonText: str, PopupText: str = None, Commands: ZBrushCommandList # TODO: Pick up type annotations here + def IClick(InterfacePath, *positions): """ Emulates a click within a specified ZBrush interface item @@ -560,7 +561,7 @@ def IFadeOut(FadeOutSpeed=0.5): pass -def IFreeze(CommandS, FadeOutSpeed=0.05): +def IFreeze(Commands: ZBrushCommandList = ..., FadeOutSpeed=0.05): """ Disables interface updates. @@ -695,7 +696,7 @@ def IHPos(InterfaceItemPath, UseGlobalCoords=0): pass -def IKeyPress(KeyCode, Commands, HCursor=None, VCursor=None): +def IKeyPress(KeyCode, Commands: ZBrushCommandList = ..., HCursor=None, VCursor=None): """ Simulates a key press @@ -921,7 +922,7 @@ def IsUnlocked(InterfaceItemPath): pass -def ISwitch(ButtonText, InitialState, PopupText, PressCommands, UnpressedCommands, InitiallyDisabled=0, ButtonWidth=0): +def ISwitch(ButtonText, InitialState, PopupText, PressCommands : ZBrushCommandList = ..., UnpressedCommands : ZBrushCommandList = ..., InitiallyDisabled=0, ButtonWidth=0): """ Creates an interactive switch InitialState (1=pressed, 0=unpressed), @@ -2155,11 +2156,12 @@ def VarSize(VariableName): # """ # Gets the value of a named variable # - Output: Value of the named variable + Output: Value of the named variable # """ # pass + def ZBrushInfo(InfoType): """ Integer type code: From 7601d8322fc85dea4c62e7c7caae50d5f3d761e9 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 22 Mar 2020 18:13:19 -0700 Subject: [PATCH 03/18] refactor into executable module; also roughin on prepass for known names --- zsc/__init__.py | 0 zsc/__main__.py | 21 ++++ zsc.py => zsc/compiler.py | 117 ++++++++++++++---- zsc/keywords.py | 238 +++++++++++++++++++++++++++++++++++++ zbrush.py => zsc/zbrush.py | 0 zsc/zbursh_names.py | 1 + 6 files changed, 357 insertions(+), 20 deletions(-) create mode 100644 zsc/__init__.py create mode 100644 zsc/__main__.py rename zsc.py => zsc/compiler.py (89%) create mode 100644 zsc/keywords.py rename zbrush.py => zsc/zbrush.py (100%) create mode 100644 zsc/zbursh_names.py diff --git a/zsc/__init__.py b/zsc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zsc/__main__.py b/zsc/__main__.py new file mode 100644 index 0000000..10a6258 --- /dev/null +++ b/zsc/__main__.py @@ -0,0 +1,21 @@ +import zsc.compiler as compiler +import argparse + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog="zsc", + description=f'Python to ZScript transpiler ({compiler.VERSION})' + ) + parser.add_argument("input", help="path to python source file") + parser.add_argument( + "--output", help="optional output file (otherwise, uses the same name as the input file with .txt extension)") + parser.add_argument( + "--show", help="if true, print the transpiled file to stdout", action='store_true') + + args = parser.parse_args() + output, result = compiler.compile(args.input, out_filename=args.output or '') + + if args.show: + print(result) + else: + print(output) diff --git a/zsc.py b/zsc/compiler.py similarity index 89% rename from zsc.py rename to zsc/compiler.py index 4fdd901..6a16a0d 100644 --- a/zsc.py +++ b/zsc/compiler.py @@ -15,12 +15,75 @@ VERSION = '0.1.0' +class Prepass(ast.NodeVisitor): + + def __init__(self): + self.functions = {} + self.zbrush_mod = None + self.zbrush_aliases = { + "sin": "SIN", + "cos": "COS", + "tan": "TAN", + "asin": "ASIN", + "acos": "ACOS", + "atan": "ATAN", + "atan2": "ATAN2", + "log": "LOG", + "log10": "LOG10", + "sqrt": "SQRT", + "abs": "ABS", + "random": "RAND", + "randint": "IRAND", + "bool": "BOOL", + "int": "INT", + "frac": "FRAC", + 'min': 'MIN', + 'max': 'MAX' + } + self.zbrush_function = {} + + def get_call_name(self, node): + try: + prefix = '' + name = node.func.id + except: + prefix = node.func.value.id + name = node.func.attr + + alias = self.zbrush_aliases.get(name,'') + if not alias and prefix == 'zbrush': + alias = name + return prefix, name, alias + + + def visit_Import(self, node): + + for name in node.names: + if 'zbrush' in name.name: + zname = name.name.split(".")[-1] + self.zbrush_aliases[name.asname or name.name] = zname + + def visit_ImportFrom(self, node): + + if node.module == 'zbrush': + for name in node.names: + self.zbrush_aliases[name.asname or name.name] = name.name + + def visit_Call(self, node): + self.functions[self.get_call_name(node)] = node + + def is_defined(self, node): + return self.get_call_name(node) in self.functions + + + + + class Analyzer(ast.NodeVisitor): def __init__(self, indent=0, input_file='', context=None): self.input_file = input_file - self.contents = io.StringIO() self.indent = indent self.context = context self.stack = [] @@ -638,6 +701,29 @@ def visit_While(self, node): finally: self.indent += 1 + # def visit_With(self, node): + # if len(node.items) != 1: + # self.abort("Cannot parts multiple context manager aliases", node) + # try: + # name = node.items[0].context_expr.func.id + # except: + # name = node.items[0].context_expr.func.attr + # var_holder = None + # if node.items[0].optional_vars: + # var_holder = node.items[0].optional_vars.id + # # todo: how to use this? + # setter = self.get_setter(var_holder) + # pathname = self.as_literal(name) + # self.stack.append(f"[{setter}, {pathname}]") + # self.stack.append(f"[{name},") + + # body_parser = self.sub_parser(*node.body) + # self.stack.append(body_parser.format()) + # self.stack.append(f"] // end {name}") + + + + def report(self): for item in self.stack: print(item) @@ -679,6 +765,16 @@ def compile(filename, out_filename=''): input_file = source.read() tree = ast.parse(input_file) + + prepass = Prepass() + prepass.visit(tree) + + import pprint + pprint.pprint (prepass.functions) + + print (prepass.zbrush_aliases) + + analyzer = Analyzer(0, input_file=input_file) analyzer.visit(tree) @@ -693,22 +789,3 @@ def compile(filename, out_filename=''): return out_filename, analyzer.format() - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - prog="zsc", - description=f'Python to ZScript transpiler ({VERSION})' - ) - parser.add_argument("input", help="path to python source file") - parser.add_argument( - "--output", help="optional output file (otherwise, uses the same name as the input file with .txt extension)") - parser.add_argument( - "--show", help="if true, print the transpiled file to stdout", action='store_true') - - args = parser.parse_args() - output, result = compile(args.input, out_filename=args.output or '') - - if args.show: - print(result) - else: - print(output) diff --git a/zsc/keywords.py b/zsc/keywords.py new file mode 100644 index 0000000..132f298 --- /dev/null +++ b/zsc/keywords.py @@ -0,0 +1,238 @@ +ZBRUSH_KEYWORDS = { + ( + 'BackColorSet', + 'ButtonFind', + 'ButtonPress', + 'ButtonSet', + 'ButtonUnPress', + 'CanvasClick', + 'CanvasGyroHide', + 'CanvasGyroShow', + 'CanvasPanGetH', + 'CanvasPanGetV', + 'CanvasPanSet', + 'CanvasStroke', + 'CanvasStrokes', + 'CanvasZoomGet', + 'CanvasZoomSet', + 'Caption', + 'CurveAddPoint', + 'CurvesCreateMesh', + 'CurvesDelete', + 'CurvesNewCurve', + 'CurvesNew', + 'CurvesToUI', + 'Delay', + 'DispMapCreate', + 'Exit', + 'FileDelete', + 'FileExecute', + 'FileExists', + 'FileGetInfo', + 'FileNameAdvance', + 'FileNameAsk', + 'FileNameExtract', + 'FileNameGetLastTyped', + 'FileNameGetLastUsed', + 'FileNameGetNext', + 'FileNameHasNext', + 'FileNameMake', + 'FileNameResolvePath', + 'FileNameSetNext', + 'FileTemplateGetNext', + 'FontSetColor', + 'FontSetOpacity', + 'FontSetSize', + 'FontSetSizeLarge', + 'FontSetSizeMedium', + 'FontSetSizeSmall', + 'FrontColorSet', + 'GetActiveToolPath', + 'HotKeyText', + 'IButton', + 'IClick', + 'IClose', + 'IColorSet', + 'IConfig', + 'IDialog', + 'IDisable', + 'IEnable', + 'IExists', + 'IFadeIn', + 'IFadeOut', + 'IFreeze', + 'IGet', + 'IGetFlags', + 'IGetHotkey', + 'IGetID', + 'IGetInfo', + 'IGetMax', + 'IGetMin', + 'IGetSecondary', + 'IGetStatus', + 'IGetTitle', + 'IHeight', + 'IHide', + 'IHPos', + 'IKeyPress', + 'ILock', + 'Image', + 'IMaximize', + 'IMinimize', + 'IModGet', + 'IModSet', + 'Interpolate', + 'IPress', + 'IReset', + 'IsDisabled', + 'IsEnabled', + 'ISet', + 'ISetHotkey', + 'ISetMax', + 'ISetMin', + 'ISetStatus', + 'IShowActions', + 'IShow', + 'ISlider', + 'IsLocked', + 'IsPolyMesh3DSolid', + 'IStroke', + 'ISubPalette', + 'IsUnlocked', + 'ISwitch', + 'IToggle', + 'IUnlock', + 'IUnPress', + 'IUpdate', + 'IVPos', + 'IWidth', + 'MemCopy', + 'MemCreate', + 'MemCreateFromFile', + 'MemDelete', + 'MemGetSize', + 'MemMove', + 'MemMultiWrite', + 'MemRead', + 'MemReadString', + 'MemResize', + 'MemSaveToFile', + 'MemWrite', + 'MemWriteString', + 'MergeUndo', + 'Mesh3DGet', + 'MessageOK', + 'MessageOKCancel', + 'MessageYesNo', + 'MessageYesNoCancel', + 'MouseHPos', + 'MouseLButton', + 'MouseVPos', + 'MTransformGet', + 'MTransformSet', + 'MVarDef', + 'MVarGet', + 'MVarSet', + 'NormalMapCreate', + 'Note', + 'NoteBar', + 'NoteIButton', + 'NoteIGet', + 'NoteISwitch', + 'PageSetWidth', + 'PaintBackground', + 'PaintBackSliver', + 'PaintPageBreak', + 'PaintRect', + 'PaintTextRect', + 'PD', + 'PenMoveCenter', + 'PenMoveDown', + 'PenMoveLeft', + 'PenMoveRight', + 'PenMove', + 'PenSetColor', + 'PixolPick', + 'PropertySet', + 'Randomize', + 'RGB', + 'RoutineCall', + 'RoutineDef', + 'SectionBegin', + 'SectionEnd', + 'ShellExecute', + 'Sleep', + 'SleepAgain', + 'SoundPlay', + 'SoundStop', + 'StrAsk', + 'StrExtract', + 'StrFind', + 'StrFromAsc', + 'StrLength', + 'StrLower', + 'StrMerge', + 'StrToAsc', + 'StrUpper', + 'StrokeGetInfo', + 'StrokeGetLast', + 'StrokeLoad', + 'StrokesLoad', + 'SubTitle', + 'SubToolGetActiveIndex', + 'SubToolGetCount', + 'SubToolGetFolderIndex', + 'SubToolGetFolderName', + 'SubToolGetID', + 'SubToolGetStatus', + 'SubToolLocate', + 'SubToolSelect', + 'SubToolSetStatus', + 'TextCalcWidth', + 'Title', + 'TLDeleteKeyFrame', + 'TLGetActiveTrackIndex', + 'TLGetKeyFramesCount', + 'TLGetKeyFrameTime', + 'TLGetTime', + 'TLGotoKeyFrameTime', + 'TLGotoTime', + 'TLNewKeyFrame', + 'TLSetActiveTrackIndex', + 'TLSetKeyFrameTime', + 'ToolGetActiveIndex', + 'ToolGetCount', + 'ToolGetPath', + 'ToolGetSubToolID', + 'ToolGetSubToolsCount', + 'ToolLocateSubTool', + 'ToolSelect', + 'ToolSetPath', + 'TransformGet', + 'TransformSet', + 'TransposeGet', + 'TransposeIsShown', + 'TransposeSet', + 'Val', + 'VarAdd', + 'VarDec', + 'VarDef', + 'VarDiv', + 'VarInc', + 'VarListCopy', + 'VarLoad', + 'VarSave,VarMul', + 'VarSet', + 'VarSize', + 'VarSub', + 'Var', + 'ZBrushInfo', + 'ZBrushPriorityGet', + 'ZBrushPrioritySet', + 'ZSphereAdd', + 'ZSphereDel', + 'ZSphereEdit', + 'ZSphereGet', + 'ZSphereSet' + ) +} diff --git a/zbrush.py b/zsc/zbrush.py similarity index 100% rename from zbrush.py rename to zsc/zbrush.py diff --git a/zsc/zbursh_names.py b/zsc/zbursh_names.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/zsc/zbursh_names.py @@ -0,0 +1 @@ + From 01d91b8970f30994b1c15c3d3243cbae4e95e968 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 22 Mar 2020 18:13:40 -0700 Subject: [PATCH 04/18] you don't exist --- zsc/zbursh_names.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 zsc/zbursh_names.py diff --git a/zsc/zbursh_names.py b/zsc/zbursh_names.py deleted file mode 100644 index 8b13789..0000000 --- a/zsc/zbursh_names.py +++ /dev/null @@ -1 +0,0 @@ - From 2cac4f21eb34f6a4effbff71a5841485211fc798 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 22 Mar 2020 20:10:03 -0700 Subject: [PATCH 05/18] refactor works again --- zsc/compiler.py | 67 ++++++++++++++++++++++++++++++++++--------------- zsc/zbrush.py | 35 ++++++++++++++------------ 2 files changed, 66 insertions(+), 36 deletions(-) diff --git a/zsc/compiler.py b/zsc/compiler.py index 6a16a0d..d8e4fb6 100644 --- a/zsc/compiler.py +++ b/zsc/compiler.py @@ -1,9 +1,9 @@ -# zsc.py - import io import ast import logging -import argparse +import inspect + +from . import zbrush logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler()) @@ -14,12 +14,21 @@ VERSION = '0.1.0' +class ParseError (ValueError): + pass class Prepass(ast.NodeVisitor): + """ + This runs a prepass which collects all function calls, + so we know which ones have been defined and which ones + are zbrush-native + """ + + ALLOWED_MODULES = 'math', 'zbrush', 'zsc.zbrush', 'random' def __init__(self): - self.functions = {} - self.zbrush_mod = None + self.user_functions = {} + self.zbrush_functions = {} self.zbrush_aliases = { "sin": "SIN", "cos": "COS", @@ -40,15 +49,21 @@ def __init__(self): 'min': 'MIN', 'max': 'MAX' } - self.zbrush_function = {} + + for k, v in inspect.getmembers(zbrush): + if (not "_" in k) and callable(v): + self.zbrush_aliases[k] = k + self.zbrush_functions[k] = inspect.getfullargspec(v) def get_call_name(self, node): + if (not isinstance(node, ast.Call)): + return '', '','' try: prefix = '' name = node.func.id except: prefix = node.func.value.id - name = node.func.attr + name = node.func.attr alias = self.zbrush_aliases.get(name,'') if not alias and prefix == 'zbrush': @@ -57,25 +72,39 @@ def get_call_name(self, node): def visit_Import(self, node): - - for name in node.names: - if 'zbrush' in name.name: - zname = name.name.split(".")[-1] - self.zbrush_aliases[name.asname or name.name] = zname + """ + Raise on i + """ + for mod in node.names: + name = mod.name.split(".")[0] + if name not in self.ALLOWED_MODULES: + raise ParseError(f"Illegal import '{name}' in line {node.lineno}. Supported imports are {self.ALLOWED_MODULES}") def visit_ImportFrom(self, node): - if node.module == 'zbrush': - for name in node.names: - self.zbrush_aliases[name.asname or name.name] = name.name + if node.module not in self.ALLOWED_MODULES: + raise ParseError(f"Illegal import '{node.module}' in line {node.lineno}. Supported imports are {self.ALLOWED_MODULES}") + + for name in node.names: + if name.asname: + self.zbrush_aliases[name.asname] = name.name def visit_Call(self, node): - self.functions[self.get_call_name(node)] = node + if self.is_zbrush_function(node): + print ("found", node) + return + self.user_functions[self.get_call_name(node)] = node def is_defined(self, node): return self.get_call_name(node) in self.functions + def is_zbrush_function(self, node): + prefix, name, alias = self.get_call_name(node) + return name in self.zbrush_aliases or alias in self.zbrush_aliases + def is_user_function(self, node): + prefix, name, alias = self.get_call_name(node) + return name not in self.zbrush_aliases and name in self.functions @@ -769,11 +798,9 @@ def compile(filename, out_filename=''): prepass = Prepass() prepass.visit(tree) - import pprint - pprint.pprint (prepass.functions) - + print (prepass.user_functions) print (prepass.zbrush_aliases) - + print (prepass.zbrush_functions) analyzer = Analyzer(0, input_file=input_file) analyzer.visit(tree) diff --git a/zsc/zbrush.py b/zsc/zbrush.py index a449ce4..a43d83c 100644 --- a/zsc/zbrush.py +++ b/zsc/zbrush.py @@ -15,13 +15,17 @@ are omitted are represented by other python constructs """ -from typing import Optional, Any, NewType +from typing import Optional, Any, NewType, Callable -StrokeData = NewType() -MultipleStrokeData = NewType() -MemBlock = NewType() -ZBrushCommandList = NewType() +class StrokeData: + pass + +class MultipleStrokeData: + pass +class MemBlock: + pass + def BackColorSet(Red: int, Green: int, Blue: int) -> None: """ @@ -238,7 +242,7 @@ def FileDelete(FileName: str) -> int: pass -def FileExecute(FileName: str, MethodName: str, TextInputMem: MemBlock = None, Number: float = 0, InOutMem1: MemBlock = None, InOutMem2: MemBlock: None) -> int: +def FileExecute(FileName: str, MethodName: str, TextInputMem: MemBlock = None, Number: float = 0, InOutMem1: MemBlock = None, InOutMem2: MemBlock = None) -> int: """ Executes the specified plugin file (DLL). @@ -463,7 +467,7 @@ def HotKeyText(InterfacePath: str) -> None: #------------- interface -def IButton(ButtonText: str, PopupText: str = None, Commands: ZBrushCommandList = ..., Disabled: int = 0, Width: int = 0, Hotkey: str = '', Icon: str = '', Height: int = 0) -> None: +def IButton(ButtonText: str, PopupText: str = None, Commands: Callable = ..., Disabled: int = 0, Width: int = 0, Hotkey: str = '', Icon: str = '', Height: int = 0) -> None: """ Creates an interactive push button """ @@ -561,7 +565,7 @@ def IFadeOut(FadeOutSpeed=0.5): pass -def IFreeze(Commands: ZBrushCommandList = ..., FadeOutSpeed=0.05): +def IFreeze(Commands: Callable = ..., FadeOutSpeed=0.05): """ Disables interface updates. @@ -696,7 +700,7 @@ def IHPos(InterfaceItemPath, UseGlobalCoords=0): pass -def IKeyPress(KeyCode, Commands: ZBrushCommandList = ..., HCursor=None, VCursor=None): +def IKeyPress(KeyCode, Commands: Callable = ..., HCursor=None, VCursor=None): """ Simulates a key press @@ -922,7 +926,7 @@ def IsUnlocked(InterfaceItemPath): pass -def ISwitch(ButtonText, InitialState, PopupText, PressCommands : ZBrushCommandList = ..., UnpressedCommands : ZBrushCommandList = ..., InitiallyDisabled=0, ButtonWidth=0): +def ISwitch(ButtonText, InitialState, PopupText, PressCommands : Callable = ..., UnpressedCommands : Callable = ..., InitiallyDisabled=0, ButtonWidth=0): """ Creates an interactive switch InitialState (1=pressed, 0=unpressed), @@ -2156,8 +2160,7 @@ def VarSize(VariableName): # """ # Gets the value of a named variable # - Output: Value of the named variable - +# Output: Value of the named variable # """ # pass @@ -2211,7 +2214,7 @@ def ZBrushPrioritySet(Priority): pass -def ZSphereAdd(xPos, yPos, zPos, Radius, ParentIndex, color=0x000000, Mask=0, TimeStamp=0, Flags=0): +def ZSphereAdd(xPos: float, yPos: float, zPos: float, Radius:float , ParentIndex: int = 0, color=0x000000, Mask=0, TimeStamp=0, Flags=0) -> int: """ Adds new ZSphere to the currently active ZSpheres tool @@ -2221,7 +2224,7 @@ def ZSphereAdd(xPos, yPos, zPos, Radius, ParentIndex, color=0x000000, Mask=0, T pass -def ZSphereDel(ZSphereIndex): +def ZSphereDel(ZSphereIndex) -> int: """ Deletes a ZSphere from the currently active ZSpheres tool @@ -2230,7 +2233,7 @@ def ZSphereDel(ZSphereIndex): pass -def ZSphereEdit(ZSphereCommand, StoreUndo): +def ZSphereEdit(ZSphereCommand, StoreUndo: int = 0) -> int: """ Prepares the currently active ZSpheres tool for ZScript editing session. @@ -2277,7 +2280,7 @@ def ZSphereGet(Property, ZSphereIndex=None, SecondIndex=None): pass -def ZSphereSet(Property, ZSphereIndex=None, NewValue): +def ZSphereSet(Property, ZSphereIndex: int = 0, NewValue: int = 0) -> None: """ Sets property on the current ZSphere tool or tool at supplied index From cc95f6f3e11c0d26cf1f099338267ddd5e7b08bf Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 22 Mar 2020 20:10:18 -0700 Subject: [PATCH 06/18] launch script uses module --- .vscode/launch.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8de0aaf --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Runme", + "type": "python", + "request": "launch", + "cwd": "${workspaceFolder}", + "module": r"zsc", + "console": "integratedTerminal", + "args": [r"C:\\Users\\steve\\Desktop\\dummy.py"] + } + ] +} \ No newline at end of file From 1134882bf72edb6594d4c678bb30f9fb39651ba4 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 22 Mar 2020 22:07:12 -0700 Subject: [PATCH 07/18] move prepass to own file --- zsc/compiler.py | 106 ++------------------------------- zsc/prepass.py | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 102 deletions(-) create mode 100644 zsc/prepass.py diff --git a/zsc/compiler.py b/zsc/compiler.py index d8e4fb6..c55fab3 100644 --- a/zsc/compiler.py +++ b/zsc/compiler.py @@ -1,9 +1,9 @@ import io import ast import logging -import inspect -from . import zbrush +from .prepass import ParseError, Prepass + logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler()) @@ -14,102 +14,6 @@ VERSION = '0.1.0' -class ParseError (ValueError): - pass - -class Prepass(ast.NodeVisitor): - """ - This runs a prepass which collects all function calls, - so we know which ones have been defined and which ones - are zbrush-native - """ - - ALLOWED_MODULES = 'math', 'zbrush', 'zsc.zbrush', 'random' - - def __init__(self): - self.user_functions = {} - self.zbrush_functions = {} - self.zbrush_aliases = { - "sin": "SIN", - "cos": "COS", - "tan": "TAN", - "asin": "ASIN", - "acos": "ACOS", - "atan": "ATAN", - "atan2": "ATAN2", - "log": "LOG", - "log10": "LOG10", - "sqrt": "SQRT", - "abs": "ABS", - "random": "RAND", - "randint": "IRAND", - "bool": "BOOL", - "int": "INT", - "frac": "FRAC", - 'min': 'MIN', - 'max': 'MAX' - } - - for k, v in inspect.getmembers(zbrush): - if (not "_" in k) and callable(v): - self.zbrush_aliases[k] = k - self.zbrush_functions[k] = inspect.getfullargspec(v) - - def get_call_name(self, node): - if (not isinstance(node, ast.Call)): - return '', '','' - try: - prefix = '' - name = node.func.id - except: - prefix = node.func.value.id - name = node.func.attr - - alias = self.zbrush_aliases.get(name,'') - if not alias and prefix == 'zbrush': - alias = name - return prefix, name, alias - - - def visit_Import(self, node): - """ - Raise on i - """ - for mod in node.names: - name = mod.name.split(".")[0] - if name not in self.ALLOWED_MODULES: - raise ParseError(f"Illegal import '{name}' in line {node.lineno}. Supported imports are {self.ALLOWED_MODULES}") - - def visit_ImportFrom(self, node): - - if node.module not in self.ALLOWED_MODULES: - raise ParseError(f"Illegal import '{node.module}' in line {node.lineno}. Supported imports are {self.ALLOWED_MODULES}") - - for name in node.names: - if name.asname: - self.zbrush_aliases[name.asname] = name.name - - def visit_Call(self, node): - if self.is_zbrush_function(node): - print ("found", node) - return - self.user_functions[self.get_call_name(node)] = node - - def is_defined(self, node): - return self.get_call_name(node) in self.functions - - def is_zbrush_function(self, node): - prefix, name, alias = self.get_call_name(node) - return name in self.zbrush_aliases or alias in self.zbrush_aliases - - def is_user_function(self, node): - prefix, name, alias = self.get_call_name(node) - return name not in self.zbrush_aliases and name in self.functions - - - - - class Analyzer(ast.NodeVisitor): def __init__(self, indent=0, input_file='', context=None): self.input_file = input_file @@ -121,7 +25,7 @@ def __init__(self, indent=0, input_file='', context=None): # these are functions which we recognize in input calls and convert to their # zbrush equivalents. Not all have exact equivalents -- some zbrush # functions are represents by python syntac instead - + # todo: As written we only check these against the name of a call, ie, # it could be 'zrush.sin()` or `math.sin()` in the python side. We should # probably regularize that for consistency & readability @@ -138,7 +42,7 @@ def __init__(self, indent=0, input_file='', context=None): "log10": "LOG10", "sqrt": "SQRT", "abs": "ABS", - "random": "RAND", + "random": "RAND", "randint": "IRAND", "bool": "BOOL", "int": "INT", @@ -799,8 +703,6 @@ def compile(filename, out_filename=''): prepass.visit(tree) print (prepass.user_functions) - print (prepass.zbrush_aliases) - print (prepass.zbrush_functions) analyzer = Analyzer(0, input_file=input_file) analyzer.visit(tree) diff --git a/zsc/prepass.py b/zsc/prepass.py new file mode 100644 index 0000000..1de4f18 --- /dev/null +++ b/zsc/prepass.py @@ -0,0 +1,151 @@ +import ast +import inspect +from . import zbrush + +class ParseError (ValueError): + pass + +class Prepass(ast.NodeVisitor): + """ + This runs a prepass which collects all function calls, + so we know which ones have been defined and which ones + are zbrush-native + """ + + ALLOWED_MODULES = 'math', 'zbrush', 'zsc', 'random' + + def __init__(self): + self.user_functions = {} + self.zbrush_functions = {} + self.zbrush_aliases = { + "sin": "SIN", + "cos": "COS", + "tan": "TAN", + "asin": "ASIN", + "acos": "ACOS", + "atan": "ATAN", + "atan2": "ATAN2", + "log": "LOG", + "log10": "LOG10", + "sqrt": "SQRT", + "abs": "ABS", + "random": "RAND", + "randint": "IRAND", + "bool": "BOOL", + "int": "INT", + "frac": "FRAC", + 'min': 'MIN', + 'max': 'MAX' + } + + for k, v in inspect.getmembers(zbrush): + if (not "_" in k) and callable(v): + self.zbrush_aliases[k] = k + self.zbrush_functions[k] = inspect.getfullargspec(v) + + self.math_imports = {("sin", "cos", "tan", "asin", "acos", + "atan", "atan2", "log", "log10", "sqrt", "abs")} + self.random_imports = {('random', 'randint')} + self.intrinsics = {('max', 'min', 'len', 'abs', 'bool', 'int', 'float', 'frac')} + + def get_call_name(self, node): + if (not isinstance(node, ast.Call)): + return '', '', '' + try: + prefix = '' + name = node.func.id + except: + prefix = node.func.value.id + name = node.func.attr + + alias = self.zbrush_aliases.get(name, '') + if not alias and prefix == 'zbrush': + alias = name + return prefix, name, alias + + def visit_Import(self, node): + """ + Raise on illegal imports + """ + for mod in node.names: + print ("import", mod.name, mod.asname) + name = mod.name.split(".")[0] + if name not in self.ALLOWED_MODULES: + raise ParseError( + f"Illegal import '{name}' in line {node.lineno}. Supported imports are {self.ALLOWED_MODULES}") + + def visit_ImportFrom(self, node): + """ + Raise on illegal imports + """ + if node.module not in self.ALLOWED_MODULES: + raise ParseError( + f"Illegal import '{node.module}' in line {node.lineno}. Supported imports are {self.ALLOWED_MODULES}") + + if node.module == 'zbrush' or 'zsc.zbrush': + allowed_names = self.zbrush_functions + elif node.module == 'math': + allowed_names = self.math_imports + elif node.module == 'random': + allowed_names = self.random_imports + + for name in node.names: + print ("importfrom", name.name, name.asname) + if name.name not in allowed_names: + raise ParseError(f"Unrecognized import '{name.name}' from module '{node.module}' in line {node.lineno}") + if name.asname and node.module in ('zbrush', 'zsc.zbrush'): + self.zbrush_aliases[name.asname] = name.name + + def visit_FunctionDef(self, node): + """ + Save an argSpec for user defined functions + """ + args = [a.arg for a in node.args.args] + if node.args.kwonlyargs: + raise ParseError(f"Keyword-only arguments not supported in Zbursh: def {node.name}, line {node.lineno}") + if node.args.vararg: + raise ParseError(f"Variable arguments not supported in Zbursh: def {node.name}, line {node.lineno}") + if node.args.kw_defaults: + raise ParseError(f"keyword default arguments not supported in Zbrush: def {node.name}, line {node.lineno}") + if node.args.defaults: + raise ParseError(f"default arguments not supported in Zbrush: def {node.name}, line {node.lineno}") + if node.args.kwarg: + raise ParseError(f"keyword arguments not allowed in Zbrush user : def {node.name}, line {node.lineno}") + + self.user_functions[node.name] = inspect.FullArgSpec(args = args, varargs = None, varkw=None, defaults=None, kwonlyargs=None, kwonlydefaults=None, annotations = {}) + + def visit_Call(self, node): + + prefix, name, alias = self.get_call_name(node) + + if name in self.user_functions: + expected = len(self.user_functions[name].args) + got = len(node.args) + if got != expected: + raise ParseError(f"Function {name} requires {expected} arguments, was called with {got} in line {node.lineno}") + + if prefix in ('zbrush', 'zsc.zbrush'): + if name not in self.zbrush_functions and alias not in self.zbrush_functions: + raise ParseError(f"{alias or name} is not a Zbrush function: line {node.lineno}") + + if name in self.zbrush_functions or alias in self.zbrush_functions: + # todo : here's where we ensure that the zbrush func signature is respected + # defaults = 0 + # if self.zbrush_functions[name].defaults: + # defaults = len(self.zbrush_functions[name].defaults) + # print ("called", name, alias, len(node.args), len(self.zbrush_functions[name].args), defaults) + pass + + + def is_zbrush_function(self, node): + prefix, name, alias = self.get_call_name(node) + return name in self.zbrush_aliases or alias in self.zbrush_aliases + + def is_user_function(self, node): + prefix, name, alias = self.get_call_name(node) + return name not in self.zbrush_aliases and name in self.user_functions + + def get_zbrush_func(self, node): + prefix, name, alias = self.get_call_name(node) + + From 15049fe950a0b234ddba00f5d006b4eaa09e1c35 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 22 Mar 2020 22:47:21 -0700 Subject: [PATCH 08/18] better prepass aliasing --- zsc/compiler.py | 1 + zsc/prepass.py | 44 +++++++++++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/zsc/compiler.py b/zsc/compiler.py index c55fab3..7a78531 100644 --- a/zsc/compiler.py +++ b/zsc/compiler.py @@ -703,6 +703,7 @@ def compile(filename, out_filename=''): prepass.visit(tree) print (prepass.user_functions) + print (prepass.module_aliases) analyzer = Analyzer(0, input_file=input_file) analyzer.visit(tree) diff --git a/zsc/prepass.py b/zsc/prepass.py index 1de4f18..2681a3e 100644 --- a/zsc/prepass.py +++ b/zsc/prepass.py @@ -15,6 +15,7 @@ class Prepass(ast.NodeVisitor): ALLOWED_MODULES = 'math', 'zbrush', 'zsc', 'random' def __init__(self): + self.module_aliases = {} self.user_functions = {} self.zbrush_functions = {} self.zbrush_aliases = { @@ -58,21 +59,29 @@ def get_call_name(self, node): prefix = node.func.value.id name = node.func.attr - alias = self.zbrush_aliases.get(name, '') - if not alias and prefix == 'zbrush': - alias = name - return prefix, name, alias + return prefix, name def visit_Import(self, node): """ Raise on illegal imports """ for mod in node.names: - print ("import", mod.name, mod.asname) - name = mod.name.split(".")[0] - if name not in self.ALLOWED_MODULES: + + if '.' in mod.name: + mod_name, _, func = mod.name.rpartition(".") + check_name = mod_name + else: + mod_name = mod.asname or mod.name + func = "" + check_name = mod.name + + self.module_aliases[mod_name] = mod.name + + if check_name not in self.ALLOWED_MODULES: raise ParseError( - f"Illegal import '{name}' in line {node.lineno}. Supported imports are {self.ALLOWED_MODULES}") + f"Illegal import '{check_name}' in line {node.lineno}. Supported imports are {self.ALLOWED_MODULES}") + + def visit_ImportFrom(self, node): """ @@ -90,7 +99,7 @@ def visit_ImportFrom(self, node): allowed_names = self.random_imports for name in node.names: - print ("importfrom", name.name, name.asname) + if name.name not in allowed_names: raise ParseError(f"Unrecognized import '{name.name}' from module '{node.module}' in line {node.lineno}") if name.asname and node.module in ('zbrush', 'zsc.zbrush'): @@ -116,7 +125,7 @@ def visit_FunctionDef(self, node): def visit_Call(self, node): - prefix, name, alias = self.get_call_name(node) + prefix, name = self.get_call_name(node) if name in self.user_functions: expected = len(self.user_functions[name].args) @@ -125,10 +134,10 @@ def visit_Call(self, node): raise ParseError(f"Function {name} requires {expected} arguments, was called with {got} in line {node.lineno}") if prefix in ('zbrush', 'zsc.zbrush'): - if name not in self.zbrush_functions and alias not in self.zbrush_functions: - raise ParseError(f"{alias or name} is not a Zbrush function: line {node.lineno}") + if name not in self.zbrush_functions: + raise ParseError(f"{name} is not a Zbrush function: line {node.lineno}") - if name in self.zbrush_functions or alias in self.zbrush_functions: + if name in self.zbrush_functions: # todo : here's where we ensure that the zbrush func signature is respected # defaults = 0 # if self.zbrush_functions[name].defaults: @@ -138,14 +147,15 @@ def visit_Call(self, node): def is_zbrush_function(self, node): - prefix, name, alias = self.get_call_name(node) - return name in self.zbrush_aliases or alias in self.zbrush_aliases + prefix, name = self.get_call_name(node) + return name in self.zbrush_aliases in self.zbrush_aliases def is_user_function(self, node): - prefix, name, alias = self.get_call_name(node) + prefix, name = self.get_call_name(node) return name not in self.zbrush_aliases and name in self.user_functions def get_zbrush_func(self, node): - prefix, name, alias = self.get_call_name(node) + prefix, name = self.get_call_name(node) + return self.zbrush_aliases[name] From 74e6d13388c37b3c435ae306d69c5899ca68a10f Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 28 Mar 2020 21:39:06 -0700 Subject: [PATCH 09/18] add logging; add support for detecting what functions have returns --- zsc/prepass.py | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/zsc/prepass.py b/zsc/prepass.py index 2681a3e..a5e7d0b 100644 --- a/zsc/prepass.py +++ b/zsc/prepass.py @@ -1,6 +1,10 @@ import ast import inspect from . import zbrush +import logging +logger = logging.getLogger(__name__) +logger.addHandler(logging.StreamHandler()) +logger.setLevel(logging.DEBUG) class ParseError (ValueError): pass @@ -44,10 +48,10 @@ def __init__(self): self.zbrush_aliases[k] = k self.zbrush_functions[k] = inspect.getfullargspec(v) - self.math_imports = {("sin", "cos", "tan", "asin", "acos", - "atan", "atan2", "log", "log10", "sqrt", "abs")} - self.random_imports = {('random', 'randint')} - self.intrinsics = {('max', 'min', 'len', 'abs', 'bool', 'int', 'float', 'frac')} + self.math_imports = {"sin", "cos", "tan", "asin", "acos", + "atan", "atan2", "log", "log10", "sqrt", "abs"} + self.random_imports = {'random', 'randint'} + self.intrinsics = {'max', 'min', 'len', 'abs', 'bool', 'int', 'float', 'frac'} def get_call_name(self, node): if (not isinstance(node, ast.Call)): @@ -138,6 +142,11 @@ def visit_Call(self, node): raise ParseError(f"{name} is not a Zbrush function: line {node.lineno}") if name in self.zbrush_functions: + sig = self.zbrush_functions[name] + return_type = sig.annotations.get("return") + if return_type: + return_type = "-> " + return_type.__name__ + logger.debug (f"function signature: {sig.args} {return_type}") # todo : here's where we ensure that the zbrush func signature is respected # defaults = 0 # if self.zbrush_functions[name].defaults: @@ -148,7 +157,7 @@ def visit_Call(self, node): def is_zbrush_function(self, node): prefix, name = self.get_call_name(node) - return name in self.zbrush_aliases in self.zbrush_aliases + return name in self.zbrush_aliases def is_user_function(self, node): prefix, name = self.get_call_name(node) @@ -156,6 +165,28 @@ def is_user_function(self, node): def get_zbrush_func(self, node): prefix, name = self.get_call_name(node) - return self.zbrush_aliases[name] + return self.zbrush_aliases.get(name) + + + + def has_return_type(self, node): + + if self.is_user_function(node): + return False + + _, name = self.get_call_name(node) + + if name in self.intrinsics or name in self.math_imports or name in self.random_imports: + return True + + zfunc = self.get_zbrush_func(node) + if not zfunc: + return False + + details = self.zbrush_functions.get(zfunc) + if details: + return 'return' in details.annotations + + return False From 6afdfcfba6e770efa00b476d8573da0fb61431aa Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 28 Mar 2020 21:39:42 -0700 Subject: [PATCH 10/18] defer a bunch of work to the prepass --- zsc/compiler.py | 182 +++++++++++++++++++++--------------------------- 1 file changed, 79 insertions(+), 103 deletions(-) diff --git a/zsc/compiler.py b/zsc/compiler.py index 7a78531..0807441 100644 --- a/zsc/compiler.py +++ b/zsc/compiler.py @@ -7,7 +7,7 @@ logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler()) -logger.setLevel(logging.WARNING) +logger.setLevel(logging.DEBUG) WARN_ON_COMPARISONS = True @@ -15,12 +15,14 @@ VERSION = '0.1.0' class Analyzer(ast.NodeVisitor): - def __init__(self, indent=0, input_file='', context=None): + def __init__(self, indent=0, input_file='', context=None, prepass=None): self.input_file = input_file self.indent = indent self.context = context self.stack = [] self.defined = [] + self.prepass = prepass # an instance of the prepass visitor which collects all the function calls + # these are functions which we recognize in input calls and convert to their # zbrush equivalents. Not all have exact equivalents -- some zbrush @@ -58,6 +60,7 @@ def __init__(self, indent=0, input_file='', context=None): self.defined = self.context.defined self.funcs = self.context.funcs self.top_level_defs = self.context.top_level_defs + self.prepass = self.context.prepass def format(self): "newline separated list, with tabs" @@ -457,112 +460,105 @@ def handle_array_assign(self, node): f"[VarSet, {varname}({idx}), {self.as_literal(item)}]") def visit_Assign(self, node): + logger.debug(f"assign (line {node.lineno})") varval = (node.value) varname = (node.targets[0].id) setter = self.get_setter(varname) if isinstance(varval, ast.BinOp) and isinstance(varval.left, ast.List): + #eg, xxx = [0] * 10 self.handle_array_assign(node) return if isinstance(varval, ast.List): + # eg xxx = [1,2,3] self.handle_array_assign(node) return if type(varval) in (ast.Num, ast.Str, ast.Name): + # xxx = "A" + # xxx = 1 + # xxx = some_variable varval = self.as_literal(varval) - elif isinstance(varval, ast.UnaryOp): + self.stack.append(f'[{setter}, {varname}, {varval}]') + return + + if isinstance(varval, ast.BinOp): + # xxx = a + b , etc + parser = self.sub_parser(varval) + subval = parser.format_inline() + self.stack.append(f'[{setter}, {varname}, {subval}]') + return + + if isinstance(varval, ast.UnaryOp) and isinstance(varval.op, ast.USub): + # a = -b -> [VarSet, a, [NEG, #b]] + target = self.as_literal(varval.operand) + self.stack.append (f"[{setter}, {varname}, [NEG, {target}]]") + return + + if not isinstance(varval, ast.Call): + self.abort(f"can't parse {varval}", node) + + # below here, we know it's a function call + # that must be either a zbrush function with a return type + # a math function (like 'sin' or 'cos') + # or a memory read + + prefix, name = self.prepass.get_call_name(varval) + logger.debug(f"assign call {prefix}.{name}") + + has_return = self.prepass.has_return_type(varval) + + logger.debug(f"is zfunc with return: {has_return}") + if isinstance(varval.func, ast.Attribute): + var_root = varname.split("(")[0] + if self.context or (var_root in self.top_level_defs): + setter = 'VarSet' + else: + setter = 'VarDef' + self.top_level_defs.append(var_root) + + caller = varval.func.value.id + + is_mem_create = varval.func.attr == 'MemCreate' - if isinstance(varval.op, ast.USub): - target = self.as_literal(varval.operand) - self.stack.append (f"[{setter}, {varname}, [NEG, {target}]]") + if is_mem_create: + arg_parser = self.sub_parser(*varval.args) + arg_string = arg_parser.format_inline() + if arg_string: + arg_string = ", " + arg_string + + self.stack.append(f'[MemCreate, {varname}{arg_string}]') return - elif isinstance(varval, ast.Call): - # odo - refactor this out - - if isinstance(varval.func, ast.Attribute): - var_root = varname.split("(")[0] - if self.context or (var_root in self.top_level_defs): - setter = 'VarSet' - else: - setter = 'VarDef' - self.top_level_defs.append(var_root) - - caller = ", " + varval.func.value.id - - is_mem_create = varval.func.attr == 'MemCreate' - - if is_mem_create: - arg_parser = self.sub_parser(*varval.args) - arg_string = arg_parser.format_inline() - if arg_string: - arg_string = ", " + arg_string - - self.stack.append(f'[MemCreate, {varname}{arg_string}]') - return - - allowed_funcs = ( - "read_", - ) - - is_allowed = False - for f in allowed_funcs: - is_allowed = is_allowed or f in varval.func.attr - - if (not is_allowed and varval.func.attr not in self.funcs): - self.abort( - "can only call zbrush functions or memory block functions in an assignment", node) - - if varval.func.attr in self.funcs: - m_name = self.funcs[varval.func.attr] - typecode = None - caller = "" - else: - # it's a memory object functon - m_name, typecode = self.format_mem_op(varval.func.attr) - if not m_name: - self.abort( - f"Unrecognized memory operation {varval.func.attr}", node) - - arg_parse = self.sub_parser(*varval.args, func=True) + arg_parse = self.sub_parser(*varval.args, func=True) + if not has_return: + + mem_name, mem_op = self.format_mem_op(name) + if not mem_name: + self.abort("can only call zbrush functions or memory block functions in an assignment", node) + args = arg_parse.stack - if typecode: - args.insert(1, typecode) + if mem_op: + args.insert(1, mem_op) tail = '' if args: tail = ", ".join(args) tail = ", " + tail - self.stack.append( - f'[{setter}, {varname}, [{m_name}{caller}{tail}]]') + self.stack.append(f'[{setter}, {varname}, [{mem_name}, {caller}{tail}]]') return else: - if varval.func.id == "len": - # is a pound sign needed here? - self.stack.append( - f"[VarSet, {varname}, [VarSize, {varval.args[0].id}]]") - return - elif varval.func.id in self.funcs: - args = [str(self.as_literal(v)) for v in varval.args] - if args: - args = ", ".join(args) - - func_name = self.funcs.get(varval.func.id) - self.stack.append(f'[{setter}, {varname}, [{func_name}, {args}]]') - return - self.abort("Can't assign a function call in ZBrush", varval) - - elif isinstance(varval, ast.BinOp): - parser = self.sub_parser(varval) - varval = parser.format_inline() + _, funcname = self.prepass.get_call_name(varval) + z_name = self.prepass.zbrush_aliases[funcname] + args = arg_parse.stack + tail = '' + if args: + tail = ", ".join(args) + tail = ", " + tail + self.stack.append(f'[{setter}, {varname}, [{z_name}{tail}]]') + - if not self.context and varval not in self.defined: - setter = f'[VarDef, {varname}, {varval}]' - self.defined.append(varname) - else: - setter = f"[VarSet, {varname}, {varval}]" - self.stack.append(setter) def visit_Lt(self, node): self.stack.append(" < ") @@ -634,26 +630,6 @@ def visit_While(self, node): finally: self.indent += 1 - # def visit_With(self, node): - # if len(node.items) != 1: - # self.abort("Cannot parts multiple context manager aliases", node) - # try: - # name = node.items[0].context_expr.func.id - # except: - # name = node.items[0].context_expr.func.attr - # var_holder = None - # if node.items[0].optional_vars: - # var_holder = node.items[0].optional_vars.id - # # todo: how to use this? - # setter = self.get_setter(var_holder) - # pathname = self.as_literal(name) - # self.stack.append(f"[{setter}, {pathname}]") - # self.stack.append(f"[{name},") - - # body_parser = self.sub_parser(*node.body) - # self.stack.append(body_parser.format()) - # self.stack.append(f"] // end {name}") - @@ -705,7 +681,7 @@ def compile(filename, out_filename=''): print (prepass.user_functions) print (prepass.module_aliases) - analyzer = Analyzer(0, input_file=input_file) + analyzer = Analyzer(0, input_file=input_file, prepass=prepass) analyzer.visit(tree) out_filename = out_filename or filename.replace('.py', '.txt') From dce8fed777abf5fdcc9ac32cbb736f1276fe2f6a Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 28 Mar 2020 21:40:00 -0700 Subject: [PATCH 11/18] more function annotations --- zsc/zbrush.py | 160 +++++++++++++++++++++++++------------------------- 1 file changed, 81 insertions(+), 79 deletions(-) diff --git a/zsc/zbrush.py b/zsc/zbrush.py index a43d83c..e2559b8 100644 --- a/zsc/zbrush.py +++ b/zsc/zbrush.py @@ -17,15 +17,18 @@ """ from typing import Optional, Any, NewType, Callable + class StrokeData: pass + class MultipleStrokeData: pass + class MemBlock: pass - + def BackColorSet(Red: int, Green: int, Blue: int) -> None: """ @@ -926,7 +929,7 @@ def IsUnlocked(InterfaceItemPath): pass -def ISwitch(ButtonText, InitialState, PopupText, PressCommands : Callable = ..., UnpressedCommands : Callable = ..., InitiallyDisabled=0, ButtonWidth=0): +def ISwitch(ButtonText, InitialState, PopupText, PressCommands: Callable = ..., UnpressedCommands: Callable = ..., InitiallyDisabled=0, ButtonWidth=0): """ Creates an interactive switch InitialState (1=pressed, 0=unpressed), @@ -984,7 +987,7 @@ def IWidth(InterfaceItemPath): pass -def MemCopy(FromBlock, FromOffset, ToBlock, ToOffset, NumBytes=None): +def MemCopy(FromBlock, FromOffset, ToBlock, ToOffset, NumBytes=None) -> int: """ Copies data from one memory block into another. if NumBytes is supplied, limit to that number of b @@ -995,19 +998,18 @@ def MemCopy(FromBlock, FromOffset, ToBlock, ToOffset, NumBytes=None): pass -def MemCreate(BlockID, BlockSize, InitialFill=None): +def MemCreate(BlockID, BlockSize, InitialFill=None) -> int: """ Creates a new memory block. If InitialFill is supplied, fil with that value Output: Returns the size of the new memory block or error code...0=Error -1=Memory already exists -2=Can't create memory block. - """ pass -def MemCreateFromFile(BlockId, FileName, FileOffset=0, MaxBytes=None): +def MemCreateFromFile(BlockId, FileName, FileOffset=0, MaxBytes=None) -> int: """ Creates a new memory block from a disk file. if FileOffset is supplied, read fron that byte @@ -1020,7 +1022,7 @@ def MemCreateFromFile(BlockId, FileName, FileOffset=0, MaxBytes=None): pass -def MemDelete(BlockID): +def MemDelete(BlockID) -> int: """ Deletes a memory block. @@ -1030,7 +1032,7 @@ def MemDelete(BlockID): pass -def MemGetSize(BlockID): +def MemGetSize(BlockID) -> int: """ Returns the size of a memory block (Also useful for determining if a memory block already exists @@ -1040,7 +1042,7 @@ def MemGetSize(BlockID): pass -def MemMove(BlockID, FromOffset, ToOffset, NumBytes): +def MemMove(BlockID, FromOffset, ToOffset, NumBytes) -> int: """ Move data within an existing memory block. @@ -1050,7 +1052,7 @@ def MemMove(BlockID, FromOffset, ToOffset, NumBytes): pass -def MemMultiWrite(BlockID, Value, Format, Offset, RepeatCount, SubsequentOffset): +def MemMultiWrite(BlockID, Value, Format, Offset, RepeatCount, SubsequentOffset) -> int: """ Write data to a memory block. Format: (0=omited=float, 1=signed char, 2=unsigned char, 3=signed short, 4=unsigned short, 5=signed long, 6=unsigned long, 7=fixed16 (16.16) @@ -1060,7 +1062,7 @@ def MemMultiWrite(BlockID, Value, Format, Offset, RepeatCount, SubsequentOffset) pass -def MemRead(BlockID, ReadVariable, Format, Offset): +def MemRead(BlockID, ReadVariable, Format, Offset) -> int: """ Reads data from a memory block. Data format (0=omited=float, 1=signed char, 2=unsigned char, 3=signed short, 4=unsigned short, 5=signed long, 6=unsigned long, 7=fixed16 (16.16) @@ -1071,7 +1073,7 @@ def MemRead(BlockID, ReadVariable, Format, Offset): pass -def MemReadString(BlockID, StringVar, Offset, BreakAtLineEnd=0, SkipWhiteSpace=0, MaxReadLength=255): +def MemReadString(BlockID, StringVar, Offset, BreakAtLineEnd=0, SkipWhiteSpace=0, MaxReadLength=255) -> int: """ Reads a string from a memory block. @@ -1081,7 +1083,7 @@ def MemReadString(BlockID, StringVar, Offset, BreakAtLineEnd=0, SkipWhiteSpace=0 pass -def MemResize(BlockID, NewSize, FillValue=None): +def MemResize(BlockID, NewSize, FillValue=None) -> int: """ Resizes an exsiting memory block. if FillValue supplied, use it to fill newly allocated memory @@ -1092,7 +1094,7 @@ def MemResize(BlockID, NewSize, FillValue=None): pass -def MemSaveToFile(BlockID, FileName, OverwriteIfExists=0): +def MemSaveToFile(BlockID, FileName, OverwriteIfExists=0) -> int: """ Saves an exisiting memory block to a disk file. Overwrite if exists? Set to nonzero value to save the file even if an identically named file already exists on disk. Default=Do not overwrite. @@ -1103,7 +1105,7 @@ def MemSaveToFile(BlockID, FileName, OverwriteIfExists=0): pass -def MemWrite(BlockID, Value, Format=0, Offset=0): +def MemWrite(BlockID, Value, Format=0, Offset=0) -> int: """ Write data to a memory block. Format (0=omited=float, 1=signed char, 2=unsigned char, 3=signed short, 4=unsigned short, 5=signed long, 6=unsigned long, 7=fixed16 (16.16) @@ -1114,7 +1116,7 @@ def MemWrite(BlockID, Value, Format=0, Offset=0): pass -def MemWriteString(BlockID, Value, Offset=0, WriteNullTerminator=1): +def MemWriteString(BlockID, Value, Offset=0, WriteNullTerminator=1) -> int: """ Writes a string into a memory block. @@ -1131,7 +1133,7 @@ def MergeUndo(): pass -def Mesh3DGet(Property, IndexInput, OptionalInput, OptionalOutput1=None, OptionalOutput2=None, OptionalOutput3=None, OptionalOutput4=None, OptionalOutput5=None, OptionalOutput6=None, OptionalOutput7=None, OptionalOutput8=None): +def Mesh3DGet(Property, IndexInput, OptionalInput, OptionalOutput1=None, OptionalOutput2=None, OptionalOutput3=None, OptionalOutput4=None, OptionalOutput5=None, OptionalOutput6=None, OptionalOutput7=None, OptionalOutput8=None) -> int: """ Gets information about the currently active Mesh3D tool. Property: 0=PointsCount, 1=FacesCount, 2=XYZ bounds, 3=UVBounds, 4=1stUVTile, 5=NxtUVTile, 6=PolysInUVTile, 7=3DAreaOfUVTile, 8=Full3DMeshArea @@ -1152,7 +1154,7 @@ def MessageOK(Message, Title): pass -def MessageOKCancel(Message, Title): +def MessageOKCancel(Message, Title) -> int: """ Displays a user message with CANCEL and OK buttons @@ -1162,7 +1164,7 @@ def MessageOKCancel(Message, Title): pass -def MessageYesNo(Message, Title): +def MessageYesNo(Message, Title) -> int: """ Displays a user message with YES and NO buttons @@ -1172,7 +1174,7 @@ def MessageYesNo(Message, Title): pass -def MessageYesNoCancel(Message, Title): +def MessageYesNoCancel(Message, Title) -> int: """ Displays a user message with YES, NO and CANCEL buttons @@ -1182,7 +1184,7 @@ def MessageYesNoCancel(Message, Title): pass -def MouseHPos(UseGlobalCoordinates=0): +def MouseHPos(UseGlobalCoordinates=0) -> int: """ Returns the current H position of the mouse in Canvas or Global coordinates. @@ -1192,7 +1194,7 @@ def MouseHPos(UseGlobalCoordinates=0): pass -def MouseLButton(): +def MouseLButton() -> int: """ Returns the current state of the left mouse button @@ -1202,7 +1204,7 @@ def MouseLButton(): pass -def MouseVPos(UseGlobalCoordinates=0): +def MouseVPos(UseGlobalCoordinates=0) -> int: """ Returns the current V position of the mouse in Canvas or Global coordinates. @@ -1228,7 +1230,7 @@ def MTransformSet(BlockID, VariableIndex=0): pass -def MVarDef(BlockID, Count, InitialFill=0): +def MVarDef(BlockID, Count, InitialFill=0) -> int: """ pass @@ -1240,7 +1242,7 @@ def MVarDef(BlockID, Count, InitialFill=0): pass -def MVarGet(BlockID, VariableIndex): +def MVarGet(BlockID, VariableIndex) -> float: """ Reads a float value from a memory block. @@ -1260,7 +1262,7 @@ def MVarSet(BlockID, VariableIndex): pass -def NormalMapCreate(ImageWidth, ImageHeight, Smooth=1, SubPoly=0, Border=0, UVTile=None, UseTangentCoords=0): +def NormalMapCreate(ImageWidth, ImageHeight, Smooth=1, SubPoly=0, Border=0, UVTile=None, UseTangentCoords=0) -> int: """ Creates NormalMap @@ -1270,7 +1272,7 @@ def NormalMapCreate(ImageWidth, ImageHeight, Smooth=1, SubPoly=0, Border=0, UVTi pass -def Note(Text, InterfaceItemPath=None, DisplayDuration=0, PopupBackgroundColor=0x606060, OffsetDistance=48, Width=400, WindowFillColor=None, FrameHorizontalSize=1, FrameVerticalSize=1, FrameLeft=0, FrameTop=0, IconFileName=None): +def Note(Text, InterfaceItemPath=None, DisplayDuration=0, PopupBackgroundColor=0x606060, OffsetDistance=48, Width=400, WindowFillColor=None, FrameHorizontalSize=1, FrameVerticalSize=1, FrameLeft=0, FrameTop=0, IconFileName=None) -> int: """ Displays a note to the user. @@ -1305,7 +1307,7 @@ def NoteIButton(ButtonText, ButtonIcon=None, InitiallyPressed=0, InitiallyDisabl pass -def NoteIGet(NoteButtonIndexOrName): +def NoteIGet(NoteButtonIndexOrName) -> int: """ Returns the value of am NoteIButton which was shown in the last displayed Note. @@ -1429,7 +1431,7 @@ def PenSetColor(Red, Green, Blue): pass -def PixolPick(ComponentIndex, HPosition, VPosition): +def PixolPick(ComponentIndex, HPosition, VPosition) -> int: """ Retrieves information about a specified Pixol componentIndex: 0=CompositeColor ( 0x000000<->0xffffff or red*65536+green*256+blue) 1=Z(-32576 to 32576) 2=Red(0 to 255 ) 3=Green(0 to 255 ) 4=Blue(0 to 255 ) 5=MaterialIndex(0 to 255 ) 6=XNormal(-1 to 1) 7=YNormal(-1 to 1) 8=ZNormal(-1 to 0) @@ -1529,7 +1531,7 @@ def SleepAgain(Time, EventType): pass -def SoundPlay(BlockID, PlayMode): +def SoundPlay(BlockID, PlayMode) -> int: """ Plays the sounds loaded into a specified memory block. PlayMode: . 0=default=Play once, dont wait for completion. 1=Play once, wait for completion. 2=Play loop, dont wait for completion.): @@ -1541,7 +1543,7 @@ def SoundPlay(BlockID, PlayMode): pass -def SoundStop(BlockId): +def SoundStop(BlockId) -> int: """ Stops the currently specified sound. @@ -1554,7 +1556,7 @@ def SoundStop(BlockId): #----- strings -def StrAsk(InitialString, Title=""): +def StrAsk(InitialString, Title="") -> str: """ Asks user to input a string. @@ -1564,7 +1566,7 @@ def StrAsk(InitialString, Title=""): pass -def StrExtract(InputString, StartCharacterIndex, EndCharacterIndex): +def StrExtract(InputString, StartCharacterIndex, EndCharacterIndex) -> str: """ Returns specified portion of the input string @@ -1574,7 +1576,7 @@ def StrExtract(InputString, StartCharacterIndex, EndCharacterIndex): pass -def StrFind(FindStr, InStr, StartIndex=0): +def StrFind(FindStr, InStr, StartIndex=0) -> int: """ Locate a string within a string. @@ -1584,7 +1586,7 @@ def StrFind(FindStr, InStr, StartIndex=0): pass -def StrFromAsc(CharacterNum): +def StrFromAsc(CharacterNum) -> str: """ Returns the character of the specified Ascii value. @@ -1594,7 +1596,7 @@ def StrFromAsc(CharacterNum): pass -def StrLength(InputStr): +def StrLength(InputStr) -> int: """ Returns the number of characters in the input string. @@ -1604,7 +1606,7 @@ def StrLength(InputStr): pass -def StrLower(InputString): +def StrLower(InputString) -> str: """ Returns the lowercase version of the input string. @@ -1614,7 +1616,7 @@ def StrLower(InputString): pass -def StrMerge(Str1, Str2, Str3="", Str4="", Str5="", Str6="", Str7="", Str8="", Str9="", Str10="", Str11="", Str12=""): +def StrMerge(Str1, Str2, Str3="", Str4="", Str5="", Str6="", Str7="", Str8="", Str9="", Str10="", Str11="", Str12="") -> str: """ Combines two (or more) strings into one string. @@ -1624,7 +1626,7 @@ def StrMerge(Str1, Str2, Str3="", Str4="", Str5="", Str6="", Str7="", Str8="", S pass -def StrToAsc(InputString, Offset=0): +def StrToAsc(InputString, Offset=0) -> int: """ Returns the Ascii value of a character. @@ -1634,7 +1636,7 @@ def StrToAsc(InputString, Offset=0): pass -def StrUpper(InputStr): +def StrUpper(InputStr) -> str: """ Returns the uppercase version of the input string. @@ -1644,7 +1646,7 @@ def StrUpper(InputStr): pass -def StrokeGetInfo(StrokeVariable, InfoNumber, PointIndex): +def StrokeGetInfo(StrokeVariable, InfoNumber, PointIndex) -> object: """ Retrieves the information from a specified Stroke-type Variable @@ -1656,7 +1658,7 @@ def StrokeGetInfo(StrokeVariable, InfoNumber, PointIndex): # stroke ------------- -def StrokeGetLast(): +def StrokeGetLast() -> object: """ Retrieves the last drawn brush stroke @@ -1666,7 +1668,7 @@ def StrokeGetLast(): pass -def StrokeLoad(FileName): +def StrokeLoad(FileName) -> object: """ Loads a brush-stroke text file @@ -1676,7 +1678,7 @@ def StrokeLoad(FileName): pass -def StrokesLoad(FileName): +def StrokesLoad(FileName) -> object: """ Loads a brush-strokes text file @@ -1694,7 +1696,7 @@ def SubTitle(Text): pass -def SubToolGetActiveIndex(): +def SubToolGetActiveIndex() -> int: """ Returns the index of the active subtool @@ -1704,7 +1706,7 @@ def SubToolGetActiveIndex(): pass -def SubToolGetCount(): +def SubToolGetCount() -> int: """ Returns the number of subtools in the active tool @@ -1714,7 +1716,7 @@ def SubToolGetCount(): pass -def SubToolGetFolderIndex(SubtoolIndex): +def SubToolGetFolderIndex(SubtoolIndex) -> int: """ Returns the folder index in which this subtool is contained If SubtoolIndex omited then use the currently selected tool. @@ -1727,7 +1729,7 @@ def SubToolGetFolderIndex(SubtoolIndex): pass -def SubToolGetFolderName(SubtoolIndex): +def SubToolGetFolderName(SubtoolIndex) -> str: """ Returns the ffolder name of the specified subtool If SubtoolIndex omited then use the currently selected tool. @@ -1738,7 +1740,7 @@ def SubToolGetFolderName(SubtoolIndex): pass -def SubToolGetID(SubtoolIndex): +def SubToolGetID(SubtoolIndex) -> int: """ Returns the unique subtool ID If SubtoolIndex omited then use the currently selected tool. @@ -1749,7 +1751,7 @@ def SubToolGetID(SubtoolIndex): pass -def SubToolGetStatus(SubtoolIndex): +def SubToolGetStatus(SubtoolIndex) -> int: """ Returns the status of a subtool If SubtoolIndex omited then use the currently selected tool. @@ -1760,7 +1762,7 @@ def SubToolGetStatus(SubtoolIndex): pass -def SubToolLocate(SubtoolID): +def SubToolLocate(SubtoolID) -> int: """ Locates a subtool by the specified unique ID @@ -1770,7 +1772,7 @@ def SubToolLocate(SubtoolID): pass -def SubToolSelect(SubtoolIndex): +def SubToolSelect(SubtoolIndex) -> int: """ Selects the specified subtool index @@ -1780,7 +1782,7 @@ def SubToolSelect(SubtoolIndex): pass -def SubToolSetStatus(SubtoolIndex, Value): +def SubToolSetStatus(SubtoolIndex, Value) -> int: """ Sets the status of a subtool If SubtoolIndex omited then use the currently selected tool. @@ -1789,7 +1791,7 @@ def SubToolSetStatus(SubtoolIndex, Value): pass -def TextCalcWidth(Text): +def TextCalcWidth(Text) -> int: """ Calculates the pixel-width of the specified string @@ -1807,7 +1809,7 @@ def Title(Text): pass -def TLDeleteKeyFrame(KeyIndex): +def TLDeleteKeyFrame(KeyIndex) -> int: """ Delete specified key frame index of the active track @@ -1817,7 +1819,7 @@ def TLDeleteKeyFrame(KeyIndex): pass -def TLGetActiveTrackIndex(): +def TLGetActiveTrackIndex() -> int: """ Returns the index of the active track @@ -1827,7 +1829,7 @@ def TLGetActiveTrackIndex(): pass -def TLGetKeyFramesCount(): +def TLGetKeyFramesCount() -> int: """ Returns the total number of key frames in the active track @@ -1837,7 +1839,7 @@ def TLGetKeyFramesCount(): pass -def TLGetKeyFrameTime(KeyIndex): +def TLGetKeyFrameTime(KeyIndex) -> int: """ Get the time of the specified key frame index of the active track @@ -1847,7 +1849,7 @@ def TLGetKeyFrameTime(KeyIndex): pass -def TLGetTime(): +def TLGetTime() -> float: """ Returns the current TimeLine knob position in 0.0 to 1.0 range @@ -1857,7 +1859,7 @@ def TLGetTime(): pass -def TLGotoKeyFrameTime(KeyIndex): +def TLGotoKeyFrameTime(KeyIndex) -> float: """ Move TimeLine knob position to specified key frame index of the active track @@ -1867,7 +1869,7 @@ def TLGotoKeyFrameTime(KeyIndex): pass -def TLGotoTime(Time): +def TLGotoTime(Time) -> int: """ Sets the current TimeLine knob position in 0.0 to 1.0 range @@ -1877,7 +1879,7 @@ def TLGotoTime(Time): pass -def TLNewKeyFrame(Time=None): +def TLNewKeyFrame(Time=None) -> int: """ Create a new key frame in the active track if Time is omitted, use current time @@ -1888,7 +1890,7 @@ def TLNewKeyFrame(Time=None): pass -def TLSetActiveTrackIndex(TrackIndex): +def TLSetActiveTrackIndex(TrackIndex) -> int: """ Sets the active track index @@ -1898,7 +1900,7 @@ def TLSetActiveTrackIndex(TrackIndex): pass -def TLSetKeyFrameTime(KeyIndex, Time): +def TLSetKeyFrameTime(KeyIndex, Time) -> int: """ Set the time of the specified key frame index of the active track Time is 0-1 @@ -1909,7 +1911,7 @@ def TLSetKeyFrameTime(KeyIndex, Time): pass -def ToolGetActiveIndex(): +def ToolGetActiveIndex() -> int: """ Returns the index of the active tool @@ -1919,7 +1921,7 @@ def ToolGetActiveIndex(): pass -def ToolGetCount(): +def ToolGetCount() -> int: """ Returns the number of available tools @@ -1929,7 +1931,7 @@ def ToolGetCount(): pass -def ToolGetPath(ToolIndex=None): +def ToolGetPath(ToolIndex: int = None) -> int: """ Returns the file path or name of the specified tool If ToolIndex is omited then use the currently selected tool. @@ -1940,7 +1942,7 @@ def ToolGetPath(ToolIndex=None): pass -def ToolGetSubToolID(ToolIndex=None, SubtoolIndex=None): +def ToolGetSubToolID(ToolIndex: int = None, SubtoolIndex: int = None) -> int: """ Returns the unique subtool ID If ToolIndex is omited then use the currently selected tool. @@ -1952,7 +1954,7 @@ def ToolGetSubToolID(ToolIndex=None, SubtoolIndex=None): pass -def ToolGetSubToolsCount(ToolIndex): +def ToolGetSubToolsCount(ToolIndex: int) -> int: """ Returns the number of subtools in the specified tool index If ToolIndex is omited then use the currently selected tool. @@ -1963,7 +1965,7 @@ def ToolGetSubToolsCount(ToolIndex): pass -def ToolLocateSubTool(SubToolID, SubtoolIndex=None): +def ToolLocateSubTool(SubToolID: int, SubtoolIndex: int = None) -> int: """ Locates a subtool by the specified unique ID @@ -1972,7 +1974,7 @@ def ToolLocateSubTool(SubToolID, SubtoolIndex=None): pass -def ToolSelect(SubToolIndex): +def ToolSelect(SubToolIndex: int) -> int: """ Selects the specified tool index @@ -1982,7 +1984,7 @@ def ToolSelect(SubToolIndex): pass -def ToolSetPath(SubToolIndex=None, NewPath=""): +def ToolSetPath(SubToolIndex: int = None, NewPath: str = "") -> int: """ Sets the file path or name of the specified tool If Tool Index omited then use the currently selected tool. @@ -2002,7 +2004,7 @@ def TransformGet(xPos, yPos, zPos, xScale, yScale, zScale, xRotate, yRotate, zRo pass -def TransformSet(xPos, yPos, zPos, xScale, yScale, zScale, xRotate, yRotate, zRotate): +def TransformSet(xPos: float, yPos: float, zPos: float, xScale: float, yScale: float, zScale: float, xRotate: float, yRotate: float, zRotate: float): """ Sets new transformation values. @@ -2010,7 +2012,7 @@ def TransformSet(xPos, yPos, zPos, xScale, yScale, zScale, xRotate, yRotate, zRo pass -def TransposeGet(StarXPos, StartYPos, StartZPos, EndXPos, EndYPos, EndZPos, LineLength, RedAxisX, RedAxisY, RedAxisZ, GreenAxisX, GreenAxisY, GreenAxisZ, BlueAxisX, BlueAxisY, BlueAxisZ): +def TransposeGet(StarXPos: int, StartYPos: int, StartZPos: int, EndXPos: int, EndYPos: int, EndZPos: int, LineLength: int, RedAxisX: int, RedAxisY: int, RedAxisZ: int, GreenAxisX: int, GreenAxisY: int, GreenAxisZ: int, BlueAxisX: int, BlueAxisY: int, BlueAxisZ: int): """ Gets current Transpose Action Line values. @@ -2136,7 +2138,7 @@ def VarSave(VariableName, FileName): # handled via len() for arrays (TODO: has to distinguish between strings and arrays!) -def VarSize(VariableName): +def VarSize(VariableName: str) -> int: """ Returns the number of items in a variable or in a list @@ -2165,7 +2167,7 @@ def VarSize(VariableName): # pass -def ZBrushInfo(InfoType): +def ZBrushInfo(InfoType: int) -> int: """ Integer type code: @@ -2192,7 +2194,7 @@ def ZBrushInfo(InfoType): """ -def ZBrushPriorityGet(): +def ZBrushPriorityGet() -> int: """ Returns the task-priority of ZBrush. @@ -2201,7 +2203,7 @@ def ZBrushPriorityGet(): pass -def ZBrushPrioritySet(Priority): +def ZBrushPrioritySet(Priority: int) -> int: """ Sets the task-priority of ZBrush. -2: Low @@ -2214,7 +2216,7 @@ def ZBrushPrioritySet(Priority): pass -def ZSphereAdd(xPos: float, yPos: float, zPos: float, Radius:float , ParentIndex: int = 0, color=0x000000, Mask=0, TimeStamp=0, Flags=0) -> int: +def ZSphereAdd(xPos: float, yPos: float, zPos: float, Radius: float, ParentIndex: int = 0, color=0x000000, Mask=0, TimeStamp=0, Flags=0) -> int: """ Adds new ZSphere to the currently active ZSpheres tool From 4b9e100a611f70f35d160dc91b083c6aa6047efb Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 4 Apr 2020 10:38:00 -0700 Subject: [PATCH 12/18] fix indenting on while loops prefer # in variables inside func call --- zsc/compiler.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/zsc/compiler.py b/zsc/compiler.py index 0807441..14cb821 100644 --- a/zsc/compiler.py +++ b/zsc/compiler.py @@ -119,6 +119,8 @@ def visit_For(self, node): self.stack.append("") self.stack.append(f'[Loop, {loop_max},') loop_parser = self.sub_parser(*node.body) + if self.context: + loop_parser.indent += 1 # loop body self.stack.append(loop_parser.format()) @@ -622,13 +624,16 @@ def visit_While(self, node): ctx=ast.Load(), args=[ast.Num(n=65534)]), body=body_block ) - try: - self.indent -= 1 - subp = self.sub_parser(new_node) + #try: + #self.indent -= 1 + subp = self.sub_parser(new_node) + if not self.context: subp.indent -= 1 - self.stack.append(subp.format()) - finally: - self.indent += 1 + #if not self.context: + # subp.indent -= 1 + self.stack.append(subp.format()) + #finally: + #self.indent += 1 @@ -665,7 +670,7 @@ class FunctionAnalyzer(Analyzer): def visit_Name(self, node): # Q - should this use the # prefix? - self.stack.append(node.id) + self.stack.append(f"#{node.id}") def compile(filename, out_filename=''): From 0d3a04aa74a5f824b5f87aa3ef13dbaa1100395c Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 4 Apr 2020 22:57:44 -0700 Subject: [PATCH 13/18] pass names in arguments to support passing funcions as arguments --- zsc/prepass.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zsc/prepass.py b/zsc/prepass.py index a5e7d0b..c2f9a1d 100644 --- a/zsc/prepass.py +++ b/zsc/prepass.py @@ -54,8 +54,10 @@ def __init__(self): self.intrinsics = {'max', 'min', 'len', 'abs', 'bool', 'int', 'float', 'frac'} def get_call_name(self, node): + if (not isinstance(node, ast.Call)): - return '', '', '' + if hasattr(node, 'id'): + return '', node.id try: prefix = '' name = node.func.id From f745318c2987ee2dfdaf58fde4e09dec67febf71 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 4 Apr 2020 22:58:16 -0700 Subject: [PATCH 14/18] support for passing functions as callbacks --- zsc/compiler.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/zsc/compiler.py b/zsc/compiler.py index 14cb821..00d8ba9 100644 --- a/zsc/compiler.py +++ b/zsc/compiler.py @@ -654,7 +654,7 @@ def abort(self, message, node): def sub_parser(self, *args, **kwargs): if kwargs.get('func'): - sub_parser = FunctionAnalyzer(context=self) + sub_parser = FunctionAnalyzer(context=self, prepass=self.prepass) else: sub_parser = Analyzer(context=self) sub_parser.indent += 1 @@ -669,8 +669,21 @@ class FunctionAnalyzer(Analyzer): """ def visit_Name(self, node): - # Q - should this use the # prefix? - self.stack.append(f"#{node.id}") + if self.prepass.is_user_function(node): + # for UI Elements or other items that expect command lists, + # python code should make a no-arg function and pass it + # directly, ie, + # + # def test(): + # zbrush.Note("OK") + # + # zBrush.IButton("Test", "", test) + + self.stack.append(f"[RoutineCall, {node.id}]") + else: + # for variables passed as arguments to a function, use the # prefix + self.stack.append(f"#{node.id}") + def compile(filename, out_filename=''): From d07cc06cdb0490aa6bac4ddedcec36f516c6bb0b Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 18 Apr 2020 22:50:35 -0700 Subject: [PATCH 15/18] add 'get signature' --- zsc/prepass.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/zsc/prepass.py b/zsc/prepass.py index c2f9a1d..37b59a1 100644 --- a/zsc/prepass.py +++ b/zsc/prepass.py @@ -54,7 +54,10 @@ def __init__(self): self.intrinsics = {'max', 'min', 'len', 'abs', 'bool', 'int', 'float', 'frac'} def get_call_name(self, node): - + """ + returns the prefix and the string name of the function + represented by + """ if (not isinstance(node, ast.Call)): if hasattr(node, 'id'): return '', node.id @@ -169,9 +172,6 @@ def get_zbrush_func(self, node): prefix, name = self.get_call_name(node) return self.zbrush_aliases.get(name) - - - def has_return_type(self, node): if self.is_user_function(node): @@ -192,3 +192,7 @@ def has_return_type(self, node): return False + + def get_signature(self, funcname): + sig = self.zbrush_functions[funcname] + return sig \ No newline at end of file From f9a1902f943e6bd469853448101e567634eaf834 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 18 Apr 2020 22:50:57 -0700 Subject: [PATCH 16/18] Dount use [NEG] for literal assignments --- zsc/compiler.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/zsc/compiler.py b/zsc/compiler.py index 00d8ba9..ffd8b9b 100644 --- a/zsc/compiler.py +++ b/zsc/compiler.py @@ -345,6 +345,8 @@ def visit_Call(self, node): # it's a zbrush function. de-alias in possible and return if func_name in self.funcs: func_name = self.funcs.get(node.func.id, func_name) + sig = self.prepass.get_signature(func_name) + logger.info(f'zbrush signature for {func_name}:\n\t {sig}') func_string = f'[{func_name}{arg_string}]' self.stack.append(func_string) @@ -494,9 +496,14 @@ def visit_Assign(self, node): return if isinstance(varval, ast.UnaryOp) and isinstance(varval.op, ast.USub): - # a = -b -> [VarSet, a, [NEG, #b]] target = self.as_literal(varval.operand) - self.stack.append (f"[{setter}, {varname}, [NEG, {target}]]") + if isinstance(varval.operand, ast.Num): + # a = -1 -> [VarSet, a, -1] + self.stack.append (f"[{setter}, {varname}, -{target}]") + else: + # a = -b -> [VarSet, a, [NEG, #b]] + target = self.as_literal(varval.operand) + self.stack.append (f"[{setter}, {varname}, [NEG, {target}]]") return if not isinstance(varval, ast.Call): @@ -608,7 +615,11 @@ def visit_Expr(self, node): self.stack.append(comp) def visit_While(self, node): - + # ZScript does not support a while loop. So, we + # loop a finite number of times checking the exit + # condifition. + # Todo: rather than using int16 (65535) as the + # loop, allow user to set it with a constant from code? breakout = ast.If( test=node.test, body=[ast.Expr(value=ast.Continue())], From 41acc6c54ed85d2bc73d0931edd837be4b57052b Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 18 Apr 2020 22:51:13 -0700 Subject: [PATCH 17/18] prepass tests --- tests.py | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 tests.py diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..2bf9597 --- /dev/null +++ b/tests.py @@ -0,0 +1,183 @@ +import unittest +import zsc.prepass as prepass +import zsc.compiler as compiler +import ast +import inspect + + +class TestPrepass(unittest.TestCase): + + def test_get_call_name_zfunc(self): + p = prepass.Prepass() + example = ast.parse('''zbrush.IClick("a:b:c")''', mode='eval') + assert (p.get_call_name(example.body)) == ('zbrush', 'IClick') + + def test_get_call_name_raw(self): + p = prepass.Prepass() + example = ast.parse('''somefunction()''', mode='eval') + assert (p.get_call_name(example.body)) == ('', 'somefunction') + + def test_get_is_zfunc_normal(self): + p = prepass.Prepass() + raw = '''import zbrush\nzbrush.IClick()''' + p.visit(ast.parse(raw)) + example = ast.parse('''zbrush.IClick("A")''', mode='eval') + assert (p.is_zbrush_function(example.body)) + + def test_get_is_zfunc_fail_unrecognized(self): + p = prepass.Prepass() + raw = '''import zbrush\nzbrush.IClick()''' + p.visit(ast.parse(raw)) + example = ast.parse('''dummy("A")''', mode='eval') + assert (not p.is_zbrush_function(example.body)) + + def test_get_zfunc_mod_alias(self): + p = prepass.Prepass() + raw = '''import zbrush as zb\nzb.IClick()''' + p.visit(ast.parse(raw)) + example = ast.parse('''zb.IClick("A")''', mode='eval') + assert (p.is_zbrush_function(example.body)) + + def test_get_zfunc_func_alias(self): + p = prepass.Prepass() + raw = '''from zbrush import IClick as blah''' + p.visit(ast.parse(raw)) + example = ast.parse('''blah("A")''', mode='eval') + assert (p.is_zbrush_function(example.body)) + + def test_get_zfunc_signature(self): + p = prepass.Prepass() + raw = '''from zbrush import IClick as blah''' + p.visit(ast.parse(raw)) + assert isinstance(p.get_signature('IClick'), inspect.FullArgSpec) + + def test_allows_zbrush_import(self): + p = prepass.Prepass() + raw = '''import zbrush''' + p.visit(ast.parse(raw)) + + def test_allows_math_import(self): + p = prepass.Prepass() + raw = '''import math''' + p.visit(ast.parse(raw)) + + def test_allows_random_import(self): + p = prepass.Prepass() + raw = '''import random''' + p.visit(ast.parse(raw)) + + def test_no_other_import_plain(self): + p = prepass.Prepass() + raw = '''import csv''' + def bad(): return p.visit(ast.parse(raw)) + self.assertRaises(prepass.ParseError, bad) + + def test_no_other_math_import_aliased(self): + p = prepass.Prepass() + raw = '''import csv as xyz''' + def bad(): return p.visit(ast.parse(raw)) + self.assertRaises(prepass.ParseError, bad) + + def test_user_function(self): + p = prepass.Prepass() + raw = '''def userfunc():\n return 1''' + p.visit(ast.parse(raw)) + call = ast.Call(ast.Name("userfunc")) + assert p.is_user_function(call) + + def test_user_function_negative(self): + p = prepass.Prepass() + raw = '''def userfunc():\n return 1''' + p.visit(ast.parse(raw)) + call = ast.Call(ast.Name("not_userfunc")) + assert not p.is_user_function(call) + + def test_has_return_type(self): + p = prepass.Prepass() + raw = '''import zbrush, math, random''' + p.visit(ast.parse(raw)) + call = ast.Call(ast.Name("GetActiveToolPath")) + assert p.has_return_type(call) + + def test_has_return_type_neg(self): + p = prepass.Prepass() + raw = '''import zbrush, math, random''' + p.visit(ast.parse(raw)) + call = ast.Call(ast.Name("MTransformSet")) + assert not p.has_return_type(call) + + def test_return_type_math(self): + p = prepass.Prepass() + raw = '''import zbrush, math, random''' + p.visit(ast.parse(raw)) + call = ast.Call(ast.Name("sin")) + assert p.has_return_type(call) + + def test_return_type_random(self): + p = prepass.Prepass() + raw = '''import zbrush, math, random''' + p.visit(ast.parse(raw)) + call = ast.Call(ast.Name("randint")) + assert p.has_return_type(call) + + def test_return_type_user(self): + # note that user functions _CANT_ have return types + # in zbrush, so this ignores the python. The compiler + # raises an error for this + p = prepass.Prepass() + raw = '''def userfunc():\n return 1''' + p.visit(ast.parse(raw)) + call = ast.Call(ast.Name("usefunc")) + assert not p.has_return_type(call) + + + +class TestAnalyzer(unittest.TestCase): + + def parse (self, stringval): + tree = ast.parse(stringval) + p = prepass.Prepass() + p.visit(tree) + analyzer = compiler.Analyzer(0, input_file="dummy", prepass=p) + return analyzer, tree + + + + def test_assign_int(self): + analyzer, tree = self.parse('''a = 1''') + analyzer.visit(tree) + assert analyzer.format() == '[VarDef, a, 1]' + + def test_assign_float(self): + analyzer, tree = self.parse('''a = -1.0''') + analyzer.visit(tree) + assert analyzer.format() == '[VarDef, a, -1.0]' + + def test_assign_str(self): + analyzer, tree = self.parse('''a = "fred"''') + analyzer.visit(tree) + assert analyzer.format() == '[VarDef, a, "fred"]' + + def test_assign_var(self): + analyzer, tree = self.parse('''a = 1\nb = a''') + analyzer.visit(tree) + assert analyzer.format() == '[VarDef, a, 1]\n[VarDef, b, #a]' + + def test_assign_var_neg(self): + analyzer, tree = self.parse('''a = 1\nb = -a''') + analyzer.visit(tree) + assert analyzer.format() == '[VarDef, a, 1]\n[VarDef, b, [NEG, #a]]' + + def test_assign_def_vs_set(self): + # use VarDef at the top level but VarSet inside of functions + analyzer, tree = self.parse('''a = 1\ndef func(input, output):\n temp = input + 1\n output = temp''') + analyzer.visit(tree) + result = analyzer.format() + assert '[VarDef, a, 1]' in result + assert '[VarSet, temp, ' in result + assert '[VarSet, output' in result + + + +if __name__ == '__main__': + unittest.main() From 658548a5e8819e25d2d3942301fdeff8970a03ae Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 18 Apr 2020 23:16:05 -0700 Subject: [PATCH 18/18] more annotations --- zsc/zbrush.py | 60 ++++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/zsc/zbrush.py b/zsc/zbrush.py index e2559b8..6c090be 100644 --- a/zsc/zbrush.py +++ b/zsc/zbrush.py @@ -479,7 +479,7 @@ def IButton(ButtonText: str, PopupText: str = None, Commands: Callable = ..., Di # TODO: Pick up type annotations here -def IClick(InterfacePath, *positions): +def IClick(InterfacePath: str, *positions) -> None: """ Emulates a click within a specified ZBrush interface item @@ -487,7 +487,7 @@ def IClick(InterfacePath, *positions): pass -def IClose(InterfacePath, ShowZoom=0, TargetParent=0): +def IClose(InterfacePath: str, ShowZoom: int = 0, TargetParent: int = 0): """ Closes an interface item. @@ -495,7 +495,7 @@ def IClose(InterfacePath, ShowZoom=0, TargetParent=0): pass -def IColorSet(Red, Green, Blue): +def IColorSet(Red: int, Green: int, Blue: int): """ Sets the active color to a new value @@ -511,7 +511,7 @@ def IConfig(Config): pass -def IDialog(Title, TitleMode=0, Icon='', LeftInset=0, RightInset=0, LeftTop=0, RightBottom=0): +def IDialog(Title: str, TitleMode: int = 0, Icon: str = '', LeftInset: int = 0, RightInset: int = 0, LeftTop: int = 0, RightBottom: int = 0) -> int: """ Adds a subpalette to ZBrush interface. Mode : @@ -526,7 +526,7 @@ def IDialog(Title, TitleMode=0, Icon='', LeftInset=0, RightInset=0, LeftTop=0, R pass -def IDisable(WindowPath, WindowID): +def IDisable(WindowPath: str, WindowID: int) -> None: """ Disables a ZScript interface item (can only be used for ZScript-generated interface items) @@ -534,7 +534,7 @@ def IDisable(WindowPath, WindowID): pass -def IEnable(WindowPath, WindowID): +def IEnable(WindowPath: str, WindowID: int) -> None: """ Enables a ZScript interface item (can only be used for ZScript-generated interface items) @@ -542,7 +542,7 @@ def IEnable(WindowPath, WindowID): pass -def IExists(InterfaceItemPath): +def IExists(InterfaceItemPath: str) -> int: """ Verifies that a specified interface item exists. @@ -552,7 +552,7 @@ def IExists(InterfaceItemPath): pass -def IFadeIn(FadeOutSpeed=0.5): +def IFadeIn(FadeOutSpeed: float = 0.5) -> None: """ Fades ZBrush window to black. @@ -560,7 +560,7 @@ def IFadeIn(FadeOutSpeed=0.5): pass -def IFadeOut(FadeOutSpeed=0.5): +def IFadeOut(FadeOutSpeed: float = 0.5) -> None: """ Fades ZBrush window to black. @@ -568,14 +568,15 @@ def IFadeOut(FadeOutSpeed=0.5): pass -def IFreeze(Commands: Callable = ..., FadeOutSpeed=0.05): +def IFreeze(Commands: Callable = ..., FadeOutSpeed=0.05) -> None: """ Disables interface updates. """ + pass -def IGet(InterfaceItemPath): +def IGet(InterfaceItemPath: str) -> object: """ Returns the current value of a ZBrush or ZScript interface item @@ -585,7 +586,7 @@ def IGet(InterfaceItemPath): pass -def IGetFlags(InterfaceItemPath): +def IGetFlags(InterfaceItemPath: str) -> int: """ Returns the status flags of the specified interface item @@ -595,7 +596,7 @@ def IGetFlags(InterfaceItemPath): pass -def IGetHotkey(InterfaceItemPath): +def IGetHotkey(InterfaceItemPath: str) -> str: """ Returns the hotkey of the specified interface item @@ -605,7 +606,7 @@ def IGetHotkey(InterfaceItemPath): pass -def IGetID(InterfaceItemPath): +def IGetID(InterfaceItemPath: str) -> str: """ Returns the window ID code of the specified interface item @@ -615,7 +616,7 @@ def IGetID(InterfaceItemPath): pass -def IGetInfo(InterfaceItemPath): +def IGetInfo(InterfaceItemPath: str) -> str: """ Returns the info (popup info) of the specified interface item @@ -625,7 +626,7 @@ def IGetInfo(InterfaceItemPath): pass -def IGetMax(InterfaceItemPath): +def IGetMax(InterfaceItemPath: str) -> float: """ Returns the maximum possible value of a ZBrush or ZScript interface item @@ -635,7 +636,7 @@ def IGetMax(InterfaceItemPath): pass -def IGetMin(InterfaceItemPath): +def IGetMin(InterfaceItemPath: str) -> float: """ Returns the minimum possible value of a ZBrush or ZScript interface item @@ -645,7 +646,7 @@ def IGetMin(InterfaceItemPath): pass -def IGetSecondary(InterfaceItemPath): +def IGetSecondary(InterfaceItemPath: str) -> float: """ Returns the the scondary value of a 2D interface item @@ -654,7 +655,7 @@ def IGetSecondary(InterfaceItemPath): pass -def IGetStatus(InterfaceItemPath): +def IGetStatus(InterfaceItemPath: str) -> float: """ Returns the Enabled/Disabled status of a ZBrush or ZScript interface item @@ -665,7 +666,7 @@ def IGetStatus(InterfaceItemPath): pass -def IGetTitle(InterfaceItemPath, ReturnFullPath): +def IGetTitle(InterfaceItemPath: str, ReturnFullPath: int) -> str: """ Returns the title of the specified interface item @@ -675,7 +676,7 @@ def IGetTitle(InterfaceItemPath, ReturnFullPath): pass -def IHeight(InterfaceItemPath): +def IHeight(InterfaceItemPath: str) -> int: """ Returns the pixel-height of an interface item. @@ -685,7 +686,7 @@ def IHeight(InterfaceItemPath): pass -def IHide(InterfaceItemPath, ShowZoomRectangles=0, TargetParentWindow=0): +def IHide(InterfaceItemPath: str, ShowZoomRectangles: int = 0, TargetParentWindow: int = 0) -> None: """ Hides an interface item. @@ -693,7 +694,7 @@ def IHide(InterfaceItemPath, ShowZoomRectangles=0, TargetParentWindow=0): pass -def IHPos(InterfaceItemPath, UseGlobalCoords=0): +def IHPos(InterfaceItemPath: str, UseGlobalCoords: int = 0) -> int: """ Returns the H position of the interface item in Canvas or Global coordinates. @@ -703,7 +704,8 @@ def IHPos(InterfaceItemPath, UseGlobalCoords=0): pass -def IKeyPress(KeyCode, Commands: Callable = ..., HCursor=None, VCursor=None): +# how to annotate this? +def IKeyPress(KeyCode: str, Commands: Callable = ..., HCursor=None, VCursor=None): """ Simulates a key press @@ -719,14 +721,14 @@ def ILock(WindowPath, WindowID): pass -def Image(FileName, Align, ResizedWidth): +def Image(FileName: str, Align: int, ResizedWidth: int) -> None: """ Loads and displays an image Align (0=center 1=left 2=right """ -def IMaximize(InterfaceItemPath, MaximizeSubPalettes): +def IMaximize(InterfaceItemPath: str, MaximizeSubPalettes: int) -> None: """ Locates an interface item and (if possible) maximize its size. @@ -734,7 +736,7 @@ def IMaximize(InterfaceItemPath, MaximizeSubPalettes): pass -def IMinimize(InterfaceItemPath, MinimizeSubPalettes): +def IMinimize(InterfaceItemPath: str, MinimizeSubPalettes: int) -> None: """ Locates an interface item and (if possible) minimize its size. @@ -742,7 +744,7 @@ def IMinimize(InterfaceItemPath, MinimizeSubPalettes): pass -def IModGet(InterfaceItemPath): +def IModGet(InterfaceItemPath: str) -> int: """ Returns the current modifiers binary state of a ZBrush or ZScript interface item @@ -752,7 +754,7 @@ def IModGet(InterfaceItemPath): pass -def IModSet(InterfaceItemPath, value): +def IModSet(InterfaceItemPath: str, value:int) ->None: """ Sets the modifiers binary value of a ZBrush or a ZScript interface item