Convert legacy .ini machine profiles to .def.json machine definitions #1316

Open
probonopd opened this Issue Jan 7, 2017 · 3 comments

Projects

None yet

3 participants

@probonopd
probonopd commented Jan 7, 2017 edited

How does one convert legacy .ini machine profiles to .def.json machine definitions?
I would like to convert this .ini which came with the machine to the .def.json format that newer versoins of Cura require that I could put in ~/.local/share/cura/definitions, without losing any information.

I found LegacyCuraMachineConverter but it seems unmaintained.

Especially:

  • This .ini seems to be "machine" related, yet newer Cura versions seem to suggest that there are "Printer" settings, and separate from that, "Profiles". The .ini that came with the machine seems to be a mixture of both. How do I split this into "Printer" and "Profile"?
  • Are there instructions on how to do this, or a converter to do it automatically? (I see that there is Cura/plugins/LegacyProfileReader/ but no documentation that I could find)
  • Are the keys named the same in the .ini and .def.json formats? (I see that there is Cura/plugins/LegacyProfileReader/DictionaryOfDoom.json but no documentation that I could find)
  • Are all keys from .ini supported in .def.json and vice versa?

Using http://quillford.github.io/CuraProfileMaker/ only gives a very rudimentary profile, compared to profiles like this one.

@nallath
Contributor
nallath commented Jan 9, 2017

There is indeed a difference between a "profile", which describes things like "print this on normal / high quality" or material "pla"/ "abs". The profiles can also describe something like "Abs high quality needs to be printed like this". To even further complicate things, there can be a "Generic ABS high quality" and a "Ultimaker 3 high quality" profile.

There are also machine definitions; These define standards of a machine. Think about physical dimensions, but also settings that due to it's setup should be considered default (eg; Bowden style printers tend to support higher acceleration)

The keys are different. Have a look at the "dictionary of doom". It contains a mapping of all settings from legacy to 2.x.

As for an automated script that fully uses all of this, i don't think it exists. It's just too complicated to do that. Your best bet is to do it by hand.

@awhiemstra
Member

From a brief look at the profile you mentioned, I'd say you want to at least include the [machine] and [alterations] sections of the ini file in the definition. Then you need to figure out what to do with the [profile] section. Some of those can probably be set as new defaults in the definition, but some may simply have become irrelevant.

@probonopd
probonopd commented Jan 15, 2017 edited

Wrote a converter that translates old .ini to new .def.json machine definitions:

import os, sys, urllib, json, math
import configparser # sudo apt install python-configparser

also_include_default_values = True # Set to True if all values should be converted even if they are default

ini_url = "https://gist.githubusercontent.com/probonopd/0dc2afa55fc31caa5ce1f5eea0f3255b/raw/cb1296e900ff1b05abba5adeb35300d835af2097/RF100.ini"
ini_filename = os.path.basename(ini_url)

dict_url = "https://raw.githubusercontent.com/Ultimaker/Cura/master/plugins/LegacyProfileReader/DictionaryOfDoom.json"
dict_filename = os.path.basename(dict_url)

def get_file(url):
    if not os.path.isfile(os.path.basename(url)):
        urllib.urlretrieve (url, os.path.basename(url))

get_file(ini_url)
get_file(dict_url)

parser = configparser.ConfigParser(interpolation = None)
try:
    with open(ini_filename) as f:
        parser.readfp(f)  # Parse the INI file.
except Exception as e:
    print("Unable to open legacy profile %s: %s", file_name, str(e))

try:
    with open(dict_filename, "r") as f:
        dict_of_doom = json.load(f)  # Parse the Dictionary of Doom.
except IOError as e:
    print("Could not open DictionaryOfDoom.json for reading: %s", str(e))
except Exception as e:
    print("Could not parse DictionaryOfDoom.json: %s", str(e))

if "target_version" not in dict_of_doom:
    print("Dictionary of Doom has no target version. Is it the correct JSON file?")

if "translation" not in dict_of_doom:
    print("Dictionary of Doom has no translation. Is it the correct JSON file?")

