tb2097 / wacom-gui

Python/PyQt Wacom GUI for KDE
GNU General Public License v3.0
133 stars 35 forks source link

Feature Request: Add support for monitor aspect mapping #16

Closed jcpearson closed 5 years ago

jcpearson commented 6 years ago

Some of our users prefer to have a tablet area that matches their monitor(s) aspect ratio - i.e. so a circle on the tablet is a circle on screen etc - even when using 2 monitors (which means only half the tablet area is used) - but is also useful when just using one monitor that doesn't match the aspect ratio of the tablet

The (largish) patch below adds this support to the 'Other Settings' tab

A few notes:

wacom-gui current assumes the tablet is in landscape mode which (usually) maps to monitor(s) also in landscape mode. The selectable Aspect Mapping setting are:

However, if the monitor(s) are in a portrait layout (screen width is less than height), then the Aspect Mappings will map to the Right, Middle and Left parts of the tablet area - corresponding to the Top, Middle and Bottom selections (there isn't any support if the tablet is used in portrait mode ...)

To make life a lot easier, the patch also adds support for 'preferences' that are saved and loaded when wacom-gui exits/starts - which saves having to probe the wacom device for settings and attempting to match with what wacom-gui expects.

I've added preferences not only for the "Aspect Mapping" settings, but for the "Screen Area" and "Tablet Orientation" settings - if there are no existing saved settings for these, it will fall-back to probing the device as before. Adding preferences for "Tablet Orientation" is not actually needed for Aspect Mapping support, but as I had added it for "Screen Area", it seemed silly not to :-)

These preferences are saved in a file 'prefs.json' in the same directory as the 'default.sh' script for each tablet type

AFAIK, it all appears to work as expected - however, python is not my first language, so there may well be more pythonesque ways of doing what it does ...

Let me know what you think

--- ./wacom-gui/help.html.dist  2018-06-19 16:08:27.000000000 +0100                                        
+++ ./wacom-gui/help.html       2018-07-31 12:05:57.343886905 +0100                                        
@@ -134,6 +134,7 @@ section ul {                                                                           
         <section id="other"><h2>Other Settings</h2>                                                       
             <ul>                                                                                          
                 <li>Map tablet input to a specific monitor or all (if more than one)</li>                 
+                <li>Map tablet area keeping the aspect ratio of the screen(s) using the Top, Middle or Bottom of the tablet - <b>Note</b>: if screen(s) are in 'portrait' mode, then Top will map to the Left of the tablet and Bottom to the Right of the tablet</li>                                                                         
                 <li>Switch tablet between left and right orientation</li>                                      
                 <li>Restore tablet to all default settings</li>                                                
             </ul>                                                                                              
--- ./wacom-gui/options.py.dist 2018-06-19 16:08:27.000000000 +0100                                             
+++ ./wacom-gui/options.py      2018-07-31 12:05:57.344886918 +0100                                             
@@ -7,7 +7,7 @@ import re                                                                                       

 class otherOptions(QtGui.QWidget):                                                                             
-    def __init__(self, deviceNames, parent=None):                                                              
+    def __init__(self, deviceNames, prefs, parent=None):                                                       
         QtGui.QWidget.__init__(self, parent)                                                                   
         #self.tabletName = tabletName.replace('Pad', 'Pen')                                                    
         # use the detected device names                                                                        
@@ -17,16 +17,20 @@ class otherOptions(QtGui.QWidget):                                                          
         self.tabletTouch = deviceNames['touch']                                                                
         self.tabletPad = deviceNames['pad']                                                                    
         self.deviceNames = deviceNames                                                                         
+        self.prefs = prefs                                                                                     
+        #print prefs                                                                                           
         self.initUI()                                                                                          

     def initUI(self):                                                                                          
         self.devices = []                                                                                      
         self.tabletActiveArea = ""                                                                             
+        self.tabletAspectArea = "-1 -1 -1 -1"                                                                  
         self.orient = ''                                                                                       
         # layout code                                                                                          
         self.mainLayout = QtGui.QHBoxLayout()                                                                  
         self.mainLayout.setAlignment(QtCore.Qt.AlignLeft)                                                      
+        self.mainLayout.addWidget(self.aspectOptions())                                                        
         screens = self.screenOptions()                                                                         
         if screens:                                                                                            
             self.mainLayout.addWidget(screens)                                                                 
@@ -35,25 +39,64 @@ class otherOptions(QtGui.QWidget):                                                          
         self.setLayout(self.mainLayout)                                                                        

+    def aspectOptions(self):                                                                                   
+        groupBox = QtGui.QGroupBox("Aspect Mapping")                                                           
+        groupBox.setAlignment(QtCore.Qt.AlignHCenter)                                                          
+        groupBox.setFixedHeight(120)                                                                           
+        self.aspectGroup = QtGui.QButtonGroup(groupBox)                                                        
+        self.aspectNone = QtGui.QRadioButton("None")                                                           
+        self.aspectTop = QtGui.QRadioButton("Top")                                                             
+        self.aspectMiddle = QtGui.QRadioButton("Middle")                                                       
+        self.aspectBottom = QtGui.QRadioButton("Bottom")                                                       
+        self.aspectGroup.addButton(self.aspectNone)                                                            
+        self.aspectGroup.addButton(self.aspectTop)                                                             
+        self.aspectGroup.addButton(self.aspectMiddle)                                                          
+        self.aspectGroup.addButton(self.aspectBottom)                                                          
+        aspectLayout = QtGui.QVBoxLayout()                                                                     
+        aspectLayout.addWidget(self.aspectNone)                                                                
+        aspectLayout.addWidget(self.aspectTop)                                                                 
+        aspectLayout.addWidget(self.aspectMiddle)                                                              
+        aspectLayout.addWidget(self.aspectBottom)                                                              
+       aspectLayout.addStretch(1)                                                                              
+        # See if we have saved prefs                                                                           
+       if "Aspect Mapping" in self.prefs:                                                                      
+            if self.prefs['Aspect Mapping'] == "None":                                                         
+                self.aspectNone.setChecked(1)                                                                  
+            elif self.prefs['Aspect Mapping'] == "Top":                                                        
+                self.aspectTop.setChecked(1)                                                                   
+            elif self.prefs['Aspect Mapping'] == "Middle":                                                     
+                self.aspectMiddle.setChecked(1)                                                                
+            elif self.prefs['Aspect Mapping'] == "Bottom":                                                     
+                self.aspectBottom.setChecked(1)                                                                
+        else:                                                                                                  
+            # Default is 'None'                                                                                
+            self.aspectNone.setChecked(1)                                                                      
+            self.prefs['Aspect Mapping'] = "None"                                                              
+        self.aspectGroup.buttonClicked.connect(self.aspectChange)                                              
+        groupBox.setLayout(aspectLayout)                                                                       
+        return groupBox                                                                                        
+                                                                                                               
+                                                                                                               
     def screenOptions(self):                                                                                   
         if QtGui.QDesktopWidget().numScreens() == 1:                                                           
             self.screenFull = None                                                                             
+            self.prefs['Screen Area'] = "All Monitors"                                                         
             return None                                                                                        
         groupBox = QtGui.QGroupBox("Screen Area")                                                              
         groupBox.setAlignment(QtCore.Qt.AlignHCenter)                                                          
         groupBox.setFixedHeight(120)                                                                           
         self.screenGroup = QtGui.QButtonGroup(groupBox)                                                        
         self.displays = []                                                                                     
+        self.screenFull = QtGui.QRadioButton("All Monitors")                                                   
         for x in range(0, QtGui.QDesktopWidget().numScreens()):                                                
             self.displays.append(QtGui.QRadioButton("Monitor %d" % x))                                         
-        self.screenFull = QtGui.QRadioButton("All Monitors")                                                   
         for screen in self.displays:                                                                           
             self.screenGroup.addButton(screen)                                                                 
         self.screenGroup.addButton(self.screenFull)                                                            
         screenLayout = QtGui.QVBoxLayout()                                                                     
+        screenLayout.addWidget(self.screenFull)                                                                
         for screen in self.displays:                                                                           
             screenLayout.addWidget(screen)                                                                     
-        screenLayout.addWidget(self.screenFull)                                                                
         screenLayout.addStretch(1)                                                                             
         self.screenGroup.buttonClicked.connect(self.screenChange)                                              
         groupBox.setLayout(screenLayout)                                                                       
@@ -73,16 +116,29 @@ class otherOptions(QtGui.QWidget):                                                         
         flipLayout.addWidget(self.tabletRight)                                                                 
         flipLayout.addWidget(self.tabletLeft)                                                                  
         flipLayout.addStretch(1)                                                                               
-        getCommand = os.popen("xsetwacom --get \"%s stylus\" Rotate" % self.tabletStylus).readlines()          
-        # check correct button for orientation                                                                 
-        if getCommand[0] == "none\n":                                                                          
-            self.orient = "xsetwacom --set \"%s stylus\" Rotate none" % self.tabletStylus                      
-            self.orient += "\nxsetwacom --set \"%s eraser\" Rotate none" % self.tabletEraser                   
-            self.tabletRight.setChecked(1)                                                                     
-        elif getCommand[0] == "half\n":                                                                        
-            self.orient = "xsetwacom --set \"%s stylus\" Rotate half" % self.tabletStylus                      
-            self.orient += "\nxsetwacom --set \"%s eraser\" Rotate half" % self.tabletEraser                   
-            self.tabletLeft.setChecked(1)                                                                      
+        # See if we have saved prefs - if we don't, probe for existing setting                                 
+        if "Tablet Orientation" in self.prefs:                                                                 
+            if self.prefs['Tablet Orientation'] == "Right-Handed":                                             
+                self.tabletRight.setChecked(1)                                                                 
+            elif self.prefs['Tablet Orientation'] == "Left-Handed":                                            
+                self.tabletLeft.setChecked(1)                                                                  
+        else:                                                                                                  
+            getCommand = os.popen("xsetwacom --get \"%s stylus\" Rotate" % self.tabletStylus).readlines()      
+            # check correct button for orientation                                                             
+            if getCommand[0] == "none\n":                                                                      
+                self.orient = "xsetwacom --set \"%s stylus\" Rotate none" % self.tabletStylus                  
+                self.orient += "\nxsetwacom --set \"%s eraser\" Rotate none" % self.tabletEraser               
+                self.tabletRight.setChecked(1)                                                                 
+                self.prefs['Tablet Orientation'] = "Right-Handed"                                              
+            elif getCommand[0] == "half\n":                                                                    
+                self.orient = "xsetwacom --set \"%s stylus\" Rotate half" % self.tabletStylus                  
+                self.orient += "\nxsetwacom --set \"%s eraser\" Rotate half" % self.tabletEraser               
+                self.tabletLeft.setChecked(1)                                                                  
+                self.prefs['Tablet Orientation'] = "Left-Handed"                                               
+            else:                                                                                              
+                # Default to Right-Handed                                                                      
+                self.tabletRight.setChecked(1)                                                                 
+                self.prefs['Tablet Orientation'] = "Right-Handed"                                              
         self.tabletFlipGroup.buttonClicked.connect(self.tabletFlipChange)                                      
         groupBox.setLayout(flipLayout)                                                                         
         return groupBox                                                                                        
@@ -95,11 +151,87 @@ class otherOptions(QtGui.QWidget):                                                         
         elif buttonId.text() == "Left-Handed":                                                                 
             self.orient = "xsetwacom --set \"%s stylus\" Rotate half" % self.deviceNames['stylus']             
             self.orient += "\nxsetwacom --set \"%s eraser\" Rotate half" % self.deviceNames['eraser']          
+        self.prefs['Tablet Orientation'] = str(buttonId.text())                                                
         flipTablet = os.popen(self.orient)                                                                     

+    def aspectChange(self, buttonId):                                                                          
+        self.aspectChangeMain(str(buttonId.text()), False)                                                     
+                                                                                                               
+                                                                                                               
+    def aspectChangeMain(self, buttonText, screenDone):                                                        
+        self.prefs['Aspect Mapping'] = buttonText                                                              
+        #print self.prefs['Aspect Mapping']                                                                    
+                                                                                                               
+        # reset the tablet area to the full tablet before getting the area                                     
+        self.tabletAspectArea = "-1 -1 -1 -1"                                                                  
+        cmd = "xsetwacom --set \"%s stylus\" area %s" % (self.deviceNames['stylus'], self.tabletAspectArea)    
+        cmd += "\nxsetwacom --get \"%s stylus\" area" % self.deviceNames['stylus']                             
+        [tx, ty] = os.popen(cmd).read().split()[2:]                                                            
+                                                                                                               
+        # if set to "None" - we don't need to do any more here                                                 
+        if buttonText == "None":                                                                               
+            self.tabletAspectArea = "-1 -1 -1 -1"                                                              
+        else:                                                                                                  
+            sa = self.prefs['Screen Area']                                                                     
+            if sa == "All Monitors":                                                                           
+                # get the total screen dimensions                                                              
+                cmd = "xdpyinfo | grep dimensions | awk '{print $2}' | awk -Fx '{print $1, $2}'"               
+                [sx, sy] = os.popen(cmd).read().split()                                                        
+            else:                                                                                              
+                # get the dimensions on the current Monitor                                                    
+                id = sa.split(' ')[1]                                                                          
+                screen = QtGui.QDesktopWidget().screenGeometry(int(id))                                        
+                sx = screen.width()                                                                            
+                sy = screen.height()                                                                           
+                                                                                                               
+            # default 'new' tablet area                                                                        
+            ox = 0                                                                                             
+            oy = 0                                                                                             
+            nx = int(tx)                                                                                       
+            ny = int(ty)                                                                                       
+                                                                                                               
+            if float(sy)/float(sx) <= 1.0:                                                                     
+                # screen landscape mode                                                                        
+                ny = int(float(tx) * float(sy) / float(sx))                                                    
+                # Move tablet Y origin (already 0 for "Top")                                                   
+                if buttonText == "Middle":                                                                     
+                    oy = (int(ty) - ny) / 2                                                                    
+                elif buttonText == "Bottom":                                                                   
+                    oy = int(ty) - ny                                                                          
+            else:                                                                                              
+                # screen portrait mode                                                                         
+                # Top = Left, Middle = Middle and Bottom = Right                                               
+                nx = int(float(ty) * float(sx) / float(sy))                                                    
+                # Move tablet X origin (already 0 for "Left")                                                  
+                if buttonText == "Middle":                                                                     
+                    ox = (int(tx) - nx) / 2                                                                    
+                elif buttonText == "Bottom":                                                                   
+                    ox = int(tx) - nx                                                                          
+            ny += oy                                                                                           
+            nx += ox                                                                                           
+            self.tabletAspectArea = "%d %d %d %d" % (ox, oy, nx, ny)                                           
+                                                                                                               
+        #print self.tabletAspectArea                                                                           
+        for device in self.devices:                                                                            
+            if device != "pad" and device != 'touch':                                                          
+                cmd = "xsetwacom --set \"%s %s\" Area %s" % (self.deviceNames[device], device, self.tabletAspectArea)                                                                                                          
+                setCommand = os.popen(cmd)                                                                     
+        # This probably isn't needed, but as we do something similar                                           
+        # when Monitors change wrt Aspect, so update Monitors based                                            
+        # on previous pref (if required)                                                                       
+        if screenDone == False:                                                                                
+            self.screenChangeMain(self.prefs['Screen Area'], True)                                             
+                                                                                                               

     def screenChange(self, buttonId):                                                                          
-        if buttonId.text() == "All Monitors":                                                                  
+        self.screenChangeMain(str(buttonId.text()), False)                                                     
+                                                                                                               
+                                                                                                               
+    def screenChangeMain(self, buttonText, aspectDone):                                                        
+        # is we only have one screen, then we don't make any changes here                                      
+        if QtGui.QDesktopWidget().numScreens() == 1:                                                           
+            return                                                                                             
+        if buttonText == "All Monitors":                                                                       
             self.tabletActiveArea = "1 0 0 0 1 0 0 0 1"                                                        
             for device in self.devices:                                                                        
                 if device != "pad":                                                                            
@@ -107,14 +239,30 @@ class otherOptions(QtGui.QWidget):                                                        
                           % (self.deviceNames[device], device, self.tabletActiveArea)                          
                     setCommand = os.popen(cmd)                                                                 
         else:                                                                                                  
-            self.tabletActiveArea = "HEAD-%s" % buttonId.text().split(' ')[1]                                  
+            self.tabletActiveArea = "HEAD-%s" % buttonText.split(' ')[1]                                       
             for device in self.devices:                                                                        
                 if device != "pad":                                                                            
                     cmd = "xsetwacom set \"%s %s\" MapToOutput %s" % (self.deviceNames[device], device, self.tabletActiveArea)                                                                                                 
                     setCommand = os.popen(cmd)                                                                 
+        self.prefs['Screen Area'] = buttonText                                                                 
+        # As Monitor mapping has changed, the Aspect mapping may not match                                     
+        # so update Aspect based on previous pref (if required)                                                
+        if aspectDone == False:                                                                                
+            self.aspectChangeMain(self.prefs['Aspect Mapping'], True)                                          

     def getTabletArea(self):                                                                                   
+        sa = "Screen Area"                                                                                     
+        # See if we have saved prefs - if we don't, probe for existing setting                                 
+        if sa in self.prefs:                                                                                   
+            if self.prefs[sa] == "All Monitors":                                                               
+                 self.screenFull.setChecked(1)                                                                 
+                 return                                                                                        
+            # only check if we have more than one screen                                                       
+            for x in range(0, QtGui.QDesktopWidget().numScreens()):                                            
+                if self.prefs[sa] == "Monitor %d" % x:                                                         
+                    self.displays[x].setChecked(1)                                                             
+                    return                                                                                     
         # get current tablet area                                                                              
         tabletInfo = os.popen("xinput list-props \"%s stylus\" | grep Coordinate" % self.tabletStylus).readlines()                                                                                                             
         tabletInfo[0] = tabletInfo[0][41:].rstrip('\n')                                                        
@@ -139,6 +287,7 @@ class otherOptions(QtGui.QWidget):                                                          
                     break                                                                                      
         if fullScreen:                                                                                         
             self.screenFull.setChecked(1)                                                                      
+            self.prefs[sa] = "All Monitors"                                                                    
         # have to build array then compare... boo                                                              
         else:                                                                                                  
             for id in range(0, QtGui.QDesktopWidget().numScreens()):                                           
@@ -165,7 +314,11 @@ class otherOptions(QtGui.QWidget):                                                         
                             break                                                                              
                 if valid:                                                                                      
                     self.displays[id].setChecked(1)                                                            
+                    self.prefs[sa] = "Monitor %d" % id                                                         
                     break                                                                                      
+        # Default to all screens                                                                               
+        self.screenFull.setChecked(1)                                                                          
+        self.prefs[sa] = "All Monitors"                                                                        

     def setDevices(self, devices):                                                                             
@@ -180,6 +333,7 @@ class otherOptions(QtGui.QWidget):                                                          
             self.tabletActiveArea = "HEAD-0"                                                                   
         for device in self.devices:                                                                            
             if device != "pad" and device != 'touch':                                                          
+                setCommands.append("xsetwacom --set \"%s %s\" Area %s" % (self.deviceNames[device], device, self.tabletAspectArea))                                                                                            
                 if 'HEAD' in self.tabletActiveArea:                                                            
                     setCommands.append("xsetwacom --set \"%s %s\" MapToOutput %s" %                            
                                        (self.deviceNames[device], device, self.tabletActiveArea))              
@@ -197,11 +351,16 @@ class otherOptions(QtGui.QWidget):                                                        
         self.orient = "xsetwacom --set \"%s stylus\" Rotate none" % self.deviceNames['stylus']                 
         self.orient += "\nxsetwacom --set \"%s eraser\" Rotate none" % self.deviceNames['eraser']              
         self.tabletRight.setChecked(1)                                                                         
+        self.prefs['Tablet Orientation'] = "Right-Handed"                                                      
         os.popen(self.orient)                                                                                  
         for device in self.devices:                                                                            
             if device != "pad":                                                                                
                 cmd = "xinput set-prop \"%s %s\" --type=float \"Coordinate Transformation Matrix\" 1 0 0 0 1 0 0 0 1" \                                                                                                        
                       % (self.deviceNames[device], device)                                                     
+                cmd += "\nxsetwacom --set \"%s %s\" Area -1 -1 -1 -1" % (self.deviceNames[device], device)     
                 setCommand = os.popen(cmd)                                                                     
         if self.screenFull is not None:                                                                        
             self.screenFull.setChecked(True)                                                                   
+        self.prefs['Screen Area'] = "All Monitors"                                                             
+        self.prefs['Aspect Mapping'] = "None"                                                                  
+        self.aspectNone.setChecked(1)                                                                          
--- ./wacom-gui/pad.py.dist     2018-06-19 16:08:27.000000000 +0100                                             
+++ ./wacom-gui/pad.py  2018-07-31 12:05:57.344886918 +0100                                                     
@@ -7,6 +7,7 @@ import distutils.dir_util                                                                       
 import shutil                                                                                                  
 import os                                                                                                      
 import re                                                                                                      
+import json                                                                                                    
 from os.path import expanduser                                                                                 
 from PyQt4 import QtCore, QtGui                                                                                
 from wacom_data import tabletidentities                                                                        
@@ -115,6 +116,19 @@ class Pad(QtGui.QWidget):                                                                  
             QtGui.QMessageBox.warning(w, "Information", label)                                                 
             w.show()                                                                                           

+        prefsFile = "%s/.wacom-gui/%s/prefs.json" % (expanduser("~"), self.Tablet.devID)                       
+        if os.path.exists(prefsFile) and os.access(prefsFile, os.R_OK):                                        
+            try:                                                                                               
+                with open(prefsFile, 'r') as f:                                                                
+                    self.Tablet.prefs = json.load(f)                                                           
+            except:                                                                                            
+                # ignore problems with the prefs file for now ...                                              
+                self.Tablet.prefs = {}                                                                         
+        else:                                                                                                  
+            self.Tablet.prefs = {}                                                                             
+                                                                                                               
+        self.Tablet.prefsFile = prefsFile                                                                      
+                                                                                                               
         opPath = os.path.dirname(os.path.realpath(__file__))                                                   

         self.setWindowIcon(QtGui.QIcon(opPath + '/images/wacom-gui.svg'))                                      
@@ -223,6 +237,8 @@ class Pad(QtGui.QWidget):                                                                   
                 return item                                                                                    
         return self.TabletIds.Tablets[len(self.TabletIds.Tablets)-1]                                           

+    def getTabletPrefs(self):                                                                                  
+        return self.Tablet.prefs                                                                               

     def getTabletName(self):                                                                                   
         return self.Tablet.Name
--- ./wacom-gui/wacom-gui.py.dist       2018-06-19 16:08:27.000000000 +0100
+++ ./wacom-gui/wacom-gui.py    2018-07-31 12:05:57.343886905 +0100
@@ -8,6 +8,7 @@ import os
 import time
 # import threading
 import subprocess
+import json
 from os.path import expanduser
 from PyQt4 import QtCore, QtGui

@@ -31,7 +32,7 @@ class WacomGui(QtGui.QWidget):
         self.eraserControl = pressure(self.pad.Tablet.deviceNames['eraser'], 'eraser')
         self.cursorControl = pressure(self.pad.Tablet.deviceNames['cursor'], 'cursor')
         self.touch = touch(self.pad.Tablet.deviceNames['touch'])
-        self.options = otherOptions(self.pad.Tablet.deviceNames)
+        self.options = otherOptions(self.pad.Tablet.deviceNames, self.pad.Tablet.prefs)
         self.help = Help()
         self.setMaximumSize(1000, 500)
         self.setMinimumSize(1000, 500)
@@ -211,6 +212,9 @@ class WacomGui(QtGui.QWidget):
             config.close()
             os.chmod(self.pad.Tablet.config, 0774)

+        with open(self.pad.Tablet.prefsFile, 'w') as f:
+            json.dump(self.pad.Tablet.prefs, f, indent=4)
+
     def closeEvent(self, event):
         reply = QtGui.QMessageBox.question(self, 'Message',
                                            "Quit and write config file?",
tb2097 commented 6 years ago

This patch actually uses the mapping method I had used previously before mapping to a specific input device. If the monitors are not laid out evenly (ie. portait monitor is centered against the landscape monitor) you end up with an edge case where the wacom starts to freak out. I've started looking at another way to solve this but I'm still hitting the edge case. I'm gone for a week but I'll keep poking at it when I get back.

jcpearson commented 6 years ago

What is the mapping method in the code that causes this edge case? - and what does the wacom do in this case?

I wasn't aware I was using any mapping in the patch that isn't already used in the code?

I didn't actually try with monitors is the set up you mention - but I will do when I get time ...

jcpearson commented 6 years ago

I tried setting up the dual monitor layout you suggested (portrait monitor centred against a landscape monitor) - and my aspect mapping changes appeared to work as expected ... I didn't have any weird problems with the wacom ?

jcpearson commented 6 years ago

Looks like I mucked up the commands saved with this patch - which I've now fixed and attached as a replacement patch - aspect-mapping-v2.patch.gz

tb2097 commented 5 years ago

Closing this issue as the feature is now part of the 3.0.x release series. If any issues arise with the feature please open a new ticket.