Skip to main content

Filter by idea status

10000+ Ideas

RyanAlbrachtParticipant

Tree Scatter Tool - (Plan Renderings)Open for Voting

Why?For plan renderings, I currently use tree/forest scatter brushes with layer styles applied, but this method doesn't look as great due to not having the shadow in the tree canopy. I thought about having a pattern overlay that incorporates trees canopy shadows, but then you have to get precise with tree brush placements on the edge of forests to match up with the texture and if needing to change the shadow direction, the pattern overlay would need to be rotated too and the forest border would need to be refined.General SolutionCreate a tool that can scatter trees on individual layers so bevel and emboss can be applied to each canopy vs an entire forest canopy mass. Downside is it's processing intensive and if there are significant design updates, the quickest thing would be to delete all trees and run the script again so the script needs to be efficient to run on your average laptop. There would be a "forest area" layer that is just a solid fill for where trees should be scattered/spawned so that border can be easily selected to run the script. What I've done so farUsed AI to create scripts and experimented with different methods to try and get desired results with optimization. I first tried to randomly place trees as smart objects, but couldn't get the script to respect a pre-selected forest area very well and it was very slow. Then tried a grid sample approach if a point was in the selected area to spawn trees, this was slow and I let my pc process overnight, but never saw results. Lastly, switched to a stamp method (see attached image and script) which works alright but it didn't achieve the desired density and was relatively slow since it has to open and close a tree.png for every tree placement which could be hundreds of times, which is not ideal and some trees are too overlapped. I tried improving it to increase density, reduce overlap, and even open all the tree .pngs first and/or add them to the same .psd and reference them for better performance but couldn't get those options to really work. Another solution would be to have an array of trees with realistic shadows vs dropshadows that are connected to a dynamic border, but I haven't seen anything like that in Photoshop to know if that's feasible.  Disclaimer: the trees and the effects below are just a quick draft to see if it's possible to work with a script (tree .png's are attached). If someone here can improve this script or Adobe creates a better workflow for this, then I'll create much better looking trees and effects. #target photoshop // Define the resolution parameter (pixels/inch) var resolution = 200; // Default to 200 DPI, user can modify // Define the server path var serverPath = "C:\\Vegetation\\Trees\\Plan"; // Define the tree file names var treeFiles = ["Tree01.png", "Tree02.png", "Tree03.png", "Tree04.png", "Tree05.png", "Tree06.png"]; // Load .png files from server function loadPNGsFromServer(serverPath, treeFiles) { var pngPaths = []; for (var i = 0; i < treeFiles.length; i++) { var fullPath = new File(serverPath + "\\" + treeFiles[i]); if (fullPath.exists) { pngPaths.push(fullPath); } else { alert("File not found: " + fullPath.fsName); } } return pngPaths; } // Scatter .png files within a preselected area function scatterPNGsInArea(pngPaths, scale, treeDensity, resolution, minSizeFeet, maxSizeFeet) { var docRef = app.activeDocument; try { if (docRef.selection && docRef.selection.bounds && docRef.selection.bounds.length > 0) { var selectionBounds = docRef.selection.bounds; var xMin = selectionBounds[0].value; var yMin = selectionBounds[1].value; var xMax = selectionBounds[2].value; var yMax = selectionBounds[3].value; var width = xMax - xMin; var height = yMax - yMin; var area = width * height; // Convert min/max sizes from feet to pixels var minSizePx = (minSizeFeet / scale) * resolution; // Convert to pixels var maxSizePx = (maxSizeFeet / scale) * resolution; // Convert to pixels // Calculate tree count for specified density var avgSizePx = (minSizePx + maxSizePx) / 2; var effectiveArea = area * (treeDensity / 100); // Density as percentage var treesPerRow = Math.max(1, Math.floor(Math.sqrt(effectiveArea) / avgSizePx)); var treeCount = Math.min(1000, Math.max(100, Math.floor(treesPerRow * treesPerRow))); // Increased density by 10x var placedTrees = 0; for (var i = 0; i < treeCount; i++) { try { var randomIndex = Math.floor(Math.random() * pngPaths.length); var fileRef = pngPaths[randomIndex]; var treeDoc = app.open(fileRef); // Ensure the tree document is at the correct resolution treeDoc.resizeImage(undefined, undefined, resolution, ResampleMethod.NONE); treeDoc.selection.selectAll(); treeDoc.selection.copy(); treeDoc.close(SaveOptions.DONOTSAVECHANGES); docRef.paste(); var layerRef = docRef.activeLayer; // Random size within min/max range var sizePx = minSizePx + (Math.random() * (maxSizePx - minSizePx)); // Resize the layer using percentage scaling (base size is 100px) var scaleFactor = (sizePx / 100) * 100; // Percentage relative to 100px layerRef.resize(scaleFactor, scaleFactor, AnchorPosition.MIDDLECENTER); // Get dimensions for positioning var newWidth = layerRef.bounds[2].value - layerRef.bounds[0].value; var newHeight = layerRef.bounds[3].value - layerRef.bounds[1].value; // Random position ensuring the entire tree stays within bounds var xPos = xMin + (newWidth / 2) + Math.random() * (width - newWidth); var yPos = yMin + (newHeight / 2) + Math.random() * (height - newHeight); layerRef.translate(xPos - layerRef.bounds[0].value, yPos - layerRef.bounds[1].value); // Random rotation var rotation = Math.random() * 360; var idTrnf = charIDToTypeID("Trnf"); var desc3 = new ActionDescriptor(); desc3.putEnumerated(charIDToTypeID("FTcs"), charIDToTypeID("QCSt"), charIDToTypeID("Qcsa")); var idOfst = charIDToTypeID("Ofst"); var desc4 = new ActionDescriptor(); desc4.putUnitDouble(charIDToTypeID("Hrzn"), charIDToTypeID("#Pxl"), 0); desc4.putUnitDouble(charIDToTypeID("Vrtc"), charIDToTypeID("#Pxl"), 0); desc3.putObject(idOfst, charIDToTypeID("Ofst"), desc4); desc3.putUnitDouble(charIDToTypeID("Angl"), charIDToTypeID("#Ang"), rotation); executeAction(idTrnf, desc3, DialogModes.NO); placedTrees++; } catch (e) { alert("Failed to place tree " + (i + 1) + ". Error: " + e + "\nContinuing with next tree."); continue; } } docRef.selection.deselect(); alert("Successfully placed " + placedTrees + " trees in the selected area."); } else { alert("No valid selection found. Please draw a selection with the Marquee Tool (M) before running the script."); } } catch (e) { alert("Error processing selection.\nError: " + e + "\nPlease ensure a selection exists and try again."); } } // Main function function main() { var pngPaths = loadPNGsFromServer(serverPath, treeFiles); if (pngPaths.length === 0) { alert("No valid tree files found. Check the server path and file names."); return; } // Create a dialog for setting parameters var dlg = new Window("dialog", "Tree Scatter"); dlg.orientation = "column"; dlg.alignChildren = "left"; dlg.add("statictext", undefined, "Resolution (DPI):"); var resolutionInput = dlg.add("edittext", undefined, "200"); resolutionInput.characters = 5; dlg.add("statictext", undefined, "Scale:"); var scaleInput = dlg.add("edittext", undefined, "100"); scaleInput.characters = 5; dlg.add("statictext", undefined, "Minimum Size (feet diameter):"); var minSizeInput = dlg.add("edittext", undefined, "15"); minSizeInput.characters = 5; dlg.add("statictext", undefined, "Maximum Size (feet diameter):"); var maxSizeInput = dlg.add("edittext", undefined, "50"); maxSizeInput.characters = 5; dlg.add("statictext", undefined, "Density Percent:"); var densityInput = dlg.add("edittext", undefined, "95"); densityInput.characters = 5; var scatterButton = dlg.add("button", undefined, "Scatter Trees"); scatterButton.onClick = function() { var resolution = parseFloat(resolutionInput.text); var scale = parseFloat(scaleInput.text); var minSizeFeet = parseFloat(minSizeInput.text); var maxSizeFeet = parseFloat(maxSizeInput.text); var treeDensity = parseFloat(densityInput.text); if (isNaN(resolution) || isNaN(scale) || isNaN(minSizeFeet) || isNaN(maxSizeFeet) || isNaN(treeDensity)) { alert("Please enter valid numerical values for all fields."); return; } if (resolution <= 0 || scale <= 0 || minSizeFeet <= 0 || maxSizeFeet <= 0 || treeDensity <= 0 || treeDensity > 100) { alert("Resolution, scale, sizes, and density must be positive numbers; density must be 0-100."); return; } if (minSizeFeet > maxSizeFeet) { alert("Minimum size cannot be greater than maximum size."); return; } scatterPNGsInArea(pngPaths, scale, treeDensity, resolution, minSizeFeet, maxSizeFeet); dlg.close(); }; dlg.show(); } main();