Why unit conversion doesn't work?

An input like “0.15” can only be converted correctly with .to_l on systems with a period as a decimal separator. As well as “0,15” only converts correctly with .to_l on systems with a comma as decimal separator.

For this conversion you will first have to look at which decimal separator the user uses.
You can then provide the values ​​in the wallin Array with the correct decimal separator.
Now no ArgumentError will be caused because the correct decimal separator is applied.

1 Like

I had such a problem before with Russian keyboards but “0.15” was created by my plugin, not by the user. By the way, I will ask the user to use “,” instead of “.” and will inform you of the result. Is there any way to know user system use “.” or “,” for decimal separator?

Sketchup::RegionalSettings.decimal_separator

This returns the decimal character for the current user’s locale as a String.

1 Like

NO. This is a computer wise setting.

Your plugin needs to be regional-agile.

if Sketchup::RegionalSettings.decimal_separator != '.'
  info = wallin.map {|s| s.sub('.',',') }.map(&:to_l)
else
  info = wallin.map(&:to_l)
end
1 Like

Avoid representing lengths as either Floats or Strings in your code base. Use Length whenever you can. Only use Strings at the UI layer and only use Floats for storing values where you can’t use Length, e.g. to a config file. Always use Lengths in between.

The Length/String conversion is aware and dependent on the user’s settings such as system decimal sign, model unit, display precision etc. Length/Float conversion is always the same and can safely be used to store data.

When the user has entered Strings, immediately turn them to Lengths before using them. If you need to save them to a config file, convert them to floats before doing so. Never save the user input Strings directly to a config file if, as they can’t be read back reliable if the user settings change in between. SketchUp lengths string representations are meant for humans, not machines.

This shouldn’t be needed if the right classes are used. Also this would break if any other decimal separator than “.” and “,” is ever added or if “.” is used denote other things than decimal separator in the input.

4 Likes

Usually, I do the following steps.
1- Read the last saved data and use them in UI.inputbox and let users change them as they wish.
2- Convert data to length from string. (to_l)
3- If cannot convert reset data.
4- Save data in the way the user entered.
for example, if users write 10.cm for a length next time also 10.cm will show them.
Would you please let me know how can we do it?
If I change data to float, I will lose the unit entered by users (cm in this example).
Thank you for your attention.

Try instead:

  1. Read saved values (convert from Float to Length)
  2. Display UI
  3. Parse user input to Length
  4. Save values (converted to floats)

Do not save the string the user entered. Those strings only belong in the UI layer and are dead once they’ve been parsed to Lengths.

No SketchUp tool tries to show the value in the same unit as entered. If the user wants to read the value in cm, they can go to Window > Model Info > Units and set it to be cm.

1 Like

If system unit is inch and user enter “10.cm”, next time I will show him “3.937007874”. It is very simple and very safe but not so kind to users.

It would more likely display as “~3.93”, rounded according to the user´s preferences.

In any case, saving the input string directly is more likely to cause inconsistency as one field could say “3m”, one say “10 cm” and one say “15”, instead of them saying “3000 mm”, “100 mm” and “15 mm”.

Saving the input as a string can also lead to data loss if you type “3” in one model being set to m and have it read back in a model set to mm.

1 Like

For solving this problem I consider different saving spaces for different unit. So data that saved in meter unit is not same as data that saved for inch.

That alone wouldn’t guard against changing the system decimal separator, and it would prevent the previous input from being correctly formatted with the model unit if you mix m, cm and mm in your input. The string representations of Lengths just aren’t designed to be stored for later use this way.

You could still split up saving of values to avoid things like “~3.93"”, but for that splitting imperil from metric is probably better so a user keep their values when changing between say mm and cm.

1 Like

Btw, here’s a code snippet to detect metric/imperial model units. This can also be used to pick measurement system specific default values.

def metric?(model = Sketchup.active_model)
  unit_options = model.options["UnitsOptions"]
  return false unless unit_options["LengthFormat"] == Length::Decimal

  [
    Length::Millimeter,
    Length::Centimeter,
    Length::Meter
  ].include?(unit_options["LengthUnit"])
end

# Example usage
default = metric? ? 100.mm : 4.inch
1 Like

Majid, please read ThomThom’s blog …

1 Like

Thank you for the very useful document. Following come from it.
" Because users have different decimal separators depending on their locale, never store unit data as formatted strings! Don’t write Length values out as length.to_s – instead convert it to a Float . That way you can be sure that you can read the data back regardless of the locale of the target system because Float.to_s always use period as decimal separator – and String.to_f always expect a period. To load a unit from a float stored as string correctly you use: string.to_f.to_l ."

I think “string.to_f.to_l” is really good because in this way we never have errors even wrong number format like “x10”.(“x10”.to_f.to_l = 0). (x10.to_l = error). I think the best way is to read numbers and units from the user, convert them to float, and save numbers and its unit in our format. is it ok?
I used the following ruby code and it works well. If you enter 5in or 5", “5.0 Inch” will save.

if Sketchup::RegionalSettings.decimal_separator == '.'
  wallin = wallin.map {|s| s.sub(',', '.')}
else
  wallin = wallin.map {|s| s.sub('.', ',')}
end
for i in 0...9
  begin
    Float(wallin[i]) # if string is not purely a number error will happen.
    info[i] = wallin[i].to_l
    wallin[i] = wallin[i].to_f.to_s
  rescue
    if wallin[i].to_f != 0 # if it is 0 means data is not a number.
      if wallin[i].end_with?('"', "Inch", "inch", "INCH", "Inches", "INCHES", "inches", "in", "IN")
        info[i] = wallin[i].to_f
        wallin[i] = "#{wallin[i].to_f} Inch"
      elsif wallin[i].end_with?("'", "Foot", "foot", "ft", "FT", "FOOT", "Feet", "feet", "FEET")
        info[i] = wallin[i].to_f * 12
        wallin[i] = "#{wallin[i].to_f} ft"
      elsif wallin[i].end_with?("mm", "MM")
        info[i] = wallin[i].to_f / 25.4
        wallin[i] = "#{wallin[i].to_f} mm"
      elsif wallin[i].end_with?("cm", "CM")
        info[i] = wallin[i].to_f / 2.54
        wallin[i] = "#{wallin[i].to_f} cm"
      elsif wallin[i].end_with?("m", "M")
        info[i] = wallin[i].to_f / 0.0254
        wallin[i] = "#{wallin[i].to_f} m"
      elsif wallin[i].end_with?("Yard", "yard", "YARD", "yd", "YD")
        info[i] = wallin[i].to_f * 36.0
        wallin[i] = "#{wallin[i].to_f} yd"
      else
        info[i] = 1000000 # This is out of range number and cause all data will reset.
      end
    else
      info[i] = 1000000  # This is out of range number and cause all data will reset.
    end
  end
end

Thank you so much my plugin user problem solved by your help.

1 Like

I totally agree.

But for the record, when I posted that, I wasn’t giving a response based on best practices, but only directed at his specific use of an array of strings.


@Majid,

If you edit your plugin to follow Thomas’ (and Juila’s) advice and change the wallin array to hold Float values in inches (with period as the decimal separator,) then you will not need the character substitution.

Storing the values as Float, and convert to Length for the UI.inputbox call, it will automatically display and read user input in the user’s model units and return values as Length (decimal inches.)

Then you will not need the rigmarole you just posted at all.

1 Like

This approach does not work. “10,5” would be red as 10, because String#to_f doesn’t honor the user’s chosen decimal sign. Also it doesn’t differentiate “10 m”, “10 cm” and “10””.

Only use strings in the UI layer. Only use floats to serialize the value for storage. In ALL other cases, use Length for length values.

Failing on invalid input is not a bad thing. That’s where you notify the user the input was invalid.

1 Like

This is the reason at the beginning I used the following codes.

if Sketchup::RegionalSettings.decimal_separator == '.'
  wallin = wallin.map {|s| s.sub(',', '.')}
else
  wallin = wallin.map {|s| s.sub('.', ',')}
end

If your system decimal separator is “.” and you type 10,5 it will change to 10.5. As you can see in my codes, I only use string.to_f.to_l if it is a valid pure number.

Rather than trying to duplicate String#to_l you can just use String#to_l. It is less work, less error prone and it automatically works with any new units SketchUp may add in the future.

4 Likes

You wouldn’t get “10,5” is you wrote the Length to file by first converting it to Float. Float#to_s will always use period for decimal separator.

Then, when you read the file, you have the unit “10.5” as a string, which you convert to a Float, which also will always work because String.to_f always expect period as decimal separator.

Most often people serialize by converting the float to string. It’s simplest. Though you could serialize by using pack/unpack but the code becomes harder to read, I’d only do that if I had some good reason to serialize to binary format.