├── Place Linked Bitmap.sketchplugin └── Contents │ ├── Resources │ └── place-linked-bitmap.png │ └── Sketch │ ├── PlaceLinkedBitmap.js │ ├── handlers.cocoascript │ └── manifest.json ├── README.md └── appcast.xml /Place Linked Bitmap.sketchplugin/Contents/Resources/place-linked-bitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frankko/Place-Linked-Bitmap/b3afd8345dfa44c4f9ae5eacc9a1044ab303474e/Place Linked Bitmap.sketchplugin/Contents/Resources/place-linked-bitmap.png -------------------------------------------------------------------------------- /Place Linked Bitmap.sketchplugin/Contents/Sketch/PlaceLinkedBitmap.js: -------------------------------------------------------------------------------- 1 | const sketch = require("sketch"); 2 | const Image = require('sketch/dom'); 3 | 4 | var PlaceLinkedBitmap = { 5 | "addFillToShapeLayer": function(context,layer,url) { 6 | var doc = context.document; 7 | 8 | var filePath = url.toString(); 9 | filePath = filePath.replace("file:///","/"); 10 | filePath = this.util.decodeString(filePath); 11 | 12 | var imageData = [[NSImage alloc] initWithContentsOfFile:filePath]; 13 | var newImage = [[MSImageData alloc] initWithImage:imageData]]; 14 | 15 | var fill = layer.style().fills().firstObject(); 16 | [fill setImage:newImage]; 17 | [fill setFillType:4]; 18 | [fill setPatternFillType:1]; 19 | return layer; 20 | }, 21 | "findAllTaggedLayers": function(context,layers) { 22 | var command = context.command; 23 | var foundLayers = []; 24 | 25 | for (var i = 0; i < [layers count]; i++) { 26 | var layer = layers[i]; 27 | if ([command valueForKey:"originalURL" onLayer:layer]) { 28 | foundLayers.push(layer); 29 | } 30 | } 31 | return foundLayers; 32 | }, 33 | "findBitmapTaggedLayers": function(context,layers) { 34 | var command = context.command; 35 | var foundLayers = []; 36 | 37 | for (var i = 0; i < [layers count]; i++) { 38 | var layer = layers[i]; 39 | if ([command valueForKey:"originalURL" onLayer:layer] && ([layer className] == "MSBitmapLayer")) { 40 | foundLayers.push(layer); 41 | } 42 | } 43 | return foundLayers; 44 | }, 45 | "findFillTaggedLayers": function(context,layers) { 46 | var command = context.command; 47 | var foundLayers = []; 48 | 49 | for (var i = 0; i < [layers count]; i++) { 50 | var layer = layers[i]; 51 | if ([command valueForKey:"originalURL" onLayer:layer] && (([layer className] == "MSShapeGroup") || ([layer className] == "MSShapePathLayer") || ([layer className] == "MSOvalShape") || ([layer className] == "MSRectangleShape"))) { 52 | foundLayers.push(layer); 53 | } 54 | } 55 | return foundLayers; 56 | }, 57 | "getFilenameFromLocalURL": function(tmpFilePath) { 58 | filePath = tmpFilePath.toString(); 59 | filePath = filePath.replace("file:///",""); 60 | var filename = filePath.split('/').pop(); 61 | 62 | return filename; 63 | }, 64 | "getDirFromLocalURL": function(tmpFilePath,addProtocol) { 65 | filePath = tmpFilePath.toString(); 66 | filePath = filePath.replace("file:///",""); 67 | var filePathParts = filePath.split('/'); 68 | filePathParts.pop(); 69 | 70 | var newFilePath = '/' + filePathParts.join('/') + '/'; 71 | 72 | if (addProtocol) { 73 | newFilePath = 'file://' + newFilePath; 74 | } 75 | 76 | return newFilePath; 77 | }, 78 | "getRelativeDir": function(fileURL,tmpDocDir) { 79 | var fileName = this.getFilenameFromLocalURL(fileURL,false).toString(); 80 | var fileDir = this.getDirFromLocalURL(fileURL,true).toString(); 81 | var docDir = tmpDocDir.toString(); 82 | 83 | var newPath = fileDir; 84 | 85 | if (newPath.indexOf(docDir) != -1) { 86 | newPath = newPath.replace(docDir,'./'); 87 | } 88 | 89 | var newURL = newPath + fileName; 90 | return newURL; 91 | }, 92 | "expandRelativePath": function(tmpFileURL,tmpDocDir) { 93 | var docDir = tmpDocDir.toString(); 94 | var fileURL = tmpFileURL.toString(); 95 | 96 | if (fileURL.indexOf('./') != -1) { 97 | fileURL = fileURL.replace('./',docDir); 98 | } 99 | 100 | return fileURL; 101 | }, 102 | "makeBitmapLayer": function(container,name,url) { 103 | var filePath = url.toString(); 104 | filePath = filePath.replace("file:///","/"); 105 | filePath = this.util.decodeString(filePath); 106 | 107 | var newImage = [[NSImage alloc] initWithContentsOfFile:filePath]; 108 | var imageData = [[MSImageData alloc] initWithImage:newImage]]; 109 | var layer = [[MSBitmapLayer alloc] initWithFrame:NSZeroRect image:imageData]; 110 | 111 | if (layer == nil) { 112 | } else { 113 | [container addLayers:[layer]]; 114 | layer.name = name; 115 | } 116 | 117 | return layer; 118 | }, 119 | "makeLayerName": function(filePath,docPath) { 120 | var filename = this.getFilenameFromLocalURL(filePath); 121 | return PLB_layer_prefix + this.util.decodeString(filename); 122 | }, 123 | "openPanel": function(filePath,message,prompt,title) { 124 | var openPanel = [NSOpenPanel openPanel]; 125 | [openPanel setMessage:message]; 126 | [openPanel setPrompt:prompt]; 127 | [openPanel setTitle:title]; 128 | [openPanel setCanCreateDirectories:false]; 129 | [openPanel setCanChooseFiles:true]; 130 | [openPanel setCanChooseDirectories:false]; 131 | [openPanel setAllowsMultipleSelection:false]; 132 | [openPanel setShowsHiddenFiles:false]; 133 | [openPanel setExtensionHidden:false]; 134 | [openPanel setDirectoryURL:[NSURL fileURLWithPath:filePath]]]; 135 | [[NSApplication sharedApplication] activateIgnoringOtherApps:true]; 136 | var openPanelButtonPressed = [openPanel runModal]; 137 | if (openPanelButtonPressed == NSFileHandlingPanelOKButton) { 138 | selectedFile = [openPanel URL]; 139 | return selectedFile; 140 | } else { 141 | return false; 142 | } 143 | }, 144 | "openPanelMultiple": function(filePath,message,prompt,title) { 145 | var openPanel = [NSOpenPanel openPanel]; 146 | [openPanel setMessage:message]; 147 | [openPanel setPrompt:prompt]; 148 | [openPanel setTitle:title]; 149 | [openPanel setCanCreateDirectories:false]; 150 | [openPanel setCanChooseFiles:true]; 151 | [openPanel setCanChooseDirectories:false]; 152 | [openPanel setAllowsMultipleSelection:true]; 153 | [openPanel setShowsHiddenFiles:false]; 154 | [openPanel setExtensionHidden:false]; 155 | [openPanel setDirectoryURL:[NSURL fileURLWithPath:filePath]]]; 156 | [[NSApplication sharedApplication] activateIgnoringOtherApps:true]; 157 | var openPanelButtonPressed = [openPanel runModal]; 158 | if (openPanelButtonPressed == NSFileHandlingPanelOKButton) { 159 | selectedFile = [openPanel URLs]; 160 | return selectedFile; 161 | } else { 162 | return false; 163 | } 164 | }, 165 | "updateBitmapLayer": function(context,layer,url) { 166 | var filePath = url.toString(); 167 | filePath = filePath.replace("file:///","/"); 168 | 169 | let jsImageLayer = Image.fromNative(layer); 170 | jsImageLayer.image = filePath; 171 | }, 172 | "updateShapeLayer": function(context,layer,url) { 173 | var filePath = url.toString(); 174 | filePath = filePath.replace("file:///","/"); 175 | 176 | var imageData = [[NSImage alloc] initWithContentsOfFile:filePath]; 177 | var newImage = [[MSImageData alloc] initWithImage:imageData]]; 178 | 179 | var fill = layer.style().fills().firstObject(); 180 | [fill setImage:newImage]; 181 | [fill setFillType:4]; 182 | [fill setPatternFillType:1]; 183 | }, 184 | "util": { 185 | "encodeString": function(tempString) { 186 | var inputNSString = [[NSString alloc] initWithString:tempString]; 187 | var encodedNSString = [inputNSString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 188 | return encodedNSString.toString(); 189 | }, 190 | "decodeString": function(tempString) { 191 | var inputNSString = [[NSString alloc] initWithString:tempString]; 192 | var decodedNSString = [inputNSString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 193 | return decodedNSString.toString(); 194 | }, 195 | "displayAlert": function(title,text) { 196 | var app = [NSApplication sharedApplication]; 197 | [app displayDialog:text withTitle:title]; 198 | }, 199 | "displayPrompt": function(doc,text,initialValue) { 200 | var capturedInput = [doc askForUserInput:text initialValue:initialValue]; 201 | return capturedInput; 202 | }, 203 | "displayMessage": function(doc,text) { 204 | [doc showMessage:text]; 205 | }, 206 | "dumpObj": function(obj) { 207 | log("#####################################################################################") 208 | log("## Dumping object " + obj ) 209 | log("## obj class is: " + [obj className]) 210 | log("#####################################################################################") 211 | 212 | log("obj.properties:") 213 | log([obj class].mocha().properties()) 214 | log("obj.propertiesWithAncestors:") 215 | log([obj class].mocha().propertiesWithAncestors()) 216 | 217 | log("obj.classMethods:") 218 | log([obj class].mocha().classMethods()) 219 | log("obj.classMethodsWithAncestors:") 220 | log([obj class].mocha().classMethodsWithAncestors()) 221 | 222 | log("obj.instanceMethods:") 223 | log([obj class].mocha().instanceMethods()) 224 | log("obj.instanceMethodsWithAncestors:") 225 | log([obj class].mocha().instanceMethodsWithAncestors()) 226 | 227 | log("obj.protocols:") 228 | log([obj class].mocha().protocols()) 229 | log("obj.protocolsWithAncestors:") 230 | log([obj class].mocha().protocolsWithAncestors()) 231 | 232 | log("obj.treeAsDictionary():") 233 | log(obj.treeAsDictionary()) 234 | }, 235 | "reloadInspector": function(doc) { 236 | [doc reloadInspector]; 237 | }, 238 | "sendAction": function(context,commandToPerform) { 239 | var doc = context.document; 240 | try { 241 | [NSApp sendAction:commandToPerform to:nil from:doc]; 242 | } catch(e) { 243 | log(e); 244 | } 245 | }, 246 | "URLtoFilePath": function(url) { 247 | var filePath = url.toString(); 248 | filePath = filePath.replace("file:///","/"); 249 | filePath = this.util.decodeString(filePath); 250 | return filePath; 251 | } 252 | } 253 | }; -------------------------------------------------------------------------------- /Place Linked Bitmap.sketchplugin/Contents/Sketch/handlers.cocoascript: -------------------------------------------------------------------------------- 1 | @import "PlaceLinkedBitmap.js" 2 | 3 | var PLB_layer_prefix = '@: '; 4 | 5 | var PLB_find_all_tagged_layers = function(context) { 6 | var doc = context.document; 7 | var plugin = context.plugin; 8 | var command = context.command; 9 | 10 | var page = [doc currentPage]; 11 | var layers = [page children]; 12 | 13 | var foundLayers = PlaceLinkedBitmap.findAllTaggedLayers(context,layers); 14 | log(foundLayers); 15 | }; 16 | 17 | var PLB_place_bitmap_as_new_layer = function(context) { 18 | var doc = context.document; 19 | var plugin = context.plugin; 20 | var command = context.command; 21 | 22 | var page = [doc currentPage]; 23 | var selection = context.selection; 24 | 25 | if ([doc fileURL]) { 26 | var docDir = PlaceLinkedBitmap.getDirFromLocalURL([doc fileURL],true); 27 | var group; 28 | 29 | if ([selection count] > 0) { 30 | var sel = [selection objectAtIndex:0]; 31 | if ([sel className] == "MSArtboardGroup") { 32 | group = sel; 33 | } else { 34 | group = [sel parentGroup]; 35 | } 36 | } else { 37 | group = page; 38 | } 39 | 40 | var selectedBitmapURL = PlaceLinkedBitmap.openPanelMultiple(docDir,"Select a bitmap file to place…","Select","Place Bitmap"); 41 | if ([selectedBitmapURL count] > 0) { 42 | for (x = 0; x < [selectedBitmapURL count]; x++) { 43 | var thisBitmap = [selectedBitmapURL objectAtIndex:x]; 44 | var relativeURL = PlaceLinkedBitmap.getRelativeDir([thisBitmap absoluteString],docDir); 45 | var layerName = PlaceLinkedBitmap.makeLayerName(relativeURL,[[doc fileURL] absoluteString]); 46 | var imageLayer = PlaceLinkedBitmap.makeBitmapLayer(group,layerName,thisBitmap); 47 | log("imageLayer:"); 48 | log(imageLayer); 49 | [command setValue:relativeURL forKey:"originalURL" onLayer:imageLayer]; 50 | log("originalURL → " + [command valueForKey:"originalURL" onLayer:imageLayer]); 51 | [imageLayer select:true byExpandingSelection:false]; 52 | } 53 | } 54 | } else { 55 | PlaceLinkedBitmap.util.displayAlert("Place Linked Bitmap…","Please save your Sketch document before placing a bitmap."); 56 | } 57 | }; 58 | 59 | var PLB_place_bitmap_as_fill = function(context) { 60 | var doc = context.document; 61 | var plugin = context.plugin; 62 | var command = context.command; 63 | 64 | var page = [doc currentPage]; 65 | var selection = context.selection; 66 | 67 | if ([doc fileURL]) { 68 | var docDir = PlaceLinkedBitmap.getDirFromLocalURL([doc fileURL],true); 69 | 70 | if ([selection count] > 0) { 71 | var validLayers = []; 72 | for (x = 0; x < [selection count]; x++) { 73 | var layer = [selection objectAtIndex:x]; 74 | if (([layer className] == "MSShapeGroup") || ([layer className] == "MSShapePathLayer") || ([layer className] == "MSOvalShape") || ([layer className] == "MSRectangleShape")) { 75 | validLayers.push(layer); 76 | } 77 | } 78 | if (validLayers.length > 0) { 79 | var selectedBitmapURL = PlaceLinkedBitmap.openPanel(docDir,"Select a bitmap file to place…","Select","Place Bitmap"); 80 | if (selectedBitmapURL) { 81 | var relativeURL = PlaceLinkedBitmap.getRelativeDir([selectedBitmapURL absoluteString],docDir); 82 | for (x = 0; x < validLayers.length; x++) { 83 | var fillLayer = PlaceLinkedBitmap.addFillToShapeLayer(context,validLayers[x],selectedBitmapURL); 84 | [command setValue:relativeURL forKey:"originalURL" onLayer:fillLayer]; 85 | log("originalURL → " + [command valueForKey:"originalURL" onLayer:fillLayer]); 86 | [fillLayer select:true byExpandingSelection:false]; 87 | } 88 | } 89 | } else { 90 | PlaceLinkedBitmap.util.displayAlert("Place Linked Bitmap…","Please select a shape layer to fill."); 91 | } 92 | } else { 93 | PlaceLinkedBitmap.util.displayAlert("Place Linked Bitmap…","Please select a shape layer to fill."); 94 | } 95 | } else { 96 | PlaceLinkedBitmap.util.displayAlert("Place Linked Bitmap…","Please save your Sketch document before placing a bitmap."); 97 | } 98 | }; 99 | 100 | var PLB_update_bitmaps = function(context) { 101 | var doc = context.document; 102 | var plugin = context.plugin; 103 | var command = context.command; 104 | 105 | var page = [doc currentPage]; 106 | var selection = context.selection; 107 | 108 | var docDir = PlaceLinkedBitmap.getDirFromLocalURL([doc fileURL],true); 109 | 110 | var allLayers = [page children]; 111 | var validLayers = PlaceLinkedBitmap.findAllTaggedLayers(context,allLayers); 112 | 113 | if (validLayers.length > 0) { 114 | for (var x = 0; x < validLayers.length; x++) { 115 | var layer = validLayers[x]; 116 | if ([command valueForKey:"originalURL" onLayer:layer]) { 117 | var taggedFileURL = [command valueForKey:"originalURL" onLayer:layer]; 118 | var fileURL = PlaceLinkedBitmap.expandRelativePath(taggedFileURL,docDir); 119 | var fileDir = PlaceLinkedBitmap.getDirFromLocalURL(fileURL,false); 120 | var fileURLUnix = fileURL.toString().replace('file:///','/'); 121 | fileURLUnix = PlaceLinkedBitmap.util.decodeString(fileURLUnix); 122 | 123 | var fileExists = [[NSFileManager defaultManager] fileExistsAtPath:fileURLUnix]; 124 | 125 | if (fileExists) { 126 | var fileURLEncoded = fileURL; 127 | if ([layer className] == "MSBitmapLayer") { 128 | var layerFrame = [layer frame]; 129 | var layerFrameX = [layerFrame x]; 130 | var layerFrameY = [layerFrame y]; 131 | var layerFrameW = [layerFrame width]; 132 | var layerFrameH = [layerFrame height]; 133 | var layerIsConstrained = false; 134 | PlaceLinkedBitmap.updateBitmapLayer(context,layer,fileURLUnix); 135 | [[layer frame] setX:layerFrameX]; 136 | [[layer frame] setY:layerFrameY]; 137 | /* 138 | if ([layer constrainProportions] == true) { 139 | layerIsConstrained = true; 140 | } 141 | */ 142 | 143 | layer.constrainProportions = false; 144 | [[layer frame] setWidth:layerFrameW]; 145 | [[layer frame] setHeight:layerFrameH]; 146 | if (layerIsConstrained){ 147 | // layer.constrainProportions = true; 148 | } 149 | log(fileURLUnix + " updated"); 150 | } else if (([layer className] == "MSShapeGroup") || ([layer className] == "MSShapePathLayer") || ([layer className] == "MSOvalShape") || ([layer className] == "MSRectangleShape")) { 151 | PlaceLinkedBitmap.updateShapeLayer(context,layer,fileURLUnix); 152 | log(fileURLUnix + " updated"); 153 | } 154 | } else { 155 | log(fileURLUnix + " not found, skipping"); 156 | } 157 | } 158 | } 159 | } else { 160 | PlaceLinkedBitmap.util.displayAlert("Update Bitmaps","No bitmaps to update."); 161 | } 162 | }; 163 | 164 | var PLB_replace_bitmap = function(context) { 165 | var doc = context.document; 166 | var page = [doc currentPage]; 167 | var selection = context.selection; 168 | 169 | log("PLB_replace_bitmap"); 170 | }; 171 | -------------------------------------------------------------------------------- /Place Linked Bitmap.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "appcast": "https://raw.githubusercontent.com/frankko/Place-Linked-Bitmap/master/appcast.xml", 3 | "commands": [ 4 | { 5 | "handler": "PLB_place_bitmap_as_new_layer", 6 | "identifier": "PLB-placebitmapasnewlayer", 7 | "name": "Place Bitmap as New Layer…", 8 | "description": "Add bitmap file(s) as new layer(s).", 9 | "script": "handlers.cocoascript", 10 | "shortcut": "control command i" 11 | }, 12 | { 13 | "handler": "PLB_place_bitmap_as_fill", 14 | "identifier": "PLB-placebitmapasfill", 15 | "name": "Place Bitmap as Layer Fill…", 16 | "description": "Add bitmap file as a fill for selected layer.", 17 | "script": "handlers.cocoascript" 18 | }, 19 | { 20 | "handler": "PLB_find_all_tagged_layers", 21 | "identifier": "PLB-findalltaggedlayers", 22 | "name": "Find All Tagged Layers", 23 | "script": "handlers.cocoascript" 24 | }, 25 | { 26 | "handler": "PLB_update_bitmaps", 27 | "identifier": "PLB-updatebitmaps", 28 | "name": "Update All Bitmaps", 29 | "description": "Update all placed bitmaps.", 30 | "script": "handlers.cocoascript", 31 | "shortcut": "control command u" 32 | } 33 | ], 34 | "icon": "place-linked-bitmap.png", 35 | "menu": { 36 | "items": [ 37 | "PLB-placebitmapasnewlayer", 38 | "PLB-placebitmapasfill", 39 | "PLB-updatebitmaps" 40 | ], 41 | "title": "Place Linked Bitmap" 42 | }, 43 | "identifier": "io.kolo.sketch.place-linked-bitmap", 44 | "author": "Frank Kolodziej", 45 | "authorEmail": "frank@kolo.io", 46 | "compatibleVersion": "99", 47 | "description": "Place external bitmap files into Sketch; update Sketch layers after external bitmaps are updated.", 48 | "name": "Place Linked Bitmap", 49 | "version": "1.99.1" 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Place Linked Bitmap 2 | ## A Plugin for Sketch 99+ 3 | 4 | ### What is this? 5 | 6 | This plugin allows you to take a bitmap file (JPG, PNG, PSD, etc) and place it inside Sketch. “But Sketch can already do that” you might be thinking. It can, but what the plugin adds is the ability to easily update the placed bitmap _after it’s been placed, without having to re-place it_. 7 | 8 | Place a PSD file on an artboard, handle your business in Sketch, then maybe switch to Photoshop to tweak the PSD, then return to Sketch, run the “Update All Bitmaps…” command, and your placed PSD has the changes you just made. 9 | 10 | ### But wait, there’s more… 11 | 12 | If you don’t use Photoshop (or Pixelmator, or Acorn, or etc), I envy you. But you still might want to give this a try, and here’s why: 13 | 14 | As of Sketch 3.4, an artboard can’t be turned into a symbol. You can, of course, export an artboard as a bitmap. So export the artboard and place the bitmap of the artboard in your .sketch file. Then, after you edit and re-export the original artboard, “Update All Bitmaps…” 15 | 16 | ### So How Does This Work? 17 | 18 | Either open an existing .sketch file or create a new one (be sure to save the new document somewhere first). To place a bitmap, you have two options: 19 | 20 | 1. **Place Bitmap as New Layer…** will create a new bitmap layer with the contents of the bitmap file 21 | 2. **Place Bitmap as Layer Fill…** will place the contents of the bitmap file as an image fill on a selected shape layer *[great for avatars and hero images that mimic CSS background-fills]* 22 | 23 | When one of your bitmap files has been updated, run the **“Update All Bitmaps…”** command. 24 | 25 | ### For Best Results 26 | 27 | If you roll solo, like I do, you don’t have too much to worry about. But if you’re part of a team, with lots of people touching the .sketch files, I have two recommendations: 28 | 29 | 1. keep your placed assets in the same directory as your .sketch file, or in a subdirectory of the directory in which your .sketch file lives. 30 | 2. or, if your team works off a file server, keep your placed assets on that server. *[Because, theoretically, the paths to the files will be the same for every person who uses that server.]* 31 | 32 | ### Frequently Asked Questions 33 | 34 | 1. **Can I place Photoshop files?** Yes! That’s actually why I created the plug-in. 35 | 1. **What about vector files?** Sort of. You can do it, but they’ll be rendered as bitmaps, meaning the image can’t be scaled up. 36 | 1. **Okay, what about... a .sketch file?** That would be interesting, but no, that won’t work. 37 | 1. **If I move a bitmap file to a different folder, can I still update it in Sketch?** Not at the moment, no. I’m looking into it. 38 | 1. **If I move a placed bitmap from one artboard to another, will it still update?** Yes. 39 | 1. **What about from one document to another?** Maybe. If both .sketch files are in the same folder, definitely. If they’re not... 40 | 1. **If I copy a placed bitmap from one artboard and paste it into another artboard, will it still update?** Yes, they both will update. 41 | 1. **If the size of my bitmap changes, will the size of the placed bitmap change, too?** Right now, the size of the bitmap layer won’t change. I’m still thinking of ways to make it smarter, based on complicated math and dumb luck. But I don’t have a solution yet. 42 | 1. **Is there any way to have the plugin automatically update a bitmap after it’s changed, like Adobe’s apps can?** At the moment, Sketch plugins can only run when you tell them to. I’ve heard Bohemian Coding might be looking into other events to trigger plugins, but until then, the answer is no, unfortunately. 43 | 1. **I already have a bunch of placed bitmaps in my .sketch file. Will the plugin update them for me?** That would be great, but no. *Bitmaps have to be placed by the plugin*, so that the plugin can store location of the bitmap file. 44 | 45 | ### Roadmap 46 | 47 | - a way to “relink” and/or “replace” a bitmap 48 | - a way to “unlink” a bitmap 49 | 50 | ### Version History 51 | 52 | - **1.99.1** 53 | - Fixes image fills not updating. 54 | - **1.99.0** 55 | - updates for Sketch 99. 56 | - **1.77.0** 57 | - updates for Sketch 77. 58 | - **1.72.0** 59 | - updates for Sketch 72. 60 | - **1.52.0** 61 | - updates for Sketch 52. 62 | - **1.51.0** 63 | - updates for Sketch 51. 64 | - **1.50.0** 65 | - updates for Sketch 50. 66 | - **1.47.1** 67 | - bug fix for Sketch 47. 68 | - **1.43.5** 69 | - Support for Sketch 45’s plugin auto-updating. 70 | - **1.43.2** 71 | - compatible with Sketch 43. 72 | - **1.39.2** 73 | - compatible with Sketch 39 & Sketch 40 beta. 74 | - **1.39.1** 75 | - updated for Sketch 39. **Don’t update if you’re sticking with Sketch 3.8.** 76 | - **0.9.3** 77 | - updated for Sketch 3.8. 78 | - **0.9.2** 79 | - **Place Bitmap as New Layer…** can now select multiple bitmap files at one time to place as multiple layers 80 | - fixes for Sketch 3.5 81 | - **0.9.1** 82 | - **Removed support for the Mac App Store version of Sketch.** 83 | - preserve dimensions for bitmap layers with strange DPIs. (fixes #1) 84 | - **0.9** 85 | - Initial release / Public beta 86 | 87 | * * * 88 | 89 | ### Who? 90 | 91 | I’m Frank Kolodziej, a Wichita, KS-based freelance designer & developer. I am [available for hire](http://kolo.io/). I’m [@frankko](https://twitter.com/frankko) on Twitter. 92 | 93 | #### Other Plugins 94 | 95 | - [★ Utility Belt](https://github.com/frankko/UtilityBelt): An always-expanding collection of simple, focused plugins for Sketch. 96 | - [Artboard Tools](https://github.com/frankko/Artboard-Tools): Plugins for arranging artboards and navigating between artboards. -------------------------------------------------------------------------------- /appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Place Linked Bitmap 5 | https://raw.githubusercontent.com/frankko/Place-Linked-Bitmap/master/appcast.xml 6 | Place external bitmap files into Sketch; update Sketch layers after external bitmaps are updated. 7 | en 8 | 9 | Version 1.99.1 10 | 11 | 13 |
  • 2024-05-17: Fixes image fills not updating.
  • 14 | 15 | ]]> 16 |
    17 | 18 |
    19 | 20 | Version 1.99.0 21 | 22 | 24 |
  • 2024-05-09: Now compatible with Sketch 99+.
  • 25 | 26 | ]]> 27 |
    28 | 29 |
    30 | 31 | Version 1.77.0 32 | 33 | 35 |
  • 2021-09-29: fix for latest versions of Sketch.
  • 36 | 37 | ]]> 38 |
    39 | 40 |
    41 | 42 | Version 1.72.0 43 | 44 | 46 |
  • 2021-05-13: updates for Sketch 72.
  • 47 | 48 | ]]> 49 |
    50 | 51 |
    52 | 53 | Version 1.52.0 54 | 55 | 57 |
  • updates for Sketch 52.
  • 58 | 59 | ]]> 60 |
    61 | 62 |
    63 | 64 | Version 1.51.0 65 | 66 | 68 |
  • updates for Sketch 51.
  • 69 | 70 | ]]> 71 |
    72 | 73 |
    74 | 75 | Version 1.50.0 76 | 77 | 79 |
  • updates for Sketch 50.
  • 80 | 81 | ]]> 82 |
    83 | 84 |
    85 | 86 | Version 1.47.1 87 | 88 | 90 |
  • Bug fix for Sketch 47.
  • 91 | 92 | ]]> 93 |
    94 | 95 |
    96 | 97 | Version 1.43.5 98 | 99 | 101 |
  • Support for Sketch 45's plugin auto-updating.
  • 102 | 103 | ]]> 104 |
    105 | 106 |
    107 |
    108 |
    --------------------------------------------------------------------------------