I’m trying to input a texture image from an HTML form and then save it as a .jpg locally in my plugin folder.
Is there a specific method for doing this in the API or should I just be looking at file IO methods using more generic Ruby.
I’m trying to input a texture image from an HTML form and then save it as a .jpg locally in my plugin folder.
Is there a specific method for doing this in the API or should I just be looking at file IO methods using more generic Ruby.
Here is my code:
imagefilename = File.join(this_dir,"library_mats/#{mat_name}.jpg")
begin
File.open(imagefilename, 'wb') { |file|
file.write(mat_image)
}
rescue StandardError => e
UI.messagebox(e)
puts "Error Message: #{e}"
end
and then here is what I get (if I convert the supposed image file to a text file:
[object File]
A few observations:
If you can use PNG rather than JPG, do so. JPG is lossy and does not support transparency.
Use the Kernel#__dir__()
global method, and cut the interpolation string down to as short as possible, and use single quoted strings when not doing interpolation or using embedded control characters like "\n"
, etc.
imagefilename = File.join( __dir__, 'library_mats', "#{mat_name}.jpg" )
HOWEVER, … it is much smarter to define constants early in your load cycle (… myself, I use a file with a “vars” suffix that does this, … ie, "SomePlugin_vars.rb"
, and is usually the first file that gets loaded,) and then use a constant reference deep in the code that will not need changing if you decide to reorganize your plugin folder structure or folder names. Instead you just change the constant definition in the “vars” file … one place, and all the other code is still “happy”.
DIR_MATS = File.join(__dir__,'library_mats')
… then later in the code …
imagefilename = File.join(DIR_MATS,"#{mat_name}.jpg")
… is frivolous. A rescue
clause traps all StandardError
(and subclass) exceptions.
So this is just as good …
rescue => e
REF: File: exceptions.rdoc [Ruby 2.0.0]
Your code snippet shows us what the desired destination pathname is (ie, imagefilename
), … but it not said what is being returned from the HTML form, … we guess this is a UI::WebDialog
orUI::HtmlDialog
instance ?
My point: There are unexplained references in your code snippet. It really helps if you explain before such snippets what some references are (class and value.)
Example:
Where mat_name
is a String entered by the user into a HTML <input type="text">
element,
and mat_image
is … whatever … (We don’t know and I’d rather not guess.)
… and then you post your snippet.
The big issue here is that we do not have enough information to help you because you assume we naturally know what you are doing. We don’t.
So, IF the file mat_image
already exists (someplace) it changes what our advice will be.
IF the actual image needs to be created, we need to go in another direction.
Help us help you, by explaining better where your code is and what it has, not just where you want it to get to.
… and do we even need to discuss why someone would WANT to do such a thing ?
(Ie, an image file is a binary file, not a text file.)
Your correct, I haven’t provided enough background.
Here is my HTML within my HTMLDialog instance:
<input id="mat_image" type="file" name="mat_image" accept=".jpg, .jpeg, .png, .bmp, .psd, .tif, .tga">
Then here is my javascript that pickups the variables and passes them to Ruby:
var i1 = document.getElementById("mat_name");
var v1 = i1.value;
var s2 = document.getElementById("mat_type");
var v2 = s2.options[s2.selectedIndex].value;
var i3 = document.getElementById("mat_color");
var v3 = i3.value;
var i4 = document.getElementById("mat_hscale");
var v4 = i4.value;
var i5 = document.getElementById("mat_vscale");
var v5 = i5.value;
var i6 = document.getElementById("mat_image");
var v6_filename = i6.value;
if (v2 == 'T')
{
var v6 = document.getElementById("mat_image").files[0];
console.log(v6);
}
else
{
var v6 = "empty";
}
document.getElementById("submitstatus").innerHTML = 'Creating Material...';
var paramstring1 = v1 + '|' + v2 + '|' + v3 + '|' + v4 + '|' + v5 + '|' + v6;
var query1 = 'skp:GET_MAT_ADD@' + paramstring1;
window.location.href = query1;
On the Ruby side I catch the incoming variables with this:
dlg02.add_action_callback(“GET_MAT_ADD”) {|action_context, params|
if params params = params.to_s plist = [] plist = params.split("|") mat_name_new = plist[0] mat_type_new = plist[1] mat_color_new = plist[2] mat_hscale_new = plist[3].to_f mat_vscale_new = plist[4].to_f mat_image_new = plist[5] if @Unitstemplate == "metric" mat_hscale_new = mat_hscale_new * @m_in mat_vscale_new = mat_vscale_new * @m_in end MedeekMethods.add_mat mat_name_new, mat_type_new, mat_color_new, mat_hscale_new, mat_vscale_new, mat_image_new else # Input Cancelled exit 0 end }
In the method add_mat I then have a local variable name mat_image that supposedly contains the image file itself and I would like to write that to a sub folder within my plugin folder so it can be used as a texture for a material.
Alternatively I guess I could try and use the UI:Openpanel and then somehow bring the image into the HTML for preview purposes.
You say “supposedly” but the documentation does not say this and clearly states …
Value
A file input’s
value
attribute contains aDOMString
that represents the path to the selected file(s). If the user selected multiple files, thevalue
represents the first file in the list of files they selected. The other files can be identified using the input’sHTMLInputElement.files
property.
REF: <input type="file"> - HTML: HyperText Markup Language | MDN
So, … you have a PATHNAME to an existing image file, NOT a reference to an image file object.
You can either use this pathname directly to set a Sketchup::Material
object’s texture, or use the Ruby standard library FileUtils module methods ::copy_file()
or ::cp()
to copy them into your plugin’s materials collection.
Also, I think it asking for trouble to use old UI::WebDialog
parameter passing with UI::HtmlDialog
class objects. (This support could end in any new version and your plugins in the field will fail and anger customers.)
With the new class you do not need to concatenate all the values into 1 string and split it on the Ruby side. Just pass an Array of values an it’ll be an array on the Rubyside. OR, … pass a JSON string and read it into Ruby hash on the Rubyside.
The trick is in your JavaScript method that sends data to Ruby, you use a conditional that tests for the existence of the sketchup
object, … if it is there you use UI::HtmlDialog
parameter passing via the sketchup
object, … else you assume UI::WebDialog
and concat values and pass using the old skp:
protocol.
Ex:
if ( typeof sketchup != "undefined" ) {
sketchup.callback_name([val1,val2,val3]);
} else {
window.location = "skp:callback_name@"+[val1,val2,val3].join('|');
}
Actually with HTML5’s FileAPI you are able to manipulate the file contents on the client side. Initially this was the route I was taking but it turned out that using the UI.openpanel was easier and then combining that with FileUtils module provided stock in Ruby I was able to do everything I needed to do.
I was using the old webdialog method to pass the variables since I was trying to maintain backwards compatibility, however I see where you are going with this I will rework these sections of the code.
This issue aside, that is always something I’m probably going to have to contend with, things getting broken when a newer version of SU is released. Hopefully I can do everything I need to do to minimize this risk but when it does happen I just need to roll with the punches and fix anything that gets broken, in a sense its a form of job security.