├── Export-More.sketchplugin └── Contents │ └── Sketch │ ├── GIFX │ ├── Generate.js │ └── Manifest.json ├── Preview.png └── README.md /Export-More.sketchplugin/Contents/Sketch/GIFX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathco/Export-More/c0bfbd60d03b556fc48cf6521b7f745a943dcf2f/Export-More.sketchplugin/Contents/Sketch/GIFX -------------------------------------------------------------------------------- /Export-More.sketchplugin/Contents/Sketch/Generate.js: -------------------------------------------------------------------------------- 1 | // Plugin: Export More 2 | // Source: github.com/nathco/Export-More 3 | // Author: Nathan Rutzky 4 | // Update: 1.5 5 | 6 | function GenerateGIF(context) { 7 | 8 | var doc = context.document 9 | var selection = context.selection 10 | var artboards 11 | 12 | if ([selection count] == 0) { 13 | artboards = [[doc currentPage] artboards] 14 | } else { 15 | artboards = context.selection 16 | } 17 | 18 | if ([artboards count] < 1) { 19 | 20 | var error = [[NSTask alloc] init] 21 | 22 | [error setLaunchPath:@"/bin/bash"] 23 | [error setArguments:["-c", "afplay /System/Library/Sounds/Basso.aiff"]] 24 | [error launch] 25 | [doc showMessage:"Error: Artboard Not Found"] 26 | return false 27 | } 28 | 29 | var menuItems = [NSArray arrayWithArray:['Frame Rate - No Delay','Frame Rate - 100 ms','Frame Rate - 200 ms','Frame Rate - 300 ms','Frame Rate - 400 ms','Frame Rate - 500 ms','Frame Rate - 600 ms','Frame Rate - 700 ms','Frame Rate - 800 ms','Frame Rate - 900 ms','Frame Rate - 1000 ms','Frame Rate - 1500 ms','Frame Rate - 2000 ms','Frame Rate - 3000 ms','Frame Rate - 4000 ms','Frame Rate - 5000 ms']] 30 | var menuPopup = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0,0,300,25)] 31 | menuPopup.addItemsWithTitles(menuItems) 32 | 33 | var alert = NSAlert.alloc().init() 34 | alert.setMessageText('GIF Export Options') 35 | alert.setInformativeText('Generate animated GIF files from a sequence of artboards. Select playback and frame rate.') 36 | alert.addButtonWithTitle('Continue') 37 | alert.addButtonWithTitle('Cancel') 38 | alert.setAccessoryView(menuPopup) 39 | alert.setShowsSuppressionButton(true) 40 | alert.suppressionButton().setTitle('Loop Animation?') 41 | 42 | var response = alert.runModal() 43 | var menuItem = menuPopup.indexOfSelectedItem() 44 | var scriptPath = context.scriptPath 45 | var gifx = [scriptPath stringByDeletingLastPathComponent] + "/GIFX" 46 | var tempPath = NSTemporaryDirectory() 47 | var string = [[NSProcessInfo processInfo] globallyUniqueString] 48 | var gifsetPath = [tempPath stringByAppendingPathComponent: string + @".gifset"] 49 | var fileManager = [NSFileManager defaultManager] 50 | 51 | [fileManager createDirectoryAtPath:gifsetPath withIntermediateDirectories:true attributes:nil error:nil] 52 | 53 | for (var i=0; i < [artboards count]; i++) { 54 | 55 | var artboard = [artboards objectAtIndex:i] 56 | var artboardName = [artboard name] 57 | var fileName = [gifsetPath stringByAppendingPathComponent: artboardName + ".png"] 58 | 59 | if ([artboardName hasSuffix:@"Lock"]) continue 60 | 61 | [doc saveArtboardOrSlice:artboard toFile:fileName] 62 | } 63 | 64 | if (response === NSAlertFirstButtonReturn) { 65 | 66 | gifLoop = alert.suppressionButton().state() == NSOnState? '-l' : ''; 67 | gifPath = savePath() 68 | 69 | if (menuItem == 0) generateFile(0) 70 | if (menuItem == 1) generateFile(10) 71 | if (menuItem == 2) generateFile(20) 72 | if (menuItem == 3) generateFile(30) 73 | if (menuItem == 4) generateFile(40) 74 | if (menuItem == 5) generateFile(50) 75 | if (menuItem == 6) generateFile(60) 76 | if (menuItem == 7) generateFile(70) 77 | if (menuItem == 8) generateFile(80) 78 | if (menuItem == 9) generateFile(90) 79 | if (menuItem == 10) generateFile(100) 80 | if (menuItem == 11) generateFile(150) 81 | if (menuItem == 12) generateFile(200) 82 | if (menuItem == 13) generateFile(300) 83 | if (menuItem == 14) generateFile(400) 84 | if (menuItem == 15) generateFile(500) 85 | 86 | function generateFile(option) { 87 | 88 | var convertTask = [[NSTask alloc] init] 89 | var createsTask = [[NSTask alloc] init] 90 | var convertGIF = "find \"" + gifsetPath + "\" -name '*.png' -exec sips -s format gif --out {}.gif {} \\;" 91 | var createsGIF = "find \"" + gifsetPath + "\" -name '*.png.gif' -execdir bash -c '\"" + gifx + "\" \"" + gifLoop + "\" -d \"" + option + "\" '*.png.gif' --out \"" + gifPath + "\"' \\;" 92 | 93 | [convertTask setLaunchPath:@"/bin/bash"] 94 | [convertTask setArguments:["-c", convertGIF]] 95 | [convertTask launch] 96 | [convertTask waitUntilExit] 97 | [createsTask setLaunchPath:@"/bin/bash"] 98 | [createsTask setArguments:["-c", createsGIF]] 99 | [createsTask launch] 100 | [createsTask waitUntilExit] 101 | 102 | if ([createsTask terminationStatus] == 0) { 103 | 104 | [doc showMessage:@"Export Complete..."] 105 | 106 | } else { 107 | 108 | var error = [[NSTask alloc] init] 109 | 110 | [error setLaunchPath:@"/bin/bash"] 111 | [error setArguments:["-c", "afplay /System/Library/Sounds/Basso.aiff"]] 112 | [error launch] 113 | [doc showMessage:@"Export Failed..."] 114 | } 115 | 116 | [fileManager removeItemAtPath:gifsetPath error:nil] 117 | } 118 | 119 | function savePath() { 120 | 121 | var filePath = [doc fileURL] ? [[[doc fileURL] path] stringByDeletingLastPathComponent] : @"~" 122 | var fileName = [[doc displayName] stringByDeletingPathExtension] 123 | var savePanel = [NSSavePanel savePanel] 124 | 125 | [savePanel setTitle:@"Export Animated GIF"] 126 | [savePanel setNameFieldLabel:@"Export As:"] 127 | [savePanel setPrompt:@"Export"] 128 | [savePanel setAllowedFileTypes: [NSArray arrayWithObject:@"gif"]] 129 | [savePanel setAllowsOtherFileTypes:false] 130 | [savePanel setCanCreateDirectories:true] 131 | [savePanel setDirectoryURL:[NSURL fileURLWithPath:filePath]] 132 | [savePanel setNameFieldStringValue:fileName] 133 | 134 | if ([savePanel runModal] != NSOKButton) exit 135 | 136 | return [[savePanel URL] path] 137 | } 138 | 139 | return [response, menuItem] 140 | } 141 | }; 142 | 143 | function GenerateICNS(context) { 144 | 145 | var doc = context.document 146 | var artboards = [[doc currentPage] artboards] 147 | 148 | if ([artboards count] < 1) { 149 | 150 | var error = [[NSTask alloc] init] 151 | 152 | [error setLaunchPath:@"/bin/bash"] 153 | [error setArguments:["-c", "afplay /System/Library/Sounds/Basso.aiff"]] 154 | [error launch] 155 | [doc showMessage:"Error: Artboard Not Found"] 156 | return false 157 | } 158 | 159 | var menuItems = [[NSArray alloc] initWithObjects:'Automatically','From Sequence'] 160 | var menuPopup = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0,0,300,25)] 161 | menuPopup.addItemsWithTitles(menuItems) 162 | 163 | var alert = NSAlert.alloc().init() 164 | alert.setMessageText('ICNS Export Options') 165 | alert.setInformativeText('Generate ICNS files from a sequence of artboards or automatically from a single artboard.') 166 | alert.addButtonWithTitle('Continue') 167 | alert.addButtonWithTitle('Cancel') 168 | alert.setAccessoryView(menuPopup) 169 | 170 | var response = alert.runModal() 171 | var menuItem = menuPopup.indexOfSelectedItem() 172 | var tempPath = NSTemporaryDirectory() 173 | var string = [[NSProcessInfo processInfo] globallyUniqueString] 174 | var iconsetPath = [tempPath stringByAppendingPathComponent: string + @".iconset"] 175 | var pngPath = [tempPath stringByAppendingPathComponent: string + @".png"] 176 | var fileManager = [NSFileManager defaultManager] 177 | 178 | [fileManager createDirectoryAtPath:iconsetPath withIntermediateDirectories:true attributes:nil error:nil] 179 | 180 | if (response === NSAlertFirstButtonReturn) { 181 | 182 | iconPath = savePath() 183 | 184 | if (menuItem == 0) { 185 | 186 | for (var i=0; i < [artboards count]; i++) { 187 | 188 | if ([[artboards objectAtIndex:i] isSelected]) { 189 | 190 | artboard = [artboards objectAtIndex:i] 191 | break 192 | 193 | } else { 194 | 195 | artboard = [artboards firstObject] 196 | } 197 | } 198 | 199 | [doc saveArtboardOrSlice:artboard toFile:pngPath] 200 | 201 | var pngSize = [NSDictionary dictionaryWithObjectsAndKeys: 202 | @" 16 16 ", @"icon_16x16.png", 203 | @" 32 32 ", @"icon_16x16@2x.png", 204 | @" 32 32 ", @"icon_32x32.png", 205 | @" 64 64 ", @"icon_32x32@2x.png", 206 | @" 128 128 ", @"icon_128x128.png", 207 | @" 256 256 ", @"icon_128x128@2x.png", 208 | @" 256 256 ", @"icon_256x256.png", 209 | @" 512 512 ", @"icon_256x256@2x.png", 210 | @" 512 512 ", @"icon_512x512.png", 211 | @" 1024 1024 ", @"icon_512x512@2x.png", 212 | nil] 213 | var enumerator = [pngSize keyEnumerator] 214 | var png = nil 215 | 216 | while (png = [enumerator nextObject]) { 217 | var convertTask = [[NSTask alloc] init] 218 | var convertIcon = "sips -z" + [pngSize valueForKey:png] + pngPath + " --out " + [iconsetPath stringByAppendingPathComponent:png] 219 | 220 | [convertTask setLaunchPath:"/bin/bash"] 221 | [convertTask setArguments:["-c", convertIcon]] 222 | [convertTask launch] 223 | [convertTask waitUntilExit] 224 | } 225 | 226 | generateIcon(iconsetPath, iconPath) 227 | } 228 | 229 | if (menuItem == 1) { 230 | 231 | for (var i=0; i < [artboards count]; i++) { 232 | 233 | var artboard = [artboards objectAtIndex:i] 234 | var artboardName = [artboard name] 235 | var fileName = [iconsetPath stringByAppendingPathComponent: artboardName + ".png"] 236 | 237 | if ([artboardName hasSuffix:@"Lock"]) continue 238 | 239 | [doc saveArtboardOrSlice:artboard toFile:fileName] 240 | } 241 | 242 | generateIcon(iconsetPath, iconPath) 243 | } 244 | 245 | function generateIcon(iconsetPath, iconPath) { 246 | 247 | var createTask = [[NSTask alloc] init] 248 | var createIcon = "iconutil -c icns \"" + iconsetPath + "\" --out \"" + iconPath + "\"" 249 | 250 | [createTask setLaunchPath:@"/bin/bash"] 251 | [createTask setArguments:["-c", createIcon]] 252 | [createTask launch] 253 | [createTask waitUntilExit] 254 | 255 | if ([createTask terminationStatus] == 0) { 256 | 257 | [doc showMessage:@"Export Complete..."] 258 | 259 | } else { 260 | 261 | var error = [[NSTask alloc] init] 262 | 263 | [error setLaunchPath:@"/bin/bash"] 264 | [error setArguments:["-c", "afplay /System/Library/Sounds/Basso.aiff"]] 265 | [error launch] 266 | [doc showMessage:@"Export Failed..."] 267 | } 268 | 269 | [fileManager removeItemAtPath:iconsetPath error:nil] 270 | [fileManager removeItemAtPath:pngPath error:nil] 271 | } 272 | 273 | function savePath() { 274 | 275 | var filePath = [doc fileURL] ? [[[doc fileURL] path] stringByDeletingLastPathComponent] : @"~" 276 | var fileName = [[doc displayName] stringByDeletingPathExtension] 277 | var savePanel = [NSSavePanel savePanel] 278 | 279 | [savePanel setTitle:@"Export ICNS"] 280 | [savePanel setNameFieldLabel:@"Export As:"] 281 | [savePanel setPrompt:@"Export"] 282 | [savePanel setAllowedFileTypes: [NSArray arrayWithObject:@"icns"]] 283 | [savePanel setAllowsOtherFileTypes:false] 284 | [savePanel setCanCreateDirectories:true] 285 | [savePanel setDirectoryURL:[NSURL fileURLWithPath:filePath]] 286 | [savePanel setNameFieldStringValue:fileName] 287 | 288 | if ([savePanel runModal] != NSOKButton) exit 289 | 290 | return [[savePanel URL] path] 291 | } 292 | 293 | return [response, menuItem] 294 | } 295 | }; -------------------------------------------------------------------------------- /Export-More.sketchplugin/Contents/Sketch/Manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Export More...", 3 | "identifier" : "com.sketchapp.plugin.Export-More", 4 | "version" : "1.5", 5 | "description" : "Adds support for exporting ICNS and animated GIF file formats.", 6 | "authorEmail" : "code@nath.co", 7 | "author" : "Nathan Rutzky", 8 | "commands" : [{ 9 | "script" : "Generate.js", 10 | "handler" : "GenerateGIF", 11 | "shortcut" : "", 12 | "name" : "Artboards to GIF", 13 | "identifier" : "GenerateGIF" 14 | }, { 15 | "script" : "Generate.js", 16 | "handler" : "GenerateICNS", 17 | "shortcut" : "", 18 | "name" : "Artboards to ICNS", 19 | "identifier" : "GenerateICNS" 20 | }] 21 | } 22 | -------------------------------------------------------------------------------- /Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathco/Export-More/c0bfbd60d03b556fc48cf6521b7f745a943dcf2f/Preview.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Export More 2 | Sketch.app plugin adding export support for Apple Icon Image ( .icns ) and Animated GIF ( .gif ) file formats. Based on my previous [Generate ICNS](http://github.com/nathco/Generate-ICNS) and [Generate GIF](http://github.com/nathco/Generate-GIF) plugins. Now compatible with **Mac OS Sierra** and **Sketch 42**. 3 | 4 | ## Preview 5 | ![Preview](Preview.png) 6 | 7 | ## Installation 8 | 1. Download and open `Export-More-master.zip` 9 | 2. Locate and double-click `Export-More.sketchplugin` 10 | 3. Locate the `GIFX` file in the bundle and double-click ( only if plugin fails after install ) 11 | 12 | ## Animated GIF 13 | Create your content on a sequence of artboards using a `XXX 01`, `XXX 02`, `XXX 03` naming convention, where "XXX" is the artboard name. Each artboard acts as an animation keyframe and will play sequentially according to the naming structure. When ready, navigate to `Plugins ▸ Export More ▸ Artboards to GIF ` and select a playback method and frame animation rate ( see options below ). The file will export to the directory of your choosing. 14 | 15 | **Playback Options** 16 | `Default: play animation once` 17 | `Loop: play animation forever` 18 | 19 | **Frame Rate Options** 20 | `No Delay` 21 | `100ms, 200ms, 300ms, 400ms, 500ms, 600ms, 700ms, 800ms, 900ms` 22 | `1000ms, 1500ms, 2000ms, 3000ms, 4000ms, 5000ms` 23 | 24 | **Usage Notes** 25 | If you notice issues with transparency and / or antialiasing, try adding a solid background color to the artboard. You can prevent individual artboards from exporting by appending `Lock` to the name, or selecting only the artboards you need. Frame rate options can be modified by editing the plugin files. 26 | 27 | ## ICNS: Automatically 28 | Create your icon on a single `1024x1024` sized artboard. When ready, navigate to `Plugins ▸ Export More ▸ Artboards to ICNS ` and select `Automatically` from the dropdown menu. The file will export to the directory of your choosing. 29 | 30 | **Sizes Generated** 31 | `16x16, 32x32, 128x128, 256x256, 512x512` 32 | `16x16@2x, 32x32@2x, 128x128@2x, 256x256@2x, 512x512@2x` 33 | 34 | **Usage Notes** 35 | The auto generator will scale-up artboards smaller than `1024x1024` to fulfill the maximum icon size of `512x512@2x`. Be sure to use `1024x1024` for the artboard size, otherwise the hi-res icons will appear blurry. If multiple artboards exist, and none are selected, the first artboard in the layer list will export. 36 | 37 | ## ICNS: From Sequence 38 | Create your icon using the **Mac App Icon** template from `File ▸ New From Template ▸ Mac App Icon` in the application menu bar. When ready, navigate to `Plugins ▸ Export More ▸ Artboards to ICNS ` and select `From Sequence` in the dropdown menu. The file will export to the directory of your choosing. 39 | 40 | **Usage Notes** 41 | The artboard naming structure, for example: `icon_32x32` and `icon_32x32@2x`, must remain intact for the generator to work properly. You can prevent individual artboards from exporting by appending `Lock` to the name. 42 | 43 | ## Release Notes 44 | 45 | **Export More 1.5** 46 | - Fixed an issue where exports failed on High Sierra 47 | 48 | **Export More 1.4** 49 | - Fixed an issue where exports failed on Sierra 50 | 51 | **Export More 1.3** 52 | - Added option to export GIFs from selected or all artboards 53 | 54 | **Export More 1.2** 55 | - Added more frame rate options 56 | - Added checkbox for playback option ( loop or once ) 57 | - Minor code cleanup and fixes 58 | 59 | **Export More 1.1** 60 | - Fixed export options `Cancel` button 61 | 62 | **Export More 1.0** 63 | - Initial Release 64 | 65 | ## Feedback 66 | If you discover any issues or have questions regarding usage, please send a message to [code@nath.co](mailto:code@nath.co) or find me on GitHub [@nathco](https://github.com/nathco). --------------------------------------------------------------------------------