├── .gitignore ├── README.md └── pixel-cleanup.sketchplugin └── Contents └── Sketch ├── cleanup.cocoascript └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pixel Cleanup For Sketch 2 | 3 | - This repository is no longer supported! Please feel free to fork and update if it isn't working with your version of Sketch. 4 | 5 | One of the best things about Sketch is having your designs be pixel perfect. However, there are a variety of things that can end up with pesky portion-of-a-pixel positioning. 6 | 7 | This beta plugin will round all of your pixels to whole pixels. 8 | 9 | It works well for the following: 10 | 11 | 1. Lines 12 | 1. Rectangles 13 | 1. Circles 14 | 1. Groups 15 | 1. Nested groups 16 | 17 | Working but not extensively tested: 18 | 19 | 2. Symbols 20 | 2. Complex paths 21 | 22 | Untested: 23 | 24 | 3. Images 25 | 3. Extensive edge cases such as scaling + rotation. 26 | 3. Compound shapes 27 | 3. Masks 28 | 3. Anything not listed above 29 | 30 | 31 | It may work with others, but I haven't tested it fully. 32 | 33 | ### Instructions 34 | 35 | 1. Select 1 or more layers or groups 36 | 2. Press the keyboard shortcut`option-command-p` 37 | 3. Bask in the whole pixel glory. 38 | 39 | ### Things To Know 40 | • If a shape is in a group, it is possible that `both the shape, and the group itself may have been positioned\shaped at partial pixels`. 41 | 42 | If you do select a shape in a group and run the command, it will adjust all parent groups and the shape, but it will not not automatically adjust the other sibling shapes. It's easy enough to add that as a feature, but I wanted to avoid unintended consequences. Sometimes you really do mean to have partial pixel placement. 43 | 44 | • If you select a group and run the command this will clean up `the group AND all of its children, and all sub groups, and all of THEIR children` 45 | 46 | Note: In Sketch, the inspector doesn't instantly update refresh when these properties are changed, so though things will move correctly, you have to deselect/reselect to see the change in the inspector. I didn't auto-deselect so you wouldn't loose your initial selection. 47 | 48 | All feedback and issues are welcome! 49 | Happy pixel hunting! 50 | #### Obligitory Warning 51 | This is a beta. Use at your own risk. 52 | It's generally good practice anyway but use version control before trying this out on an important document 53 | 54 | 55 | -------------------------------------------------------------------------------- /pixel-cleanup.sketchplugin/Contents/Sketch/cleanup.cocoascript: -------------------------------------------------------------------------------- 1 | var onRun = function (context) { 2 | var doc = context.document; 3 | var selection = context.selection; 4 | 5 | var artboard = doc.currentPage().currentArtboard() 6 | 7 | for (var i = 0; i < selection.count(); i++) { 8 | var layer = selection[i]; 9 | 10 | cleanup(layer); 11 | var parentGroup = findGroupForLayer(layer); 12 | 13 | if (parentGroup) { 14 | cleanupGroup(parentGroup); 15 | } 16 | doc.currentPage.deselectAllLayers(); 17 | context.document.currentPage().deselectAllLayers(); 18 | selection[i].select_byExpandingSelection(true, true); 19 | } 20 | 21 | function cleanup(toClean){ 22 | if (toClean.isLine()) { 23 | log("cleaning Line " + toClean)); 24 | 25 | cleanupLine(toClean); 26 | }else{ 27 | log("cleaning Group" + toClean)); 28 | cleanupGroup(toClean); 29 | if(toClean && toClean.isKindOfClass(MSLayerGroup)){ 30 | for (var z = 0; z < toClean.containedLayersCount(); z++) { 31 | var anItem = toClean.layerAtIndex(z); 32 | if (anItem != toClean) { 33 | cleanup(anItem); 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | //returns nil for artboard 42 | function findGroupForLayer(toFind){ 43 | var parent_layer; 44 | 45 | var isTopmost = layerIsInGroup(toFind, artboard); 46 | 47 | if (!isTopmost) { 48 | for(var i=0; i < artboard_layers.length(); i++){ 49 | var temp_layer = artboard_layers.objectAtIndex(i); 50 | if (temp_layer.children().indexOf(toFind)) { 51 | parent_layer = temp_layer; 52 | // log("Layer is one of the children!" + temp_layer); 53 | // log ("Layers: " + temp_layer.layers()); 54 | layerIsInGroup(toFind, parent_layer); 55 | } 56 | } 57 | log("Parent = " + parent_layer); 58 | } 59 | return parent_layer; 60 | } 61 | 62 | function layerIsInGroup(toFind, inGroup){ 63 | var layers = inGroup.layers(); 64 | var isFound = false; 65 | for (var k = 0; k < layers.count(); k++) { 66 | // log(layers.objectAtIndex(k)); 67 | if (layers.objectAtIndex(k) == toFind) { 68 | isFound = true; 69 | } 70 | } 71 | if (!isFound){ 72 | log("Nested more than one layer deep though"); 73 | }else{ 74 | log("Single Group Deep"); 75 | } 76 | return isFound; 77 | } 78 | 79 | function cleanupGroup(shapeGroup){ 80 | var aFrame = shapeGroup.frame(); 81 | shapeGroup.frame = MSRect.rectWithRect(NSMakeRect(Math.round(aFrame.x()), Math.round(aFrame.y()), Math.round(aFrame.width()), Math.round(aFrame.height()))); 82 | 83 | } 84 | 85 | function cleanupLine(shapeGroup){ 86 | var thickness = shapeGroup.style().borders()[0].thickness(); 87 | var thickCenter = (thickness%2)/2; 88 | shapeGroup.x1 = (shapeGroup.x1()%1 != thickCenter) ? Math.round(shapeGroup.x1())+thickCenter : shapeGroup.x1(); 89 | shapeGroup.x2 = (shapeGroup.x2()%1 != thickCenter) ? Math.round(shapeGroup.x2())+thickCenter :shapeGroup.x2() ; 90 | shapeGroup.y1 = (shapeGroup.y1()%1 != thickCenter) ? Math.round(shapeGroup.y1())+thickCenter : shapeGroup.y1(); 91 | shapeGroup.y2 = (shapeGroup.y2()%1 != thickCenter) ? Math.round(shapeGroup.y2())+thickCenter : shapeGroup.y2() 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /pixel-cleanup.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pixel Cleanup", 3 | "description": "Turn all of those pesky partial pixels into the real deal.", 4 | "author": "Avery Bloom", 5 | "homepage": "https://github.com/swiadek/pixel-perfecter-sketch-plugin", 6 | "version": 0.5, 7 | "identifier": "com.wearestack.com.sketch-cleanup", 8 | "compatibleVersion": 3.4, 9 | "bundleVersion": 1, 10 | "commands": [ 11 | { 12 | "name": "Cleanup Pixel Placement", 13 | "identifier": "cleanup", 14 | "shortcut": "cmd alt p", 15 | "script": "cleanup.cocoascript" 16 | } 17 | 18 | ], 19 | "menu": { 20 | "items": [ 21 | "cleanup" 22 | ] 23 | } 24 | } 25 | --------------------------------------------------------------------------------