Welcome to WSL!

New to the forum? Please read this and this.

Get the absolute path to project using variable

User avatar
malbred
Posts: 48
Joined: Wed Aug 16, 2017 9:17 pm
Answers: 1
Location: Saint-Petersburg, Russia
Contact:

Get the absolute path to project using variable

#1

Post by malbred »

Hello guys
I have a macro with simple interface control compiled in resolve. This macro used import FBX node (FBXMesh3D) and after using on fusion page I have no open FBX file because path file is absolute.

I want to get the path of the current project file *.drp. After that, i need to edit the path using LUA code and insert the correct path (path to fbx file) in the FBX import node inside the macro.

Structure of my project:
--------------------------
PROJECT.dra
-MediaFiles
--fbx_files
--pic
-project.drp
--------------------------

Now with active MediaIn and this LUA
  1. path = tostring(mediaprops.MEDIA_PATH)
  2. print(tostring(path))
i get the right path to project texture like a "D:\work\fusion_resolve\test_project\pic\input_texture.jpg"
next, I wanted to cut off the last characters from the resulting string ("D:\work\fusion_resolve\test_project\"),
add the fbx folder to it to get something similar to this "D:\work\fusion_resolve\test_project\fbx_files\",
and finaly connect my string(path to fbx folder) and a file name to get the correct fbx path to insert this path to FBXMesh3D node ("D:\work\fusion_resolve\test_project\fbx_files\cube.fbx")


Am I on the right way?
Are there any errors in my solution?
it seems to me that my option is too complicated and there is a simpler solution.
by Midgardsormr » Thu Jun 10, 2021 7:50 am
Thanks, Peter! Once again I have reinvented something that already exists. :D

The output of split is a table, so you can't concatenate it with the string SPLIT -

Replace line 76 with this:

Code: Select all

print('SPLIT - ')
dump(split(strInput, SEPARATOR))
You can assign the value of table.getn() to a variable or dump its value like any other function:

Code: Select all

pathTable = split(strInput, SEPARATOR)
 
tableLength = table.getn(pathTable)

print("Table Length = "..tableLength)

newpath = table.concat(pathTable, SEPARATOR)

print("New Path: "..newpath)
Go to full post
Last edited by malbred on Wed Jun 09, 2021 8:59 pm, edited 4 times in total.

User avatar
Midgardsormr
Fusionator
Posts: 2172
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 28
Location: Los Angeles, CA, USA
Been thanked: 41 times
Contact:

Re: Get the absolute path to project using variable

#2

Post by Midgardsormr »

Okay, I was in the middle of writing a response to your original question, and now it's entirely changed… But I don't want to waste what I just wrote, so that's here:

That'll simply dump all of the available path maps from the Fusion preferences. But the Resolve project file isn't a part of the Fusion path mapping system, so it won't help you.

I'm not terribly familiar with the Resolve API, but I think you can query the Project with GetCurrentProject(), then perhaps GetSetting() without arguments:

dump(GetCurrentProject():GetSetting())

Maybe. I don't have Resolve installed to test it.

https://deric.github.io/DaVinciResolve-API-Docs/

The above obviously makes no sense in the context of the new revised question. :mrgreen:

Onward!

I have some library functions to handle this kind of thing. Unfortunately, the network at the office seems to be down right now, so I can't connect to grab them. Well. I can at least give you parts of it. In the bmd.scriptlib are some handy functions:

Code: Select all

SEPARATOR = package.config:sub(1,1) -- Folder separator used by the Operating System.

------------------------------------------------------------------------------
-- parseFilename()
--
-- this is a great function for ripping a filepath into little bits
-- returns a table with the following
--
-- FullPath	: The raw, original path sent to the function
-- Path		: The path, without filename
-- FullName	: The name of the clip w\ extension
-- Name     : The name without extension
-- CleanName: The name of the clip, without extension or sequence
-- SNum		: The original sequence string, or "" if no sequence
-- Number 	: The sequence as a numeric value, or nil if no sequence
-- Extension: The raw extension of the clip
-- Padding	: Amount of padding in the sequence, or nil if no sequence
-- UNC		: A true or false value indicating whether the path is a UNC path or not
------------------------------------------------------------------------------
function parseFilename(filename)
	local seq = {}
	seq.FullPath = filename
	string.gsub(seq.FullPath, "^(.+[/\\])(.+)", function(path, name) seq.Path = path seq.FullName = name end)
	string.gsub(seq.FullName, "^(.+)(%..+)$", function(name, ext) seq.Name = name seq.Extension = ext end)

	if not seq.Name then -- no extension?
		seq.Name = seq.FullName
	end

	string.gsub(seq.Name,     "^(.-)(%d+)$", function(name, SNum) seq.CleanName = name seq.SNum = SNum end)

	if seq.SNum then
		seq.Number = tonumber( seq.SNum )
		seq.Padding = string.len( seq.SNum )
	else
	   seq.SNum = ""
	   seq.CleanName = seq.Name
	end

	if seq.Extension == nil then seq.Extension = "" end
	seq.UNC = ( string.sub(seq.Path, 1, 2) == [[\\]] )

	return seq
end

-----------------------------------------------------
-- split(strInput, delimit)
--
-- converts string strInput into a table, separating
-- records using the provided delimiter string
--
-----------------------------------------------------
function split(strInput, delimit)
	local strLength
	local strTemp
	local strCollect
	local tblSplit
	local intCount

	tblSplit = {}
	intCount = 0
	strCollect = ""
	if delimit == nil then
		delimit = ","
	end

	strLength = string.len(strInput)
	for i = 1, strLength do
		strTemp = string.sub(strInput, i, i)
		if strTemp == delimit then
			intCount = intCount + 1
			tblSplit[intCount] = trim(strCollect)
			strCollect = ""
		else
			strCollect = strCollect .. strTemp
		end
	end
	intCount = intCount + 1
	tblSplit[intCount] = trim(strCollect)

	return tblSplit
end

These are modified a bit to remove them from the bmd namespace, making them easier to port into your own scripts. I often use split() to turn a path into a table, which can be more easily manipulated. With table.getn(), you can find the end index of the table, then count backward down your folder structure to get to whichever root folder you need, then replace the branches as desired.

Up at the top, you'll see a line I borrowed from Andrew to get the operating system's folder delimiter. Use SEPARATOR in place of any slashes, and you don't have to worry about the difference between Windows and Unix-style paths.

To reassemble the table into a path, I use another function:

Code: Select all

function tableToPath(pathTable, delimiter)
    local out = ""
    i = 1
    while i < table.getn(pathTable) do
        out = out..item..delimiter
        i = i+1
    end
    
    out = out..pathtable[table.getn(pathtable)]

    return out
end
That reassembles the table into a path. Maybe test that one more stringently than the others—I rewrote it from memory.

Anyway, you can easily add or remove entries from the table using the functions in the table library, which should hopefully make it a little easier to manage your paths.

Hope that helps somewhat!

User avatar
malbred
Posts: 48
Joined: Wed Aug 16, 2017 9:17 pm
Answers: 1
Location: Saint-Petersburg, Russia
Contact:

Re: Get the absolute path to project using variable

#3

Post by malbred »

Thanks for your reply. I sit down to study your material.
Sorry for changing my question. I wanted to describe my problem in detail

User avatar
malbred
Posts: 48
Joined: Wed Aug 16, 2017 9:17 pm
Answers: 1
Location: Saint-Petersburg, Russia
Contact:

Re: Get the absolute path to project using variable

#4

Post by malbred »

I don't understand scripts very well and have some error.

What I do:
1) Rename one MediaIn with image to IMG_IN