def prepareDefaults(json):
    defaults = {}
    for key in json["defaults"]:  # We have to copy over all defaults from the JSON handle to a normal dict.
        defaults[key] = json["defaults"][key]
    return defaults

def prepareLocals(config_parser, config_section, defaults):
    copied_locals = defaults.copy()  # Don't edit the original!
    for option in config_parser.options(config_section):
        copied_locals[option] = config_parser.get(config_section, option)
    return copied_locals

section = ""
for found_section in parser.sections():
    if found_section.startswith("profile"):
        section = found_section
        break
if not section:  # No section starting with "profile" was found. Probably not a proper INI file.
    print("No section starting with 'profile' was found. Probably not a proper INI file.")

defaults = prepareDefaults(dict_of_doom)
legacy_settings = prepareLocals(parser, section, defaults) #Gets the settings from the legacy profile.

data = {}

data['id'] = parser.get('machine', 'machine_name')
data['name'] = parser.get('machine', 'machine_name')
data['version'] = 2 # FIXME: Do not hardcode
data['inherits'] = "fdmprinter" # FIXME: Do not hardcode
metadata = {}
metadata['visible'] = True
metadata['author'] = "Converted from %s" % (ini_filename)
metadata['manufacturer'] = parser.get('machine', 'machine_name')
metadata['category'] = "Other"
metadata['file_formats'] = "text/x-gcode" # FIXME: Do not hardcode
metadata['platform_offset'] = [0, 0, 0] # FIXME: Do not hardcode
data['metadata'] = metadata

overrides = {}

for new_setting in dict_of_doom["translation"]:
    old_setting_expression = dict_of_doom["translation"][new_setting]
    # print old_setting_expression
    compiled = compile(old_setting_expression, new_setting, "eval")
    try:
        new_value = eval(compiled, {"math": math}, legacy_settings)
        value_using_defaults = eval(compiled, {"math": math}, defaults)
    except Exception:
        print("Setting " + new_setting + " could not be set because the evaluation failed. Something is probably missing from the imported legacy profile.")
        continue
    if new_value != value_using_defaults:
        try:
            overrides[new_setting] = {"default_value": float(new_value)}
        except:
            try:
                if((new_value == "True") or (new_value == "False")):
                    overrides[new_setting] = {"default_value": bool(new_value)}
                else:
                    overrides[new_setting] = {"default_value": new_value}
            except:
                overrides[new_setting] = {"default_value": new_value}
    else:
        if(also_include_default_values):
            try:
                overrides[new_setting] = {"default_value": float(new_value)}
            except:
                try:
                    if((new_value == "True") or (new_value == "False")):
                        overrides[new_setting] = {"default_value": bool(new_value)}
                    else:
                        overrides[new_setting] = {"default_value": new_value}
                except:
                    overrides[new_setting] = {"default_value": new_value}
overrides["adhesion_type"] = {"default_value": "skirt"}
overrides["machine_name"] = {"default_value": parser.get('machine', 'machine_name')}
overrides["machine_width"] = {"default_value": int(parser.get('machine', 'machine_width'))}
overrides["machine_height"] = {"default_value": int(parser.get('machine', 'machine_height'))}
overrides["machine_depth"] = {"default_value": int(parser.get('machine', 'machine_depth'))}
mystring = parser.get('alterations', 'start.gcode').replace("travel_speed", "speed_travel")
while '  ' in mystring:
    mystring = mystring.replace('  ', ' ')
overrides["machine_start_gcode"] = {"default_value": mystring}
mystring = parser.get('alterations', 'end.gcode').replace("travel_speed", "speed_travel")
while '  ' in mystring:
    mystring = mystring.replace('  ', ' ')
overrides["machine_end_gcode"] = {"default_value":mystring}
overrides["machine_gcode_flavor"] = {"default_value": parser.get('machine', 'gcode_flavor')}
data['overrides'] = overrides

if(parser.get('machine', 'has_heated_bed') == "False"):
    categories = {}
    material = {}
    settings = {}
    material_bed_temperature = {}
    material_bed_temperature['visible'] = False
    settings['material_bed_temperature'] = material_bed_temperature
    material['settings'] = settings
    categories['material'] = material
    data['categories'] = categories

