From 14d3560b70cc97e0e36c405de5b81053520ab483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucio=20Andr=C3=A9s=20Illanes=20Albornoz?= Date: Sat, 21 Sep 2019 11:27:52 +0200 Subject: [PATCH] Initial {rotate,tile} operator implementation. --- .TODO | 1 + .vscode/launch.json | 70 ++++++++++++++++++++++++++ .vscode/tasks.json | 12 +++++ assets/text/TODO | 1 + liboperators/OperatorRotate.py | 38 ++++++++++++++ liboperators/OperatorTile.py | 39 ++++++++++++++ libroar/RoarCanvasCommands.py | 3 ++ libroar/RoarCanvasCommandsOperators.py | 43 ++++------------ libroar/RoarCanvasCommandsTools.py | 1 + libroar/RoarCanvasWindow.py | 47 ++++++++++++++++- libtools/ToolObject.py | 2 +- 11 files changed, 220 insertions(+), 37 deletions(-) create mode 100644 .TODO create mode 100755 .vscode/launch.json create mode 100755 .vscode/tasks.json create mode 100755 liboperators/OperatorRotate.py create mode 100755 liboperators/OperatorTile.py diff --git a/.TODO b/.TODO new file mode 100644 index 0000000..1ce9cf2 --- /dev/null +++ b/.TODO @@ -0,0 +1 @@ +text bug: a) select text tool b) paste stuff c) undo d) artifacts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100755 index 0000000..2164a8c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,70 @@ +{ + // 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": "Python: Current File (Integrated Terminal)", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal" + }, + { + "name": "Python: Remote Attach", + "type": "python", + "request": "attach", + "port": 5678, + "host": "localhost", + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "." + } + ] + }, + { + "name": "Python: Module", + "type": "python", + "request": "launch", + "module": "enter-your-module-name-here", + "console": "integratedTerminal" + }, + { + "name": "Python: Django", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/manage.py", + "console": "integratedTerminal", + "args": [ + "runserver", + "--noreload", + "--nothreading" + ], + "django": true + }, + { + "name": "Python: Flask", + "type": "python", + "request": "launch", + "module": "flask", + "env": { + "FLASK_APP": "app.py" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "jinja": true + }, + { + "name": "Python: Current File (External Terminal)", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "externalTerminal" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100755 index 0000000..5c46c6c --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,12 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "echo", + "type": "shell", + "command": "echo Hello" + } + ] +} \ No newline at end of file diff --git a/assets/text/TODO b/assets/text/TODO index 9eebe9a..1355111 100644 --- a/assets/text/TODO +++ b/assets/text/TODO @@ -9,6 +9,7 @@ Low-priority list: 8) Incremental auto{load,save} & {backup,restore} (needs Settings window) 9) Composition, parametrisation & keying of tools from higher-order operators (brushes, functions, filters, outlines, patterns & shaders) and unit tools 10) Sprites & scripted (Python?) animation on the basis of asset traits and {composable,parametrised} patterns (metric flow, particle system, rigging, ...) +11) Integrate ENNTool code in the form of OpenGL-based animation window (see 9) and 10)) High-priority list: 1) unit tools: arrow, {cloud,speech bubble}, curve, measure, pick, polygon, triangle, unicode diff --git a/liboperators/OperatorRotate.py b/liboperators/OperatorRotate.py new file mode 100755 index 0000000..7178a61 --- /dev/null +++ b/liboperators/OperatorRotate.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# +# OperatorRotate.py +# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz +# + +from Operator import Operator +import math + +class OperatorRotate(Operator): + name = "Rotate" + + # + # apply2(self, mapPoint, mousePoint, regionOld, region) + def apply2(self, mapPoint, mousePoint, regionOld, region): + if self.originPoint == None: + self.originPoint = list(mousePoint) + delta = [b - a for a, b in zip(self.originPoint, mousePoint)] + radius = math.sqrt(math.pow(delta[0], 2) + math.pow(delta[1], 2)) + if radius >= 10: + regionSize = (len(region[0]), len(region)) + theta = math.atan2(-delta[1], delta[0]); cos, sin = math.cos(theta), math.sin(theta); + for numCol in range(regionSize[0]): + for numRow in range(regionSize[1]): + numRow_, numCol_ = (numRow / regionSize[1]) * 2 - 1, (numCol / regionSize[0]) * 2 - 1 + b, a = (numCol_ * sin) + (numRow_ * cos), (numCol_ * cos) - (numRow_ * sin) + numRow_, numCol_ = int((b + 1) / 2 * regionSize[1]), int((a + 1) / 2 * regionSize[0]) + if (numRow_ < regionSize[1]) and (numCol_ < regionSize[0]): + region[numRow][numCol] = list(regionOld[numRow_][numCol_]) + return region + else: + return region + + # __init__(self, *args): initialisation method + def __init__(self, *args): + self.originPoint = None + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/liboperators/OperatorTile.py b/liboperators/OperatorTile.py new file mode 100755 index 0000000..c4f7ef5 --- /dev/null +++ b/liboperators/OperatorTile.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# +# OperatorTile.py +# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz +# + +from Operator import Operator +import copy + +class OperatorTile(Operator): + name = "Tile" + + # + # apply2(self, mapPoint, mousePoint, regionOld, region) + def apply2(self, mapPoint, mousePoint, regionOld, region): + if self.lastPoint == None: + self.lastPoint = list(mapPoint) + if self.tileObject == None: + self.tileObject = copy.deepcopy(region) + delta = [b - a for a, b in zip(self.lastPoint, mapPoint)] + if delta[1] > 0: + for numNewRow in range(delta[1]): + newRow = copy.deepcopy(self.tileObject[len(region) % len(self.tileObject)]) + if len(newRow) < len(region[0]): + for numNewCol in range(len(newRow), len(region[0])): + newRow += [list(self.tileObject[len(region) % len(self.tileObject)][numNewCol % len(self.tileObject[len(region) % len(self.tileObject)])])] + region += [newRow] + if delta[0] > 0: + for numRow in range(len(region)): + for numNewCol in range(len(region[numRow]), len(region[numRow]) + delta[0]): + region[numRow] += [list(self.tileObject[numRow % len(self.tileObject)][numNewCol % len(self.tileObject[numRow % len(self.tileObject)])])] + self.lastPoint = list(mapPoint) + return region + + # __init__(self, *args): initialisation method + def __init__(self, *args): + self.lastPoint, self.tileObject = None, None + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libroar/RoarCanvasCommands.py b/libroar/RoarCanvasCommands.py index 2a48c29..62d53ed 100644 --- a/libroar/RoarCanvasCommands.py +++ b/libroar/RoarCanvasCommands.py @@ -64,6 +64,9 @@ class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCan self.parentFrame.SetTitle("roar") if "toolName" in self.lastPanelState: textItems.append("Current tool: {}".format(self.lastPanelState["toolName"])) + if ("operator" in self.lastPanelState) \ + and (self.lastPanelState["operator"] != None): + textItems.append("Current operator: {}".format(self.lastPanelState["operator"])) if "dirty" in self.lastPanelState \ and self.lastPanelState["dirty"]: textItems.append("*") diff --git a/libroar/RoarCanvasCommandsOperators.py b/libroar/RoarCanvasCommandsOperators.py index 8f218a6..f02867d 100644 --- a/libroar/RoarCanvasCommandsOperators.py +++ b/libroar/RoarCanvasCommandsOperators.py @@ -7,6 +7,8 @@ from OperatorFlipHorizontal import OperatorFlipHorizontal from OperatorFlipVertical import OperatorFlipVertical from OperatorInvert import OperatorInvert +from OperatorRotate import OperatorRotate +from OperatorTile import OperatorTile from GuiFrame import GuiCommandListDecorator from ToolObject import ToolObject import copy, wx @@ -16,41 +18,13 @@ class RoarCanvasCommandsOperators(): @GuiCommandListDecorator(0, "Flip", "&Flip", None, None, None) @GuiCommandListDecorator(1, "Flip horizontally", "Flip &horizontally", None, None, None) @GuiCommandListDecorator(2, "Invert", "&Invert", None, None, None) + @GuiCommandListDecorator(3, "Rotate", "&Rotate", None, None, None) + @GuiCommandListDecorator(4, "Tile", "&Tile", None, None, None) def canvasOperator(self, f, idx): def canvasOperator_(event): - applyOperator = [OperatorFlipVertical, OperatorFlipHorizontal, OperatorInvert][idx]() - if (self.currentTool.__class__ == ToolObject) \ - and (self.currentTool.toolState >= self.currentTool.TS_SELECT): - region = self.currentTool.getRegion(self.parentCanvas.canvas) - else: - region = self.parentCanvas.canvas.map - region = applyOperator.apply(copy.deepcopy(region)) - if (self.currentTool.__class__ == ToolObject) \ - and (self.currentTool.toolState >= self.currentTool.TS_SELECT): - if self.parentCanvas.popupEventDc == None: - eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas) - else: - eventDc = self.parentCanvas.popupEventDc - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - self.currentTool.setRegion(self.parentCanvas.canvas, None, region, [len(region[0]), len(region)], self.currentTool.external) - self.currentTool.onSelectEvent(self.parentCanvas.canvas, (0, 0), self.parentCanvas.dispatchPatchSingle, eventDc, True, wx.MOD_NONE, None, self.currentTool.targetRect) - eventDc.SetDeviceOrigin(*eventDcOrigin) - else: - if self.parentCanvas.popupEventDc == None: - eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas) - else: - eventDc = self.parentCanvas.popupEventDc - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - self.parentCanvas.canvas.journal.begin() - dirty = False - for numRow in range(len(region)): - for numCol in range(len(region[numRow])): - if not dirty: - self.parentCanvas.dirty = True - self.parentCanvas.dispatchPatchSingle(eventDc, False, [numCol, numRow, *region[numRow][numCol]]) - self.parentCanvas.canvas.journal.end() - self.parentCanvas.commands.update(dirty=self.parentCanvas.dirty, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel) - eventDc.SetDeviceOrigin(*eventDcOrigin) + self.currentOperator = [OperatorFlipVertical, OperatorFlipHorizontal, OperatorInvert, OperatorRotate, OperatorTile][idx]() + self.operatorState = None + self.parentCanvas.applyOperator(self.currentTool, self.parentCanvas.brushPos, None, False, self.currentOperator, self.parentCanvas.GetViewStart()) setattr(canvasOperator_, "attrDict", f.attrList[idx]) return canvasOperator_ # }}} @@ -60,9 +34,10 @@ class RoarCanvasCommandsOperators(): def __init__(self): self.menus = ( ("&Operators", - self.canvasOperator(self.canvasOperator, 0), self.canvasOperator(self.canvasOperator, 1), self.canvasOperator(self.canvasOperator, 2), + self.canvasOperator(self.canvasOperator, 0), self.canvasOperator(self.canvasOperator, 1), self.canvasOperator(self.canvasOperator, 2), self.canvasOperator(self.canvasOperator, 3), self.canvasOperator(self.canvasOperator, 4), ), ) self.toolBars = () + self.currentOperator, self.operatorState = None, None # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 diff --git a/libroar/RoarCanvasCommandsTools.py b/libroar/RoarCanvasCommandsTools.py index 665d383..05e3491 100644 --- a/libroar/RoarCanvasCommandsTools.py +++ b/libroar/RoarCanvasCommandsTools.py @@ -31,6 +31,7 @@ class RoarCanvasCommandsTools(): self.lastTool, self.currentTool = self.currentTool, [ToolCircle, None, ToolFill, ToolLine, ToolObject, ToolRect, ToolText][idx] if self.currentTool != None: self.currentTool = self.currentTool() + self.currentOperator, self.operatorState = None, None self.parentFrame.menuItemsById[self.canvasTool.attrList[idx]["id"]].Check(True) toolBar = self.parentFrame.toolBarItemsById[self.canvasTool.attrList[idx]["id"]].GetToolBar() toolBar.ToggleTool(self.canvasTool.attrList[idx]["id"], True) diff --git a/libroar/RoarCanvasWindow.py b/libroar/RoarCanvasWindow.py index b9b68e7..450509d 100644 --- a/libroar/RoarCanvasWindow.py +++ b/libroar/RoarCanvasWindow.py @@ -7,7 +7,7 @@ from GuiWindow import GuiWindow from ToolObject import ToolObject from ToolText import ToolText -import json, wx, sys +import copy, json, wx, sys class RoarCanvasWindowDropTarget(wx.TextDropTarget): # {{{ done(self) @@ -54,6 +54,47 @@ class RoarCanvasWindow(GuiWindow): self.canvas.journal.pushCursor(patchDelta) # }}} + # {{{ applyOperator(self, currentTool, mapPoint, mouseLeftDown, mousePoint, operator, viewRect) + def applyOperator(self, currentTool, mapPoint, mouseLeftDown, mousePoint, operator, viewRect): + self.canvas.dirtyCursor = False + if (currentTool.__class__ == ToolObject) \ + and (currentTool.toolState >= currentTool.TS_SELECT): + region = currentTool.getRegion(self.canvas) + else: + region = self.canvas.map + if hasattr(operator, "apply2"): + if mouseLeftDown: + if self.commands.operatorState == None: + self.commands.operatorState = True + region = operator.apply2(mapPoint, mousePoint, region, copy.deepcopy(region)) + self.commands.update(operator=self.commands.currentOperator.name) + elif self.commands.operatorState != None: + self.commands.currentOperator = None + self.commands.update(operator=None) + return + else: + region = operator.apply(copy.deepcopy(region)) + self.commands.currentOperator = None + if (currentTool.__class__ == ToolObject) \ + and (currentTool.toolState >= currentTool.TS_SELECT): + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) if self.popupEventDc == None else self.popupEventDc + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + currentTool.setRegion(self.canvas, None, region, [len(region[0]), len(region)], currentTool.external) + currentTool.onSelectEvent(self.canvas, (0, 0), self.dispatchPatchSingle, eventDc, True, wx.MOD_NONE, None, currentTool.targetRect) + currentTool._drawSelectRect(currentTool.targetRect, self.dispatchPatchSingle, eventDc) + eventDc.SetDeviceOrigin(*eventDcOrigin) + else: + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) if self.popupEventDc == None else self.popupEventDc + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + self.canvas.journal.begin() + for numRow in range(len(region)): + for numCol in range(len(region[numRow])): + self.dirty = True if not self.dirty else self.dirty + self.dispatchPatchSingle(eventDc, False, [numCol, numRow, *region[numRow][numCol]]) + self.canvas.journal.end() + self.commands.update(dirty=self.dirty, undoLevel=self.canvas.journal.patchesUndoLevel) + eventDc.SetDeviceOrigin(*eventDcOrigin) + # }}} # {{{ applyTool(self, eventDc, eventMouse, keyChar, keyCode, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, tool, viewRect) def applyTool(self, eventDc, eventMouse, keyChar, keyCode, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, tool, viewRect): eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); @@ -223,7 +264,9 @@ class RoarCanvasWindow(GuiWindow): viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect); mouseDragging, mouseLeftDown, mouseRightDown = event.Dragging(), event.LeftIsDown(), event.RightIsDown() mapPoint = self.backend.xlateEventPoint(event, eventDc, viewRect) - if mouseRightDown \ + if self.commands.currentOperator != None: + self.applyOperator(self.commands.currentTool, mapPoint, mouseLeftDown, event.GetLogicalPosition(eventDc), self.commands.currentOperator, viewRect) + elif mouseRightDown \ and (self.commands.currentTool.__class__ == ToolObject) \ and (self.commands.currentTool.toolState >= self.commands.currentTool.TS_SELECT): self.popupEventDc = eventDc; self.PopupMenu(self.operatorsMenu); self.popupEventDc = None; diff --git a/libtools/ToolObject.py b/libtools/ToolObject.py index 429f676..55c0638 100644 --- a/libtools/ToolObject.py +++ b/libtools/ToolObject.py @@ -162,7 +162,7 @@ class ToolObject(Tool): elif self.objectSize != objectSize: if self.objectSize == None: self.objectSize = objectSize - self.targetRect[1] = [t + d for t, d in zip(self.targetRect[1], (a - b for a, b in zip(self.objectSize, objectSize)))] + self.targetRect[1] = [t + d for t, d in zip(self.targetRect[1], (b - a for a, b in zip(self.objectSize, objectSize)))] if self.srcRect == None: self.srcRect = self.targetRect self.objectMap, self.objectSize = objectMap, objectSize