------------------------------------------------------------------------------- -- PoliigonFStormBridge.ms -- Manual-download bridge: Poliigon ZIP/folder -> native FStorm material. -- It never accesses Poliigon credentials, account data, credits, or downloads. -- Download in your own browser, then build from the selected folder or newest -- Poliigon_*.zip in the watch folder. ------------------------------------------------------------------------------- global PoliigonFStormBridge_LastMaterial global PoliigonFStormBridge_LastWidthCM global PoliigonFStormBridge_LastHeightCM global PoliigonFStormBridge_LastAssetURL if PoliigonFStormBridge_LastAssetURL == undefined do PoliigonFStormBridge_LastAssetURL = "" fn pfEnsureSlash p = ( if p == undefined then return "" if p == "" then return "" if (substring p p.count 1) == "\\" then p else (p + "\\") ) -- Legacy ZIP names do not contain their public page slug. Keep verified mappings -- for assets encountered locally; unknown assets still accept a pasted page URL. fn pfKnownAssetPage assetName = ( case (toLower assetName) of ( "tilesmosaicyubi018": "https://www.poliigon.com/texture/yubi-mosaic-finger-tile-texture-khaki/7347" "poliigon_stonewalltile_11542": "https://www.poliigon.com/texture/square-slate-tile-texture/11542" default: "" ) ) fn pfNewestZip folder = ( -- Current Poliigon downloads use both `Poliigon_*` and legacy asset names -- such as `TilesMosaicYubi018.zip`, so the selected watch folder is the -- authority: import its most recently modified ZIP. local best = undefined local bestTime = undefined for f in (getFiles ((pfEnsureSlash folder) + "*.zip")) do ( local dt = undefined try (dt = (dotNetObject "System.IO.FileInfo" f).LastWriteTime) catch() if dt != undefined and (bestTime == undefined or (dt.CompareTo bestTime) > 0) do ( best = f bestTime = dt ) ) best ) fn pfExtractClean zipPath targetFolder = ( try ( local dir = dotNetClass "System.IO.Directory" if dir.Exists targetFolder do dir.Delete targetFolder true dir.CreateDirectory targetFolder (dotNetClass "System.IO.Compression.ZipFile").ExtractToDirectory zipPath targetFolder true ) catch ( messageBox ("Could not extract:\n" + zipPath + "\n\n" + (getCurrentException() as string)) title:"Poliigon FStorm Bridge" false ) ) fn pfIsImage f = ( local ext = toLower (getFilenameType f) findItem #(".jpg", ".jpeg", ".png", ".tif", ".tiff", ".tga", ".bmp", ".exr") ext > 0 ) fn pfCollectImages folder depth = ( local result = #() for f in (getFiles ((pfEnsureSlash folder) + "*.*")) do ( if pfIsImage f do append result f ) if depth > 0 do ( for d in (getDirectories ((pfEnsureSlash folder) + "*")) do join result (pfCollectImages d (depth - 1)) ) result ) fn pfHasToken filePath tokens = ( local n = toUpper (getFilenameFile filePath) local found = false for token in tokens while not found do if matchPattern n pattern:("*" + token + "*") do found = true found ) fn pfPickMap files tokens = ( local selected = undefined for f in files while selected == undefined do if pfHasToken f tokens do selected = f selected ) fn pfConfigureBitmap bmp filePath isColor isNormal = ( bmp.filename = filePath bmp.mapping = 2 bmp.map_channel = 1 bmp.real_world = false bmp.tiling = [1,1,1] bmp.input_gamma = true -- Keep every imported FStorm bitmap at explicit gamma 1.0, including BaseColor. bmp.gamma = 1.0 bmp.normal_map = isNormal if isNormal do bmp.normal_map_power = 100.0 bmp ) fn pfBuildMaterialFromFolder sourceFolder materialName = ( local files = pfCollectImages sourceFolder 4 if files.count == 0 then ( messageBox ("No supported image maps were found under:\n" + sourceFolder) title:"Poliigon FStorm Bridge" return undefined ) -- Supports current Poliigon PBR map names plus legacy COL/GLOSS/NRM names. local baseMap = pfPickMap files #("BASECOLOR", "BASE_COLOR", "COLOR", "ALBEDO", "DIFFUSE", "_COL") local roughMap = pfPickMap files #("ROUGHNESS", "_ROUGH") local glossMap = pfPickMap files #("GLOSSINESS", "_GLOSS") local normMap = pfPickMap files #("NORMAL", "_NRM") local dispMap = pfPickMap files #("DISPLACEMENT", "HEIGHT", "_DISP") local metalMap = pfPickMap files #("METALLIC", "METALNESS", "_METAL") local reflMap = pfPickMap files #("REFLECTION", "_REFL") if baseMap == undefined and normMap == undefined and roughMap == undefined and glossMap == undefined then ( messageBox "No recognized Poliigon PBR maps found. Expected BaseColor, Roughness, Normal, Displacement or Metallic map names." title:"Poliigon FStorm Bridge" return undefined ) local mat = sceneMaterials[materialName] if mat == undefined or classOf mat != FStorm then ( mat = FStorm() mat.name = materialName sceneMaterials[materialName] = mat ) if baseMap != undefined do ( mat.diffuse_tex_on = true mat.diffuse_tex = pfConfigureBitmap (FStormBitmap()) baseMap true false ) if glossMap != undefined then ( mat.reflection_glossy_tex_on = true mat.reflection_glossy_tex = pfConfigureBitmap (FStormBitmap()) glossMap false false mat.refl_glossiness_invert = false ) else if roughMap != undefined then ( mat.reflection_glossy_tex_on = true mat.reflection_glossy_tex = pfConfigureBitmap (FStormBitmap()) roughMap false false mat.refl_glossiness_invert = true ) if normMap != undefined do ( mat.bump_texture_on = true mat.bump_texture = pfConfigureBitmap (FStormBitmap()) normMap false true mat.bump_texture_amount = 1.0 ) if dispMap != undefined do ( mat.displacement_texture_on = true mat.displacement_texture = pfConfigureBitmap (FStormBitmap()) dispMap false false ) if metalMap != undefined do ( try ( mat.metalness_tex_on = true mat.metalness_tex = pfConfigureBitmap (FStormBitmap()) metalMap false false ) catch() ) if reflMap != undefined do ( mat.reflection_tex_on = true mat.reflection_tex = pfConfigureBitmap (FStormBitmap()) reflMap true false ) PoliigonFStormBridge_LastMaterial = mat mat ) fn pfBuildFromNewestDownload watchFolder extractRoot = ( local zipPath = pfNewestZip watchFolder if zipPath == undefined then ( messageBox ("No ZIP was found in:\n" + watchFolder + "\n\nDownload an asset in your browser first, or use Build from selected folder.") title:"Poliigon FStorm Bridge" return undefined ) local base = getFilenameFile zipPath local destination = (pfEnsureSlash extractRoot) + base + "\\" if pfExtractClean zipPath destination then pfBuildMaterialFromFolder destination base else undefined ) -- Public Poliigon asset pages expose Physical Size. Direct page requests can -- be blocked by CDN protection, so use the reader endpoint only for public, -- user-supplied Poliigon page URLs. No account, cookie, download, or credit -- data is sent or accessed. fn pfCmValue raw unit = ( local v = raw as float if (toLower unit) == "m" then v * 100.0 else v ) fn pfFetchPhysicalSize pageURL = ( if pageURL == undefined or pageURL == "" then return undefined try ( local wc = dotNetObject "System.Net.WebClient" wc.Headers.Add "User-Agent" "Mozilla/5.0" local cleanURL = pageURL if matchPattern (toLower cleanURL) pattern:"https://*" then cleanURL = substring cleanURL 9 (cleanURL.count - 8) else if matchPattern (toLower cleanURL) pattern:"http://*" then cleanURL = substring cleanURL 8 (cleanURL.count - 7) local readerURL = "https://r.jina.ai/http://" + cleanURL local text = wc.DownloadString readerURL local rx = dotNetClass "System.Text.RegularExpressions.Regex" local hm = rx.Match text "Height\\s+([0-9.]+)\\s*(cm|m)" local wm = rx.Match text "Width\\s+([0-9.]+)\\s*(cm|m)" if hm.Success and wm.Success then ( PoliigonFStormBridge_LastHeightCM = pfCmValue hm.Groups.Item[1].Value hm.Groups.Item[2].Value PoliigonFStormBridge_LastWidthCM = pfCmValue wm.Groups.Item[1].Value wm.Groups.Item[2].Value PoliigonFStormBridge_LastAssetURL = pageURL #(PoliigonFStormBridge_LastWidthCM, PoliigonFStormBridge_LastHeightCM) ) else undefined ) catch (undefined) ) fn pfApplyPhysicalUVW widthCM heightCM = ( if selection.count == 0 then return 0 local mapW = units.decodeValue ((widthCM as string) + "cm") local mapH = units.decodeValue ((heightCM as string) + "cm") local applied = 0 for obj in selection where (superClassOf obj == GeometryClass) do ( local uvw = UVWMap() uvw.name = "Poliigon Physical UVW " + (widthCM as string) + "x" + (heightCM as string) + "cm" uvw.maptype = 4 -- box mapping: robust for walls, floors and solids uvw.width = mapW uvw.length = mapH uvw.height = mapW uvw.realWorldMapSize = false -- texture coordinates are defined by this exact gizmo size addModifier obj uvw applied += 1 ) applied ) try(destroyDialog PoliigonFStormBridge_Rollout)catch() rollout PoliigonFStormBridge_Rollout "Poliigon <-> FStorm Bridge" width:440 ( group "1. Poliigon" ( edittext pf_search text:"" fieldWidth:404 button pf_searchBtn "Search on Poliigon" width:180 ) label pf_note "Downloads use your own Poliigon browser login and credits." height:16 group "2. Build FStorm material" ( edittext pf_watch text:"C:\\Users\\ROG\\Downloads\\" fieldWidth:310 across:2 button pf_watchBrowse "Watch..." width:90 edittext pf_extract text:"D:\\Poliigon\\textures\\" fieldWidth:310 across:2 button pf_extractBrowse "Extract..." width:90 button pf_buildNewest "Build from newest Poliigon download" width:404 height:30 button pf_buildFolder "Build from selected extracted folder" width:404 height:26 ) group "3. Physical UVW from Poliigon page" ( edittext pf_assetURL text:"" fieldWidth:404 button pf_fetchSize "Fetch Physical Size from page" width:404 height:24 edittext pf_widthCM "Width cm" text:"" fieldWidth:190 across:2 edittext pf_heightCM "Height cm" text:"" fieldWidth:190 label pf_sizeNote "Paste exact public asset page URL, then fetch size." height:18 ) group "4. Apply" ( button pf_apply "Apply last material to selection" width:404 height:24 button pf_applyUVW "Apply physical UVW to selection" width:404 height:26 label pf_status "Ready" height:18 ) on pf_searchBtn pressed do ( local query = pf_search.text if query == undefined or query == "" then query = "tiles" local url = "https://www.poliigon.com/search?q=" + (dotNetClass "System.Uri").EscapeDataString(query) ShellLaunch url "" pf_status.text = "Opened Poliigon search: " + query ) on pf_watchBrowse pressed do ( local p = getSavePath caption:"Choose Poliigon download folder" initialDir:pf_watch.text if p != undefined do pf_watch.text = pfEnsureSlash p ) on pf_extractBrowse pressed do ( local p = getSavePath caption:"Choose Poliigon extraction folder" initialDir:pf_extract.text if p != undefined do pf_extract.text = pfEnsureSlash p ) on pf_buildNewest pressed do ( pf_status.text = "Building..." local m = pfBuildFromNewestDownload pf_watch.text pf_extract.text pf_status.text = if m == undefined then "No material built" else ("Built: " + m.name) ) on pf_buildFolder pressed do ( local p = getSavePath caption:"Choose extracted Poliigon asset folder" if p != undefined do ( pf_status.text = "Building..." local m = pfBuildMaterialFromFolder (pfEnsureSlash p) (getFilenameFile p) pf_status.text = if m == undefined then "No material built" else ("Built: " + m.name) ) ) on pf_fetchSize pressed do ( local pageURL = pf_assetURL.text if pageURL == undefined or pageURL == "" then pageURL = PoliigonFStormBridge_LastAssetURL if pageURL == undefined or pageURL == "" then ( local newest = pfNewestZip pf_watch.text if newest != undefined do pageURL = pfKnownAssetPage (getFilenameFile newest) ) if pageURL == undefined or pageURL == "" then ( pf_sizeNote.text = "Unknown legacy ZIP: paste its exact Poliigon asset page URL." pf_status.text = "No asset page URL available" ) else ( pf_assetURL.text = pageURL pf_status.text = "Reading public Poliigon page..." local size = pfFetchPhysicalSize pageURL if size == undefined then ( pf_sizeNote.text = "Size not found. Use the exact /texture/... Poliigon page URL." pf_status.text = "No physical size found" ) else ( pf_widthCM.text = formattedPrint size[1] format:".3f" pf_heightCM.text = formattedPrint size[2] format:".3f" pf_sizeNote.text = "Verified page size: " + pf_widthCM.text + " x " + pf_heightCM.text + " cm" pf_status.text = "Physical size read from Poliigon page" ) ) ) on pf_applyUVW pressed do ( local w = undefined local h = undefined try (w = pf_widthCM.text as float; h = pf_heightCM.text as float) catch() if w == undefined or h == undefined or w <= 0 or h <= 0 then ( messageBox "Fetch the physical size from a Poliigon asset page first, or enter positive Width and Height values in cm." title:"Poliigon FStorm Bridge" ) else if selection.count == 0 then ( messageBox "Select at least one geometry object first." title:"Poliigon FStorm Bridge" ) else ( local count = pfApplyPhysicalUVW w h pf_status.text = "Applied " + (w as string) + " x " + (h as string) + " cm UVW to " + (count as string) + " object(s)." ) ) on pf_apply pressed do ( if PoliigonFStormBridge_LastMaterial == undefined then messageBox "Build a material first." title:"Poliigon FStorm Bridge" else if selection.count == 0 then messageBox "Select at least one object first." title:"Poliigon FStorm Bridge" else ( for obj in selection do obj.material = PoliigonFStormBridge_LastMaterial pf_status.text = "Applied to " + (selection.count as string) + " object(s)." ) ) ) fn OpenPoliigonFStormBridge = (createDialog PoliigonFStormBridge_Rollout width:440 height:610) macroScript OpenPoliigonFStormBridge category:"PoliigonFStormBridge" toolTip:"Poliigon <-> FStorm Bridge" buttonText:"Poliigon Bridge" (on execute do OpenPoliigonFStormBridge()) OpenPoliigonFStormBridge()