2) On fusion page open the console and run:

a) function split - I didn't change it

Code: Select all

function split(strInput, delimit)
	local strLength
	local strTemp
	local strCollect
	local tblSplit
	local intCount

	tblSplit = {}
	intCount = 0
	strCollect = ""
	if delimit == nil then
		delimit = ","
	end

	strLength = string.len(strInput)
	for i = 1, strLength do
		strTemp = string.sub(strInput, i, i)
		if strTemp == delimit then
			intCount = intCount + 1
			tblSplit[intCount] = trim(strCollect)
			strCollect = ""
		else
			strCollect = strCollect .. strTemp
		end
	end
	intCount = intCount + 1
	tblSplit[intCount] = trim(strCollect)

	return tblSplit
end
b) This to check split function

Code: Select all

filename = comp.IMG_IN:GetData('MediaProps.MEDIA_PATH')
dump('Checking path - ' .. filename)
split(filename, "^")
---------------------
My error is - [string "???"]:27: attempt to call global 'trim' (a nil value)
:cry:
PIC0001.jpeg
You do not have the required permissions to view the files attached to this post.

User avatar
PeterLoveday
Fusioneer
Posts: 225
Joined: Sun Sep 14, 2014 6:09 pm
Answers: 9
Been thanked: 6 times

Re: Get the absolute path to project using variable

