From 513941097f8d84365c94ea3d0f6905cd7026bea9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 17 Dec 2015 13:20:25 +0100 Subject: [PATCH] Initial LegacyProfileReader plugin implementation This plugin reads a profile from legacy Cura versions. It hasn't been tested much except that there are no syntax errors. It is currently being blocked by issue 34. Contributes to issue CURA-37. --- .../LegacyProfileReader/DictionaryOfDoom.json | 75 +++++++++++++++++ .../LegacyProfileReader.py | 84 +++++++++++++++++++ plugins/LegacyProfileReader/__init__.py | 27 ++++++ 3 files changed, 186 insertions(+) create mode 100644 plugins/LegacyProfileReader/DictionaryOfDoom.json create mode 100644 plugins/LegacyProfileReader/LegacyProfileReader.py create mode 100644 plugins/LegacyProfileReader/__init__.py diff --git a/plugins/LegacyProfileReader/DictionaryOfDoom.json b/plugins/LegacyProfileReader/DictionaryOfDoom.json new file mode 100644 index 0000000000..c357c72bcd --- /dev/null +++ b/plugins/LegacyProfileReader/DictionaryOfDoom.json @@ -0,0 +1,75 @@ +{ + "source_version": "15.04", + "target_version": "2.1", + + "translation": { + "line_width": "nozzle_size", + "layer_height": "layer_height", + "layer_height_0": "bottom_thickness", + "shell_thickness": "wall_thickness", + "top_bottom_thickness": "solid_layer_thickness", + "top_thickness": "solid_top", + "bottom_thickness": "solid_bottom", + "skin_no_small_gaps_heuristic": "fix_horrible_extensive_stitching", + "infill_sparse_density": "fill_density", + "infill_overlap": "fill_overlap", + "infill_before_walls": "perimeter_before_infill", + "material_print_temperature": "print_temperature", + "material_bed_temperature": "print_bed_temperature", + "material_diameter": "filament_diameter", + "material_flow": "filament_flow", + "retraction_enable": "retraction_enable", + "retraction_amount": "retraction_amount", + "retraction_speed": "retraction_speed", + "retraction_min_travel": "retraction_min_travel", + "retraction_hop": "retraction_hop", + "speed_print": "print_speed", + "speed_infill": "infill_speed", + "speed_wall_0": "inset0_speed", + "speed_wall_x": "insetx_speed", + "speed_topbottom": "solidarea_speed", + "speed_travel": "travel_speed", + "speed_layer_0": "bottom_layer_speed", + "retraction_combing": "retraction_combing", + "cool_fan_enabled": "fan_enabled", + "cool_fan_speed_min": "fan_speed", + "cool_fan_speed_max": "fan_speed_max", + "cool_fan_full_at_height": "fan_full_height", + "cool_min_layer_time": "cool_min_layer_time", + "cool_min_speed": "cool_min_feedrate", + "cool_lift_head": "cool_head_lift", + "support_enable": "support == \"None\" ? False : True", + "support_type": "support == \"Touching buildplate\" ? \"buildplate\" : \"everywhere\"", + "support_angle": "support_angle", + "support_xy_distance": "support_xy_distance", + "support_z_distance": "support_z_distance", + "support_pattern": "support_type.lower()", + "support_infill_rate": "support_fill_rate", + "platform_adhesion": "platform_adhesion.lower()", + "skirt_line_count": "skirt_line_count", + "skirt_gap": "skirt_gap", + "skirt_minimal_length": "skirt_minimal_length", + "brim_line_count": "brim_line_count", + "raft_margin": "raft_margin", + "raft_airgap": "raft_airgap_all", + "raft_surface_layers": "raft_surface_layers", + "raft_surface_thickness": "raft_surface_thickness", + "raft_surface_line_width": "raft_surface_linewidth", + "raft_surface_line_spacing": "raft_line_spacing", + "raft_interface_thickness": "raft_interface_thickness", + "raft_interface_line_width": "raft_interface_linewidth", + "raft_interface_line_spacing": "raft_line_spacing", + "raft_base_thickness": "raft_base_thickness", + "raft_base_line_width": "raft_base_linewidth", + "raft_base_line_spacing": "raft_line_spacing", + "meshfix_union_all": "fix_horrible_union_all_type_a", + "meshfix_union_all_remove_holes": "fix_horrible_union_all_type_b", + "meshfix_extensive_stitching": "fix_horrible_extensive_stitching", + "meshfix_keep_open_polygons": "fix_horrible_use_open_bits", + "magic_mesh_surface_mode": "simple_mode", + "magic_spiralize": "spiralize", + "prime_tower_enable": "wipe_tower", + "prime_tower_size": "sqrt(wipe_tower_volume / layer_height)", + "ooze_shield_enabled": "ooze_shield" + } +} \ No newline at end of file diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py new file mode 100644 index 0000000000..1ff8ef330a --- /dev/null +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -0,0 +1,84 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import json #For reading the Dictionary of Doom. + +from UM.Application import Application #To get the machine manager to create the new profile in. +from UM.Settings.Profile import Profile +from UM.Settings.ProfileReader import ProfileReader + +## A plugin that reads profile data from legacy Cura versions. +# +# It reads a profile from an .ini file, and performs some translations on it. +# Not all translations are correct, mind you, but it is a best effort. +class LegacyProfileReader(ProfileReader): + ## Initialises the legacy profile reader. + # + # This does nothing since the only other function is basically stateless. + def __init__(self): + super().__init__() + + ## Reads a legacy Cura profile from a file and returns it. + # + # \param file_name The file to read the legacy Cura profile from. + # \return The legacy Cura profile that was in the file, if any. If the + # file could not be read or didn't contain a valid profile, \code None + # \endcode is returned. + def read(self, file_name): + profile = Profile(machine_manager = Application.getInstance().getMachineManager(), read_only = False) #Create an empty profile. + profile.setName("Imported Legacy Profile") + + stream = io.StringIO(serialised) #ConfigParser needs to read from a stream. + parser = configparser.ConfigParser(interpolation = None) + parser.readfp(stream) + + #Legacy Cura saved the profile under the section "profile_N" where N is the ID of a machine, except when you export in which case it saves it in the section "profile". + #Since importing multiple machine profiles is out of scope, just import the first section we find. + 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. + return None + + legacy_settings = prepareLocals(parser, section) #Gets the settings from the legacy profile. + + try: + with open(os.path.join(PluginRegistry.getInstance().getPluginPath("LegacyProfileReader"), "DictionaryOfDoom.json"), "r", -1, "utf-8") as f: + dict_of_doom = json.load(f) #Parse the Dictionary of Doom. + except IOError as e: + Logger.log("e", "Could not open DictionaryOfDoom.json for reading: %s", str(e)) + return None + except Exception as e: + Logger.log("e", "Could not parse DictionaryOfDoom.json: %s", str(e)) + return None + + if "translations" not in dict_of_doom: + Logger.log("e", "Dictionary of Doom has no translations. Is it the correct JSON file?") + return None + for new_setting, old_setting_expression in dict_of_doom["translations"]: #Evaluate all new settings that would get a value from the translations. + compiled = compile(old_setting_expression, new_setting, "eval") + new_value = eval(compiled, {}, legacy_settings) #Pass the legacy settings as local variables to allow access to in the evaluation. + profile.setSettingValue(new_setting, new_value) #Store the setting in the profile! + + return profile + + ## Prepares the local variables that can be used in evaluation of computing + # new setting values from the old ones. + # + # This fills a dictionary with all settings from the legacy Cura version + # and their values, so that they can be used in evaluating the new setting + # values as Python code. + # + # \param parser The ConfigParser that finds the settings in the legacy + # profile. + # \param section The section in the profile where the settings should be + # found. + # \return A set of local variables, one for each setting in the legacy + # profile. + def prepareLocals(self, parser, section): + locals = {} + for option in parser.options(): + locals[option] = parser.get(section, option) + return locals \ No newline at end of file diff --git a/plugins/LegacyProfileReader/__init__.py b/plugins/LegacyProfileReader/__init__.py new file mode 100644 index 0000000000..e3b0ccc4b2 --- /dev/null +++ b/plugins/LegacyProfileReader/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from . import LegacyProfileReader + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + +def getMetaData(): + return { + "plugin": { + "name": catalog.i18nc("@label", "Legacy Cura Profile Reader"), + "author": "Ultimaker", + "version": "1.0", + "description": catalog.i18nc("@info:whatsthis", "Provides support for importing profiles from legacy Cura versions."), + "api": 2 + }, + "profile_reader": [ + { + "extension": "ini", + "description": catalog.i18nc("@item:inlistbox", "Cura 15.04 profiles") + } + ] + } + +def register(app): + return { "profile_reader": CuraProfileReader.CuraProfileReader() }