json_data = json.dumps(data)

print json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))

with open(parser.get('machine', 'machine_name').lower() + '.def.json', 'w') as outfile:
    json.dump(data, outfile, sort_keys=True, indent=4, separators=(',', ': '))

With also_include_default_values = True this gives:

{
    "categories": {
        "material": {
            "settings": {
                "material_bed_temperature": {
                    "visible": false
                }
            }
        }
    },
    "id": "RF100-PLA",
    "inherits": "fdmprinter",
    "metadata": {
        "author": "Converted from RF100.ini",
        "category": "Other",
        "file_formats": "text/x-gcode",
        "manufacturer": "RF100-PLA",
        "platform_offset": [
            0,
            0,
            0
        ],
        "visible": true
    },
    "name": "RF100-PLA",
    "overrides": {
        "adhesion_type": {
            "default_value": "skirt"
        },
        "bottom_thickness": {
            "default_value": 0.5
        },
        "brim_line_count": {
            "default_value": 20.0
        },
        "cool_fan_enabled": {
            "default_value": true
        },
        "cool_fan_full_at_height": {
            "default_value": 0.5
        },
        "cool_fan_speed_max": {
            "default_value": 100.0
        },
        "cool_fan_speed_min": {
            "default_value": 100.0
        },
        "cool_lift_head": {
            "default_value": true
        },
        "cool_min_layer_time": {
            "default_value": 5.0
        },
        "cool_min_speed": {
            "default_value": 10.0
        },
        "infill_before_walls": {
            "default_value": 1.0
        },
        "infill_overlap": {
            "default_value": 15.0
        },
        "infill_sparse_density": {
            "default_value": 15.0
        },
        "layer_0_z_overlap": {
            "default_value": 0.22
        },
        "layer_height": {
            "default_value": 0.1
        },
        "layer_height_0": {
            "default_value": 0.3
        },
        "line_width": {
            "default_value": 0.4
        },
        "machine_depth": {
            "default_value": 100
        },
        "machine_end_gcode": {
            "default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
        },
        "machine_gcode_flavor": {
            "default_value": "RepRap (Marlin/Sprinter)"
        },
        "machine_height": {
            "default_value": 100
        },
        "machine_name": {
            "default_value": "RF100-PLA"
        },
        "machine_nozzle_size": {
            "default_value": 0.4
        },
        "machine_start_gcode": {
            "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F{speed_travel} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM117 Printing..."
        },
        "machine_width": {
            "default_value": 100
        },
        "magic_mesh_surface_mode": {
            "default_value": "surface"
        },
        "magic_spiralize": {
            "default_value": true
        },
        "material_bed_temperature": {
            "default_value": 70.0
        },
        "material_diameter": {
            "default_value": 1.75
        },
        "material_flow": {
            "default_value": 100.0
        },
        "material_print_temperature": {
            "default_value": 210.0
        },
        "meshfix_extensive_stitching": {
            "default_value": true
        },
        "meshfix_keep_open_polygons": {
            "default_value": true
        },
        "meshfix_union_all": {
            "default_value": true
        },
        "meshfix_union_all_remove_holes": {
            "default_value": true
        },
        "ooze_shield_enabled": {
            "default_value": true
        },
        "prime_tower_enable": {
            "default_value": true
        },
        "prime_tower_size": {
            "default_value": 12.24744871391589
        },
        "raft_airgap": {
            "default_value": 0.22
        },
        "raft_base_line_spacing": {
            "default_value": 3.0
        },
        "raft_base_line_width": {
            "default_value": 1.0
        },
        "raft_base_thickness": {
            "default_value": 0.3
        },
        "raft_interface_line_spacing": {
            "default_value": 3.0
        },
        "raft_interface_line_width": {
            "default_value": 0.4
        },
        "raft_interface_thickness": {
            "default_value": 0.27
        },
        "raft_margin": {
            "default_value": 5.0
        },
        "raft_surface_layers": {
            "default_value": 2.0
        },
        "raft_surface_line_spacing": {
            "default_value": 3.0
        },
        "raft_surface_line_width": {
            "default_value": 0.4
        },
        "raft_surface_thickness": {
            "default_value": 0.27
        },
        "retraction_amount": {
            "default_value": 2.0
        },
        "retraction_combing": {
            "default_value": "all"
        },
        "retraction_enable": {
            "default_value": true
        },
        "retraction_hop_enabled": {
            "default_value": 1.0
        },
        "retraction_min_travel": {
            "default_value": 1.5
        },
        "retraction_speed": {
            "default_value": 40.0
        },
        "skin_overlap": {
            "default_value": 15.0
        },
        "skirt_brim_minimal_length": {
            "default_value": 150.0
        },
        "skirt_gap": {
            "default_value": 3.0
        },
        "skirt_line_count": {
            "default_value": 1.0
        },
        "speed_infill": {
            "default_value": 50.0
        },
        "speed_layer_0": {
            "default_value": 30.0
        },
        "speed_print": {
            "default_value": 50.0
        },
        "speed_topbottom": {
            "default_value": 30.0
        },
        "speed_travel": {
            "default_value": 50.0
        },
        "speed_wall_0": {
            "default_value": 25.0
        },
        "speed_wall_x": {
            "default_value": 35.0
        },
        "support_angle": {
            "default_value": 60.0
        },
        "support_enable": {
            "default_value": 0.0
        },
        "support_infill_rate": {
            "default_value": 15.0
        },
        "support_pattern": {
            "default_value": "lines"
        },
        "support_type": {
            "default_value": "everywhere"
        },
        "support_xy_distance": {
            "default_value": 0.5
        },
        "support_z_distance": {
            "default_value": 0.1
        },
        "top_thickness": {
            "default_value": 0.5
        },
        "wall_thickness": {
            "default_value": 0.8
        }
    },
    "version": 2
}