#5

Post by PeterLoveday »

Midgardsormr wrote: Wed Jun 09, 2021 8:21 pm

Code: Select all

function tableToPath(pathTable, delimiter)
    local out = ""
    i = 1
    while i < table.getn(pathTable) do
        out = out..item..delimiter
        i = i+1
    end
    
    out = out..pathtable[table.getn(pathtable)]

    return out
end
See also:

Code: Select all

Lua> =table.concat({"A","B","C"}, "/")
A/B/C

User avatar
malbred
Posts: 48
Joined: Wed Aug 16, 2017 9:17 pm
Answers: 1
Location: Saint-Petersburg, Russia
Contact:

Re: Get the absolute path to project using variable

#6

Post by malbred »

I don't understand:
1) How can I check or see the number of table.getn() ? (syntax problem)
2) How to check the result of the split() function? (syntax problem)

My code with comments:

  1. SEPARATOR = package.config:sub(1,1)
  2.  
  3. --IMG_IN is MediaIN node with texture
  4. filename = comp.IMG_IN:GetData('MediaProps.MEDIA_PATH')
  5.  
  6.  
  7. -------------
  8. function parseFilename(filename)
  9.     local seq = {}
  10.     seq.FullPath = filename
  11.     string.gsub(seq.FullPath, "^(.+[/\\])(.+)", function(path, name) seq.Path = path seq.FullName = name end)
  12.     string.gsub(seq.FullName, "^(.+)(%..+)$", function(name, ext) seq.Name = name seq.Extension = ext end)
  13.  
  14.     if not seq.Name then -- no extension?
  15.         seq.Name = seq.FullName
  16.     end
  17.  
  18.     string.gsub(seq.Name,     "^(.-)(%d+)$", function(name, SNum) seq.CleanName = name seq.SNum = SNum end)
  19.  
  20.     if seq.SNum then
  21.         seq.Number = tonumber( seq.SNum )
  22.         seq.Padding = string.len( seq.SNum )
  23.     else
  24.        seq.SNum = ""
  25.        seq.CleanName = seq.Name
  26.     end
  27.  
  28.     if seq.Extension == nil then seq.Extension = "" end
  29.     seq.UNC = ( string.sub(seq.Path, 1, 2) == [[\\]] )
  30.  
  31.     return seq
  32. end
  33.  
  34.  
  35. -- Check file path
  36. dump('PATH IS -  ' .. parseFilename(filename).Path)
  37.  
  38.  
  39. function split(strInput, delimit)
  40.     local strLength
  41.     local strTemp
  42.     local strCollect
  43.     local tblSplit
  44.     local intCount
  45.  
  46.     tblSplit = {}
  47.     intCount = 0
  48.     strCollect = ""
  49.     if delimit == nil then
  50.         delimit = ","
  51.     end
  52.  
  53.     strLength = string.len(strInput)
  54.     for i = 1, strLength do
  55.         strTemp = string.sub(strInput, i, i)
  56.         if strTemp == delimit then
  57.             intCount = intCount + 1
  58.             tblSplit[intCount] = trim(strCollect)
  59.             strCollect = ""
  60.         else
  61.             strCollect = strCollect .. strTemp
  62.         end
  63.     end
  64.     intCount = intCount + 1
  65.     tblSplit[intCount] = trim(strCollect)
  66.  
  67.     return tblSplit
  68. end
  69.  
  70.  
  71.  
  72. strInput = parseFilename(filename).Path
  73.  
  74. -- STRING - 74!!!
  75. --Check split result
  76. dump('SPLIT - ' .. split(strInput, SEPARATOR))
  77.  
  78. --table.getn(???)
  79. --table.concat({"A","B","C"}, SEPARATOR)

After compilate see - [string "???"]:74: attempt to concatenate a table value
Last edited by malbred on Thu Jun 10, 2021 8:19 am, edited 1 time in total.

User avatar
Midgardsormr
Fusionator
Posts: 2172
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 28
Location: Los Angeles, CA, USA
Been thanked: 41 times
Contact:

Re: Get the absolute path to project using variable

#7

Post by Midgardsormr »

Thanks, Peter! Once again I have reinvented something that already exists. :D

The output of split is a table, so you can't concatenate it with the string SPLIT -

Replace line 76 with this:

Code: Select all

print('SPLIT - ')
dump(split(strInput, SEPARATOR))
You can assign the value of table.getn() to a variable or dump its value like any other function:

Code: Select all

pathTable = split(strInput, SEPARATOR)
 
tableLength = table.getn(pathTable)

print("Table Length = "..tableLength)

newpath = table.concat(pathTable, SEPARATOR)

print("New Path: "..newpath)

User avatar
malbred
Posts: 48
Joined: Wed Aug 16, 2017 9:17 pm
Answers: 1
Location: Saint-Petersburg, Russia
Contact:

Re: Get the absolute path to project using variable

#8

Post by malbred »

Thank you so much for suggesting.
Script is working now.I do understand the logic of writing code, but I don't know the syntax. You've been very helpful, I appreciate your support a lot.

There are 3 more questions.
1)
To determine the first original path, I use this code
  1. filename = comp.Tex_In:GetData('MediaProps.MEDIA_PATH')
Here is my project in DaVinci.
PIC001.jpeg
It's placed on the Fusion page. These are several textures and a macro in which I need to redefine the paths to the FBX files.
Did I write this code correctly? My doubts are about "comp" before Tex_In. Maybe I need to write "fu" or something else for correct work?

2)
As I understand it, I need to run the script once after load the project to redefine the paths.
Where should I place my script for that?

3)
Are there any special features in the script when working with Mac or PC? like SEPARATOR variable.
You do not have the required permissions to view the files attached to this post.

User avatar
Midgardsormr
Fusionator
Posts: 2172
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 28
Location: Los Angeles, CA, USA
Been thanked: 41 times
Contact:

Re: Get the absolute path to project using variable

#9

Post by Midgardsormr »

1) Your Tex_In node is a member of comp, which is the currently active composite. As long as the script is running from within that composite, comp will be defined, and you'll be able to address nodes in that fashion. So that is correct.

If you're executing the script from outside Fusion—from an IDE or a remote process—then you'd need to first define the active comp, and for that you'd need Fusion(). Here's my library code for that:

Code: Select all

--======================== ENVIRONMENT SETUP ============================--

------------------------------------------------------------------------
-- getFusion()
--
-- check if global fusion is set, meaning this script is being
-- executed from within fusion
--
-- Arguments: None
-- Returns: handle to the Fusion instance
------------------------------------------------------------------------
function getFusion()
	if fusion == nil then 
		-- remotely get the fusion ui instance
		fusion = bmd.scriptapp("Fusion", "localhost")
	end
	return fusion
end -- end of getFusion()

-- get fusion instance
_fusion = getFusion()

-- ensure a fusion instance was retrieved
if not _fusion then
	error("Please open the Fusion GUI before running this tool.")
end

-- get composition
_comp = _fusion.CurrentComp
SetActiveComp(_comp)

2) I'm not sure about the answer to this one within the context of Resolve. In Fusion standalone, you can rename the script with the extension .scriptlib and put it in the Scripts/Comp folder, and it will execute every time a comp is opened. I don't know if that would work in Resolve or not. I've only experimented with scriptlib files a little, and that was before Fusion 16 released. There are some scant details in the Fusion 8 Scripting Guide on page 24.

3) Probably, but the path separator is the only one I've needed to worry about so far. @AndrewHazelden might know more about that. Most of what I'm sharing came from him originally, anyway. :)

User avatar
AndrewHazelden
Fusionator
Posts: 1768
Joined: Fri Apr 03, 2015 3:20 pm
Answers: 11
Location: West Dover, Nova Scotia, Canada
Been thanked: 39 times
Contact:

Re: Get the absolute path to project using variable

#10

Post by AndrewHazelden »

Hi. I'm just looking at this thread now. It appears you want to modify both an FBX file's path, and an input texture map so it points at your current project folder location on disk. Another route to solve this effort would be to:

1. Switch the MediaIn node you are using to access your FBX model's texture maps to a Loader node.

2. Then use a custom PathMap (shortcut) for the project folder location that is used to hold both the FBX mesh, and the Loader node accessed texture map.

An example of what a custom PathMap could look like in the Fusion > Fusion Settings > PathMap preferences would be:

From:
ActiveProject:

To:
D:\work\fusion_resolve\Background_generator\

Image

Using PathMaps to Access Images and Models

Then in the Loader node you would write the relative PathMap based filepath as:
ActiveProject:\pic\input_HDRI.jpg

For the FBX model you would write the relative PathMap based filepath as:
ActiveProject:\fbx_files\cube.fbx


Image

Scripting PathMap Usage

You could use the following Lua code to create / modify the PathMap entry:
Code: [Select all] [Expand/Collapse] [Download] (CreatePathmap.lua)
  1. -- ---------------------------------------------------------------------------------------------
  2. -- Create a new PathMap
  3.  
  4. -- Current working project folder path
  5. active_project_path = [[D:\work\fusion_resolve\Background_generator\]]
  6.  
  7. -- Add an "ActiveProject:" PathMap entry
  8. app:SetPrefs("Global.Paths.Map.ActiveProject:", active_project_path)

When you want to access the fully expanded the PathMap into an "absolute" filepath you can use the following Lua code:
Code: [Select all] [Expand/Collapse] [Download] (ExpandPathmap.lua)
  1. -- ---------------------------------------------------------------------------------------------
  2. -- Expand a PathMap
  3.  
  4. file_path = app:MapPath([[ActiveProject:\fbx_files\cube.fbx]])
  5. print("[Absolute Filepath] ", file_path)
Image

User avatar
malbred
Posts: 48
Joined: Wed Aug 16, 2017 9:17 pm
Answers: 1
Location: Saint-Petersburg, Russia
Contact:

Re: Get the absolute path to project using variable

#11

Post by malbred »

Thank you. I'll try this way

User avatar
malbred
Posts: 48
Joined: Wed Aug 16, 2017 9:17 pm
Answers: 1
Location: Saint-Petersburg, Russia
Contact:

Re: Get the absolute path to project using variable

#12

Post by malbred »

Thank you @AndrewHazelden, pathMaps to Access Images and Models works correctly but have little problem...

My steps:
1) I have make corrections to the FBX and images loader nodes. Saved the project: S:\ssssss\vvvvv\hhhhh\016\Wall generator.dra (folder *016).
2) Then export project archive and placed it to the: S:\ssssss\vvvvv\hhhhh\017\Wall generator.dra (folder *017).
3) Clear ActiveProject PathMap from options, delete current project, reopen Davinci Resolve.
4) Then I imported the project again(from folder *017). Launched my script. After that, a small problem appears.

PIC_0001.jpeg
(1) MediaIn - IN_TEXTURE node
(2) Right path to the texture folder from media Pool in Metadata tab (*017)
(3) Result of lua code

If I check path for IN_TEXTURE node with lua code:
  1. dump('PATH TEXTURE -  ' .. comp.IN_TEXTURE:GetData('MediaProps.MEDIA_PATH'))
For the IN_TEXTURE node path is = (*016)


If I drag&drop input_texture from media Pool to Nodes (MediaIn1) and check path again:
  1. dump('PATH TEXTURE -  ' .. comp.MediaIn1:GetData('MediaProps.MEDIA_PATH'))
For MediaIn1 node path is = (*017) - RIGHT for imported project



At the beginning of my script, I define the path like this:
  1. filename = comp.IN_TEXTURE:GetData('MediaProps.MEDIA_PATH')
My questions
1) - How can I get the correct path from media Pool to texture folder in Metadata tab (4)?
2) - Is it possible to get the correct path from IN_TEXTURE node? I can't switch the MediaIn node(IN_TEXTURE) to loader node.
3) - Is it possible to get the path for input_texture that is located in Media Pool from MetaData tab?
You do not have the required permissions to view the files attached to this post.

User avatar
AndrewHazelden
Fusionator
Posts: 1768
Joined: Fri Apr 03, 2015 3:20 pm
Answers: 11
Location: West Dover, Nova Scotia, Canada
Been thanked: 39 times
Contact:

Re: Get the absolute path to project using variable

#13

Post by AndrewHazelden »

In response to #2:

Is there a specific technical reason why you need to use a MediaIn node instead of a Loader node? Is this just a personal preference? In 99% of all use cases where you want to script media i/o inside of a custom macro, the MediaIn node is a nightmare due to the way MediaID tags are unique to the active DRP project.

(I can understand if you wanted to use a movie file as the texture map that a MediaIn node makes sense given that Resolve's Fusion page has a handicapped Loader node, when compared to Fusion Studio's supported file formats.)

Added in 8 minutes 47 seconds:
I think a more natural feeling workflow could be developed if you are able to explain a bit more high level detail about what you want this type of macro to do, and how an end user would interact with it.

Essentially, the way the macro is interacting with the Media Pool, there are ever-changing Resolve projects involved as a dependency, and the macro is self-modifying sort of indicates you are fighting against some workflow and design issues that could be smoothed out with a bit more insight.

User avatar
malbred
Posts: 48
Joined: Wed Aug 16, 2017 9:17 pm
Answers: 1
Location: Saint-Petersburg, Russia
Contact:

Re: Get the absolute path to project using variable

#14

Post by malbred »

AndrewHazelden wrote: Wed Jul 07, 2021 12:42 pmIn response to #2:

Is there a specific technical reason why you need to use a MediaIn node instead of a Loader node? Is this just a personal preference? In 99% of all use cases where you want to script media i/o inside of a custom macro, the MediaIn node is a nightmare due to the way MediaID tags are unique to the active DRP project.

(I can understand if you wanted to use a movie file as the texture map that a MediaIn node makes sense given that Resolve's Fusion page has a handicapped Loader node, when compared to Fusion Studio's supported file formats.)

Added in 8 minutes 47 seconds:
I think a more natural feeling workflow could be developed if you are able to explain a bit more high level detail about what you want this type of macro to do, and how an end user would interact with it.

Essentially, the way the macro is interacting with the Media Pool, there are ever-changing Resolve projects involved as a dependency, and the macro is self-modifying sort of indicates you are fighting against some workflow and design issues that could be smoothed out with a bit more insight.

Hello. I'm sorry that I didn't answer for a long time.


First I will describe the project task.
The macro generates a 3d wall. It has 3 inputs(mediaIn). Its private preview of project

PIC_001.jpeg
Required only (2) input. This is the animation mask for wall.
(1) is a texture that paints individual wall primitives. In the my custom menu, it has several options for using. Embedded textures in the macro, custom(the user connect his texture), none(input is not used)
(3) - the same as (1) only for HDRI

With your help, I was able to solve the problem with the paths inside the macro. I replaced all MediaIN to loader.
There are several files in the project. This is the input texture, HDRI and mask. The entire project was made on the fusion page and the main problem was in the paths to FBX that are stored together with the project files.



Yes, there is a reason for use MadiaIn.

My main problem is to define a new path to the source files of the project after importing the template by user.
If I replace (1) to loader node and run a script that overrides the paths, then it does not work correctly.
Here is the lua string that determines the correct path.
  1. filename = comp.IN_TEXT:GetData('MediaProps.MEDIA_PATH')
If IN_TEXT is a loader node, then after importing the project, the path will be incorrect (Path will be the previous one, before exporting project) and all other paths will be redefined incorrectly.
If IN_TEXT works as a MediaIn (this texture is located along with the another external project files), then after importing the project and script result, all path is determined correctly.

So I can't use loader node. Therefore, after importing the project, I need to somehow find out where the user installed the template.
The method that exists now is also not very good. If the user connect custom texture after downloading, the script will not be able to determine the path in the next transfer(next export may be) of the project.

Maybe there is another way to determine the project location after import without using nodes (1),(2),(3)?
PIC_002.jpeg
also found this
ProjectManager:GetCurrentFolder()
ProjectManager:GetCurrentProject()
MediaStorage:GetFileList()
MediaStorage:GetFiles()

Maybe it will still be MediaIN, but it will be hidden inside the macro so that the user cannot delete it.

The ideal solution is to make this project as an effect. Drag&drop to footage and that is it. The input texture and HDRI can be replaced with a user open file dialog. But I don't know how to do it
You do not have the required permissions to view the files attached to this post.