With also_include_default_values = False this gives:

{
    "categories": {
        "material": {
            "settings": {
                "material_bed_temperature": {
                    "visible": false
                }
            }
        }
    },
    "id": "RF100-PLA",
    "inherits": "fdmprinter",
    "metadata": {
        "author": "Converted from RF100.ini",
        "category": "Other",
        "file_formats": "text/x-gcode",
        "manufacturer": "RF100-PLA",
        "platform_offset": [
            0,
            0,
            0
        ],
        "visible": true
    },
    "name": "RF100-PLA",
    "overrides": {
        "adhesion_type": {
            "default_value": "skirt"
        },
        "bottom_thickness": {
            "default_value": 0.5
        },
        "infill_sparse_density": {
            "default_value": 15.0
        },
        "machine_depth": {
            "default_value": 100
        },
        "machine_end_gcode": {
            "default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
        },
        "machine_gcode_flavor": {
            "default_value": "RepRap (Marlin/Sprinter)"
        },
        "machine_height": {
            "default_value": 100
        },
        "machine_name": {
            "default_value": "RF100-PLA"
        },
        "machine_start_gcode": {
            "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F{speed_travel} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM117 Printing..."
        },
        "machine_width": {
            "default_value": 100
        },
        "material_diameter": {
            "default_value": 1.75
        },
        "retraction_amount": {
            "default_value": 2.0
        },
        "speed_layer_0": {
            "default_value": 30.0
        },
        "speed_topbottom": {
            "default_value": 30.0
        },
        "speed_travel": {
            "default_value": 50.0
        },
        "speed_wall_0": {
            "default_value": 25.0
        },
        "speed_wall_x": {
            "default_value": 35.0
        },
        "support_xy_distance": {
            "default_value": 0.5
        },
        "support_z_distance": {
            "default_value": 0.1
        },
        "top_thickness": {
            "default_value": 0.5
        }
    },
    "version": 2
}

I wonder whether I can use the second, shorter version without losing information. But it seems to get e.g., the speed wrong, displaying Print Speed 60 mm/s and Travel Speed 120 mm/s in the GUI which is clearly not intended.

I had to removemagic_mesh_surface_mode and magic_spiralize which did no good and resulted in wrong slicing (only the outer surface was printed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment