From aeb06f138002bc728da24d090b53b02666252db3 Mon Sep 17 00:00:00 2001 From: Trevor SANDY Date: Fri, 2 Jun 2023 00:20:49 +0200 Subject: [PATCH] Blender addon - settings dialog --- common/lc_blenderpreferences.cpp | 3618 ++++++++++++++++++++++++++++++ common/lc_blenderpreferences.h | 484 ++++ leocad.pro | 2 + 3 files changed, 4104 insertions(+) create mode 100644 common/lc_blenderpreferences.cpp create mode 100644 common/lc_blenderpreferences.h diff --git a/common/lc_blenderpreferences.cpp b/common/lc_blenderpreferences.cpp new file mode 100644 index 00000000..0bf6e660 --- /dev/null +++ b/common/lc_blenderpreferences.cpp @@ -0,0 +1,3618 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lc_blenderpreferences.h" +#include "lc_application.h" +#include "lc_mainwindow.h" +#include "lc_model.h" +#include "lc_colors.h" +#include "lc_colorpicker.h" +#include "lc_profile.h" +#include "lc_http.h" +#include "project.h" + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) +using Qt::SkipEmptyParts; +#else +const auto SkipEmptyParts = QString::SplitBehavior::SkipEmptyParts; +#endif + +const QLatin1String LineEnding("\r\n"); + +#define LC_PRODUCTNAME_STR "LeoCAD" +#define LC_BLENDER_ADDON "ImportLDraw" +#define LC_BLENDER_ADDON_MM "ImportLDrawMM" +#define LC_BLENDER_ADDON_FILE "LDrawBlenderRenderAddons.zip" +#define LC_BLENDER_ADDON_INSTALL_FILE "install_blender_ldraw_addons.py" +#define LC_BLENDER_ADDON_CONFIG_FILE "LDrawRendererPreferences.ini" +#define LC_BLENDER_ADDON_PARAMS_FILE "BlenderLDrawParameters.lst" + +#define LC_BLENDER_ADDON_REPO_STR "https://github.com/trevorsandy" +#define LC_BLENDER_ADDON_REPO_API_STR "https://api.github.com/repos/trevorsandy" +#define LC_BLENDER_ADDON_STR LC_BLENDER_ADDON_REPO_STR "/blenderldrawrender/" +#define LC_BLENDER_ADDON_API_STR LC_BLENDER_ADDON_REPO_API_STR "/blenderldrawrender/" +#define LC_BLENDER_ADDON_LATEST_URL LC_BLENDER_ADDON_API_STR "releases/latest" +#define LC_BLENDER_ADDON_URL LC_BLENDER_ADDON_STR "releases/latest/download/" LC_BLENDER_ADDON_FILE + +#define LC_THEME_DARK_PALETTE_MIDLIGHT "#3E3E3E" // 62, 62, 62, 255 +#define LC_THEME_DEFAULT_PALETTE_LIGHT "#AEADAC" // 174, 173, 172, 255 +#define LC_THEME_DARK_DECORATE_QUOTED_TEXT "#81D4FA" // 129, 212, 250, 255 +#define LC_DISABLED_TEXT "#808080" // 128, 128, 128, 255 + +#define LC_RENDER_IMAGE_MAX_SIZE 32768 // pixels + +static QString WhatsThisDescription = QObject::tr( + " Blender LDraw Addon Settings\n\n" + " You can configure the Blender LDraw addon settings.\n" + " - Blender Executable: set Blender path. On entry,\n" + " %1 will automatically apply the setting and\n" + " attempt the configure the LDraw addon.\n" + " - %1 Blender LDraw Addon: you can update the LDraw\n" + " addon which will downlod the latest addon or apply\n" + " the current addon if the version is the same or newer\n" + " than the online version.\n" + " You can view the standard output log for the update.\n\n" + " - Enabled Addon Modules: check the desired import module.\n" + " The LDraw Import TN import module is the long-standing\n" + " Blender LDraw import addon, while the LDraw Import MM\n" + " addon was recently introduced. The latter addon also\n" + " offer LDraw export functionality.\n" + " The %1 3D Image Render addon is mandatory but if\n" + " no import module is enabled, none of the modules\n" + " will be enabled in Blender so it will not be possible\n" + " to perform an LDraw model import or render.\n\n" + " - LDraw Import Addon Paths: addon paths are specific\n" + " to the enabled addon import module.\n" + " - LDraw Import Addon Settings: addon settings are\n" + " specific to the enabled addon import module.\n" + " - Apply: apply the addon path and setting preferences.\n" + " - Show/Hide Paths: show or hide the addon paths\n" + " display box.\n" + " - Reset: reset the addon path and setting preferences.\n" + " You can select how to reset addon settings.\n" + " The choice is since last apply or system default.\n\n" + " You can see the specific description of each setting\n" + " if you hover over the setting to display its tooltip.\n\n" + " Image Width, Image Height and Render Percentage are\n" + " always updated from the current step model when the\n" + " this dialog is opened. These settngs can be manually\n" + " overridden, Also, when Crop Image is checked the\n" + " current step cropped image width and height is\n" + " calculated and and used\n\n" + " Use the dialogue window scroll bar to access the\n" + " complete selection of addon settings.\n") +.arg(QLatin1String(LC_PRODUCTNAME_STR)); + +lcBlenderPreferences::BlenderPaths lcBlenderPreferences::mBlenderPaths [NUM_PATHS]; +lcBlenderPreferences::BlenderPaths lcBlenderPreferences::mDefaultPaths [NUM_PATHS] = +{ + /* Key: MM Key: Value: Label: Tooltip (Description):*/ + /* 0 PATH_BLENDER */ {"blenderpath", "blenderpath", "", QObject::tr("Blender Path"), QObject::tr("Full file path to Blender application executable")}, + /* 1 PATH_BLENDFILE */ {"blendfile", "blendfile", "", QObject::tr("Blendfile Path"), QObject::tr("Full file path to a supplement .blend file - specify to append additional settings")}, + /* 2. PATH_ENVIRONMENT */ {"environmentfile", "environmentfile", "", QObject::tr("Environment Texture Path"), QObject::tr("Full file path to .exr environment texture file - specify if not using default bundled in addon")}, + /* 3 PATH_LDCONFIG */ {"customldconfigfile", "customldconfigfile", "", QObject::tr("Custom LDConfig Path"), QObject::tr("Full file path to custom LDConfig file - specify if not %1 alternate LDConfig file").arg(LC_PRODUCTNAME_STR)}, + /* 4 PATH_LDRAW */ {"ldrawdirectory", "ldrawpath", "", QObject::tr("LDraw Directory"), QObject::tr("Full directory path to the LDraw parts library (download from https://library.ldraw.org)")}, + /* 5 PATH_LSYNTH */ {"lsynthdirectory", "", "", QObject::tr("LSynth Directory"), QObject::tr("Full directory path to LSynth primitives - specify if not using default bundled in addon")}, + /* 6 PATH_STUD_LOGO */ {"studlogodirectory", "", "", QObject::tr("Stud Logo Directory"), QObject::tr("Full directory path to stud logo primitives - if stud logo enabled, specify if unofficial parts not used or not using default bundled in addon")}, + /* 7 PATH_STUDIO_LDRAW */ {"", "studioldrawpath", "", QObject::tr("Stud.io LDraw Path"), QObject::tr("Full filepath to the Stud.io LDraw Parts Library (download from https://www.bricklink.com/v3/studio/download.page)")} +}; + +lcBlenderPreferences::BlenderSettings lcBlenderPreferences::mBlenderSettings [NUM_SETTINGS]; +lcBlenderPreferences::BlenderSettings lcBlenderPreferences::mDefaultSettings [NUM_SETTINGS] = +{ + /* Key: Value: Label Tooltip (Description)*/ + /* 0 LBL_ADD_ENVIRONMENT */ {"addenvironment", "1", QObject::tr("Add Environment"), QObject::tr("Adds a ground plane and environment texture (affects 'Photo-realistic' look only)")}, + /* 1 LBL_ADD_GAPS */ {"gaps", "0", QObject::tr("Add Part Gap"), QObject::tr("Add a small space between each part")}, + /* 2 LBL_BEVEL_EDGES */ {"beveledges", "1", QObject::tr("Bevel Edges"), QObject::tr("Adds a Bevel modifier for rounding off sharp edges")}, + /* 3 LBL_BLENDFILE_TRUSTED */ {"blendfiletrusted", "0", QObject::tr("Trusted Blend File"), QObject::tr("Specify whether to treat the .blend file as being loaded from a trusted source")}, + /* 4 LBL_CROP_IMAGE */ {"cropimage", "0", QObject::tr("Crop Image"), QObject::tr("Crop the image border at opaque content. Requires transparent background set to True")}, + /* 5 LBL_CURVED_WALLS */ {"curvedwalls", "1", QObject::tr("Curved Walls"), QObject::tr("Makes surfaces look slightly concave")}, + /* 6 LBL_FLATTEN_HIERARCHY */ {"flattenhierarchy", "0", QObject::tr("Flatten Hierarchy"), QObject::tr("In Scene Outline, all parts are placed directly below the root - there's no tree of submodels")}, + /* 7 LBL_IMPORT_CAMERAS */ {"importcameras", "1", QObject::tr("Import Cameras"), QObject::tr("%1 can specify camera definitions within the ldraw data. Choose to load them or ignore them.").arg(LC_PRODUCTNAME_STR)}, + /* 8 LBL_IMPORT_LIGHTS */ {"importlights", "1", QObject::tr("Import Lights"), QObject::tr("%1 can specify point and sunlight definitions within the ldraw data. Choose to load them or ignore them.").arg(LC_PRODUCTNAME_STR)}, + /* 9 LBL_INSTANCE_STUDS */ {"instancestuds", "0", QObject::tr("Instance Studs"), QObject::tr("Creates a Blender Object for each and every stud (WARNING: can be slow to import and edit in Blender if there are lots of studs)")}, + /*10 LBL_KEEP_ASPECT_RATIO */ {"keepaspectratio", "1", QObject::tr("Keep Aspect Ratio"), QObject::tr("Maintain the aspect ratio when resizing the output image - this attribute is not passed to Blender")}, + /*11 LBL_LINK_PARTS */ {"linkparts", "1", QObject::tr("Link Like Parts"), QObject::tr("Identical parts (of the same type and colour) share the same mesh")}, + /*12 LBL_MINIFIG_HIERARCHY */ {"minifighierarchy", "1", QObject::tr("Parent Minifigs"), QObject::tr("Parts of minifigs are automatically parented to each other in a hierarchy")}, + /*13 LBL_NUMBER_NODES */ {"numbernodes", "1", QObject::tr("Number Objects"), QObject::tr("Each object has a five digit prefix eg. 00001_car. This keeps the list in it's proper order")}, + /*14 LBL_OVERWRITE_IMAGE */ {"overwriteimage", "1", QObject::tr("Overwrite Image"), QObject::tr("Specify whether to overwrite an existing rendered image file")}, + /*15 LBL_OVERWRITE_MATERIALS */ {"overwriteexistingmaterials", "0", QObject::tr("Use Existing Material"), QObject::tr("Overwrite existing material with the same name")}, + /*16 LBL_OVERWRITE_MESHES */ {"overwriteexistingmeshes", "0", QObject::tr("Use Existing Mesh"), QObject::tr("Overwrite existing mesh with the same name")}, + /*17 LBL_POSITION_CAMERA */ {"positioncamera", "1", QObject::tr("Position Camera"), QObject::tr("Position the camera to show the whole model")}, + /*18 LBL_REMOVE_DOUBLES */ {"removedoubles", "1", QObject::tr("No Duplicate Vertices"), QObject::tr("Remove duplicate vertices (recommended)")}, + /*19 LBL_RENDER_WINDOW */ {"renderwindow", "1", QObject::tr("Display Render Window"), QObject::tr("Specify whether to display the render window during Blender user interface image file render")}, + /*10 LBL_USE_ARCHIVE_LIBS */ {"usearchivelibrary", "0", QObject::tr("Use Archive Libraries"), QObject::tr("Add any archive (zip) libraries in the LDraw file path to the library search list")}, + /*21 LBL_SEARCH_ADDL_PATHS */ {"searchadditionalpaths", "0", QObject::tr("Search Additional Paths"),QObject::tr("Specify whether to search additional LDraw paths")}, + /*22 LBL_SMOOTH_SHADING */ {"smoothshading", "1", QObject::tr("Smooth Shading"), QObject::tr("Smooth faces and add an edge-split modifier (recommended)")}, + /*23 LBL_TRANSPARENT_BACKGROUND*/ {"transparentbackground", "0", QObject::tr("Transparent Background"), QObject::tr("Specify whether to render a background (affects 'Photo-realistic look only)")}, + /*24 LBL_UNOFFICIAL_PARTS */ {"useunofficialparts", "1", QObject::tr("Use Unofficial Parts"), QObject::tr("Specify whether to use parts from the LDraw unofficial parts library path")}, + /*25 LBL_USE_LOGO_STUDS */ {"uselogostuds", "0", QObject::tr("Use Logo Studs"), QObject::tr("Shows the LEGO logo on each stud (at the expense of some extra geometry and import time)")}, + /*26 LBL_VERBOSE */ {"verbose", "1", QObject::tr("Verbose output"), QObject::tr("Output all messages while working, else only show warnings and errors")}, + + /*27/0 LBL_BEVEL_WIDTH */ {"bevelwidth", "0.5", QObject::tr("Bevel Width"), QObject::tr("Width of the bevelled edges")}, + /*28/1 LBL_CAMERA_BORDER_PERCENT */ {"cameraborderpercentage", "5.0", QObject::tr("Camera Border Percent"), QObject::tr("When positioning the camera, include a (percentage) border around the model in the render")}, + /*29/2 LBL_DEFAULT_COLOUR */ {"defaultcolour", "16", QObject::tr("Default Colour"), QObject::tr("Sets the default part colour using LDraw colour code")}, + /*20/3 LBL_GAPS_SIZE */ {"gapwidth", "0.01", QObject::tr("Gap Width"), QObject::tr("Amount of gap space between each part")}, + /*31/4 LBL_IMAGE_WIDTH */ {"resolutionwidth", "800", QObject::tr("Image Width"), QObject::tr("Sets the rendered image width in pixels - from current step image, label shows config setting.")}, + /*32/5 LBL_IMAGE_HEIGHT */ {"resolutionheight", "600", QObject::tr("Image Height"), QObject::tr("Sets the rendered image height in pixels - from current step image, label shows config setting.")}, + /*33/6 LBL_IMAGE_SCALE */ {"scale", "0.02", QObject::tr("Image Scale"), QObject::tr("Sets the imported model scale (between .01 and 1.0). Scale is 1.0 and is huge and unwieldy in the viewport")}, + /*34/6 LBL_RENDER_PERCENTAGE */ {"renderpercentage", "100", QObject::tr("Render Percentage"), QObject::tr("Sets the rendered image percentage scale for its pixel resolution - updated from current step, label shows config setting.")}, + + /*35/0 LBL_COLOUR_SCHEME */ {"usecolourscheme", "lgeo", QObject::tr("Colour Scheme"), QObject::tr("Colour scheme options - Realistic (lgeo), Original (LDConfig), Alternate (LDCfgalt), Custom (User Defined)")}, + /*36/1 LBL_FLEX_PARTS_SOURCE */ {"uselsynthparts", "1", QObject::tr("Flex Parts Source"), QObject::tr("Source used to create flexible parts - string, hoses etc. (LDCad, LSynth or both")}, + /*27/2 LBL_LOGO_STUD_VERSION */ {"logostudversion", "4", QObject::tr("Logo Version"), QObject::tr("Which version of the logo to use ('3' (flat), '4' (rounded) or '5' (subtle rounded))")}, + /*38/3 LBL_LOOK */ {"uselook", "normal", QObject::tr("Look"), QObject::tr("Photo-realistic or Schematic 'Instruction' look")}, + /*39/4 LBL_POSITION_OBJECT */ {"positionobjectongroundatorigin", "1", QObject::tr("Position Object"), QObject::tr("The object is centred at the origin, and on the ground plane")}, + /*40/5 LBL_RESOLUTION */ {"resolution", "Standard", QObject::tr("Resolution"), QObject::tr("Resolution of part primitives, ie. how much geometry they have")}, + /*41/6 LBL_RESOLVE_NORMALS */ {"resolvenormals", "guess", QObject::tr("Resolve Normals"), QObject::tr("Some older LDraw parts have faces with ambiguous normals, this specifies what do do with them")} +}; + +lcBlenderPreferences::ComboItems lcBlenderPreferences::mComboItems [NUM_COMBO_ITEMS] = +{ + /* FIRST item set as default Data Item: */ + /* 00 LBL_COLOUR_SCHEME */ {"lgeo|ldraw|alt|custom", QObject::tr("Realistic Colours|Original LDraw Colours|Alternate LDraw Colours|Custom Colours")}, + /* 01 LBL_FLEX_PARTS_SOURCE (t/f) */ {"1|0|1", QObject::tr("LSynth|LDCad|LDCad and LSynth")}, + /* 02 LBL_LOGO_STUD_VERSION */ {"4|3|5", QObject::tr("Rounded(4)|Flattened(3)|Subtle Rounded(5)")}, + /* 03 LBL_LOOK */ {"normal|instructions", QObject::tr("Photo Realistic|Lego Instructions")}, + /* 04 LBL_POSITION_OBJECT (t/f) */ {"1|0", QObject::tr("Centered At Origin On Ground|Centered At Origin")}, + /* 05 LBL_RESOLUTION */ {"Standard|High|Low", QObject::tr("Standard Primitives|High Resolution Primitives|Low Resolution Primitives")}, + /* 06 LBL_RESOLVE_NORMALS */ {"guess|double", QObject::tr("Recalculate Normals|Two Faces Back To Back")} +}; + +lcBlenderPreferences::BlenderSettings lcBlenderPreferences::mBlenderSettingsMM [NUM_SETTINGS_MM]; +lcBlenderPreferences::BlenderSettings lcBlenderPreferences::mDefaultSettingsMM [NUM_SETTINGS_MM] = +{ + /* Key: Value: Label Tooltip (Description)*/ + /* 00 LBL_ADD_ENVIRONMENT_MM */ {"addenvironment", "1", QObject::tr("Add Environment"), QObject::tr("Adds a ground plane and environment texture")}, + /* 01 LBL_BEVEL_EDGES_MM */ {"beveledges", "0", QObject::tr("Bevel Edgest"), QObject::tr("Bevel edges. Can cause some parts to render incorrectly")}, + /* 02 LBL_BLEND_FILE_TRUSTED_MM */ {"blendfiletrusted", "0", QObject::tr("Trusted Blend File"), QObject::tr("Specify whether to treat the .blend file as being loaded from a trusted source")}, + /* 03 LBL_CROP_IMAGE_MM */ {"cropimage", "0", QObject::tr("Crop Image"), QObject::tr("Crop the image border at opaque content. Requires transparent background set to True")}, + /* 04 LBL_DISPLAY_LOGO */ {"displaylogo", "0", QObject::tr("Display Logo"), QObject::tr("Display the logo on the stud")}, + /* 05 LBL_IMPORT_CAMERAS_MM */ {"importcameras", "1", QObject::tr("Import Cameras"), QObject::tr("%1 can specify camera definitions within the ldraw data. Choose to load them or ignore them.").arg(LC_PRODUCTNAME_STR)}, + /* 06 LBL_IMPORT_EDGES */ {"importedges", "0", QObject::tr("Import Edges"), QObject::tr("Import LDraw edges as edges")}, + /* 07 LBL_IMPORT_LIGHTS_MM */ {"importlights", "0", QObject::tr("Import Lights"), QObject::tr("%1 can specify point and sunlight definitions within the ldraw data. Choose to load them or ignore them.").arg(LC_PRODUCTNAME_STR)}, + /* 08 LBL_KEEP_ASPECT_RATIO_MM */ {"keepaspectratio", "1", QObject::tr("Keep Aspect Ratio"), QObject::tr("Maintain the aspect ratio when resizing the output image - this attribute is not passed to Blender")}, + /* 09 LBL_MAKE_GAPS */ {"makegaps", "1", QObject::tr("Make Gaps"), QObject::tr("Make small gaps between bricks. A small gap is more realistic")}, + /* 10 LBL_META_BFC */ {"metabfc", "1", QObject::tr("BFC"), QObject::tr("Process LDraw Back Face Culling meta commands")}, + /* 11 LBL_META_CLEAR */ {"metaclear", "0", QObject::tr("CLEAR Command"), QObject::tr("Hides all parts in the timeline up to where this command is encountered")}, + /* 12 LBL_META_GROUP */ {"metagroup", "1", QObject::tr("GROUP Command"), QObject::tr("Process GROUP meta commands")}, + /* 13 LBL_META_PAUSE */ {"metapause", "0", QObject::tr("PAUSE Command"), QObject::tr("Not implemented")}, + /* 14 LBL_META_PRINT_WRITE */ {"metaprintwrite", "0", QObject::tr("PRINT/WRITE Command"), QObject::tr("Prints PRINT/WRITE META commands to the system console.")}, + /* 15 LBL_META_SAVE */ {"metasave", "0", QObject::tr("SAVE Command"), QObject::tr("Not implemented")}, + /* 16 LBL_META_STEP */ {"metastep", "0", QObject::tr("STEP Command"), QObject::tr("Adds a keyframe that shows the part at the moment in the timeline")}, + /* 17 LBL_META_STEP_GROUPS */ {"metastepgroups", "0", QObject::tr("STEP Groups"), QObject::tr("Create collection for individual steps")}, + /* 18 LBL_META_TEXMAP */ {"metatexmap", "1", QObject::tr("TEXMAP and DATA Command"), QObject::tr("Process TEXMAP and DATA meta commands")}, + /* 19 LBL_NO_STUDS */ {"nostuds", "0", QObject::tr("No Studs"), QObject::tr("Don't import studs")}, + /* 20 LBL_OVERWRITE_IMAGE_MM */ {"overwriteimage", "1", QObject::tr("Overwrite Image"), QObject::tr("Specify whether to overwrite an existing rendered image file")}, + /* 21 LBL_POSITION_CAMERA_MM */ {"positioncamera", "1", QObject::tr("Position Camera"), QObject::tr("Position the camera to show the whole model")}, + /* 22 LBL_PARENT_TO_EMPTY */ {"parenttoempty", "1", QObject::tr("Parent To Empty"), QObject::tr("Parent the model to an empty")}, + /* 23 LBL_PREFER_STUDIO */ {"preferstudio", "0", QObject::tr("Prefer Stud.io Library"), QObject::tr("Search for parts in Stud.io library first")}, + /* 24 LBL_PREFER_UNOFFICIAL */ {"preferunofficial", "0", QObject::tr("Prefer Unofficial Parts"), QObject::tr("Search for unofficial parts first")}, + /* 25 LBL_PRESERVE_HIERARCHY */ {"preservehierarchy", "0", QObject::tr("Preserve File Structure"), QObject::tr("Don't merge the constituent subparts and primitives into the top level part. Some parts may not render properly")}, + /* 26 LBL_PROFILE */ {"profile", "0", QObject::tr("Profile"), QObject::tr("Profile import performance")}, + /* 27 LBL_RECALCULATE_NORMALS */ {"recalculatenormals", "0", QObject::tr("Recalculate Normals"), QObject::tr("Recalculate normals. Not recommended if BFC processing is active")}, + /* 28 LBL_REMOVE_DOUBLES_MM */ {"removedoubles", "1", QObject::tr("No Duplicate Vertices"), QObject::tr("Merge vertices that are within a certain distance.")}, + /* 29 LBL_RENDER_WINDOW_MM */ {"renderwindow", "1", QObject::tr("Display Render Window"), QObject::tr("Specify whether to display the render window during Blender user interface image file render")}, + /* 30 LBL_SEARCH_ADDL_PATHS_MM */ {"searchadditionalpaths", "0", QObject::tr("Search Additional Paths"), QObject::tr("Specify whether to search additional LDraw paths")}, + /* 31 LBL_SETEND_FRAME */ {"setendframe", "1", QObject::tr("Set Step End Frame"), QObject::tr("Set the end frame to the last step")}, + /* 32 LBL_SET_TIMELINE_MARKERS */ {"settimelinemarkers", "0", QObject::tr("Set Timeline Markers"), QObject::tr("Set timeline markers for meta commands")}, + /* 33 LBL_SHADE_SMOOTH */ {"shadesmooth", "1", QObject::tr("Shade Smooth"), QObject::tr("Use flat or smooth shading for part faces")}, + /* 34 LBL_TRANSPARENT_BACKGROUND_MM */ {"transparentbackground", "0", QObject::tr("Transparent Background"), QObject::tr("Specify whether to render a background")}, + /* 35 LBL_TREAT_MODELS_WITH_SUBPARTS_AS_PARTS */ {"treatmodelswithsubpartsasparts","1", QObject::tr("Treat Subparts As Parts"), QObject::tr("Treat subpart like a part by merging its constituent parts into one object")}, + /* 36 LBL_TREAT_SHORTCUT_AS_MODEL */ {"treatshortcutasmodel", "0", QObject::tr("Treat Shortcuts As Models"),QObject::tr("Split shortcut parts into their constituent pieces as if they were models")}, + /* 37 LBL_TRIANGULATE */ {"triangulate", "0", QObject::tr("Triangulate Faces"), QObject::tr("Triangulate all faces")}, + /* 38 LBL_USE_ARCHIVE_LIBRARY_MM */ {"usearchivelibrary", "0", QObject::tr("Use Archive Libraries"), QObject::tr("Add any archive (zip) libraries in the LDraw file path to the library search list")}, + /* 39 LBL_USE_FREESTYLE_EDGES */ {"usefreestyleedges", "0", QObject::tr("Use Freestyle Edges"), QObject::tr("Render LDraw edges using freestyle")}, + /* 40 LBL_VERBOSE_MM */ {"verbose", "1", QObject::tr("Verbose output"), QObject::tr("Output all messages while working, else only show warnings and errors")}, + + /* 41/00 LBL_BEVEL_SEGMENTS */ {"bevelsegments", "4", QObject::tr("Bevel Segments"), QObject::tr("Bevel segments")}, + /* 42/01 LBL_BEVEL_WEIGHT */ {"bevelweight", "0.3", QObject::tr("Bevel Weight"), QObject::tr("Bevel weight")}, + /* 43/02 LBL_BEVEL_WIDTH_MM */ {"bevelwidth", "0.3", QObject::tr("Bevel Width"), QObject::tr("Width of the bevelled edges")}, + /* 44/03 LBL_CAMERA_BORDER_PERCENT_MM */ {"cameraborderpercent", "5", QObject::tr("Camera Border Percent"), QObject::tr("When positioning the camera, include a (percentage) border around the model in the render")}, + /* 45/04 LBL_FRAMES_PER_STEP */ {"framesperstep", "3", QObject::tr("Frames Per Step"), QObject::tr("Frames per step")}, + /* 46/05 LBL_GAP_SCALE */ {"gapscale", "0.997", QObject::tr("Gap Scale"), QObject::tr("Scale individual parts by this much to create the gap")}, + /* 47/06 LBL_IMPORT_SCALE */ {"importscale", "0.02", QObject::tr("Import Scale"), QObject::tr("What scale to import at. Full scale is 1.0 and is so huge that it is unwieldy in the viewport")}, + /* 48/07 LBL_MERGE_DISTANCE */ {"mergedistance", "0.05", QObject::tr("Merge Distance"), QObject::tr("Maximum distance between elements to merge")}, + /* 49/08 LBL_RENDER_PERCENTAGE_MM */ {"renderpercentage", "100", QObject::tr("Render Percentage"), QObject::tr("Sets the rendered image percentage scale for its pixel resolution - updated from current step, label shows config setting.")}, + /* 50/09 LBL_RESOLUTION_WIDTH */ {"resolutionwidth", "800", QObject::tr("Image Width"), QObject::tr("Sets the rendered image width in pixels - from current step image, label shows config setting.")}, + /* 51/10 LBL_RESOLUTION_HEIGHT */ {"resolutionheight", "600", QObject::tr("Image Height"), QObject::tr("Sets the rendered image height in pixels - from current step image, label shows config setting.")}, + /* 52/11 LBL_STARTING_STEP_FRAME */ {"startingstepframe", "1", QObject::tr("Starting Step Frame"), QObject::tr("Frame to add the first STEP meta command")}, + + /* 53/00 LBL_CHOSEN_LOGO */ {"chosenlogo", "logo3", QObject::tr("Chosen Logo"), QObject::tr("Which logo to display. logo and logo2 aren't used and are only included for completeness")}, + /* 54/01 LBL_COLOUR_SCHEME_MM */ {"usecolourscheme", "lgeo", QObject::tr("Colour Scheme"), QObject::tr("Colour scheme options - Realistic (lgeo), Original (LDConfig), Alternate (LDCfgalt), Custom (User Defined)")}, + /* 55/02 LBL_GAP_SCALE_STRATEGY */ {"gapscalestrategy", "constraint", QObject::tr("Gap Strategy"), QObject::tr("Apply gap to object directly or scale and empty to adjust to gaps between parts")}, + /* 56/03 LBL_GAP_TARGET */ {"gaptarget", "object", QObject::tr("Gap Target"), QObject::tr("Whether to scale the object data or mesh data")}, + /* 57/04 LBL_RESOLUTION_MM */ {"resolution", "Standard", QObject::tr("Resolution"), QObject::tr("Resolution of part primitives, ie. how much geometry they have")}, + /* 58/05 LBL_SMOOTH_TYPE */ {"smoothtype", "edge_split", QObject::tr("Smooth Type"), QObject::tr("Use either autosmooth or an edge split modifier to smooth part faces")} +}; + +lcBlenderPreferences::ComboItems lcBlenderPreferences::mComboItemsMM [NUM_COMBO_ITEMS_MM] = +{ + /* FIRST item set as default Data Item: */ + /* 00 LBL_CHOSEN_LOGO */ {"logo|logo2|logo3", QObject::tr("Logo (Not Used)|Logo2 (Not Used)|Flattened(3)")}, + /* 01 LBL_COLOUR_SCHEME_MM */ {"lgeo|ldraw|alt|custom", QObject::tr("Realistic Colours|Original LDraw Colours|Alternate LDraw Colours|Custom Colours")}, + /* 02 LBL_GAP_SCALE_STRATEGY */ {"object|constraint", QObject::tr("Gap applied directly to object|Gap scaled to adjust to gaps between parts")}, + /* 03 LBL_GAP_TARGET */ {"object|mesh", QObject::tr("Scale object data|Scale mesh data")}, + /* 04 LBL_RESOLUTION_MM */ {"Low|Standard|High", QObject::tr("Low Resolution Primitives|Standard Primitives|High Resolution Primitives")}, + /* 05 LBL_SMOOTH_TYPE */ {"edge_split|auto_smooth|bmesh_split", QObject::tr("Smooth part faces with edge split modifier|Auto-smooth part faces|Split during initial mesh processing")} +}; + +lcBlenderPreferences* gAddonPreferences; + +lcBlenderPreferencesDialog::lcBlenderPreferencesDialog(int Width, int Height, double Scale, QWidget* Parent) + : QDialog(Parent) +{ + setWindowTitle(tr("Blender LDraw Addon Settings")); + + QVBoxLayout* Layout = new QVBoxLayout(this); + setLayout(Layout); + + QGroupBox* Box = new QGroupBox(this); + Layout->addWidget(Box); + + mPreferences = new lcBlenderPreferences(Width,Height,Scale,Box); + + QDialogButtonBox* ButtonBox; + ButtonBox = new QDialogButtonBox(this); + mApplyButton = new QPushButton(tr("Apply"), ButtonBox); + mApplyButton->setToolTip(tr("Apply addon paths and settings preferences")); + mApplyButton->setEnabled(false); + ButtonBox->addButton(mApplyButton, QDialogButtonBox::ActionRole); + connect(mApplyButton,SIGNAL(clicked()), this, SLOT(accept())); + + mPathsButton = new QPushButton(tr("Hide Paths"), ButtonBox); + mPathsButton->setToolTip(tr("Hide addon path preferences dialog")); + ButtonBox->addButton(mPathsButton,QDialogButtonBox::ActionRole); + connect(mPathsButton,SIGNAL(clicked()), this, SLOT(ShowPathsGroup())); + + mResetButton = new QPushButton(tr("Reset"), ButtonBox); + mResetButton->setEnabled(false); + mResetButton->setToolTip(tr("Reset addon paths and settings preferences")); + ButtonBox->addButton(mResetButton,QDialogButtonBox::ActionRole); + connect(mResetButton,SIGNAL(clicked()), this, SLOT(ResetSettings())); + + ButtonBox->addButton(QDialogButtonBox::Cancel); + connect(ButtonBox,SIGNAL(rejected()), this, SLOT(reject())); + + if (!QFileInfo(lcGetProfileString(LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH)).isReadable() && !lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty()) + mApplyButton->setEnabled(true); + + connect(mPreferences,SIGNAL(SettingChangedSig(bool)), this, SLOT(EnableButton(bool))); + + Layout->addWidget(ButtonBox); + + setMinimumSize(Box->sizeHint().width() + 50, 500); + + setSizeGripEnabled(true); + + setModal(true); +} + +lcBlenderPreferencesDialog::~lcBlenderPreferencesDialog() +{ +} + +bool lcBlenderPreferencesDialog::GetBlenderPreferences(int& Width, int& Height, double & Scale, QWidget* Parent) +{ + lcBlenderPreferencesDialog* Dialog = new lcBlenderPreferencesDialog(Width,Height,Scale,Parent); + + bool Ok = Dialog->exec() == QDialog::Accepted; + + if (Ok) + { + Dialog->mPreferences->Apply(Ok); + Width = Dialog->mPreferences->mImageWidth; + Height = Dialog->mPreferences->mImageHeight; + Scale = Dialog->mPreferences->mScale; + } + + return Ok; +} + +void lcBlenderPreferencesDialog::ShowPathsGroup() +{ + QString const Display = mPathsButton->text().startsWith("Hide") ? tr("Show") : tr("Hide"); + mPathsButton->setText(tr("%1 Paths").arg(Display)); + mPathsButton->setToolTip(tr("%1 addon path preferences dialog").arg(Display)); + mPreferences->ShowPathsGroup(); +} + +void lcBlenderPreferencesDialog::EnableButton(bool Change) +{ + mApplyButton->setEnabled(Change); + mResetButton->setEnabled(Change); + mPathsButton->setText(tr("Hide Paths")); + mPathsButton->setToolTip(tr("Hide addon path preferences dialog")); +} + +void lcBlenderPreferencesDialog::ResetSettings() +{ + mPreferences->ResetSettings(); + mApplyButton->setEnabled(false); +} + +void lcBlenderPreferencesDialog::accept() +{ + if (mPreferences->SettingsModified()) + { + mApplyButton->setEnabled(false); + mPreferences->SaveSettings(); + QDialog::accept(); + } else + QDialog::reject(); +} + +void lcBlenderPreferencesDialog::reject() +{ + if (mPreferences->PromptCancel()) + QDialog::reject(); +} + +lcBlenderPreferences::lcBlenderPreferences(int Width, int Height, double Scale, QWidget* Parent) + : QWidget(Parent) +{ + gAddonPreferences = this; + +#ifndef QT_NO_PROCESS + mProcess = nullptr; +#endif + + mImageWidth = Width; + mImageHeight = Height; + mScale = Scale; + + mDialogCancelled = false; + + QVBoxLayout* Layout = new QVBoxLayout(Parent); + + if (Parent) + { + Parent->setLayout(Layout); + Parent->setWhatsThis(WhatsThisDescription); + } + else + { + setWindowTitle(tr("Blender LDraw Addon Settings")); + setLayout(Layout); + setWhatsThis(WhatsThisDescription); + } + + mContent = new QWidget(); + + mForm = new QFormLayout(mContent); + + mContent->setLayout(mForm); + + QPalette ReadOnlyPalette = QApplication::palette(); + const lcPreferences& Preferences = lcGetPreferences(); + if (Preferences.mColorTheme == lcColorTheme::Dark) + ReadOnlyPalette.setColor(QPalette::Base,QColor(LC_THEME_DARK_PALETTE_MIDLIGHT)); + else + ReadOnlyPalette.setColor(QPalette::Base,QColor(LC_THEME_DEFAULT_PALETTE_LIGHT)); + ReadOnlyPalette.setColor(QPalette::Text,QColor(LC_DISABLED_TEXT)); + + QGroupBox* BlenderExeBox = new QGroupBox(tr("Blender Executable"),mContent); + mForm->addRow(BlenderExeBox); + + mExeGridLayout = new QGridLayout(BlenderExeBox); + BlenderExeBox->setLayout(mExeGridLayout); + + mBlenderVersionLabel = new QLabel(BlenderExeBox); + mExeGridLayout->addWidget(mBlenderVersionLabel,0,0); + + mBlenderVersionEdit = new QLineEdit(BlenderExeBox); + mBlenderVersionEdit->setPalette(ReadOnlyPalette); + mBlenderVersionEdit->setReadOnly(true); + mExeGridLayout->addWidget(mBlenderVersionEdit,0,1,1,2); + + QLabel* PathLabel = new QLabel(BlenderExeBox); + mExeGridLayout->addWidget(PathLabel,1,0); + + QLineEdit* PathLineEdit = new QLineEdit(BlenderExeBox); + mExeGridLayout->addWidget(PathLineEdit,1,1); + mPathLineEditList << PathLineEdit; + connect(PathLineEdit, SIGNAL(editingFinished()), this, SLOT(ConfigureBlenderAddon())); + + QPushButton* PathBrowseButton = new QPushButton(tr("Browse..."), BlenderExeBox); + mExeGridLayout->addWidget(PathBrowseButton,1,2); + mPathBrowseButtonList << PathBrowseButton; + connect(PathBrowseButton, SIGNAL(clicked(bool)), this, SLOT(BrowseBlender(bool))); + + QGroupBox* BlenderAddonVersionBox = new QGroupBox(tr("%1 Blender LDraw Addon").arg(LC_PRODUCTNAME_STR),mContent); + mForm->addRow(BlenderAddonVersionBox); + + mAddonGridLayout = new QGridLayout(BlenderAddonVersionBox); + BlenderAddonVersionBox->setLayout(mAddonGridLayout); + + mAddonVersionLabel = new QLabel(BlenderAddonVersionBox); + mAddonGridLayout->addWidget(mAddonVersionLabel,0,0); + + mAddonVersionEdit = new QLineEdit(BlenderAddonVersionBox); + mAddonVersionEdit->setToolTip(tr("%1 Blender LDraw import and image renderer addon").arg(LC_PRODUCTNAME_STR)); + mAddonVersionEdit->setPalette(ReadOnlyPalette); + mAddonVersionEdit->setReadOnly(true); + mAddonGridLayout->addWidget(mAddonVersionEdit,0,1); + mAddonGridLayout->setColumnStretch(1,1/*1 is greater than 0 (default)*/); + + mAddonUpdateButton = new QPushButton(tr("Update"), BlenderAddonVersionBox); + mAddonUpdateButton->setToolTip(tr("Update %1 Blender LDraw addon").arg(LC_PRODUCTNAME_STR)); + mAddonGridLayout->addWidget(mAddonUpdateButton,0,2); + connect(mAddonUpdateButton, SIGNAL(clicked(bool)), this, SLOT(UpdateBlenderAddon())); + + mAddonStdOutButton = new QPushButton(tr("Output..."), BlenderAddonVersionBox); + mAddonStdOutButton->setToolTip(tr("Open the standrd output log")); + mAddonStdOutButton->setEnabled(false); + mAddonGridLayout->addWidget(mAddonStdOutButton,0,3); + connect(mAddonStdOutButton, SIGNAL(clicked(bool)), this, SLOT(GetStandardOutput())); + + mModulesBox = new QGroupBox(tr("Enabled Addon Modules"),mContent); + QHBoxLayout* ModulesLayout = new QHBoxLayout(mModulesBox); + mModulesBox->setLayout(ModulesLayout); + mAddonGridLayout->addWidget(mModulesBox,1,0,1,4); + + mImportActBox = new QCheckBox(tr("LDraw Import TN"),mModulesBox); + mImportActBox->setToolTip(tr("Enable addon import module (adapted from LDraw Import by Toby Nelson) in Blender")); + ModulesLayout->addWidget(mImportActBox); + connect(mImportActBox, SIGNAL(clicked(bool)), this, SLOT(EnableImportModule())); + + mImportMMActBox = new QCheckBox(tr("LDraw Import MM"),mModulesBox); + mImportMMActBox->setToolTip(tr("Enable addon import module (adapted from LDraw Import by Matthew Morrison) in Blender")); + ModulesLayout->addWidget(mImportMMActBox); + connect(mImportMMActBox, SIGNAL(clicked(bool)), this, SLOT(EnableImportModule())); + + mRenderActBox = new QCheckBox(tr("%1 Image Render").arg(LC_PRODUCTNAME_STR), mModulesBox); + mRenderActBox->setToolTip(tr("Addon image render module in Blender")); + mRenderActBox->setEnabled(false); + ModulesLayout->addWidget(mRenderActBox); + + LoadSettings(); + + mConfigured = !lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty(); + + const int CtlIdx = PATH_BLENDER; + PathLabel->setText(mBlenderPaths[CtlIdx].label); + PathLabel->setToolTip(mBlenderPaths[CtlIdx].tooltip); + + PathLineEdit->setText(mBlenderPaths[CtlIdx].value); + PathLineEdit->setToolTip(mBlenderPaths[CtlIdx].tooltip); + + if (mAddonVersion.isEmpty()) + { + mModulesBox->setEnabled(false); + mAddonUpdateButton->setEnabled(false); + mImportActBox->setChecked(true); // default addon module + } + else + { + mAddonVersionEdit->setText(mAddonVersion); + mRenderActBox->setChecked(true); + mImportActBox->setChecked( lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE) == QLatin1String("TN")); + mImportMMActBox->setChecked(lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE) == QLatin1String("MM")); + } + + QString VersionTextColour = QApplication::palette().text().color().name(),AddonTextColour; + QString VersionText = tr("Blender"), AddonText = tr("Blender Addon"); + if (mConfigured) + { + AddonTextColour = VersionTextColour; + mBlenderVersionEdit->setText(mBlenderVersion); + mBlenderVersionEdit->setVisible(true); + } + else + { + QString const TextColour = Preferences.mColorTheme == lcColorTheme::Dark + ? QLatin1String(LC_THEME_DARK_DECORATE_QUOTED_TEXT) + : QLatin1String("blue"); + + bool BlenderConfigured = !lcGetProfileString(LC_PROFILE_BLENDER_PATH).isEmpty(); + if (!BlenderConfigured) + { + VersionText = tr("Blender not configured."); + VersionTextColour = TextColour; + } + else + mBlenderVersionEdit->setText(mBlenderVersion); + + if (QFileInfo(QString("%1/Blender/%2").arg(mDataDir).arg(LC_BLENDER_ADDON_FILE)).isReadable()) + { + mModulesBox->setEnabled(false); + mImportActBox->setChecked(true); + mAddonUpdateButton->setEnabled(true); + if (BlenderConfigured) + AddonText = tr("Addon not configured - Update."); + else + AddonText = tr("Addon not configured."); + AddonTextColour = TextColour; + } + } + + mAddonVersionEdit->setVisible(!mAddonVersion.isEmpty()); + mBlenderVersionLabel->setStyleSheet(QString("QLabel { color : %1; }").arg(VersionTextColour)); + mAddonVersionLabel->setStyleSheet(QString("QLabel { color : %1; }").arg(AddonTextColour)); + mBlenderVersionLabel->setText(VersionText); + mAddonVersionLabel->setText(AddonText); + + if (mImportMMActBox->isChecked()) + InitPathsAndSettingsMM(); + else + InitPathsAndSettings(); + + QScrollArea* ScrollArea = new QScrollArea(this); + ScrollArea->setWidgetResizable(true); + ScrollArea->setWidget(mContent); + Layout->addWidget(ScrollArea); +} + +lcBlenderPreferences::~lcBlenderPreferences() +{ + gAddonPreferences = nullptr; +} + +void lcBlenderPreferences::ClearGroupBox(QGroupBox* GroupBox) +{ + int Row = -1; + QFormLayout::ItemRole ItemRole = QFormLayout::SpanningRole; + + mForm->getWidgetPosition(GroupBox,& Row,& ItemRole); + + if(Row == -1 || ItemRole != QFormLayout::SpanningRole) return; + + QLayoutItem* GroupBoxIitem = mForm->itemAt ( Row, ItemRole ); + + mForm->removeItem(GroupBoxIitem); + + QWidget* WidgetItem = GroupBoxIitem->widget(); + if(WidgetItem) + { + if (GroupBox == mPathsBox) + { + delete mPathsBox; + mPathsBox = nullptr; + } + else if (GroupBox == mSettingsBox) + { + delete mSettingsBox; + mSettingsBox = nullptr; + } + } + + delete GroupBoxIitem; +} + +void lcBlenderPreferences::InitPathsAndSettings() +{ + if (!NumSettings()) + LoadSettings(); + + // Paths + ClearGroupBox(mPathsBox); + mPathsBox = new QGroupBox(mContent); + mPathsBox->setTitle(tr("LDraw Import TN Addon Paths")); + mPathsGridLayout = new QGridLayout(mPathsBox); + mPathsBox->setLayout(mPathsGridLayout); + mForm->addRow(mPathsBox); + + for (int CtlIdx = 1/*skip blender executable*/; CtlIdx < NumPaths(); ++CtlIdx) + { + int ColumnIdx = CtlIdx - 1; // adjust for skipping first item - blender executable + bool IsVisible = CtlIdx != PATH_STUDIO_LDRAW; + QLabel* PathLabel = new QLabel(mBlenderPaths[CtlIdx].label, mPathsBox); + PathLabel->setToolTip(mBlenderPaths[CtlIdx].tooltip); + mPathsGridLayout->addWidget(PathLabel,ColumnIdx,0); + PathLabel->setVisible(IsVisible); + + QLineEdit* PathLineEdit = new QLineEdit(mPathsBox); + PathLineEdit->setProperty("ControlID",QVariant(CtlIdx)); + PathLineEdit->setText(mBlenderPaths[CtlIdx].value); + PathLineEdit->setToolTip(mBlenderPaths[CtlIdx].tooltip); + if (mPathLineEditList.size() > CtlIdx) + mPathLineEditList.replace(CtlIdx, PathLineEdit); + else + mPathLineEditList << PathLineEdit; + mPathsGridLayout->addWidget(PathLineEdit,ColumnIdx,1); + PathLineEdit->setVisible(IsVisible); + + QPushButton* PathBrowseButton = new QPushButton(tr("Browse..."), mPathsBox); + if (mPathBrowseButtonList.size() > CtlIdx) + mPathBrowseButtonList.replace(CtlIdx, PathBrowseButton); + else + mPathBrowseButtonList << PathBrowseButton; + mPathsGridLayout->addWidget(PathBrowseButton,ColumnIdx,2); + PathBrowseButton->setVisible(IsVisible); + + if (IsVisible) + { + connect(PathBrowseButton, SIGNAL(clicked(bool)), this, SLOT (BrowseBlender(bool))); + connect(PathLineEdit, SIGNAL(editingFinished()), this, SLOT (PathChanged())); + } + } + + mPathsBox->setEnabled(mConfigured); + + // Settings + ClearGroupBox(mSettingsBox); + mSettingsBox = new QGroupBox(mContent); + mSettingsSubform = new QFormLayout(mSettingsBox); + mSettingsBox->setLayout(mSettingsSubform); + mForm->addRow(mSettingsBox); + + mSettingsBox->setTitle(tr("LDraw Import TN Addon Settings")); + mSettingLabelList.clear(); + mCheckBoxList.clear(); + mLineEditList.clear(); + mComboBoxList.clear(); + + int ComboBoxItemsIndex = 0; + + for (int LblIdx = 0; LblIdx < NumSettings(); LblIdx++) + { + QLabel* Label = new QLabel(mSettingsBox); + Label->setText(mBlenderSettings[LblIdx].label); + Label->setToolTip(mBlenderSettings[LblIdx].tooltip); + mSettingLabelList << Label; + + if (LblIdx < LBL_BEVEL_WIDTH) + { // QCheckBoxes + QCheckBox* CheckBox = new QCheckBox(mSettingsBox); + CheckBox->setProperty("ControlID",QVariant(LblIdx)); + CheckBox->setChecked(mBlenderSettings[LblIdx].value.toInt()); + CheckBox->setToolTip(mBlenderSettings[LblIdx].tooltip); + if (LblIdx == LBL_CROP_IMAGE) + connect(CheckBox, SIGNAL(toggled(bool)), this, SLOT (SetModelSize(bool))); + else + connect(CheckBox, SIGNAL(clicked()), this, SLOT (SettingChanged())); + mCheckBoxList << CheckBox; + mSettingsSubform->addRow(Label,CheckBox); + } + else if (LblIdx < LBL_COLOUR_SCHEME) + { // QLineEdits + QLineEdit* LineEdit = new QLineEdit(mSettingsBox); + LineEdit->setProperty("ControlID",QVariant(LblIdx)); + if (LblIdx == LBL_IMAGE_WIDTH || LblIdx == LBL_IMAGE_HEIGHT) + { + connect(LineEdit, SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + LineEdit->setValidator(new QIntValidator(16, LC_RENDER_IMAGE_MAX_SIZE)); + } + else if(LblIdx == LBL_DEFAULT_COLOUR) + { + LineEdit->setReadOnly(true); + LineEdit->setStyleSheet("Text-align:left"); + mDefaultColourEditAction = LineEdit->addAction(QIcon(), QLineEdit::TrailingPosition); + connect(mDefaultColourEditAction, SIGNAL(triggered(bool)), this, SLOT (ColorButtonClicked(bool))); + } + else + { + LineEdit->setText(mBlenderSettings[LblIdx].value); + if (LblIdx == LBL_IMAGE_SCALE) + LineEdit->setValidator(new QDoubleValidator(0.01,10.0,2)); + else if (LblIdx == LBL_RENDER_PERCENTAGE || LblIdx == LBL_CAMERA_BORDER_PERCENT) + LineEdit->setValidator(new QIntValidator(1,1000)); + else + LineEdit->setValidator(new QDoubleValidator(0.01,100.0,2)); + connect(LineEdit, SIGNAL(textEdited(const QString&)), this, SLOT (SettingChanged(const QString&))); + } + LineEdit->setToolTip(mBlenderSettings[LblIdx].tooltip); + mLineEditList << LineEdit; + if (LblIdx == LBL_DEFAULT_COLOUR) + SetDefaultColor(lcGetColorIndex(mBlenderSettings[LBL_DEFAULT_COLOUR].value.toInt())); + mSettingsSubform->addRow(Label,LineEdit); + } + else + { // QComboBoxes + QComboBox* ComboBox = new QComboBox(mSettingsBox); + ComboBox->setProperty("ControlID",QVariant(LblIdx)); + QString const Value = mBlenderSettings[LblIdx].value; + QStringList const DataList = mComboItems[ComboBoxItemsIndex].dataList.split("|"); + QStringList const ItemList = mComboItems[ComboBoxItemsIndex].itemList.split("|"); + ComboBox->addItems(ItemList); + for (int CtlIdx = 0; CtlIdx < ComboBox->count(); CtlIdx++) + ComboBox->setItemData(CtlIdx, DataList.at(CtlIdx)); + ComboBox->setToolTip(mBlenderSettings[LblIdx].tooltip); + int CurrentIndex = int(ComboBox->findData(QVariant::fromValue(Value))); + ComboBox->setCurrentIndex(CurrentIndex); + if (LblIdx == LBL_COLOUR_SCHEME) + connect(ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT (ValidateColourScheme(int))); + else + connect(ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT (SettingChanged(int))); + mComboBoxList << ComboBox; + ComboBoxItemsIndex++; + mSettingsSubform->addRow(Label,ComboBox); + } + } + + SetModelSize(); + + if (!mSettingsSubform->rowCount()) + mSettingsSubform = nullptr; + + mSettingsBox->setEnabled(mConfigured); +} + +void lcBlenderPreferences::InitPathsAndSettingsMM() +{ + if (!NumSettingsMM()) + LoadSettings(); + + // Paths + ClearGroupBox(mPathsBox); + mPathsBox = new QGroupBox(mContent); + mPathsBox->setTitle(tr("LDraw Import MM Addon Paths")); + mPathsGridLayout = new QGridLayout(mPathsBox); + mPathsBox->setLayout(mPathsGridLayout); + mForm->addRow(mPathsBox); + + for (int CtlIdx = 1/*skip blender executable*/; CtlIdx < NumPaths(); ++CtlIdx) + { + int ColumnIdx = CtlIdx - 1; // adjust for skipping first item - blender executable + bool IsVisible = CtlIdx != PATH_LSYNTH && CtlIdx != PATH_STUD_LOGO; + QLabel* PathLabel = new QLabel(mBlenderPaths[CtlIdx].label, mPathsBox); + PathLabel->setToolTip(mBlenderPaths[CtlIdx].tooltip); + mPathsGridLayout->addWidget(PathLabel,ColumnIdx,0); + PathLabel->setVisible(IsVisible); + + QLineEdit* PathLineEdit = new QLineEdit(mPathsBox); + PathLineEdit->setProperty("ControlID",QVariant(CtlIdx)); + PathLineEdit->setText(mBlenderPaths[CtlIdx].value); + PathLineEdit->setToolTip(mBlenderPaths[CtlIdx].tooltip); + if (mPathLineEditList.size() > CtlIdx) + mPathLineEditList.replace(CtlIdx, PathLineEdit); + else + mPathLineEditList << PathLineEdit; + mPathsGridLayout->addWidget(PathLineEdit,ColumnIdx,1); + PathLineEdit->setVisible(IsVisible); + + QPushButton* PathBrowseButton = new QPushButton(tr("Browse..."), mPathsBox); + if (mPathBrowseButtonList.size() > CtlIdx) + mPathBrowseButtonList.replace(CtlIdx, PathBrowseButton); + else + mPathBrowseButtonList << PathBrowseButton; + mPathsGridLayout->addWidget(PathBrowseButton,ColumnIdx,2); + PathBrowseButton->setVisible(IsVisible); + + if (IsVisible) + { + connect(PathBrowseButton, SIGNAL(clicked(bool)), this, SLOT (BrowseBlender(bool))); + connect(PathLineEdit, SIGNAL(editingFinished()), this, SLOT (PathChanged())); + } + } + + mPathsBox->setEnabled(mConfigured); + + // Settings + ClearGroupBox(mSettingsBox); + mSettingsBox = new QGroupBox(mContent); + mSettingsSubform = new QFormLayout(mSettingsBox); + mSettingsBox->setLayout(mSettingsSubform); + mForm->addRow(mSettingsBox); + + mSettingsBox->setTitle(tr("LDraw Import MM Addon Settings")); + mSettingLabelList.clear(); + mCheckBoxList.clear(); + mLineEditList.clear(); + mComboBoxList.clear(); + + int ComboBoxItemsIndex = 0; + + for (int LblIdx = 0; LblIdx < NumSettingsMM(); LblIdx++) + { + QLabel* Label = new QLabel(mSettingsBox); + Label->setText(mBlenderSettingsMM[LblIdx].label); + Label->setToolTip(mBlenderSettingsMM[LblIdx].tooltip); + mSettingLabelList << Label; + + if (LblIdx < LBL_BEVEL_SEGMENTS) + { // QCheckBoxes + QCheckBox* CheckBox = new QCheckBox(mSettingsBox); + CheckBox->setProperty("ControlID",QVariant(LblIdx)); + CheckBox->setChecked(mBlenderSettingsMM[LblIdx].value.toInt()); + CheckBox->setToolTip(mBlenderSettingsMM[LblIdx].tooltip); + if (LblIdx == LBL_CROP_IMAGE_MM) + connect(CheckBox, SIGNAL(toggled(bool)), this, SLOT (SetModelSize(bool))); + else + connect(CheckBox, SIGNAL(clicked()), this, SLOT (SettingChanged())); + mCheckBoxList << CheckBox; + mSettingsSubform->addRow(Label,CheckBox); + } + else if (LblIdx < LBL_CHOSEN_LOGO) + { // QLineEdits + QLineEdit* LineEdit = new QLineEdit(mSettingsBox); + LineEdit->setProperty("ControlID",QVariant(LblIdx)); + if (LblIdx == LBL_RESOLUTION_WIDTH || LblIdx == LBL_RESOLUTION_HEIGHT) + { + connect(LineEdit, SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + LineEdit->setValidator(new QIntValidator(16, LC_RENDER_IMAGE_MAX_SIZE)); + } + else + { + LineEdit->setText(mBlenderSettingsMM[LblIdx].value); + if (LblIdx == LBL_IMPORT_SCALE) + LineEdit->setValidator(new QDoubleValidator(0.01,10.0,2)); + else if (LblIdx == LBL_RENDER_PERCENTAGE_MM || LblIdx == LBL_CAMERA_BORDER_PERCENT_MM) + LineEdit->setValidator(new QIntValidator(1,1000)); + else if (LblIdx == LBL_GAP_SCALE || LblIdx == LBL_MERGE_DISTANCE) + LineEdit->setValidator(new QDoubleValidator(0.001,100.0,3)); + else if (LblIdx == LBL_BEVEL_WEIGHT || LblIdx == LBL_BEVEL_WIDTH_MM) + LineEdit->setValidator(new QDoubleValidator(0.0,10.0,1)); + else + LineEdit->setValidator(new QIntValidator(1, LC_RENDER_IMAGE_MAX_SIZE)); + connect(LineEdit, SIGNAL(textEdited(const QString&)), this, SLOT (SettingChanged(const QString&))); + } + LineEdit->setToolTip(mBlenderSettingsMM[LblIdx].tooltip); + mLineEditList << LineEdit; + mSettingsSubform->addRow(Label,LineEdit); + } + else + { // QComboBoxes + QComboBox* ComboBox = new QComboBox(mSettingsBox); + ComboBox->setProperty("ControlID",QVariant(LblIdx)); + QString const Value = mBlenderSettingsMM[LblIdx].value; + QStringList const DataList = mComboItemsMM[ComboBoxItemsIndex].dataList.split("|"); + QStringList const ItemList = mComboItemsMM[ComboBoxItemsIndex].itemList.split("|"); + ComboBox->addItems(ItemList); + for (int CtlIdx = 0; CtlIdx < ComboBox->count(); CtlIdx++) + ComboBox->setItemData(CtlIdx, DataList.at(CtlIdx)); + ComboBox->setToolTip(mBlenderSettingsMM[LblIdx].tooltip); + int CurrentIndex = int(ComboBox->findData(QVariant::fromValue(Value))); + ComboBox->setCurrentIndex(CurrentIndex); + if (LblIdx == LBL_COLOUR_SCHEME_MM) + connect(ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT (ValidateColourScheme(int))); + else + connect(ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT (SettingChanged(int))); + mComboBoxList << ComboBox; + ComboBoxItemsIndex++; + mSettingsSubform->addRow(Label,ComboBox); + } + } + + SetModelSize(); + + if (!mSettingsSubform->rowCount()) + mSettingsSubform = nullptr; + + mSettingsBox->setEnabled(mConfigured); +} + +void lcBlenderPreferences::UpdateBlenderAddon() +{ + mAddonUpdateButton->setEnabled(false); + + disconnect(mPathLineEditList[PATH_BLENDER], SIGNAL(editingFinished()), this, SLOT (ConfigureBlenderAddon())); + + ConfigureBlenderAddon(sender() == mPathBrowseButtonList[PATH_BLENDER], + sender() == mAddonUpdateButton); + + connect(mPathLineEditList[PATH_BLENDER], SIGNAL(editingFinished()), this, SLOT (ConfigureBlenderAddon())); +} + +void lcBlenderPreferences::ConfigureBlenderAddon(bool TestBlender, bool AddonUpdate, bool ModuleChange) +{ + mProgressBar = nullptr; + + QString const BlenderExe = QDir::toNativeSeparators(mPathLineEditList[PATH_BLENDER]->text()); + + if (BlenderExe.isEmpty()) + { + mBlenderVersion.clear(); + mConfigured = false; + StatusUpdate(false, true); + mAddonUpdateButton->setEnabled(mConfigured); + mPathsBox->setEnabled(mConfigured); + mSettingsBox->setEnabled(mConfigured); + return; + } + + if (QFileInfo(BlenderExe).isReadable()) + { + enum ProcEnc + { PR_OK, PR_FAIL, PR_WAIT, PR_INSTALL, PR_TEST }; + QString const BlenderDir = QDir::toNativeSeparators(QString("%1/Blender").arg(mDataDir)); + QString const BlenderConfigDir = QString("%1/setup/addon_setup/config").arg(BlenderDir); + QString const BlenderAddonDir = QDir::toNativeSeparators(QString("%1/addons").arg(BlenderDir)); + QString const BlenderExeCompare = QDir::toNativeSeparators(lcGetProfileString(LC_PROFILE_BLENDER_PATH)).toLower(); + QString const BlenderInstallFile = QDir::toNativeSeparators(QString("%1/%2").arg(BlenderDir).arg(LC_BLENDER_ADDON_INSTALL_FILE)); + QString const BlenderTestString = QLatin1String("###TEST_BLENDER###"); + QByteArray AddonPathsAndModuleNames; + QString Message, ShellProgram; + QStringList Arguments; + ProcEnc Result = PR_OK; + QFile Script; + + bool NewBlenderExe = BlenderExeCompare != BlenderExe.toLower(); + + if (mConfigured && !AddonUpdate && !ModuleChange && !NewBlenderExe) + return; + + auto ProcessCommand = [&](ProcEnc Action) + { + mProcess = new QProcess(); + + QString ProcessAction = tr("addon install"); + if (Action == PR_INSTALL) + { + connect(mProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(ReadStdOut())); + QString const& LdrawLibPath = QFileInfo(lcGetProfileString(LC_PROFILE_PARTS_LIBRARY)).absolutePath(); + QStringList SystemEnvironment = QProcess::systemEnvironment(); + SystemEnvironment.prepend("LDRAW_DIRECTORY=" + LdrawLibPath); + SystemEnvironment.prepend("ADDONS_TO_LOAD=" + AddonPathsAndModuleNames); + mProcess->setEnvironment(SystemEnvironment); + } + else + { + ProcessAction = tr("test"); + disconnect(&mUpdateTimer, SIGNAL(timeout()), this, SLOT(Update())); + } + + mProcess->setWorkingDirectory(BlenderDir); + + mProcess->setStandardErrorFile(QString("%1/stderr-blender-addon-install").arg(BlenderDir)); + + if (Action == PR_INSTALL) + { + mProcess->start(BlenderExe, Arguments); + } + else + { +#ifdef Q_OS_WIN + mProcess->start(ShellProgram, QStringList() << "/C" << Script.fileName()); +#else + mProcess->start(ShellProgram, QStringList() << Script.fileName()); +#endif + } + + if (!mProcess->waitForStarted()) + { + Message = tr("Cannot start Blender %1 mProcess.\n%2") + .arg(ProcessAction) + .arg(QString(mProcess->readAllStandardError())); + delete mProcess; + mProcess = nullptr; + return PR_WAIT; + } + else + { + if (mProcess->exitStatus() != QProcess::NormalExit || mProcess->exitCode() != 0) + { + Message = tr("Failed to execute Blender %1.\n%2") + .arg(ProcessAction) + .arg(QString(mProcess->readAllStandardError())); + return PR_FAIL; + } + } + + if (Action == PR_TEST) + { + while (mProcess && mProcess->state() != QProcess::NotRunning) + { + QTime Waiting = QTime::currentTime().addMSecs(500); + while (QTime::currentTime() < Waiting) + QCoreApplication::processEvents(QEventLoop::AllEvents, 100); + } + connect(&mUpdateTimer, SIGNAL(timeout()), this, SLOT(Update())); + QString const StdOut = QString(mProcess->readAllStandardOutput()); + if (!StdOut.contains(BlenderTestString)) + { + Message = tr("A simple check to test if the selected file is Blender failed." + "Please create an %1 GitHub ticket if you are sure the file is Blender 2.8 or newer." + "The ticket should contain the full path to the Blender executable.").arg(LC_PRODUCTNAME_STR); + return PR_FAIL; + } + else + { + QStringList Items = StdOut.split('\n',SkipEmptyParts).last().split(" "); + if (Items.count() > 6 && Items.at(0) == QLatin1String("Blender")) + { + Items.takeLast(); + mBlenderVersion.clear(); + for (int LblIdx = 1; LblIdx < Items.size(); LblIdx++) + mBlenderVersion.append(Items.at(LblIdx)+" "); + mBlenderVersionFound = !mBlenderVersion.isEmpty(); + if (mBlenderVersionFound) + { + mBlenderVersion = mBlenderVersion.trimmed().prepend("v").append(")"); + mBlenderVersionEdit->setText(mBlenderVersion); + // set default LDraw import module if not configured + if (!mImportActBox->isChecked() && !mImportMMActBox->isChecked()) + mImportActBox->setChecked(true); + Message = tr("Blender %1 mProcess completed. Version: %2 validated.").arg(ProcessAction).arg(mBlenderVersion); + } + } + } + } + + return PR_OK; + }; + + connect(&mUpdateTimer, SIGNAL(timeout()), this, SLOT(Update())); + mUpdateTimer.start(500); + + if (!ModuleChange) + { + mProgressBar = new QProgressBar(mContent); + mProgressBar->setMaximum(0); + mProgressBar->setMinimum(0); + mProgressBar->setValue(1); + + TestBlender |= sender() == mPathLineEditList[PATH_BLENDER]; + if (TestBlender) + { + mExeGridLayout->replaceWidget(mBlenderVersionEdit, mProgressBar); + mProgressBar->show(); + + Arguments << QString("--factory-startup"); + Arguments << QString("-b"); + Arguments << QString("--python-expr"); + Arguments << QString("\"import sys;print('%1');sys.stdout.flush();sys.exit()\"").arg(BlenderTestString); + + bool Error = false; + + QString ScriptName, ScriptCommand; + +#ifdef Q_OS_WIN + ScriptName = QLatin1String("blender_test.bat"); +#else + ScriptName = QLatin1String("blender_test.sh"); +#endif + ScriptCommand = QString("%1 %2").arg(BlenderExe).arg(Arguments.join(" ")); + + Script.setFileName(QString("%1/%2").arg(QDir::tempPath()).arg(ScriptName)); + if(Script.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QTextStream Stream(&Script); +#ifdef Q_OS_WIN + Stream << QLatin1String("@ECHO OFF& SETLOCAL") << LineEnding; +#else + Stream << QLatin1String("#!/bin/bash") << LineEnding; +#endif + Stream << ScriptCommand << LineEnding; + Script.close(); + } + else + { + Message = tr("Cannot write Blender render script file [%1] %2.") + .arg(Script.fileName()) + .arg(Script.errorString()); + Error = true; + } + + if (Error) + { + StatusUpdate(false); + return; + } + + QThread::sleep(1); + +#ifdef Q_OS_WIN + ShellProgram = QLatin1String(LC_WINDOWS_SHELL); +#else + ShellProgram = QLatin1String(LC_UNIX_SHELL); +#endif + Result = ProcessCommand(PR_TEST); + bool TestOk = Result != PR_FAIL; + QString const statusLabel = TestOk ? "" : tr("Blender test failed."); + StatusUpdate(false, TestOk, statusLabel); + if (TestOk) + { + lcSetProfileString(LC_PROFILE_BLENDER_VERSION, mBlenderVersion); + lcSetProfileString(LC_PROFILE_BLENDER_PATH, BlenderExe); + } + else + { + QString const& Title = tr ("%1 Blender LDraw Addon").arg(LC_PRODUCTNAME_STR); + QString const& Header = tr ("Blender test failed."); + ShowMessage(Header, Title, Message); + return; + } + } // Test Blender + + if (!mBlenderVersion.isEmpty() && !mImportMMActBox->isChecked() && !mImportActBox->isChecked()) + { + QString const& Title = tr ("%1 Blender LDraw Addon Modules").arg(LC_PRODUCTNAME_STR); + QString const& Header = tr ("No import module enabled." + "If you continue, the default import module (Import TN) will be used.
" + "If you select No, all addon modules will be disabled."); + QString const& Body = tr ("Continue with the default import module ?"); + int Exec = ShowMessage(Header, Title, Body, QString(), MBB_YES_NO, QMessageBox::NoIcon); + if (Exec != QMessageBox::Yes) + { + mRenderActBox->setChecked(false); + if (Exec == QMessageBox::Cancel) + return; + } + + mImportActBox->setChecked(true); + } + + if (TestBlender) + { + QString const preferredImportModule = + mImportActBox->isChecked() + ? QString("TN") + : mImportMMActBox->isChecked() + ? QString("MM") + : QString(); // disable all import modules + lcSetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE, preferredImportModule); + } + else + mBlenderVersionEdit->setVisible(AddonUpdate); + + if (mProgressBar) + { + mProgressBar->setMaximum(0); + mProgressBar->setMinimum(0); + mProgressBar->setValue(1); + mAddonGridLayout->replaceWidget(mAddonVersionEdit, mProgressBar); + mProgressBar->show(); + } + + if (!ExtractBlenderAddon(BlenderDir)) + { + if (AddonUpdate) + { + mConfigured = true; + mBlenderVersionLabel->setText(tr("Blender")); + mBlenderVersionLabel->setStyleSheet(QString("QLabel { color : %1; }").arg(QApplication::palette().text().color().name())); + mBlenderVersionEdit->setText(mBlenderVersion); + mBlenderVersionEdit->setToolTip(tr("Display the Blender and %1 Render addon version").arg(LC_PRODUCTNAME_STR)); + mBlenderVersionEdit->setVisible(mConfigured); + if (!mAddonVersion.isEmpty()) + { + mModulesBox->setEnabled(true); + mAddonVersionEdit->setText(mAddonVersion); + } + mAddonUpdateButton->setEnabled(mConfigured); + if (mProgressBar) + { + mExeGridLayout->replaceWidget(mProgressBar, mAddonVersionEdit); + mProgressBar->close(); + } + } + return; + } + + if (!QFileInfo(BlenderInstallFile).exists()) + { + ShowMessage(tr ("Could not find addon install file: %1").arg(BlenderInstallFile)); + StatusUpdate(true, true, tr("Addon file not found.")); + return; + } + + StatusUpdate(true, false, tr("Install addon...")); + + QDir ConfigDir(BlenderConfigDir); + if(!QDir(ConfigDir).exists()) + ConfigDir.mkpath("."); + } + + SaveSettings(); + + Arguments.clear(); + Arguments << QString("--background"); + Arguments << QString("--python"); + Arguments << BlenderInstallFile; + Arguments << "--"; + if (!mRenderActBox->isChecked() && !mImportActBox->isChecked() && !mImportMMActBox->isChecked()) + Arguments << QString("--disable_ldraw_addons"); + else if (!mImportActBox->isChecked()) + Arguments << QString("--disable_ldraw_import"); + else if (!mImportMMActBox->isChecked()) + Arguments << QString("--disable_ldraw_import_mm"); + else if (!mRenderActBox->isChecked()) + Arguments << QString("--disable_ldraw_render"); + Arguments << QString("--leocad"); + + Message = tr("Blender Addon Install Arguments: %1 %2").arg(BlenderExe).arg(Arguments.join(" ")); + + if (!TestBlender) + mBlenderVersionFound = false; + + if (QDir(BlenderAddonDir).entryInfoList(QDir::Dirs|QDir::NoSymLinks).count() > 0) + { + QJsonArray JsonArray; + QStringList AddonDirs = QDir(BlenderAddonDir).entryList(QDir::NoDotAndDotDot | QDir::Dirs, QDir::SortByMask); + for (QString const& Addon : AddonDirs) + { + QDir Dir(QString("%1/%2").arg(BlenderAddonDir).arg(Addon)); + Dir.setFilter(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks); + QFileInfoList List = Dir.entryInfoList(); + for (int LblIdx = 0; LblIdx < List.size(); LblIdx++) + { + if (List.at(LblIdx).fileName() == QLatin1String("__init__.py")) + { + QFile File(QFileInfo(List.at(LblIdx)).absoluteFilePath()); + if (!File.open(QFile::ReadOnly | QFile::Text)) + { + ShowMessage(tr("Cannot read addon file %1
%2").arg(List.at(LblIdx).fileName()).arg(File.errorString())); + break; + } + else + { + bool FoundModule = false; + QTextStream In(&File); + while ( ! In.atEnd()) + { + if (QString(In.readLine(0)).startsWith("bl_info")) + { + FoundModule = true; + break; + } + } + File.close(); + if (FoundModule) + { + QJsonObject JsonItemObj; + JsonItemObj["load_dir"] = QDir::toNativeSeparators(Dir.absolutePath()); + JsonItemObj["module_name"] = Dir.dirName(); + JsonArray.append(JsonItemObj); + } + } + } + } + } + QJsonDocument JsonDoc; + JsonDoc.setArray(JsonArray); + AddonPathsAndModuleNames = JsonDoc.toJson(QJsonDocument::Compact); + } + + Result = ProcessCommand(PR_INSTALL); + + if (Result != PR_OK) + StatusUpdate(true, true, tr("Addon install failed.")); + } + else + ShowMessage(tr("Blender executable not found at [%1]").arg(BlenderExe), tr("Addon install failed.")); +} + +bool lcBlenderPreferences::ExtractBlenderAddon(const QString& BlenderDir) +{ + bool Proceed = true; + + QDir Dir(BlenderDir); + if (!Dir.exists()) + Dir.mkdir(BlenderDir); + + if (GetBlenderAddon(BlenderDir)) + { + gAddonPreferences->StatusUpdate(true, false, tr("Extract addon...")); + QString const BlenderAddonFile = QDir::toNativeSeparators(QString("%1/%2").arg(BlenderDir).arg(LC_BLENDER_ADDON_FILE)); + // TODO - Replace QuaZip call to unzip addon + /* QStringList addonList = JlCompress::extractDir(blenderAddonFile, blenderDir); + if (addonList.isEmpty()) + { + showMessage(tr("Failed to extract %1 to %2") + .arg(blenderAddonFile).arg(blenderDir), tr("Extract addon")); + proceed = false; + } + */ + } + + if (!Proceed) + gAddonPreferences->StatusUpdate(true, true,tr("Extract addon failed.")); + + return Proceed; +} + +bool lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) +{ + enum AddonEnc + { + ADDON_FAIL = -1, + ADDON_NOT_FOUND, + ADDON_DOWNLOAD = ADDON_NOT_FOUND, + ADDON_ARCHIVE, + ADDON_EXTRACTED, + ADDON_RELOAD, + ADDON_CANCEL + }; + + QString const BlenderAddonDir = QDir::toNativeSeparators(QString("%1/addons").arg(BlenderDir)); + QString const BlenderAddonFile = QDir::toNativeSeparators(QString("%1/%2").arg(BlenderDir).arg(LC_BLENDER_ADDON_FILE)); + QString const AddonVersionFile = QDir::toNativeSeparators(QString("%1/%2/__version__.py").arg(BlenderAddonDir).arg(LC_BLENDER_ADDON_FOLDER_STR)); + bool ExtractedAddon = QFileInfo(AddonVersionFile).isReadable(); + bool BlenderAddonExists = ExtractedAddon || QFileInfo(BlenderAddonFile).isReadable(); + QString AddonStatus = tr("Installing Blender addon..."); + AddonEnc AddonAction = ADDON_DOWNLOAD; + QString LocalVersion, OnlineVersion; + + using namespace std; + auto VersionStringCompare = [](string V1, string V2) + { // Returns 1 if V2 is smaller, -1 if V1 is smaller, 0 if equal + int Vnum1 = 0, Vnum2 = 0; + for (quint32 i = 0, j = 0; (i < V1.length() || j < V2.length());) + { + while (i < V1.length() && V1[i] != '.') + { + Vnum1 = Vnum1 * 10 + (V1[i] - '0'); + i++; + } + while (j < V2.length() && V2[j] != '.') + { + Vnum2 = Vnum2 * 10 + (V2[j] - '0'); + j++; + } + if (Vnum1 > Vnum2) + return 1; + if (Vnum2 > Vnum1) + return -1; + Vnum1 = Vnum2 = 0; + i++; + j++; + } + + return 0; + }; + + auto GetBlenderAddonVersionMatch = [&]() + { + QByteArray Ba; + if (!ExtractedAddon) + { + // TODO - Replace QuaZip archive file check + /* + QuaZip Zip(BlenderAddonFile); + if (!Zip.open(QuaZip::mdUnzip)) + { + QString const Result = tr("Could not open archive to check content. Return code %1.
" + "Archive file %2 may be open in another program.") + .arg(Zip.getZipError()).arg(QFileInfo(BlenderAddonFile).fileName()); + emit gui->messageSig(LOG_WARNING, Result); + return false; // Download new archive + } + + QString const AddonVersionFile = QString("addons/%1/__version__.py").arg(LC_BLENDER_ADDON_FOLDER_STR); + Zip.setCurrentFile(AddonVersionFile); + QuaZipFile File(&Zip); + if (!File.open(QIODevice::ReadOnly)) + { + ShowMessage(tr("Cannot read addon archive version file: [%1]
%2.") + .arg(AddonVersionFile).arg(File.errorString())); + return false; // Download new archive + } + Ba = File.readAll(); + File.close(); + Zip.close(); + if (Zip.getZipError() != UNZ_OK) + showMessage(tr("Archive close error. Return code %1.").arg(Zip.getZipError())); + */ + ShowMessage(tr("Check addon archive is not supported. Manually extract addon and continue.")); + } + else + { + QFile File(AddonVersionFile); + if (!File.open(QIODevice::ReadOnly)) + { + ShowMessage(tr("Cannot read addon version file: [%1]
%2.") + .arg(AddonVersionFile).arg(File.errorString())); + return false; // Download new archive + } + Ba = File.readAll(); + File.close(); + } + + QTextStream Content(Ba.data()); + while (!Content.atEnd()) + { + QString Token; + Content >> Token; + if (Token == QLatin1String("version")) + { + Content >> Token; + LocalVersion = Content.readAll().trimmed().replace("(","v").replace(",",".").replace(" ","").replace(")",""); + } + } + + // TODO - Refactor, use LeoCAD mHttpReply/HttpManager + DownloadFile(LC_BLENDER_ADDON_LATEST_URL, tr("Latest Addon"),false/*promptRedirect*/,false/*showProgress*/); + QByteArray const &Response_Data = GetDownloadedFile(); + if (!Response_Data.isEmpty()) + { + QJsonDocument Json = QJsonDocument::fromJson(Response_Data); + OnlineVersion = Json.object()["tag_name"].toString(); + } + else + ShowMessage(tr("Check latest addon version failed."), tr("Latest Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); + + if (!LocalVersion.isEmpty() && !OnlineVersion.isEmpty()) + { + // LocalVersion is smaller than OnlineVersion so prompt to download new archive + if (VersionStringCompare(LocalVersion.toStdString(), OnlineVersion.toStdString()) < 0) + return false; + } + + return true; // Reload existing archive + }; + + if (BlenderAddonExists) + { + if (GetBlenderAddonVersionMatch()) + { + AddonAction = ADDON_RELOAD; + } + else if (gMainWindow) + { + if (LocalVersion.isEmpty()) + LocalVersion = gAddonPreferences->mAddonVersion; + QString const& Title = tr ("%1 Blender LDraw Addon").arg(LC_PRODUCTNAME_STR); + QString const& Header = tr ("Detected %1 Blender LDraw addon %2. A newer version %3 exists.") + .arg(LC_PRODUCTNAME_STR) + .arg(LocalVersion).arg(OnlineVersion); + QString const& Body = tr ("Do you want to download version %1 ?").arg(OnlineVersion); + int Exec = ShowMessage(Header, Title, Body, QString(), MBB_YES, QMessageBox::NoIcon); + if (Exec == QMessageBox::Cancel) + { + AddonStatus = tr("Blender addon setup cancelled"); + AddonAction = ADDON_CANCEL; + } + else if (Exec == QMessageBox::No) + { + AddonAction = ADDON_RELOAD; + } + } + + if (AddonAction == ADDON_DOWNLOAD) + AddonStatus = tr("Download addon..."); + + gAddonPreferences->StatusUpdate(true, false, AddonStatus); + + if (AddonAction == ADDON_CANCEL) + { + gAddonPreferences->mDialogCancelled = true; + return false; + } + } + + if (QFileInfo(BlenderAddonDir).exists()) + { + bool Result = true; + QDir Dir(BlenderAddonDir); + for (QFileInfo const& FileInfo : Dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks, QDir::DirsFirst)) + { + if (FileInfo.isDir()) + Result &= QDir(FileInfo.absoluteFilePath()).removeRecursively(); + else + Result &= QFile::remove(FileInfo.absoluteFilePath()); + } + + Result &= Dir.rmdir(BlenderAddonDir); + if (!Result) + ShowMessage(tr("Failed to remove Blender addon: %1").arg(BlenderAddonDir), + tr("Remove Existing Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); + } + + if (AddonAction == ADDON_DOWNLOAD) + { + BlenderAddonExists = false; + // TODO - Refactor, use LeoCAD mHttpReply/HttpManager + DownloadFile(LC_BLENDER_ADDON_URL, tr("Blender Addon"),false/*promptRedirect*/,false/*showProgress*/); + QByteArray const &Buffer = GetDownloadedFile(); + if (!Buffer.isEmpty()) + { + if (QFileInfo(BlenderAddonFile).exists()) + { + QDir Dir(BlenderDir); + if (!Dir.remove(BlenderAddonFile)) + ShowMessage(tr("Failed to remove Blender addon archive:
%1").arg(BlenderAddonFile)); + } + QFile File(BlenderAddonFile); + if (File.open(QIODevice::WriteOnly)) + { + File.write(Buffer); + File.close(); + BlenderAddonExists = true; + } + else + ShowMessage(tr("Failed to open Blender addon file:
%1:
%2") + .arg(BlenderAddonFile).arg(File.errorString())); + } + else + ShowMessage(tr("Failed to download Blender addon archive:
%1").arg(BlenderAddonFile)); + + if (!BlenderAddonExists) + { + AddonStatus = tr("Download addon failed."); + gAddonPreferences->StatusUpdate(true, true, AddonStatus); + } + } + else if (!BlenderAddonExists) + ShowMessage(tr("Blender addon archive %1 was not found").arg(BlenderAddonFile)); + + return BlenderAddonExists; +} + +void lcBlenderPreferences::StatusUpdate(bool Addon, bool Error, const QString& Message) +{ + QString Label, Colour; + QString const Which = Addon ? tr("Blender addon") : tr("Blender"); + if (mProgressBar) + { + if (Addon) + { + mAddonGridLayout->replaceWidget(mProgressBar, mAddonVersionEdit); + } + else + { + mExeGridLayout->replaceWidget(mProgressBar, mBlenderVersionEdit); + mProgressBar->hide(); + } + } + if (Error) + { + if (!Addon) + mPathLineEditList[PATH_BLENDER]->text() = QString(); + + lcSetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE, QString()); + + const lcPreferences& Preferences = lcGetPreferences(); + Label = ! Message.isEmpty() ? Message : tr("%1 not configured").arg(Which); + Colour = Message.startsWith("Error:", Qt::CaseInsensitive) + ? QLatin1String("red") + : Preferences.mColorTheme == lcColorTheme::Dark + ? QLatin1String(LC_THEME_DARK_DECORATE_QUOTED_TEXT) + : QLatin1String("blue"); + mDialogCancelled = true; + } + else + { + Label = !Message.isEmpty() ? Message : tr("%1 setup...").arg(Which); + Colour = QApplication::palette().text().color().name(); + } + + if (Addon) + { + mAddonVersionLabel->setStyleSheet(QString("QLabel { color : %1; }").arg(Colour)); + mAddonVersionLabel->setText(Label); + mAddonVersionEdit->setVisible(!mAddonVersion.isEmpty()); + } + else + { + mBlenderVersionLabel->setStyleSheet(QString("QLabel { color : %1; }").arg(Colour)); + mBlenderVersionLabel->setText(Label); + mBlenderVersionEdit->setVisible(!mBlenderVersion.isEmpty()); + } +} + +void lcBlenderPreferences::ShowResult() +{ + QString Message; + bool Error; + const QString StdErrLog = ReadStdErr(Error); + + if (mProgressBar) + mProgressBar->close(); + + WriteStdOut(); + + if (mProcess->exitStatus() != QProcess::NormalExit || mProcess->exitCode() != 0 || Error) + { + QString const BlenderDir = QString("%1/Blender").arg(mDataDir); + Message = tr("Addon install failed. See %1/stderr-blender-addon-install for details.").arg(BlenderDir); + StatusUpdate(true, true,tr("%1: Addon install failed.").arg("Error")); + mConfigured = false; + + QString const& Title = tr ("%1 Blender Addon Install").arg(LC_PRODUCTNAME_STR); + QString const& Header = "" + tr ("Addon install failed.") + ""; + QString const& Body = tr ("LDraw addon install encountered one or more errors. See Show Details..."); + ShowMessage(Header, Title, Body, StdErrLog, MBB_OK, QMessageBox::Critical); + } + else + { + QString const TextColour = QString("QLabel { color : %1; }").arg(QApplication::palette().text().color().name()); + mAddonGridLayout->replaceWidget(mProgressBar, mAddonVersionEdit); + mConfigured = true; + mBlenderVersionLabel->setText(tr("Blender")); + mBlenderVersionLabel->setStyleSheet(TextColour); + mBlenderVersionEdit->setText(mBlenderVersion); + mBlenderVersionEdit->setToolTip(tr("Display the Blender and %1 Render addon version").arg(LC_PRODUCTNAME_STR)); + mBlenderVersionEdit->setVisible(mConfigured); + mPathsBox->setEnabled(mConfigured); + mSettingsBox->setEnabled(mConfigured); + + if (!mAddonVersion.isEmpty()) + { + mAddonVersionLabel->setText(tr("Blender Addon")); + mAddonVersionLabel->setStyleSheet(TextColour); + mAddonVersionEdit->setText(mAddonVersion); + mAddonVersionEdit->setVisible(true); + mModulesBox->setEnabled(true); + mAddonUpdateButton->setEnabled(true); + lcSetProfileString(LC_PROFILE_BLENDER_VERSION, mBlenderVersion); + lcSetProfileString(LC_PROFILE_BLENDER_ADDON_VERSION, mAddonVersion); + SetModelSize(true); + SaveSettings(); + mDialogCancelled = false; + } + Message = tr("Blender version %1").arg(mBlenderVersion); + } + + delete mProcess; + mProcess = nullptr; + + if (Error) + ShowMessage(Message); +} + +void lcBlenderPreferences::SettingChanged(const QString& Value) +{ + bool Change = false; + + QLineEdit* LineEdit = qobject_cast(sender()); + if (LineEdit) + { + int LblIdx = LineEdit->property("ControlID").toInt(); + if (mImportMMActBox->isChecked()) + { + Change = mBlenderSettingsMM[LblIdx].value != Value; + } + else + { + Change = mBlenderSettings[LblIdx].value != Value; + } + + Change |= SettingsModified(false); + emit SettingChangedSig(Change); + } +} + +void lcBlenderPreferences::SettingChanged(int Index) +{ + int LblIdx = -1; + bool Change = false; + QString Item; + + if (Index > -1) + { + QComboBox* ComboBox = qobject_cast(sender()); + if (ComboBox) + { + LblIdx = ComboBox->property("ControlID").toInt(); + Item = ComboBox->itemData(Index).toString(); + } + } + else + { + QCheckBox* CheckBox = qobject_cast(sender()); + if (CheckBox) + { + LblIdx = CheckBox->property("ControlID").toInt(); + Item = QString::number(CheckBox->isChecked()); + } + } + + if (LblIdx > -1) + { + if (mImportMMActBox->isChecked()) + { + Change = mBlenderSettingsMM[LblIdx].value != Item; + } + else + { + Change = mBlenderSettings[LblIdx].value != Item; + } + } + + Change |= SettingsModified(false); + emit SettingChangedSig(Change); +} + +void lcBlenderPreferences::PathChanged() +{ + QLineEdit* LineEdit = qobject_cast(sender()); + if (LineEdit) + { + bool Change = false; + const int LblIdx = LineEdit->property("ControlID").toInt(); + const QString& Path = QDir::toNativeSeparators(LineEdit->text()).toLower(); + + if (LblIdx != PATH_BLENDER) + { + Change = QDir::toNativeSeparators(mBlenderPaths[LblIdx].value).toLower() != Path; + } + + Change |= SettingsModified(false); + emit SettingChangedSig(Change); + } +} + +void lcBlenderPreferences::GetStandardOutput() +{ + QString const LogFile = QString("%1/Blender/stdout-blender-addon-install").arg(mDataDir); + QFileInfo FileInfo(LogFile); + if (!FileInfo.exists()) + { + ShowMessage(tr("Blender addon standard output file not found: %1.").arg(FileInfo.absoluteFilePath())); + return; + } + + if (LogFile.isEmpty()) + return; + + QDesktopServices::openUrl(QUrl("file:///"+LogFile, QUrl::TolerantMode)); +} + +void lcBlenderPreferences::ReadStdOut(const QString& StdOutput, QString& Errors) +{ + QRegExp RxInfo("^INFO: "); + QRegExp RxData("^DATA: "); + QRegExp RxError("^(?:\\w)*ERROR: ", Qt::CaseInsensitive); + QRegExp RxWarning("^(?:\\w)*WARNING: ", Qt::CaseInsensitive); + QRegExp RxAddonVersion("^ADDON VERSION: ", Qt::CaseInsensitive); + + bool ErrorEncountered = false; + QStringList Items, ErrorList; + QStringList StdOutLines = StdOutput.split(QRegExp("\n|\r\n|\r")); + + QString const SaveAddonVersion = mAddonVersion; + QString const SaveVersion = mBlenderVersion; + + int EditListItems = mPathLineEditList.size(); + for (QString const& StdOutLine : StdOutLines) + { + if (StdOutLine.isEmpty()) + continue; + + if (!mBlenderVersionFound) + { + Items = StdOutLine.split(" "); + if (Items.count() > 6 && Items.at(0) == QLatin1String("Blender")) + { + Items.takeLast(); + mBlenderVersion.clear(); + for (int LblIdx = 1; LblIdx < Items.size(); LblIdx++) + mBlenderVersion.append(Items.at(LblIdx)+" "); + mBlenderVersionFound = !mBlenderVersion.isEmpty(); + if (mBlenderVersionFound) + { + mBlenderVersion = mBlenderVersion.trimmed().prepend("v").append(")"); + mBlenderVersionEdit->setText(mBlenderVersion); + if (!mImportActBox->isChecked() && !mImportMMActBox->isChecked()) + mImportActBox->setChecked(true); + } + } + } + + if (StdOutLine.contains(RxInfo)) + { + Items = StdOutLine.split(": "); + } + else if (StdOutLine.contains(RxData)) + { + Items = StdOutLine.split(": "); + if (Items.at(1) == "ENVIRONMENT_FILE") + { + mBlenderPaths[PATH_ENVIRONMENT].value = Items.at(2); + if (EditListItems > PATH_ENVIRONMENT) + mPathLineEditList[PATH_ENVIRONMENT]->setText(Items.at(2)); + } + else if (Items.at(1) == "LSYNTH_DIRECTORY") + { + mBlenderPaths[PATH_LSYNTH].value = Items.at(2); + if (EditListItems > PATH_LSYNTH) + mPathLineEditList[PATH_LSYNTH]->setText(Items.at(2)); + } + else if (Items.at(1) == "STUDLOGO_DIRECTORY") + { + mBlenderPaths[PATH_STUD_LOGO].value = Items.at(2); + if (EditListItems > PATH_STUD_LOGO) + mPathLineEditList[PATH_STUD_LOGO]->setText(Items.at(2)); + } + } + else if (StdOutLine.contains(RxError) || StdOutLine.contains(RxWarning)) + { + ErrorList << StdOutLine.trimmed() + "
"; + if (!ErrorEncountered) + ErrorEncountered = StdOutLine.contains(RxError); + } + else if (StdOutLine.contains(RxAddonVersion)) + { + Items = StdOutLine.split(":"); + mAddonVersion = tr("v%1").arg(Items.at(1).trimmed()); + mAddonVersionEdit->setText(mAddonVersion); + } + } + if (ErrorList.size()) + { + if (!mBlenderVersionFound) + { + if (mBlenderVersion != SaveVersion) + { + mConfigured = false; + mBlenderVersion = SaveVersion; + mBlenderVersionEdit->setText(mBlenderVersion); + } + } + if (mAddonVersion != SaveAddonVersion) + { + mConfigured = false; + mAddonVersion = SaveAddonVersion; + mAddonVersionEdit->setText(mAddonVersion); + } + Errors = ErrorList.join(" "); + } +} + +void lcBlenderPreferences::ReadStdOut() +{ + QString const& StdOut = QString(mProcess->readAllStandardOutput()); + + mStdOutList.append(StdOut); + + QRegExp RxInfo("^INFO: "); + QRegExp RxError("(?:\\w)*ERROR: ", Qt::CaseInsensitive); + QRegExp RxWarning("(?:\\w)*WARNING: ", Qt::CaseInsensitive); + + bool const Error = StdOut.contains(RxError); + bool const Warning = StdOut.contains(RxWarning); + + if (StdOut.contains(RxInfo) && !Error) + StatusUpdate(true, false); + + QString ErrorsAndWarnings; + + ReadStdOut(StdOut, ErrorsAndWarnings); + + if (!ErrorsAndWarnings.isEmpty()) + { + QString const StdOutLog = QDir::toNativeSeparators(QString("
- See %1/Blender/stdout-blender-addon-install") + .arg(mDataDir)); + + QMessageBox::Icon Icon = Error ? QMessageBox::Critical : QMessageBox::Warning; + QString const& Items = Error ? tr("errors%1").arg(Warning ? tr(" and warnings") : "") : Warning ? tr("warnings") : ""; + + QString const& Title = tr ("%1 Blender Addon Install").arg(LC_PRODUCTNAME_STR); + QString const& Header = "" + tr ("Addon install standard output.") + ""; + QString const& Body = tr ("LDraw addon install encountered %1. See Show Details...").arg(Items); + ShowMessage(Header, Title, Body, ErrorsAndWarnings.append(StdOutLog), MBB_OK, Icon); + } +} + +QString lcBlenderPreferences::ReadStdErr(bool& Error) const +{ + auto CleanLine = [](const QString& Line) + { + return Line.trimmed() + "
"; + }; + + Error = false; + QStringList ReturnLines; + + QString const BlenderDir = QString("%1/Blender").arg(mDataDir); + QFile File(QString("%1/stderr-blender-addon-install").arg(BlenderDir)); + if ( ! File.open(QFile::ReadOnly | QFile::Text)) + { + QString Message = tr("Failed to open log file: %1:\n%2") + .arg(File.fileName()) + .arg(File.errorString()); + return Message; + } + QTextStream In(&File); + while ( ! In.atEnd()) + { + QString const& Line = In.readLine(0); + ReturnLines << CleanLine(Line); + if (!Error) + Error = !Line.isEmpty(); + } + return ReturnLines.join(" "); +} + +void lcBlenderPreferences::WriteStdOut() +{ + QString const BlenderDir = QString("%1/Blender").arg(mDataDir); + QFile File(QString("%1/stdout-blender-addon-install").arg(BlenderDir)); + if (File.open(QFile::WriteOnly | QIODevice::Truncate | QFile::Text)) + { + QTextStream Out(&File); + for (const QString& Line : mStdOutList) + Out << Line << LineEnding; + File.close(); + mAddonStdOutButton->setEnabled(true); + } + else + ShowMessage(tr("Error writing to %1 file '%2':\n%3").arg("stdout").arg(File.fileName(), File.errorString())); +} + +bool lcBlenderPreferences::PromptCancel() +{ +#ifndef QT_NO_PROCESS + if (mProcess) + { + QString const& Title = tr ("Cancel %1 Addon Install").arg(LC_PRODUCTNAME_STR); + QString const& Header = "" + tr("Are you sure you want to cancel the add on install ?") + ""; + int Exec = ShowMessage(Header, Title, QString(), QString(), MBB_YES_NO, QMessageBox::Question); + if (Exec == QMessageBox::Yes) + { + mProcess->kill(); + delete mProcess; + mProcess = nullptr; + } + else + return false; + } +#endif + + mDialogCancelled = true; + return true; +} + +void lcBlenderPreferences::Update() +{ +#ifndef QT_NO_PROCESS + if (!mProcess) + return; + + if (mProcess->state() == QProcess::NotRunning) + ShowResult(); +#endif + QApplication::processEvents(); +} + +void lcBlenderPreferences::Apply(const int Response) +{ + if (Response == QDialog::Accepted) + { + if (SettingsModified()) + SaveSettings(); + } + else if (mDialogCancelled) + { + if (SettingsModified()) + if (PromptAccept()) + SaveSettings(); + } +} + +bool lcBlenderPreferences::SettingsModified(bool Update, const QString& Module) +{ + if (gAddonPreferences->mDialogCancelled) + return false; + + int& Width = gAddonPreferences->mImageWidth; + int& Height = gAddonPreferences->mImageHeight; + double& Scale = gAddonPreferences->mScale; + + bool ModuleMM = !Module.isEmpty() + ? Module == QLatin1String("MM") + : gAddonPreferences->mImportMMActBox->isChecked(); + + bool Ok, Modified = !QFileInfo(lcGetProfileString(LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH)).isReadable(); + qreal _Width = 0.0, _Height = 0.0, _Scale = 0.0, _Value = 0.0, _OldValue = 0.0; + QString OldValue; + + auto ItemChanged = [](qreal OldValue, qreal NewValue) + { + return NewValue > OldValue || NewValue < OldValue; + }; + + if (ModuleMM) + { + for (int LblIdx = 0; LblIdx < NumSettingsMM(); LblIdx++) + { + // checkboxes + if (LblIdx < LBL_BEVEL_SEGMENTS) + { + for (int CtlIdx = 0; CtlIdx < gAddonPreferences->mCheckBoxList.size(); CtlIdx++) + { + OldValue = mBlenderSettingsMM[LblIdx].value; + if (Update) + mBlenderSettingsMM[LblIdx].value = QString::number(gAddonPreferences->mCheckBoxList[CtlIdx]->isChecked()); + Modified |= mBlenderSettingsMM[LblIdx].value != OldValue; + if (LblIdx < LBL_VERBOSE_MM) + LblIdx++; + } + } + // lineedits + else if (LblIdx < LBL_CHOSEN_LOGO) + { + for (int CtlIdx = 0; CtlIdx < gAddonPreferences->mLineEditList.size(); CtlIdx++) + { + if (CtlIdx == CTL_RESOLUTION_WIDTH_EDIT) + { + _OldValue = Width; + _Width = gAddonPreferences->mLineEditList[CtlIdx]->text().toDouble(&Ok); + if (Ok) + { + if (Update) + { + Width = int(_Width); + mBlenderSettingsMM[LblIdx].value = QString::number(Width); + } + Modified |= ItemChanged(_OldValue, _Width); + } + } + else if (CtlIdx == CTL_RESOLUTION_HEIGHT_EDIT) + { + _OldValue = Height; + _Height = gAddonPreferences->mLineEditList[CtlIdx]->text().toDouble(&Ok); + if (Ok) + { + if (Update) + { + Height = int(_Height); + mBlenderSettingsMM[LblIdx].value = QString::number(Height); + } + Modified |= ItemChanged(_OldValue, _Height); + } + } + else if (CtlIdx == CTL_RENDER_PERCENTAGE_EDIT_MM) + { + _OldValue = Scale; + _Scale = gAddonPreferences->mLineEditList[CtlIdx]->text().toInt(&Ok); + if (Ok) + { + if (Update) + { + Scale = double(_Scale / 100); + mBlenderSettingsMM[LblIdx].value = QString::number(_Scale); + } + Modified |= ItemChanged(_OldValue, Scale); + } + } + else + { + _OldValue = mBlenderSettingsMM[LblIdx].value.toDouble(); + _Value = gAddonPreferences->mLineEditList[CtlIdx]->text().toDouble(&Ok); + if (Ok) + { + if (Update) + mBlenderSettingsMM[LblIdx].value = QString::number(_Value); + Modified |= ItemChanged(_OldValue, _Value); + } + } + if (LblIdx < LBL_STARTING_STEP_FRAME) + LblIdx++; + } + } + // comboboxes + else + { + for (int CtlIdx = 0; CtlIdx < gAddonPreferences->mComboBoxList.size(); CtlIdx++) + { + OldValue = mBlenderSettingsMM[LblIdx].value; + QString const Value = gAddonPreferences->mComboBoxList[CtlIdx]->itemData(gAddonPreferences->mComboBoxList[CtlIdx]->currentIndex()).toString(); + if (Update) + mBlenderSettingsMM[LblIdx].value = Value; + Modified |= Value != OldValue; + LblIdx++; + } + } + } + } + else + { + // settings + for (int LblIdx = 0; LblIdx < NumSettings(); LblIdx++) + { + // checkboxes + if (LblIdx < LBL_BEVEL_WIDTH) + { + for (int CtlIdx = 0; CtlIdx < gAddonPreferences->mCheckBoxList.size(); CtlIdx++) + { + OldValue = mBlenderSettings[LblIdx].value; + if (Update) + mBlenderSettings[LblIdx].value = QString::number(gAddonPreferences->mCheckBoxList[CtlIdx]->isChecked()); + Modified |= mBlenderSettings[LblIdx].value != OldValue; + if (LblIdx < LBL_VERBOSE) + LblIdx++; + } + } + // lineedits + else if (LblIdx < LBL_COLOUR_SCHEME) + { + for (int CtlIdx = 0; CtlIdx < gAddonPreferences->mLineEditList.size(); CtlIdx++) + { + if (CtlIdx == CTL_IMAGE_WIDTH_EDIT) + { + _OldValue = Width; + _Width = gAddonPreferences->mLineEditList[CtlIdx]->text().toDouble(&Ok); + if (Ok) + { + if (Update) + { + Width = int(_Width); + mBlenderSettings[LblIdx].value = QString::number(Width); + } + Modified |= ItemChanged(_OldValue, _Width); + } + } + else if (CtlIdx == CTL_IMAGE_HEIGHT_EDIT) + { + _OldValue = Height; + _Height = gAddonPreferences->mLineEditList[CtlIdx]->text().toDouble(&Ok); + if (Ok) + { + if (Update) + { + Height = int(_Height); + mBlenderSettings[LblIdx].value = QString::number(Height); + } + Modified |= ItemChanged(_OldValue, _Height); + } + } + else if (CtlIdx == CTL_RENDER_PERCENTAGE_EDIT) + { + _OldValue = Scale; + _Scale = gAddonPreferences->mLineEditList[CtlIdx]->text().toInt(&Ok); + if (Ok) + { + if (Update) + { + Scale = double(_Scale / 100); + mBlenderSettings[LblIdx].value = QString::number(_Scale); + } + Modified |= ItemChanged(_OldValue, Scale); + } + } + else + { + if (CtlIdx == CTL_DEFAULT_COLOUR_EDIT) + { + _OldValue = lcGetColorIndex(mBlenderSettings[LblIdx].value.toInt()); // colour code + _Value = gAddonPreferences->mLineEditList[CtlIdx]->property("ColorIndex").toInt(&Ok); + } + else + { + _OldValue = mBlenderSettings[LblIdx].value.toDouble(); + _Value = gAddonPreferences->mLineEditList[CtlIdx]->text().toDouble(&Ok); + } + if (Ok) + { + if (Update) + { + if (CtlIdx == CTL_DEFAULT_COLOUR_EDIT) + { + mBlenderSettings[LblIdx].value = QString::number(lcGetColorCode(qint32(_Value))); + } + else + { + mBlenderSettings[LblIdx].value = QString::number(_Value); + } + } + Modified |= ItemChanged(_OldValue, _Value); + } + } + if (LblIdx < LBL_RENDER_PERCENTAGE) + LblIdx++; + } + } + // comboboxes + else + { + for (int CtlIdx = 0; CtlIdx < gAddonPreferences->mComboBoxList.size(); CtlIdx++) + { + OldValue = mBlenderSettings[LblIdx].value; + QString const Value = gAddonPreferences->mComboBoxList[CtlIdx]->itemData(gAddonPreferences->mComboBoxList[CtlIdx]->currentIndex()).toString(); + if (Update) + mBlenderSettings[LblIdx].value = Value; + Modified |= Value != OldValue; + LblIdx++; + } + } + } + } + + // paths + for (int LblIdx = 0; LblIdx < NumPaths(); LblIdx++) + { + OldValue = mBlenderPaths[LblIdx].value; + QString const Value = gAddonPreferences->mPathLineEditList[LblIdx]->text(); + if (Update) + mBlenderPaths[LblIdx].value = Value; + Modified |= Value != OldValue; + } + + return Modified; +} + +void lcBlenderPreferences::ResetSettings() +{ + BlenderPaths const* Paths = mBlenderPaths; + BlenderSettings const* Settings = mBlenderSettings; + BlenderSettings const* SettingsMM = mBlenderSettingsMM; + + QDialog* Dialog = new QDialog(this); + Dialog->setWindowTitle(tr("Addon Reset")); + Dialog->setWhatsThis(tr("Select how to reset settings. Choice is since last apply or system default.")); + QVBoxLayout* Layout = new QVBoxLayout(Dialog); + QGroupBox* DlgGroup = new QGroupBox(Dialog); + QHBoxLayout* DlgLayout = new QHBoxLayout(DlgGroup); + QRadioButton* LastButton = new QRadioButton(tr("Last Apply")); + DlgLayout->addWidget(LastButton); + QRadioButton* DefaultButton = new QRadioButton(tr("System Default")); + DlgLayout->addWidget(DefaultButton); + DlgGroup->setLayout(DlgLayout); + Layout->addWidget(DlgGroup); + Dialog->setLayout(Layout); + + LastButton->setChecked(true); + + QDialogButtonBox ButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, + Qt::Horizontal, Dialog); + Layout->addWidget(&ButtonBox); + connect(&ButtonBox, SIGNAL(accepted()), Dialog, SLOT(accept())); + connect(&ButtonBox, SIGNAL(rejected()), Dialog, SLOT(reject())); + + if (Dialog->exec() == QDialog::Accepted) + { + if (DefaultButton->isChecked()) + { + Paths = mDefaultPaths; + Settings = mDefaultSettings; + SettingsMM = mDefaultSettingsMM; + } + } + + mConfigured = !lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty(); + + mBlenderPaths[PATH_BLENDER].value = lcGetProfileString(LC_PROFILE_BLENDER_PATH); + mBlenderVersion = lcGetProfileString(LC_PROFILE_BLENDER_VERSION); + mAddonVersion = lcGetProfileString(LC_PROFILE_BLENDER_ADDON_VERSION); + + mBlenderVersionEdit->setText(mBlenderVersion); + mAddonVersionEdit->setText(mAddonVersion); + + if (mImportActBox->isChecked()) + { + disconnect(mLineEditList[CTL_IMAGE_HEIGHT_EDIT], SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + disconnect(mLineEditList[CTL_IMAGE_WIDTH_EDIT], SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + + for (int LblIdx = 0; LblIdx < NumSettings(); LblIdx++) + { + if (LblIdx < LBL_BEVEL_WIDTH) + { + for (int CtlIdx = 0; CtlIdx < mCheckBoxList.size(); CtlIdx++) + { + mCheckBoxList[CtlIdx]->setChecked(Settings[LblIdx].value.toInt()); + if (LblIdx < LBL_VERBOSE) + LblIdx++; + } + } + else if (LblIdx < LBL_COLOUR_SCHEME) + { + for (int CtlIdx = 0; CtlIdx < mLineEditList.size(); CtlIdx++) + { + if (CtlIdx == CTL_IMAGE_WIDTH_EDIT) + mLineEditList[CtlIdx]->setText(QString::number(mImageWidth)); + else if (CtlIdx == CTL_IMAGE_HEIGHT_EDIT) + mLineEditList[CtlIdx]->setText(QString::number(mImageHeight)); + else if (CtlIdx == CTL_RENDER_PERCENTAGE_EDIT) + mLineEditList[CtlIdx]->setText(QString::number(mScale * 100)); + else if (CtlIdx == CTL_DEFAULT_COLOUR_EDIT) + SetDefaultColor(lcGetColorIndex(Settings[LBL_DEFAULT_COLOUR].value.toInt())); + else + mLineEditList[CtlIdx]->setText(Settings[LblIdx].value); + if (LblIdx < LBL_RENDER_PERCENTAGE) + LblIdx++; + } + } + else + { + for (int CtlIdx = 0; CtlIdx < mComboBoxList.size(); CtlIdx++) + { + mComboBoxList[CtlIdx]->setCurrentIndex(int(mComboBoxList[CtlIdx]->findData(QVariant::fromValue(Settings[LblIdx].value)))); + LblIdx++; + } + } + } + + for (int LblIdx = 0; LblIdx < NumPaths(); LblIdx++) + { + mPathLineEditList[LblIdx]->setText(Paths[LblIdx].value); + } + + connect(mLineEditList[CTL_IMAGE_HEIGHT_EDIT],SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + connect(mLineEditList[CTL_IMAGE_WIDTH_EDIT], SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + } + else if (mImportMMActBox->isChecked()) + { + disconnect(mLineEditList[CTL_RESOLUTION_HEIGHT_EDIT], SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + disconnect(mLineEditList[CTL_RESOLUTION_WIDTH_EDIT], SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + + for (int LblIdx = 0; LblIdx < NumSettingsMM(); LblIdx++) + { + if (LblIdx < LBL_BEVEL_SEGMENTS) + { + for (int CtlIdx = 0; CtlIdx < mCheckBoxList.size(); CtlIdx++) + { + mCheckBoxList[CtlIdx]->setChecked(SettingsMM[LblIdx].value.toInt()); + if (LblIdx < LBL_VERBOSE_MM) + LblIdx++; + } + } + else if (LblIdx < LBL_CHOSEN_LOGO) + { + for (int CtlIdx = 0; CtlIdx < mLineEditList.size(); CtlIdx++) + { + if (CtlIdx == CTL_RESOLUTION_WIDTH_EDIT) + mLineEditList[CtlIdx]->setText(QString::number(mImageWidth)); + else if (CtlIdx == CTL_RESOLUTION_HEIGHT_EDIT) + mLineEditList[CtlIdx]->setText(QString::number(mImageHeight)); + else if (CtlIdx == CTL_RENDER_PERCENTAGE_EDIT_MM) + mLineEditList[CtlIdx]->setText(QString::number(mScale * 100)); + else + mLineEditList[CtlIdx]->setText(SettingsMM[LblIdx].value); + if (LblIdx < LBL_STARTING_STEP_FRAME) + LblIdx++; + } + } + else + { + for (int CtlIdx = 0; CtlIdx < mComboBoxList.size(); CtlIdx++) + { + mComboBoxList[CtlIdx]->setCurrentIndex(int(mComboBoxList[CtlIdx]->findData(QVariant::fromValue(SettingsMM[LblIdx].value)))); + LblIdx++; + } + } + } + + for (int LblIdx = 0; LblIdx < NumPaths(); LblIdx++) + { + mPathLineEditList[LblIdx]->setText(Paths[LblIdx].value); + } + + connect(mLineEditList[CTL_RESOLUTION_HEIGHT_EDIT], SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + connect(mLineEditList[CTL_RESOLUTION_WIDTH_EDIT], SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + } + + emit SettingChangedSig(true); +} + +void lcBlenderPreferences::LoadSettings() +{ + QStringList const& DataPathList = QStandardPaths::standardLocations(QStandardPaths::DataLocation); + gAddonPreferences->mDataDir = DataPathList.first(); + + if (QFileInfo(lcGetProfileString(LC_PROFILE_BLENDER_PATH)).isReadable()) + { + if (lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty()) + lcSetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE, QLatin1String(LC_BLENDER_ADDON_IMPORT_MODULE)); + } + else + { + lcSetProfileString(LC_PROFILE_BLENDER_PATH, QString()); + lcSetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE, QString()); + } + + if (!QDir(QString("%1/Blender/addons/%2").arg(gAddonPreferences->mDataDir).arg(LC_BLENDER_ADDON_FOLDER_STR)).isReadable()) + lcSetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE, QString()); + + if (!NumPaths()) + { + QString const DefaultBlendFile = QString("%1/Blender/config/%2") + .arg(gAddonPreferences->mDataDir) + .arg(LC_BLENDER_ADDON_BLEND_FILE); + + QStringList const AddonPaths = QStringList() + /* PATH_BLENDER */ << lcGetProfileString(LC_PROFILE_BLENDER_PATH) + /* PATH_BLENDFILE */ << (QFileInfo(DefaultBlendFile).exists() ? DefaultBlendFile : QString()) + /* PATH_ENVIRONMENT */ << QString() + /* PATH_LDCONFIG */ << lcGetProfileString(LC_PROFILE_COLOR_CONFIG) + /* PATH_LDRAW */ << QFileInfo(lcGetProfileString(LC_PROFILE_PARTS_LIBRARY)).absolutePath() + /* PATH_LSYNTH */ << QString() + /* PATH_STUD_LOGO */ << QString() + /* PATH_STUDIO_LDRAW */ << QString(); + for (int LblIdx = 0; LblIdx < NumPaths(DEFAULT_SETTINGS); LblIdx++) + { + mBlenderPaths[LblIdx] = + { + mDefaultPaths[LblIdx].key, + mDefaultPaths[LblIdx].key_mm, + QDir::toNativeSeparators(AddonPaths.at(LblIdx)), + mDefaultPaths[LblIdx].label, + mDefaultPaths[LblIdx].tooltip + }; + } + } + + if (!NumSettings()) + { + for (int LblIdx = 0; LblIdx < NumSettings(DEFAULT_SETTINGS); LblIdx++) + { + mBlenderSettings[LblIdx] = + { + mDefaultSettings[LblIdx].key, + mDefaultSettings[LblIdx].value, + mDefaultSettings[LblIdx].label, + mDefaultSettings[LblIdx].tooltip + }; + } + } + + if (!NumSettingsMM()) + { + for (int LblIdx = 0; LblIdx < NumSettingsMM(DEFAULT_SETTINGS); LblIdx++) + { + mBlenderSettingsMM[LblIdx] = + { + mDefaultSettingsMM[LblIdx].key, + mDefaultSettingsMM[LblIdx].value, + mDefaultSettingsMM[LblIdx].label, + mDefaultSettingsMM[LblIdx].tooltip + }; + } + } + + QFileInfo BlenderConfigFileInfo(lcGetProfileString(LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH)); + + bool ConfigFileExists = BlenderConfigFileInfo.exists(); + + if (ConfigFileExists) + { + QSettings Settings(BlenderConfigFileInfo.absoluteFilePath(), QSettings::IniFormat); + + for (int LblIdx = 1/*skip blender executable*/; LblIdx < NumPaths(); LblIdx++) + { + if (LblIdx == PATH_STUDIO_LDRAW) + continue; + QString const& Key = QString("%1/%2").arg(LC_BLENDER_ADDON, mBlenderPaths[LblIdx].key); + QString const& Value = Settings.value(Key, QString()).toString(); + if (QFileInfo(Value).exists()) + { + mBlenderPaths[LblIdx].value = QDir::toNativeSeparators(Value); + } + } + + for (int LblIdx = 1/*skip blender executable*/; LblIdx < NumPaths(); LblIdx++) + { + if (LblIdx == PATH_LSYNTH || LblIdx == PATH_STUD_LOGO) + continue; + QString const& Key = QString("%1/%2").arg(LC_BLENDER_ADDON_MM, mBlenderPaths[LblIdx].key_mm); + QString const& Value = Settings.value(Key, QString()).toString(); + if (QFileInfo(Value).exists()) + { + mBlenderPaths[LblIdx].value = QDir::toNativeSeparators(Value); + } + } + + for (int LblIdx = 0; LblIdx < NumSettings(); LblIdx++) + { + QString const& Key = QString("%1/%2").arg(LC_BLENDER_ADDON, mBlenderSettings[LblIdx].key); + QString const& Value = Settings.value(Key, QString()).toString(); + if (!Value.isEmpty()) + { + mBlenderSettings[LblIdx].value = Value == "True" ? "1" : Value == "False" ? "0" : Value; + } + if (LblIdx == LBL_IMAGE_WIDTH || LblIdx == LBL_IMAGE_HEIGHT || LblIdx == LBL_RENDER_PERCENTAGE) + { + QString const& Label = mDefaultSettings[LblIdx].label; + mBlenderSettings[LblIdx].label = QString("%1 - Setting (%2)").arg(Label).arg(Value); + } + } + + for (int LblIdx = 0; LblIdx < NumSettingsMM(); LblIdx++) + { + QString const& Key = QString("%1/%2").arg(LC_BLENDER_ADDON_MM, mBlenderSettingsMM[LblIdx].key); + QString const& Value = Settings.value(Key, QString()).toString(); + if (!Value.isEmpty()) + { + mBlenderSettingsMM[LblIdx].value = Value == "True" ? "1" : Value == "False" ? "0" : Value; + } + if (LblIdx == LBL_RENDER_PERCENTAGE_MM || LblIdx == LBL_RESOLUTION_WIDTH || LblIdx == LBL_RESOLUTION_HEIGHT) + { + QString const& Label = mDefaultSettingsMM[LblIdx].label; + mBlenderSettingsMM[LblIdx].label = QString("%1 - Setting (%2)").arg(Label).arg(Value); + } + } + } + else + { + QString const LogFile = QString("%1/Blender/stdout-blender-addon-install").arg(gAddonPreferences->mDataDir); + if (QFileInfo(LogFile).isReadable()) + { + QFile File(LogFile); + if (File.open(QFile::ReadOnly | QFile::Text)) + + { + QByteArray Ba = File.readAll(); + File.close(); + QString Errors; + gAddonPreferences->mProgressBar = nullptr; + gAddonPreferences->ReadStdOut(QString(Ba), Errors); + } + else + { + ShowMessage(tr("Failed to open log file: %1:\n%2") + .arg(File.fileName()) + .arg(File.errorString())); + } + } + } + + mBlenderSettings[LBL_IMAGE_WIDTH].value = QString::number(gAddonPreferences->mImageWidth); + mBlenderSettings[LBL_IMAGE_HEIGHT].value = QString::number(gAddonPreferences->mImageHeight); + mBlenderSettings[LBL_RENDER_PERCENTAGE].value = QString::number(gAddonPreferences->mScale * 100); + + mBlenderSettingsMM[LBL_RESOLUTION_WIDTH].value = QString::number(gAddonPreferences->mImageWidth); + mBlenderSettingsMM[LBL_RESOLUTION_HEIGHT].value = QString::number(gAddonPreferences->mImageHeight); + mBlenderSettingsMM[LBL_RENDER_PERCENTAGE_MM].value = QString::number(gAddonPreferences->mScale * 100); + + mBlenderPaths[PATH_BLENDER].value = lcGetProfileString(LC_PROFILE_BLENDER_PATH); + gAddonPreferences->mBlenderVersion = lcGetProfileString(LC_PROFILE_BLENDER_VERSION); + gAddonPreferences->mAddonVersion = lcGetProfileString(LC_PROFILE_BLENDER_ADDON_VERSION); +} + +void lcBlenderPreferences::SaveSettings() +{ + if (!NumSettings() || !NumSettingsMM()) + LoadSettings(); + + QString const BlenderConfigDir = QString("%1/Blender/setup/addon_setup/config").arg(gAddonPreferences->mDataDir); + + QString Key, Value = mBlenderPaths[PATH_BLENDER].value; + if (Value.isEmpty()) + Value = gAddonPreferences->mPathLineEditList[PATH_BLENDER]->text(); + + lcSetProfileString(LC_PROFILE_BLENDER_PATH, QDir::toNativeSeparators(Value)); + + Value.clear(); + if (!gAddonPreferences->mBlenderVersion.isEmpty()) + Value = gAddonPreferences->mBlenderVersion; + + if (!gAddonPreferences->mAddonVersion.isEmpty()) + { + gAddonPreferences->mModulesBox->setEnabled(true); + gAddonPreferences->mAddonVersionEdit->setText(gAddonPreferences->mAddonVersion); + } + + lcSetProfileString(LC_PROFILE_BLENDER_VERSION, Value); + + Value = lcGetProfileString(LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH).isEmpty() + ? QString("%1/%2").arg(BlenderConfigDir).arg(LC_BLENDER_ADDON_CONFIG_FILE) + : lcGetProfileString(LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH); + lcSetProfileString(LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH, QDir::toNativeSeparators(Value)); + + QString searchDirectoriesKey; + QString parameterFileKey = QLatin1String("ParameterFile"); + QString ParameterFile = QString("%1/%2").arg(BlenderConfigDir).arg(LC_BLENDER_ADDON_PARAMS_FILE); + + QSettings Settings(Value, QSettings::IniFormat); + + auto concludeSettingsGroup = [&]() + { + if (!QFileInfo(ParameterFile).exists()) + ExportParameterFile(); + Value = QDir::toNativeSeparators(QFileInfo(lcGetProfileString(LC_PROFILE_PARTS_LIBRARY)).absolutePath()); + Settings.setValue(searchDirectoriesKey, QVariant(Value)); + Settings.endGroup(); + }; + + Settings.beginGroup(LC_BLENDER_ADDON); + + for (int LblIdx = 1/*skip blender executable*/; LblIdx < NumPaths(); LblIdx++) + { + if (LblIdx == PATH_STUDIO_LDRAW) + continue; + Key = mBlenderPaths[LblIdx].key; + Value = QDir::toNativeSeparators(mBlenderPaths[LblIdx].value); + Settings.setValue(Key, QVariant(Value)); + } + + for (int LblIdx = 0; LblIdx < NumSettings(); LblIdx++) + { + if (LblIdx == LBL_KEEP_ASPECT_RATIO) + { + continue; + } + else if (LblIdx < LBL_BEVEL_WIDTH) + { + Value = mBlenderSettings[LblIdx].value == "1" ? "True" : "False"; + } + else if (LblIdx > LBL_VERBOSE) + { + if (LblIdx == LBL_FLEX_PARTS_SOURCE || LblIdx == LBL_POSITION_OBJECT) + Value = mBlenderSettings[LblIdx].value == "1" ? "True" : "False"; + else + Value = mBlenderSettings[LblIdx].value; + } + + Key = mBlenderSettings[LblIdx].key; + Settings.setValue(Key, QVariant(Value)); + } + + Settings.setValue(parameterFileKey, QVariant(QDir::toNativeSeparators(ParameterFile))); + + searchDirectoriesKey = QLatin1String("additionalSearchDirectories"); + + concludeSettingsGroup(); + + Settings.beginGroup(LC_BLENDER_ADDON_MM); + + for (int LblIdx = 1/*skip blender executable*/; LblIdx < NumPaths(); LblIdx++) + { + if (LblIdx == PATH_LSYNTH || LblIdx == PATH_STUD_LOGO) + continue; + Key = mBlenderPaths[LblIdx].key_mm; + Value = QDir::toNativeSeparators(mBlenderPaths[LblIdx].value); + Settings.setValue(Key, QVariant(Value)); + } + + for (int LblIdx = 0; LblIdx < NumSettingsMM(); LblIdx++) + { + if (LblIdx == LBL_KEEP_ASPECT_RATIO_MM) + { + continue; + } + else if (LblIdx < LBL_BEVEL_SEGMENTS) + { + Value = mBlenderSettingsMM[LblIdx].value == "1" ? "True" : "False"; + } + else if (LblIdx > LBL_VERBOSE_MM) + { + Value = mBlenderSettingsMM[LblIdx].value; + } + + Key = mBlenderSettingsMM[LblIdx].key; + Settings.setValue(Key, QVariant(Value)); + } + + searchDirectoriesKey = QLatin1String("additionalSearchPaths"); + + concludeSettingsGroup(); + + QString const preferredImportModule = + gAddonPreferences->mImportActBox->isChecked() + ? QString("TN") + : gAddonPreferences->mImportMMActBox->isChecked() + ? QString("MM") : QString(); + + if (preferredImportModule != lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE)) + lcSetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE, preferredImportModule); +} + +void lcBlenderPreferences::EnableImportModule() +{ + QString saveImportModule, preferredImportModule; + if (sender() == mImportActBox && mImportActBox->isChecked()) + { + preferredImportModule = QLatin1String("TN"); + if (mImportMMActBox->isChecked()) + saveImportModule = QLatin1String("MM"); + mImportMMActBox->setChecked(false); + } + else if (sender() == mImportMMActBox && mImportMMActBox->isChecked()) + { + preferredImportModule = QLatin1String("MM"); + if (mImportActBox->isChecked()) + saveImportModule = QLatin1String("TN"); + mImportActBox->setChecked(false); + } + + if (preferredImportModule.isEmpty()) + return; + + if (SettingsModified(true, saveImportModule)) + SaveSettings(); + + lcSetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE, preferredImportModule); + + if (mImportMMActBox->isChecked()) + InitPathsAndSettingsMM(); + else + InitPathsAndSettings(); + + ConfigureBlenderAddon(false, false, true); +} + +int lcBlenderPreferences::NumSettings(bool DefaultSettings) +{ + int Size = 0; + if (!mBlenderSettings[0].key.isEmpty() || DefaultSettings) + Size = sizeof(mBlenderSettings)/sizeof(mBlenderSettings[0]); + return Size; +} + +int lcBlenderPreferences::NumSettingsMM(bool DefaultSettings) +{ + int Size = 0; + if (!mBlenderSettingsMM[0].key.isEmpty() || DefaultSettings) + Size = sizeof(mBlenderSettingsMM)/sizeof(mBlenderSettingsMM[0]); + return Size; +} + +int lcBlenderPreferences::NumPaths(bool DefaultSettings) +{ + int Size = 0; + if (!mBlenderPaths[0].key.isEmpty() || DefaultSettings) + Size = sizeof(mBlenderPaths)/sizeof(mBlenderPaths[0]); + return Size; +} + +void lcBlenderPreferences::ShowPathsGroup() +{ + if (mPathsBox->isHidden()) + mPathsBox->show(); + else + mPathsBox->hide(); + + mContent->adjustSize(); +} + +void lcBlenderPreferences::ColorButtonClicked(bool) +{ + int ColorIndex = mLineEditList[CTL_DEFAULT_COLOUR_EDIT]->property("ColorIndex").toInt(); + + QWidget* Parent = mLineEditList[CTL_DEFAULT_COLOUR_EDIT]; + lcColorPickerPopup* Popup = new lcColorPickerPopup(Parent, ColorIndex); + connect(Popup, SIGNAL(selected(int)), SLOT(SetDefaultColor(int))); + Popup->setMinimumSize(300, 200); + + const QRect DesktopGeom = QApplication::desktop()->geometry(); + + QPoint Pos = Parent->mapToGlobal(Parent->rect().bottomLeft()); + if (Pos.x() < DesktopGeom.left()) + Pos.setX(DesktopGeom.left()); + + if (Pos.y() < DesktopGeom.top()) + Pos.setY(DesktopGeom.top()); + + if ((Pos.x() + Popup->width()) > DesktopGeom.width()) + Pos.setX(DesktopGeom.width() - Popup->width()); + + if ((Pos.y() + Popup->height()) > DesktopGeom.bottom()) + Pos.setY(DesktopGeom.bottom() - Popup->height()); + + Popup->move(Pos); + + Popup->setFocus(); + Popup->show(); +} + +void lcBlenderPreferences::SetDefaultColor(int ColorIndex) +{ + QImage Image(12, 12, QImage::Format_ARGB32); + Image.fill(0); + + lcColor* Colour =& gColorList[ColorIndex]; + QPainter Painter(&Image); + Painter.setCompositionMode(QPainter::CompositionMode_Source); + Painter.setPen(Qt::darkGray); + Painter.setBrush(QColor::fromRgbF(qreal(Colour->Value[0]), qreal(Colour->Value[1]), qreal(Colour->Value[2]))); + Painter.drawRect(0, 0, Image.width() - 1, Image.height() - 1); + Painter.end(); + + int const ColourCode = lcGetColorCode(ColorIndex); + mLineEditList[CTL_DEFAULT_COLOUR_EDIT]->setText(QString("%1 (%2)").arg(Colour->Name).arg(ColourCode)); + mLineEditList[CTL_DEFAULT_COLOUR_EDIT]->setProperty("ColorIndex", QVariant::fromValue(ColorIndex)); + mDefaultColourEditAction->setIcon(QPixmap::fromImage(Image)); + mDefaultColourEditAction->setToolTip(tr("Select Colour")); + + bool Change = mBlenderSettings[LBL_DEFAULT_COLOUR].value != QString::number(ColourCode); + Change |= SettingsModified(false); + emit SettingChangedSig(Change); +} + +void lcBlenderPreferences::BrowseBlender(bool) +{ + for (int LblIdx = 0; LblIdx < NumPaths(); ++LblIdx) + { + if (sender() == mPathBrowseButtonList.at(LblIdx)) + { + QString const BlenderPath = QDir::toNativeSeparators(mBlenderPaths[LblIdx].value).toLower(); + QFileDialog FileDialog(nullptr); + FileDialog.setWindowTitle(tr("Locate %1").arg(mBlenderPaths[LblIdx].label)); + if (LblIdx < PATH_LDRAW) + FileDialog.setFileMode(QFileDialog::ExistingFile); + else + FileDialog.setFileMode(QFileDialog::Directory); + if (!BlenderPath.isEmpty()) + FileDialog.setDirectory(QFileInfo(BlenderPath).absolutePath()); + if (FileDialog.exec()) + { + QStringList SelectedPathList = FileDialog.selectedFiles(); + if (SelectedPathList.size() == 1) + { + QFileInfo PathInfo(SelectedPathList.at(0)); + if (PathInfo.exists()) + { + QString const SelectedPath = QDir::toNativeSeparators(PathInfo.absoluteFilePath()).toLower(); + mPathLineEditList[LblIdx]->setText(SelectedPathList.at(0)); + if (LblIdx != PATH_BLENDER) + { + bool Change = false; + if (mImportMMActBox->isChecked()) + Change = QDir::toNativeSeparators(mBlenderSettingsMM[LblIdx].value).toLower() != SelectedPath; + else + Change = QDir::toNativeSeparators(mBlenderSettings[LblIdx].value).toLower() != SelectedPath; + Change |= SettingsModified(false); + emit SettingChangedSig(Change); + } + + if (LblIdx == PATH_BLENDER && BlenderPath != SelectedPath) + { + mBlenderPaths[LblIdx].value = SelectedPath; + UpdateBlenderAddon(); + } + } + } + } + } + } +} + +void lcBlenderPreferences::SizeChanged(const QString& Value) +{ + const bool ImportMM = mImportMMActBox->isChecked(); + const int Keep_Aspect_Ratio = ImportMM ? int(CTL_KEEP_ASPECT_RATIO_BOX_MM) : int(CTL_KEEP_ASPECT_RATIO_BOX); + const int Width_Edit = ImportMM ? int(CTL_RESOLUTION_WIDTH_EDIT) : int(CTL_IMAGE_WIDTH_EDIT); + const int Height_Edit = ImportMM ? int(CTL_RESOLUTION_HEIGHT_EDIT) : int(CTL_IMAGE_HEIGHT_EDIT); + BlenderSettings const* Settings = ImportMM ? mBlenderSettingsMM : mBlenderSettings; + + bool Change = false; + int NewValue = Value.toInt(); + if (mCheckBoxList[Keep_Aspect_Ratio]->isChecked()) + { + if (sender() == mLineEditList[Width_Edit]) + { + disconnect(mLineEditList[Height_Edit],SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + + QString const Height = QString::number(qRound(double(mImageHeight * NewValue / mImageWidth))); + mLineEditList[Height_Edit]->setText(Height); + + Change = Settings[Height_Edit].value != Height; + + connect(mLineEditList[Height_Edit],SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + } + else if (sender() == mLineEditList[Height_Edit]) + { + disconnect(mLineEditList[Width_Edit],SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + + QString const Width = QString::number(qRound(double(NewValue * mImageWidth / mImageHeight))); + mLineEditList[Width_Edit]->setText(Width); + + Change = Settings[Height_Edit].value != Width; + + connect(mLineEditList[Width_Edit],SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + } + + // Change is provided here for consistency only as ImageWidth, + // ImageHeight, and RenderPercentage are passed at the render command + Change |= SettingsModified(false); + emit SettingChangedSig(Change); + } +} + +void lcBlenderPreferences::SetModelSize(bool Update) +{ + const bool ImportMM = mImportMMActBox->isChecked(); + const int Crop_Image = ImportMM ? int(CTL_CROP_IMAGE_BOX_MM) : int(CTL_CROP_IMAGE_BOX); + const int Add_Environment = ImportMM ? int(CTL_ADD_ENVIRONMENT_BOX_MM) : int(CTL_ADD_ENVIRONMENT_BOX); + const int Trans_Background = ImportMM ? int(CTL_TRANSPARENT_BACKGROUND_BOX_MM) : int(CTL_TRANSPARENT_BACKGROUND_BOX); + const int Keep_Aspect_Ratio = ImportMM ? int(CTL_KEEP_ASPECT_RATIO_BOX_MM) : int(CTL_KEEP_ASPECT_RATIO_BOX); + const int Width_Edit = ImportMM ? int(CTL_RESOLUTION_WIDTH_EDIT) : int(CTL_IMAGE_WIDTH_EDIT); + const int Height_Edit = ImportMM ? int(CTL_RESOLUTION_HEIGHT_EDIT) : int(CTL_IMAGE_HEIGHT_EDIT); + const int Crop_Image_Label = ImportMM ? int(LBL_CROP_IMAGE_MM) : int(LBL_CROP_IMAGE); + + QString const CropImageLabel = mSettingLabelList[Crop_Image_Label]->text(); + + int ImageWidth = mImageWidth; + int ImageHeight = mImageHeight; + + const bool CropImage = mCheckBoxList[Crop_Image]->isChecked(); + + lcModel* Model = lcGetActiveProject()->GetActiveModel(); + if (Model) + { + struct NativeImage + { + QImage RenderedImage; + QRect Bounds; + }; + + std::vector Images; + Images.push_back(NativeImage()); + NativeImage& Image = Images.back(); + Image.RenderedImage = Model->GetStepImage(false, ImageWidth, ImageHeight, Model->GetCurrentStep()); + + auto CalculateImageBounds = [](NativeImage& Image) + { + QImage& RenderedImage = Image.RenderedImage; + int Width = RenderedImage.width(); + int Height = RenderedImage.height(); + + int MinX = Width; + int MinY = Height; + int MaxX = 0; + int MaxY = 0; + + for (int x = 0; x < Width; x++) + { + for (int y = 0; y < Height; y++) + { + if (qAlpha(RenderedImage.pixel(x, y))) + { + MinX = qMin(x, MinX); + MinY = qMin(y, MinY); + MaxX = qMax(x, MaxX); + MaxY = qMax(y, MaxY); + } + } + } + + Image.Bounds = QRect(QPoint(MinX, MinY), QPoint(MaxX, MaxY)); + }; + + QtConcurrent::blockingMap(Images, CalculateImageBounds); + + ImageWidth = Image.Bounds.width(); + ImageHeight = Image.Bounds.height(); + } + + mSettingLabelList[Crop_Image_Label]->setText(QString("%1 (%2 x %3)").arg(CropImageLabel).arg(ImageWidth).arg(ImageHeight)); + + if (CropImage) + { + bool Conflict[3]; + + if ((Conflict[1] = mCheckBoxList[Add_Environment]->isChecked())) + mCheckBoxList[Add_Environment]->setChecked(!CropImage); + if ((Conflict[2] = !mCheckBoxList[Trans_Background]->isChecked())) + mCheckBoxList[Trans_Background]->setChecked(CropImage); + if ((Conflict[0] = mCheckBoxList[Keep_Aspect_Ratio]->isChecked())) + mCheckBoxList[Keep_Aspect_Ratio]->setChecked(!CropImage); + + if (Conflict[0] || Conflict[1] || Conflict[2]) + { + QString const& Title = tr ("LDraw Render Settings Conflict"); + QString const& Header = "" + tr ("Crop image configuration settings conflict were resolved.") + ""; + QString const& Body = QString("%1%2%3") + .arg(Conflict[0] ? tr("Keep aspect ratio set to false.
") : "") + .arg(Conflict[1] ? tr("Add environment (backdrop and base plane) set to false.
") : "") + .arg(Conflict[2] ? tr("Transparent background set to true.
") : ""); + ShowMessage(Header, Title, Body, QString(), MBB_OK, QMessageBox::Information); + } + } + + disconnect(mLineEditList[Width_Edit],SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + disconnect(mLineEditList[Height_Edit],SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + + QString const Width = QString::number(CropImage ? ImageWidth : mImageWidth); + QString const Height = QString::number(CropImage ? ImageHeight : mImageHeight); + + mLineEditList[Width_Edit]->setText(Width); + mLineEditList[Height_Edit]->setText(Height); + + if (Update) + SettingsModified(true); + + connect(mLineEditList[Height_Edit],SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); + connect(mLineEditList[Width_Edit],SIGNAL(textChanged(const QString&)), this, SLOT (SizeChanged(const QString&))); +} + +void lcBlenderPreferences::ValidateColourScheme(int Index) +{ + QComboBox* ComboBox = qobject_cast(sender()); + if (!ComboBox) + return; + + const bool ImportMM = mImportMMActBox->isChecked(); + const int Color_Scheme = ImportMM ? int(LBL_COLOUR_SCHEME_MM) : int(LBL_COLOUR_SCHEME); + BlenderSettings* Settings = ImportMM ? mBlenderSettingsMM : mBlenderSettings; + + if (ComboBox->itemText(Index) == "custom" && mBlenderPaths[PATH_LDCONFIG].value.isEmpty() && lcGetProfileString(LC_PROFILE_COLOR_CONFIG).isEmpty()) + { + BlenderSettings const* defaultSettings = ImportMM ? mDefaultSettingsMM : mDefaultSettings; + Settings[Color_Scheme].value = defaultSettings[Color_Scheme].value; + + QString const& Title = tr ("Custom LDraw Colours"); + QString const& Header = "" + tr ("Colour scheme 'custom' cannot be enabled. Custom LDConfig file not found.") + ""; + QString const& Body = tr ("Colour scheme 'custom' selected but no LDConfig file was specified.
" + "The default colour scheme '%1' will be used.
") + .arg(Settings[Color_Scheme].value); + ShowMessage(Header, Title, Body, QString(), MBB_OK, QMessageBox::Warning); + } + else + { + bool Change = Settings[Color_Scheme].value != ComboBox->itemText(Index); + Change |= SettingsModified(false); + emit SettingChangedSig(Change); + } +} + +bool lcBlenderPreferences::PromptAccept() +{ + QString const& Title = tr ("Render Settings Modified"); + QString const& Header = "" + tr("Do you want to accept the modified settings before quitting ?") + ""; + int Exec = ShowMessage(Header, Title, QString(), QString(), MBB_YES_NO, QMessageBox::Question); + if (Exec == QMessageBox::Yes) + return true; + + return false; +} + +void lcBlenderPreferences::LoadDefaultParameters(QByteArray& Buffer, int Which) +{ + /* + # File: BlenderLDrawParameters.lst + # + # This config file captures parameters for the Blender LDraw Render addon + # Parameters must be prefixed using one of the following predefined + # 'Item' labels: + # - lgeo_colour + # - sloped_brick + # - light_brick + + # LGEO CUSTOM COLOURS + # LGEO is a parts library for rendering LEGO using the POV-Ray + # rendering software. This is the list of LEGO colours suitable + # for realistic rendering extracted from the LGEO file 'lg_color.inc'. + # When the 'Colour Scheme' option is set to 'Realistic', the standard + # LDraw colours RGB value is overwritten with the values defined here. + # Note: You can customize these RGB values as you want. + + # Item------- ID-- R-- G-- B-- + */ + const char DefaultCustomColours[] = + { + "lgeo_colour, 0, 33, 33, 33\n" + "lgeo_colour, 1, 13, 105, 171\n" + "lgeo_colour, 2, 40, 127, 70\n" + "lgeo_colour, 3, 0, 143, 155\n" + "lgeo_colour, 4, 196, 40, 27\n" + "lgeo_colour, 5, 205, 98, 152\n" + "lgeo_colour, 6, 98, 71, 50\n" + "lgeo_colour, 7, 161, 165, 162\n" + "lgeo_colour, 8, 109, 110, 108\n" + "lgeo_colour, 9, 180, 210, 227\n" + "lgeo_colour, 10, 75, 151, 74\n" + "lgeo_colour, 11, 85, 165, 175\n" + "lgeo_colour, 12, 242, 112, 94\n" + "lgeo_colour, 13, 252, 151, 172\n" + "lgeo_colour, 14, 245, 205, 47\n" + "lgeo_colour, 15, 242, 243, 242\n" + "lgeo_colour, 17, 194, 218, 184\n" + "lgeo_colour, 18, 249, 233, 153\n" + "lgeo_colour, 19, 215, 197, 153\n" + "lgeo_colour, 20, 193, 202, 222\n" + "lgeo_colour, 21, 224, 255, 176\n" + "lgeo_colour, 22, 107, 50, 123\n" + "lgeo_colour, 23, 35, 71, 139\n" + "lgeo_colour, 25, 218, 133, 64\n" + "lgeo_colour, 26, 146, 57, 120\n" + "lgeo_colour, 27, 164, 189, 70\n" + "lgeo_colour, 28, 149, 138, 115\n" + "lgeo_colour, 29, 228, 173, 200\n" + "lgeo_colour, 30, 172, 120, 186\n" + "lgeo_colour, 31, 225, 213, 237\n" + "lgeo_colour, 32, 0, 20, 20\n" + "lgeo_colour, 33, 123, 182, 232\n" + "lgeo_colour, 34, 132, 182, 141\n" + "lgeo_colour, 35, 217, 228, 167\n" + "lgeo_colour, 36, 205, 84, 75\n" + "lgeo_colour, 37, 228, 173, 200\n" + "lgeo_colour, 38, 255, 43, 0\n" + "lgeo_colour, 40, 166, 145, 130\n" + "lgeo_colour, 41, 170, 229, 255\n" + "lgeo_colour, 42, 198, 255, 0\n" + "lgeo_colour, 43, 193, 223, 240\n" + "lgeo_colour, 44, 150, 112, 159\n" + "lgeo_colour, 46, 247, 241, 141\n" + "lgeo_colour, 47, 252, 252, 252\n" + "lgeo_colour, 52, 156, 149, 199\n" + "lgeo_colour, 54, 255, 246, 123\n" + "lgeo_colour, 57, 226, 176, 96\n" + "lgeo_colour, 65, 236, 201, 53\n" + "lgeo_colour, 66, 202, 176, 0\n" + "lgeo_colour, 67, 255, 255, 255\n" + "lgeo_colour, 68, 243, 207, 155\n" + "lgeo_colour, 69, 142, 66, 133\n" + "lgeo_colour, 70, 105, 64, 39\n" + "lgeo_colour, 71, 163, 162, 164\n" + "lgeo_colour, 72, 99, 95, 97\n" + "lgeo_colour, 73, 110, 153, 201\n" + "lgeo_colour, 74, 161, 196, 139\n" + "lgeo_colour, 77, 220, 144, 149\n" + "lgeo_colour, 78, 246, 215, 179\n" + "lgeo_colour, 79, 255, 255, 255\n" + "lgeo_colour, 80, 140, 140, 140\n" + "lgeo_colour, 82, 219, 172, 52\n" + "lgeo_colour, 84, 170, 125, 85\n" + "lgeo_colour, 85, 52, 43, 117\n" + "lgeo_colour, 86, 124, 92, 69\n" + "lgeo_colour, 89, 155, 178, 239\n" + "lgeo_colour, 92, 204, 142, 104\n" + "lgeo_colour, 100, 238, 196, 182\n" + "lgeo_colour, 115, 199, 210, 60\n" + "lgeo_colour, 134, 174, 122, 89\n" + "lgeo_colour, 135, 171, 173, 172\n" + "lgeo_colour, 137, 106, 122, 150\n" + "lgeo_colour, 142, 220, 188, 129\n" + "lgeo_colour, 148, 62, 60, 57\n" + "lgeo_colour, 151, 14, 94, 77\n" + "lgeo_colour, 179, 160, 160, 160\n" + "lgeo_colour, 183, 242, 243, 242\n" + "lgeo_colour, 191, 248, 187, 61\n" + "lgeo_colour, 212, 159, 195, 233\n" + "lgeo_colour, 216, 143, 76, 42\n" + "lgeo_colour, 226, 253, 234, 140\n" + "lgeo_colour, 232, 125, 187, 221\n" + "lgeo_colour, 256, 33, 33, 33\n" + "lgeo_colour, 272, 32, 58, 86\n" + "lgeo_colour, 273, 13, 105, 171\n" + "lgeo_colour, 288, 39, 70, 44\n" + "lgeo_colour, 294, 189, 198, 173\n" + "lgeo_colour, 297, 170, 127, 46\n" + "lgeo_colour, 308, 53, 33, 0\n" + "lgeo_colour, 313, 171, 217, 255\n" + "lgeo_colour, 320, 123, 46, 47\n" + "lgeo_colour, 321, 70, 155, 195\n" + "lgeo_colour, 322, 104, 195, 226\n" + "lgeo_colour, 323, 211, 242, 234\n" + "lgeo_colour, 324, 196, 0, 38\n" + "lgeo_colour, 326, 226, 249, 154\n" + "lgeo_colour, 330, 119, 119, 78\n" + "lgeo_colour, 334, 187, 165, 61\n" + "lgeo_colour, 335, 149, 121, 118\n" + "lgeo_colour, 366, 209, 131, 4\n" + "lgeo_colour, 373, 135, 124, 144\n" + "lgeo_colour, 375, 193, 194, 193\n" + "lgeo_colour, 378, 120, 144, 129\n" + "lgeo_colour, 379, 94, 116, 140\n" + "lgeo_colour, 383, 224, 224, 224\n" + "lgeo_colour, 406, 0, 29, 104\n" + "lgeo_colour, 449, 129, 0, 123\n" + "lgeo_colour, 450, 203, 132, 66\n" + "lgeo_colour, 462, 226, 155, 63\n" + "lgeo_colour, 484, 160, 95, 52\n" + "lgeo_colour, 490, 215, 240, 0\n" + "lgeo_colour, 493, 101, 103, 97\n" + "lgeo_colour, 494, 208, 208, 208\n" + "lgeo_colour, 496, 163, 162, 164\n" + "lgeo_colour, 503, 199, 193, 183\n" + "lgeo_colour, 504, 137, 135, 136\n" + "lgeo_colour, 511, 250, 250, 250\n" + }; + + /* + # SLOPED BRICKS + # Dictionary with part number (without any extension for decorations), as key, + # of pieces that have grainy slopes, and, as values, a set containing the angles (in + # degrees) of the face's normal to the horizontal plane. Use a | delimited tuple to + # represent a range within which the angle must lie. + + # Item-------- PartID- Angle/Angle Range (in degrees) + */ + const char DefaultSlopedBricks[] = + { + "sloped_brick, 962, 45\n" + "sloped_brick, 2341, -45\n" + "sloped_brick, 2449, -16\n" + "sloped_brick, 2875, 45\n" + "sloped_brick, 2876, 40|63\n" + "sloped_brick, 3037, 45\n" + "sloped_brick, 3038, 45\n" + "sloped_brick, 3039, 45\n" + "sloped_brick, 3040, 45\n" + "sloped_brick, 3041, 45\n" + "sloped_brick, 3042, 45\n" + "sloped_brick, 3043, 45\n" + "sloped_brick, 3044, 45\n" + "sloped_brick, 3045, 45\n" + "sloped_brick, 3046, 45\n" + "sloped_brick, 3048, 45\n" + "sloped_brick, 3049, 45\n" + "sloped_brick, 3135, 45\n" + "sloped_brick, 3297, 63\n" + "sloped_brick, 3298, 63\n" + "sloped_brick, 3299, 63\n" + "sloped_brick, 3300, 63\n" + "sloped_brick, 3660, -45\n" + "sloped_brick, 3665, -45\n" + "sloped_brick, 3675, 63\n" + "sloped_brick, 3676, -45\n" + "sloped_brick, 3678b, 24\n" + "sloped_brick, 3684, 15\n" + "sloped_brick, 3685, 16\n" + "sloped_brick, 3688, 15\n" + "sloped_brick, 3747, -63\n" + "sloped_brick, 4089, -63\n" + "sloped_brick, 4161, 63\n" + "sloped_brick, 4286, 63\n" + "sloped_brick, 4287, -63\n" + "sloped_brick, 4445, 45\n" + "sloped_brick, 4460, 16\n" + "sloped_brick, 4509, 63\n" + "sloped_brick, 4854, -45\n" + "sloped_brick, 4856, -60|-70, -45\n" + "sloped_brick, 4857, 45\n" + "sloped_brick, 4858, 72\n" + "sloped_brick, 4861, 45, 63\n" + "sloped_brick, 4871, -45\n" + "sloped_brick, 4885, 72\n" + "sloped_brick, 6069, 72, 45\n" + "sloped_brick, 6153, 60|70, 26|4\n" + "sloped_brick, 6227, 45\n" + "sloped_brick, 6270, 45\n" + "sloped_brick, 13269, 40|63\n" + "sloped_brick, 13548, 45\n" + "sloped_brick, 15571, 45\n" + "sloped_brick, 18759, -45\n" + "sloped_brick, 22390, 40|55\n" + "sloped_brick, 22391, 40|55\n" + "sloped_brick, 22889, -45\n" + "sloped_brick, 28192, 45\n" + "sloped_brick, 30180, 47\n" + "sloped_brick, 30182, 45\n" + "sloped_brick, 30183, -45\n" + "sloped_brick, 30249, 35\n" + "sloped_brick, 30283, -45\n" + "sloped_brick, 30363, 72\n" + "sloped_brick, 30373, -24\n" + "sloped_brick, 30382, 11, 45\n" + "sloped_brick, 30390, -45\n" + "sloped_brick, 30499, 16\n" + "sloped_brick, 32083, 45\n" + "sloped_brick, 43708, 72\n" + "sloped_brick, 43710, 72, 45\n" + "sloped_brick, 43711, 72, 45\n" + "sloped_brick, 47759, 40|63\n" + "sloped_brick, 52501, -45\n" + "sloped_brick, 60219, -45\n" + "sloped_brick, 60477, 72\n" + "sloped_brick, 60481, 24\n" + "sloped_brick, 63341, 45\n" + "sloped_brick, 72454, -45\n" + "sloped_brick, 92946, 45\n" + "sloped_brick, 93348, 72\n" + "sloped_brick, 95188, 65\n" + "sloped_brick, 99301, 63\n" + "sloped_brick, 303923, 45\n" + "sloped_brick, 303926, 45\n" + "sloped_brick, 304826, 45\n" + "sloped_brick, 329826, 64\n" + "sloped_brick, 374726, -64\n" + "sloped_brick, 428621, 64\n" + "sloped_brick, 4162628, 17\n" + "sloped_brick, 4195004, 45\n" + }; + + /* + # LIGHTED BRICKS + # Dictionary with part number (with extension), as key, + # of lighted bricks, light emission colour and intensity, as values. + + # Item--------- PartID--- Light-------------- Intensity + */ + const char DefaultLightedBricks[] = + { + "lighted_brick, 62930.dat, 1.000, 0.373, 0.059, 1.0\n" + "lighted_brick, 54869.dat, 1.000, 0.052, 0.017, 1.0\n" + }; + + Buffer.clear(); + if (Which == PARAMS_CUSTOM_COLOURS) + Buffer.append(DefaultCustomColours, sizeof(DefaultCustomColours)); + else if (Which == PARAMS_SLOPED_BRICKS) + Buffer.append(DefaultSlopedBricks, sizeof(DefaultSlopedBricks)); + else if (Which == PARAMS_LIGHTED_BRICKS) + Buffer.append(DefaultLightedBricks, sizeof(DefaultLightedBricks)); +} + +bool lcBlenderPreferences::ExportParameterFile() +{ + QString const BlenderConfigDir = QString("%1/Blender/setup/addon_setup/config").arg(gAddonPreferences->mDataDir); + QString const ParameterFile = QString("%1/%2").arg(BlenderConfigDir).arg(LC_BLENDER_ADDON_PARAMS_FILE); + QFile File(ParameterFile); + + if (!OverwriteFile(File.fileName())) + return true; + + QString Message; + if(File.open(QIODevice::WriteOnly | QIODevice::Text)) + { + int Counter = 1; + QByteArray Buffer; + QTextStream Stream(&File); + Stream << "# File: " << QFileInfo(ParameterFile).fileName() << LineEnding; + Stream << "" << LineEnding; + Stream << "# This config file captures parameters for the Blender LDraw Render addon" << LineEnding; + Stream << "# Parameters must be prefixed using one of the following predefined" << LineEnding; + Stream << "# 'Item' labels:" << LineEnding; + Stream << "# - lgeo_colour" << LineEnding; + Stream << "# - sloped_brick" << LineEnding; + Stream << "# - light_brick" << LineEnding; + Stream << "" << LineEnding; + Stream << "# All items must use a comma',' delimiter as the primary delimiter." << LineEnding; + Stream << "# For sloped_brick items, a pipe'|' delimiter must be used to specify" << LineEnding; + Stream << "# a range (min|max) within which the angle must lie when appropriate." << LineEnding; + Stream << "# Spaces between item attributes are not required and are used to" << LineEnding; + Stream << "# facilitate human readability." << LineEnding; + Stream << "" << LineEnding; + Stream << "" << LineEnding; + Stream << "# LGEO CUSTOM COLOURS" << LineEnding; + Stream << "# LGEO is a parts library for rendering LEGO using the POV-Ray" << LineEnding; + Stream << "# rendering software. This is the list of LEGO colours suitable" << LineEnding; + Stream << "# for realistic rendering extracted from the LGEO file 'lg_color.inc'." << LineEnding; + Stream << "# When the 'Colour Scheme' option is set to 'Realistic', the standard" << LineEnding; + Stream << "# LDraw colours RGB value is overwritten with the values defined here." << LineEnding; + Stream << "# Note: You can customize these RGB values as you want." << LineEnding; + Stream << "" << LineEnding; + Stream << "# Item----- ID- R-- G-- B--" << LineEnding; + + LoadDefaultParameters(Buffer, PARAMS_CUSTOM_COLOURS); + QTextStream colourstream(Buffer); + for (QString Line = colourstream.readLine(); !Line.isNull(); Line = colourstream.readLine()) + { + Stream << Line << LineEnding; + Counter++; + } + + Stream << "" << LineEnding; + Stream << "# SLOPED BRICKS" << LineEnding; + Stream << "# Dictionary with part number (without any extension for decorations), as key," << LineEnding; + Stream << "# of pieces that have grainy slopes, and, as values, a set containing the angles (in" << LineEnding; + Stream << "# degrees) of the face's normal to the horizontal plane. Use a | delimited tuple to" << LineEnding; + Stream << "# represent a range within which the angle must lie." << LineEnding; + Stream << "" << LineEnding; + Stream << "# Item------ PartID- Angle/Angle Range (in degrees)" << LineEnding; + + LoadDefaultParameters(Buffer, PARAMS_SLOPED_BRICKS); + QTextStream SlopedStream(Buffer); + for (QString Line = SlopedStream.readLine(); !Line.isNull(); Line = SlopedStream.readLine()) + { + Stream << Line << LineEnding; + Counter++; + } + + Stream << "" << LineEnding; + Stream << "# LIGHTED BRICKS" << LineEnding; + Stream << "# Dictionary with part number (with extension), as key," << LineEnding; + Stream << "# of lighted bricks, light emission colour and intensity, as values." << LineEnding; + Stream << "" << LineEnding; + Stream << "# Item------- PartID--- Light-------------- Intensity" << LineEnding; + + LoadDefaultParameters(Buffer, PARAMS_LIGHTED_BRICKS); + QTextStream lightedstream(Buffer); + for (QString Line = lightedstream.readLine(); !Line.isNull(); Line = lightedstream.readLine()) + { + Stream << Line << LineEnding; + Counter++; + } + + Stream << "" << LineEnding; + Stream << "# end of parameters" << LineEnding; + + File.close(); + Message = tr("Finished writing Blender parameter entries. Processed %1 lines in file [%2].") + .arg(Counter) + .arg(File.fileName()); + } + else + { + Message = tr("Failed to open Blender parameter file: %1:
%2") + .arg(File.fileName()) + .arg(File.errorString()); + ShowMessage(Message); + return false; + } + return true; +} + +bool lcBlenderPreferences::OverwriteFile(const QString& File) +{ + QFileInfo fileInfo(File); + + if (!fileInfo.exists()) + return true; + + QString const& Title = tr ("Replace Existing File"); + QString const Header = "" + QMessageBox::tr ("Existing file %1 detected.").arg(fileInfo.fileName()) + ""; + QString const Body = QMessageBox::tr ("\"%1\"
This file already exists.
Replace existing file?").arg(fileInfo.fileName()); + int Exec = ShowMessage(Header, Title, Body, QString(), MBB_YES, QMessageBox::NoIcon); + + return (Exec == QMessageBox::Yes); +} + +int lcBlenderPreferences::ShowMessage(QString const& Header, QString const& Title, QString const& Body, const QString& Detail, int const Buttons, int const Icon) +{ + if (!gMainWindow) + return QMessageBox::Ok; + + QMessageBox Box; + Box.setWindowIcon(QIcon()); + if (!Icon) + { + QPixmap Pixmap = QPixmap(":/resources/leocad.png"); + Box.setIconPixmap (Pixmap); + } + else + Box.setIcon (static_cast(Icon)); + + Box.setTextFormat (Qt::RichText); + Box.setWindowFlags (Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint); + if (!Title.isEmpty()) + Box.setWindowTitle(Title); + else + Box.setWindowTitle(tr("%1 Blender LDraw Addon").arg(LC_PRODUCTNAME_STR)); + + Box.setText (Header); + if (!Body.isEmpty()) + Box.setInformativeText (Body); + + if (!Detail.isEmpty()) + Box.setDetailedText(QString(Detail).replace("
", "\n")); + + switch (Buttons) + { + case MBB_YES: + Box.setStandardButtons (QMessageBox::Yes | QMessageBox::Cancel); + Box.setDefaultButton (QMessageBox::Yes); + break; + case MBB_YES_NO: + Box.setStandardButtons (QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + Box.setDefaultButton (QMessageBox::Yes); + break; + default: + Box.setStandardButtons (QMessageBox::Ok); + break; + } + + int MinimumWidth = 400; + int FontWidth = QFontMetrics(Box.font()).averageCharWidth(); + int FixedTextLength = (MinimumWidth / FontWidth); + if (Header.length() < Body.length() && Header.length() < FixedTextLength) + { + QGridLayout* BoxLayout = (QGridLayout*)Box.layout(); + QLayoutItem* BoxLayoutItem = BoxLayout->itemAtPosition(0, 2); + QWidget* TextWidget = BoxLayoutItem->widget(); + if (TextWidget) + { + int FixedWidth = Body.length() * FontWidth; + if (FixedWidth == MinimumWidth) + { + int Index = (MinimumWidth / FontWidth) - 1; + if (!Body.midRef(Index,1).isEmpty()) + FixedWidth = Body.indexOf(" ", Index); + } + else if (FixedWidth < MinimumWidth) + FixedWidth = MinimumWidth; + TextWidget->setFixedWidth(FixedWidth); + } + } + + return Box.exec(); +} + +// TODO - Refactor, use LeoCAD mHttpReply/HttpManager +void lcBlenderPreferences::DownloadFile(QString URL, QString Title, bool PromptRedirect, bool ShowProgress) +{ + gAddonPreferences->mTitle = Title; + gAddonPreferences->mShowProgress = ShowProgress; + gAddonPreferences->mPromptRedirect = PromptRedirect; + + if (gAddonPreferences->mShowProgress) + { + gAddonPreferences->mProgressDialog = new QProgressDialog(nullptr); + connect(gAddonPreferences->mProgressDialog, SIGNAL(canceled()), gAddonPreferences, SLOT(cancelDownload())); + + gAddonPreferences->mProgressDialog->setWindowTitle(tr("Downloading")); + gAddonPreferences->mProgressDialog->setLabelText(tr("Downloading %1").arg(gAddonPreferences->mTitle)); + gAddonPreferences->mProgressDialog->show(); + } + + gAddonPreferences->mHttpManager = new QNetworkAccessManager(gAddonPreferences); + + gAddonPreferences->mUrl = URL; + + gAddonPreferences->mHttpRequestAborted = false; + + gAddonPreferences->StartRequest(gAddonPreferences->mUrl); + + while (gAddonPreferences->mHttpReply) + QApplication::processEvents(); +} + +QByteArray lcBlenderPreferences::GetDownloadedFile() +{ + return gAddonPreferences->mByteArray; +} + +void lcBlenderPreferences::StartRequest(QUrl Url) +{ + QNetworkRequest Request(Url); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + if (!mPromptRedirect) + Request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, true); +#else + if (!mPromptRedirect) + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); +#endif +#endif + mHttpReply = mHttpManager->get(Request); + + connect(mHttpReply, SIGNAL(downloadProgress(qint64,qint64)), + this, SLOT(UpdateDownloadProgress(qint64,qint64))); + + connect(mHttpReply, SIGNAL(finished()), + this, SLOT(HttpDownloadFinished())); +} + +void lcBlenderPreferences::HttpDownloadFinished() +{ + if (mHttpRequestAborted) + { + mByteArray.clear(); + mHttpReply->deleteLater(); + mHttpReply = nullptr; + if (mShowProgress) + mProgressDialog->close(); + return; + } + + QString Message; + QString const MessageTitle = tr("%1 Download").arg(LC_PRODUCTNAME_STR); + QVariant RedirectionTarget = mHttpReply->attribute(QNetworkRequest::RedirectionTargetAttribute); + if (mHttpReply->error()) + { + mByteArray.clear(); + Message = tr("%1 download failed: %2.") + .arg(mTitle).arg(mHttpReply->errorString()); + + if (Message.endsWith("not found.")) + Message.append(tr("
Your internet connection may be interrupted.")); + + if (gMainWindow) + ShowMessage(Message, MessageTitle, QString(), QString(), MBB_OK, QMessageBox::Warning); + + } + else if (!RedirectionTarget.isNull()) + { + // This block should only trigger for redirects when Qt + // is less than 5.6.0 or if prompt redirect set to True + QUrl NewUrl = mUrl.resolved(RedirectionTarget.toUrl()); + bool ProceedToRedirect = true; + + if (mPromptRedirect && gMainWindow) + ProceedToRedirect = ShowMessage(tr("Download redirect to %1 ?").arg(NewUrl.toString()), + MessageTitle, QString(), QString(), MBB_YES_NO, QMessageBox::Question) == QMessageBox::Yes; + + if (ProceedToRedirect) + { + mUrl = NewUrl; + mHttpReply->deleteLater(); + mByteArray.resize(0); + StartRequest(mUrl); + return; + } + } + else + mByteArray = mHttpReply->readAll(); + + if (mShowProgress) + mProgressDialog->close(); + + mHttpReply->deleteLater(); + mHttpReply = nullptr; + mHttpManager = nullptr; +} + +void lcBlenderPreferences::CancelDownload() +{ + mHttpRequestAborted = true; + mHttpReply->abort(); +} + +void lcBlenderPreferences::UpdateDownloadProgress(qint64 BytesRead, qint64 BotalBytes) +{ + if (mHttpRequestAborted) + return; + + if (mShowProgress) + { + mProgressDialog->setMaximum(int(BotalBytes)); + mProgressDialog->setValue(int(BytesRead)); + } +} diff --git a/common/lc_blenderpreferences.h b/common/lc_blenderpreferences.h new file mode 100644 index 00000000..4d11448d --- /dev/null +++ b/common/lc_blenderpreferences.h @@ -0,0 +1,484 @@ +#pragma once + +#include +#include +#include "qtimer.h" + +#define LC_UNIX_SHELL "/bin/sh" +#define LC_WINDOWS_SHELL "cmd.exe" +#define LC_BLENDER_ADDON_BLEND_FILE "leocad.blend" +#define LC_BLENDER_ADDON_IMPORT_MODULE "TN" +#define LC_BLENDER_ADDON_FOLDER_STR "io_scene_render_ldraw" + +class QFormLayout; +class QGridLayout; +class QLabel; +class QGroupBox; +class QLineEdit; +class QCheckBox; +class QComboBox; +class QPushButton; +class QScrollArea; +class QProgressBar; +class QProcess; +class QTimer; + +class QNetworkAccessManager; +class QNetworkReply; +class lcBlenderPreferences; + +class lcBlenderPreferencesDialog : public QDialog +{ + Q_OBJECT +public: + + explicit lcBlenderPreferencesDialog(int Width, int Height, double Scale, QWidget* Parent = nullptr); + + virtual ~lcBlenderPreferencesDialog(); + + static bool GetBlenderPreferences(int& Width, int& Height, double& Scale, QWidget* Parent = nullptr); + +public slots: + void ShowPathsGroup(); + void ResetSettings(); + void EnableButton(bool); + void accept() override; + void reject() override; + +private: + QPushButton* mApplyButton; + QPushButton* mResetButton; + QPushButton* mPathsButton; + lcBlenderPreferences* mPreferences; +}; + +class lcBlenderPreferences : public QWidget +{ + Q_OBJECT +public: + + explicit lcBlenderPreferences(int Width, int Height, double Scale, QWidget* Parent = nullptr); + + ~lcBlenderPreferences(); + + static void LoadSettings(); + static void SaveSettings(); + static bool ExportParameterFile(); + static bool SettingsModified(bool Update = true, QString const& Module = QString()); + static int ShowMessage(QString const& Header, QString const& Title = QString(), QString const& Body = QString(), QString const& Detail = QString(), const int Buttons = MBB_OK, const int Icon = 3 /*QMessageBox::Critical*/); + + void Apply(const int Response = QDialog::Accepted); + + int mImageWidth; + int mImageHeight; + qreal mScale; + +protected: + static int NumPaths(bool DefaultSettings = false); + static int NumSettings(bool DefaultSettings = false); + static int NumSettingsMM(bool DefaultSettings = false); + static bool GetBlenderAddon(const QString& BlenderDir); + static bool ExtractBlenderAddon(const QString& BlenderDir); + static void LoadDefaultParameters(QByteArray& Buffer, int Which); + static bool OverwriteFile(const QString& File); + + // TODO - Refactor, use LeoCAD mHttpReply/HttpManager + static void DownloadFile(QString URL, QString Title, bool PromptRedirect = false, bool ShowProgress = true); + void StartRequest(QUrl Url); + static QByteArray GetDownloadedFile(); + + QString ReadStdErr(bool& Error) const; + void ClearGroupBox(QGroupBox* GroupBox); + void InitPathsAndSettings(); + void InitPathsAndSettingsMM(); + +signals: + void SettingChangedSig(bool); + +public slots: + void ResetSettings(); + void ShowPathsGroup(); + bool PromptCancel(); + +private slots: + void BrowseBlender(bool); + void ConfigureBlenderAddon(bool = false, bool = false, bool = false); + void UpdateBlenderAddon(); + void EnableImportModule(); + void SizeChanged(const QString&); + void SetDefaultColor(int); + void ColorButtonClicked(bool); + void SetModelSize(bool = false); + void ValidateColourScheme(int); + void SettingChanged(int = -1); + void SettingChanged(const QString&); + void PathChanged(); + bool PromptAccept(); + void Update(); + void ReadStdOut(); + void ReadStdOut(const QString&, QString&); + void WriteStdOut(); + void GetStandardOutput(); + void ShowResult(); + void StatusUpdate(bool, bool = true, const QString& = QString()); + + void HttpDownloadFinished(); + void CancelDownload(); + void UpdateDownloadProgress(qint64, qint64); + void FileLoaded(bool b) + { + mFileLoaded = true; + mFileLoadFail = !b; + } + +private: + enum BlenderPathType + { + PATH_BLENDER, // 0 QLineEdit/QPushButton + PATH_BLENDFILE, // 1 QLineEdit/QPushButton + PATH_ENVIRONMENT, // 2 QLineEdit/QPushButton + PATH_LDCONFIG, // 3 QLineEdit/QPushButton + PATH_LDRAW, // 4 QLineEdit/QPushButton + PATH_LSYNTH, // 5 QLineEdit/QPushButton + PATH_STUD_LOGO, // 6 QLineEdit/QPushButton + PATH_STUDIO_LDRAW, // 7 QLineEdit/QPushButton + + NUM_PATHS + }; + + enum BlenderLabelType + { + LBL_ADD_ENVIRONMENT, // 0 QCheckBox + DEFAULT_SETTINGS, // 1 + LBL_ADD_GAPS = DEFAULT_SETTINGS, // 1 QCheckBox + LBL_BEVEL_EDGES, // 2 QCheckBox + LBL_BLENDFILE_TRUSTED, // 3 QCheckBox + LBL_CROP_IMAGE, // 4 QCheckBox + LBL_CURVED_WALLS, // 5 QCheckBox + LBL_FLATTEN_HIERARCHY, // 6 QCheckBox + NUM_COMBO_ITEMS, // 7 + LBL_IMPORT_CAMERAS = NUM_COMBO_ITEMS, // 7 QCheckBox + LBL_IMPORT_LIGHTS, // 8 QCheckBox + LBL_INSTANCE_STUDS, // 9 QCheckBox + LBL_KEEP_ASPECT_RATIO, // 10 QCheckBox + LBL_LINK_PARTS, // 11 QCheckBox + LBL_MINIFIG_HIERARCHY, // 12 QCheckBox + LBL_NUMBER_NODES, // 13 QCheckBox + LBL_OVERWRITE_IMAGE, // 14 QCheckBox + LBL_OVERWRITE_MATERIALS, // 15 QCheckBox + LBL_OVERWRITE_MESHES, // 16 QCheckBox + LBL_POSITION_CAMERA, // 17 QCheckBox + LBL_REMOVE_DOUBLES, // 18 QCheckBox + LBL_RENDER_WINDOW, // 19 QCheckBox + LBL_USE_ARCHIVE_LIBS, // 20 QChekcBox + LBL_SEARCH_ADDL_PATHS, // 21 QCheckBox + LBL_SMOOTH_SHADING, // 22 QCheckBox + LBL_TRANSPARENT_BACKGROUND, // 23 QCheckBox + LBL_UNOFFICIAL_PARTS, // 24 QCheckBox + LBL_USE_LOGO_STUDS, // 25 QCheckBox + LBL_VERBOSE, // 26 QCheckBox + + LBL_BEVEL_WIDTH, // 27/0 QLineEdit + LBL_CAMERA_BORDER_PERCENT, // 28/1 QLineEdit + LBL_DEFAULT_COLOUR, // 29/2 QLineEdit + LBL_GAPS_SIZE, // 30/3 QLineEdit + LBL_IMAGE_WIDTH, // 31/4 QLineEdit + LBL_IMAGE_HEIGHT, // 32/5 QLineEdit + LBL_IMAGE_SCALE, // 33/6 QLineEdit + LBL_RENDER_PERCENTAGE, // 34/7 QLineEdit + + LBL_COLOUR_SCHEME, // 35/0 QComboBox + LBL_FLEX_PARTS_SOURCE, // 36/1 QComboBox + LBL_LOGO_STUD_VERSION, // 27/2 QComboBox + LBL_LOOK, // 38/3 QComboBox + LBL_POSITION_OBJECT, // 39/4 QComboBox + LBL_RESOLUTION, // 40/5 QComboBox + LBL_RESOLVE_NORMALS, // 41/6 QComboBox + + NUM_SETTINGS + }; + + enum BlenderControlType + { + CTL_BLENDER_PATH_EDIT, // 0 + CTL_ADD_ENVIRONMENT_BOX = CTL_BLENDER_PATH_EDIT, // 0 + CTL_BEVEL_WIDTH_EDIT = CTL_BLENDER_PATH_EDIT, // 0 + CTL_COLOUR_SCHEME_COMBO = CTL_BLENDER_PATH_EDIT, // 0 + CTL_ADD_GAPS_BOX, // 1 + CTL_CAMERA_BORDER_PERCENT_EDIT = CTL_ADD_GAPS_BOX, // 1 + CTL_FLEX_PARTS_SOURCE_COMBO = CTL_ADD_GAPS_BOX, // 1 + CTL_BEVEL_EDGES_BOX, // 2 + CTL_DEFAULT_COLOUR_EDIT = CTL_BEVEL_EDGES_BOX, // 2 + CTL_LOGO_STUD_VERSION_COMBO = CTL_BEVEL_EDGES_BOX, // 2 + CTL_BLENDFILE_TRUSTED_BOX, // 3 + CTL_GAPS_SIZE_EDIT = CTL_BLENDFILE_TRUSTED_BOX, // 3 + CTL_LOOK_COMBO = CTL_BLENDFILE_TRUSTED_BOX, // 3 + CTL_CROP_IMAGE_BOX, // 4 + CTL_IMAGE_WIDTH_EDIT = CTL_CROP_IMAGE_BOX, // 4 + CTL_POSITION_OBJECT_COMBO = CTL_CROP_IMAGE_BOX, // 4 + CTL_CURVED_WALLS_BOX, // 5 + CTL_IMAGE_HEIGHT_EDIT = CTL_CURVED_WALLS_BOX, // 5 + CTL_RESOLUTION_COMBO = CTL_CURVED_WALLS_BOX, // 5 + CTL_FLATTEN_HIERARCHY_BOX, // 6 + CTL_IMAGE_SCALE_EDIT = CTL_FLATTEN_HIERARCHY_BOX, // 6 + CTL_RESOLVE_NORMALS_COMBO = CTL_FLATTEN_HIERARCHY_BOX, // 6 + CTL_IMPORT_CAMERAS_BOX, // 7 + CTL_RENDER_PERCENTAGE_EDIT = CTL_IMPORT_CAMERAS_BOX, // 7 + CTL_IMPORT_LIGHTS_BOX, // 8 + CTL_INSTANCE_STUDS_BOX, // 9 + CTL_KEEP_ASPECT_RATIO_BOX, // 10 + CTL_LINK_PARTS_BOX, // 11 + CTL_MINIFIG_HIERARCHY_BOX, // 12 + CTL_NUMBER_NODES_BOX, // 13 + CTL_OVERWRITE_IMAGE_BOX, // 14 + CTL_OVERWRITE_MATERIALS_BOX, // 15 + CTL_OVERWRITE_MESHES_BOX, // 16 + CTL_POSITION_CAMERA_BOX, // 17 + CTL_REMOVE_DOUBLES_BOX, // 18 + CTL_RENDER_WINDOW_BOX, // 19 + CTL_USE_ARCHIVE_LIBS_BOX, // 20 + CTL_SEARCH_ADDL_PATHS_BOX, // 21 + CTL_SMOOTH_SHADING_BOX, // 22 + CTL_TRANSPARENT_BACKGROUND_BOX, // 23 + CTL_UNOFFICIAL_PARTS_BOX, // 24 + CTL_USE_LOGO_STUDS_BOX, // 25 + CTL_VERBOSE_BOX, // 26 + }; + + enum BlenderLabelTypeMM + { + LBL_ADD_ENVIRONMENT_MM, // 0 QCheckBox + LBL_BEVEL_EDGES_MM, // 1 QCheckBox + LBL_BLEND_FILE_TRUSTED_MM, // 2 QCheckBox + LBL_CROP_IMAGE_MM, // 3 QCheckBox + LBL_DISPLAY_LOGO, // 4 QCheckBox + LBL_IMPORT_CAMERAS_MM, // 5 QCheckBox + LBL_IMPORT_EDGES, // 6 QCheckBox + NUM_COMBO_ITEMS_MM, // 7 + LBL_IMPORT_LIGHTS_MM = NUM_COMBO_ITEMS_MM, // 7 QCheckBox + LBL_KEEP_ASPECT_RATIO_MM, // 8 QCheckBox + LBL_MAKE_GAPS, // 9 QCheckBox + LBL_META_BFC, // 10 QCheckBox + LBL_META_CLEAR, // 11 QCheckBox + LBL_META_GROUP, // 12 QCheckBox + LBL_META_PAUSE, // 13 QCheckBox + LBL_META_PRINT_WRITE, // 14 QCheckBox + LBL_META_SAVE, // 15 QCheckBox + LBL_META_STEP, // 16 QCheckBox + LBL_META_STEP_GROUPS, // 17 QCheckBox + LBL_META_TEXMAP, // 18 QCheckBox + LBL_NO_STUDS, // 19 QCheckBox + LBL_OVERWRITE_IMAGE_MM, // 20 QCheckBox + LBL_POSITION_CAMERA_MM, // 21 QCheckBox + LBL_PARENT_TO_EMPTY, // 22 QCheckBox + LBL_PREFER_STUDIO, // 23 QCheckBox + LBL_PREFER_UNOFFICIAL, // 24 QCheckBox + LBL_PRESERVE_HIERARCHY, // 25 QCheckBox + LBL_PROFILE, // 26 QCheckBox + LBL_RECALCULATE_NORMALS, // 27 QCheckBox + LBL_REMOVE_DOUBLES_MM, // 28 QCheckBox + LBL_RENDER_WINDOW_MM, // 29 QCheckBox + LBL_SEARCH_ADDL_PATHS_MM, // 30 QCheckBox + LBL_SETEND_FRAME, // 31 QCheckBox + LBL_SET_TIMELINE_MARKERS, // 32 QCheckBox + LBL_SHADE_SMOOTH, // 33 QCheckBox + LBL_TRANSPARENT_BACKGROUND_MM, // 34 QCheckBox + LBL_TREAT_MODELS_WITH_SUBPARTS_AS_PARTS, // 35 QCheckBox + LBL_TREAT_SHORTCUT_AS_MODEL, // 36 QCheckBox + LBL_TRIANGULATE, // 37 QCheckBox + LBL_USE_ARCHIVE_LIBRARY_MM, // 38 QCheckBox + LBL_USE_FREESTYLE_EDGES, // 39 QCheckBox + LBL_VERBOSE_MM, // 40 QCheckBox + + LBL_BEVEL_SEGMENTS, // 41/ 0 QLineEdit + LBL_BEVEL_WEIGHT, // 42/ 1 QLineEdit + LBL_BEVEL_WIDTH_MM, // 43/ 2 QLineEdit + LBL_CAMERA_BORDER_PERCENT_MM, // 44/ 3 QLineEdit + LBL_FRAMES_PER_STEP, // 45/ 4 QLineEdit + LBL_GAP_SCALE, // 46/ 5 QLineEdit + LBL_IMPORT_SCALE, // 47/ 6 QLineEdit + LBL_MERGE_DISTANCE, // 48/ 7 QLineEdit + LBL_RENDER_PERCENTAGE_MM, // 49/ 8 QLineEdit + LBL_RESOLUTION_WIDTH, // 50/ 9 QLineEdit + LBL_RESOLUTION_HEIGHT, // 51/10 QLineEdit + LBL_STARTING_STEP_FRAME, // 52/11 QLineEdit + + LBL_CHOSEN_LOGO, // 53/ 0 QComboBox + LBL_COLOUR_SCHEME_MM, // 54/ 1 QComboBox + LBL_GAP_SCALE_STRATEGY, // 55/ 2 QComboBox + LBL_GAP_TARGET, // 56/ 3 QComboBox + LBL_RESOLUTION_MM, // 57/ 4 QComboBox + LBL_SMOOTH_TYPE, // 58/ 5 QComboBox + + NUM_SETTINGS_MM + }; + + enum BlenderControlTypeMM + { + CTL_BLENDER_PATH_EDIT_MM, // 0 + CTL_ADD_ENVIRONMENT_BOX_MM = CTL_BLENDER_PATH_EDIT_MM, // 0 + CTL_BEVEL_SEGMENTS_EDIT = CTL_BLENDER_PATH_EDIT_MM, // 0 + CTL_CHOSEN_LOGO_COMBO = CTL_BLENDER_PATH_EDIT_MM, // 0 + CTL_BEVEL_EDGES_BOX_MM, // 1 + CTL_BEVEL_WEIGHT_EDIT = CTL_BEVEL_EDGES_BOX_MM, // 1 + CTL_COLOUR_SCHEME_COMBO_MM = CTL_BEVEL_EDGES_BOX_MM, // 1 + CTL_BLEND_FILE_TRUSTED_BOX_MM, // 2 + CTL_BEVEL_WIDTH_EDIT_MM = CTL_BLEND_FILE_TRUSTED_BOX_MM,// 2 + CTL_GAP_SCALE_STRATEGY_COMBO = CTL_BLEND_FILE_TRUSTED_BOX_MM,// 2 + CTL_CROP_IMAGE_BOX_MM, // 3 + CTL_CAMERA_BORDER_PERCENT_EDIT_MM = CTL_CROP_IMAGE_BOX_MM, // 3 + CTL_GAP_TARGET_COMBO = CTL_CROP_IMAGE_BOX_MM, // 3 + CTL_DISPLAY_LOGO_BOX, // 4 + CTL_FRAMES_PER_STEP_EDIT = CTL_DISPLAY_LOGO_BOX, // 4 + CTL_RESOLUTION_COMBO_MM = CTL_DISPLAY_LOGO_BOX, // 4 + CTL_IMPORT_CAMERAS_BOX_MM, // 5 + CTL_GAP_SCALE_EDIT = CTL_IMPORT_CAMERAS_BOX_MM, // 5 + CTL_SMOOTH_TYPE_COMBO = CTL_IMPORT_CAMERAS_BOX_MM, // 5 + CTL_IMPORT_EDGES_BOX, // 6 + CTL_IMPORT_SCALE_EDIT = CTL_IMPORT_EDGES_BOX, // 6 + CTL_IMPORT_LIGHTS_BOX_MM, // 7 + CTL_MERGE_DISTANCE_EDIT = CTL_IMPORT_LIGHTS_BOX_MM, // 7 + CTL_KEEP_ASPECT_RATIO_BOX_MM, // 8 + CTL_RENDER_PERCENTAGE_EDIT_MM = CTL_KEEP_ASPECT_RATIO_BOX_MM, // 8 + CTL_MAKE_GAPS_BOX, // 9 + CTL_RESOLUTION_WIDTH_EDIT = CTL_MAKE_GAPS_BOX, // 9 + CTL_META_BFC_BOX, // 10 + CTL_RESOLUTION_HEIGHT_EDIT = CTL_META_BFC_BOX, // 10 + CTL_META_CLEAR_BOX, // 11 + CTL_STARTING_STEP_FRAME_EDIT = CTL_META_CLEAR_BOX, // 11 + CTL_META_GROUP_BOX, // 12 + CTL_META_PAUSE_BOX, // 13 + CTL_META_PRINT_WRITE_BOX, // 14 + CTL_META_SAVE_BOX, // 15 + CTL_META_STEP_BOX, // 16 + CTL_META_STEP_GROUPS_BOX, // 17 + CTL_META_TEXMAP_BOX, // 18 + CTL_NO_STUDS_BOX, // 19 + CTL_POSITION_CAMERA_BOX_MM, // 10 + CTL_OVERWRITE_IMAGE_BOX_MM, // 21 + CTL_PARENT_TO_EMPTY_BOX, // 22 + CTL_PREFER_STUDIO_BOX, // 23 + CTL_PREFER_UNOFFICIAL_BOX, // 24 + CTL_PRESERVE_HIERARCHY_BOX, // 25 + CTL_PROFILE_BOX, // 26 + CTL_RECALCULATE_NORMALS_BOX, // 27 + CTL_REMOVE_DOUBLES_BOX_MM, // 28 + CTL_RENDER_WINDOW_BOX_MM, // 29 + CTL_SEARCH_ADDL_PATHS_MM_BOX, // 20 + CTL_SETEND_FRAME_BOX, // 31 + CTL_SET_TIMELINE_MARKERS_BOX, // 32 + CTL_SHADE_SMOOTH_BOX, // 33 + CTL_TRANSPARENT_BACKGROUND_BOX_MM, // 34 + CTL_TREAT_MODELS_WITH_SUBPARTS_AS_PARTS_BOX, // 35 + CTL_TREAT_SHORTCUT_AS_MODEL_BOX, // 36 + CTL_TRIANGULATE_BOX, // 37 + CTL_USE_ARCHIVE_LIBRARY_BOX_MM, // 38 + CTL_USE_FREESTYLE_EDGES_BOX, // 39 + CTL_VERBOSE_BOX_MM // 40 + }; + + enum BlenderBrickType + { + PARAMS_CUSTOM_COLOURS, + PARAMS_SLOPED_BRICKS, + PARAMS_LIGHTED_BRICKS + }; + + enum MessageBoxButtonsType + { + MBB_OK, + MBB_YES, + MBB_YES_NO + }; + + struct BlenderSettings + { + QString key; + QString value; + QString label; + QString tooltip; + }; + + struct BlenderPaths + { + QString key; + QString key_mm; + QString value; + QString label; + QString tooltip; + }; + + struct ComboItems + { + QString dataList; + QString itemList; + }; + + static BlenderPaths mBlenderPaths[]; + static BlenderPaths mDefaultPaths[]; + static BlenderSettings mBlenderSettings[]; + static BlenderSettings mBlenderSettingsMM[]; + static BlenderSettings mDefaultSettings[]; + static BlenderSettings mDefaultSettingsMM[]; + static ComboItems mComboItems[]; + static ComboItems mComboItemsMM[]; + + QList mPathLineEditList; + QList mPathBrowseButtonList; + QList mSettingLabelList; + QList mCheckBoxList; + QList mLineEditList; + QList mComboBoxList; + + QWidget* mContent = nullptr; + + QFormLayout* mForm = nullptr; + QGridLayout* mExeGridLayout = nullptr; + QGridLayout* mAddonGridLayout = nullptr; + QGridLayout* mPathsGridLayout = nullptr; + QFormLayout* mSettingsSubform = nullptr; + + QLineEdit* mBlenderVersionEdit = nullptr; + QLabel* mBlenderVersionLabel = nullptr; + QLineEdit* mAddonVersionEdit = nullptr; + QLabel* mAddonVersionLabel = nullptr; + QGroupBox* mModulesBox = nullptr; + QCheckBox* mImportActBox = nullptr; + QCheckBox* mImportMMActBox = nullptr; + QCheckBox* mRenderActBox = nullptr; + QGroupBox* mSettingsBox = nullptr; + QGroupBox* mPathsBox = nullptr; + + QAction* mDefaultColourEditAction = nullptr; + QPushButton* mAddonUpdateButton = nullptr; + QPushButton* mAddonStdOutButton = nullptr; + QPushButton* mPathsGroupButton = nullptr; + + QProgressBar* mProgressBar = nullptr; + QProcess* mProcess = nullptr; + + // TODO - Refactor, use LeoCAD mHttpReply/HttpManager + QProgressDialog* mProgressDialog = nullptr; + QNetworkAccessManager* mHttpManager = nullptr; + QNetworkReply* mHttpReply = nullptr; + QByteArray mByteArray; + QString mTitle; + QUrl mUrl; + bool mPromptRedirect = false; + bool mHttpRequestAborted = false; + bool mFileLoaded = false; + bool mFileLoadFail = false; + bool mShowProgress = false; + + QTimer mUpdateTimer; + + QStringList mStdOutList; + QString mDataDir; + QString mBlenderVersion; + QString mAddonVersion; + bool mConfigured; + bool mDialogCancelled; + bool mBlenderVersionFound; +}; + +extern class lcBlenderPreferences* gAddonPreferences; diff --git a/leocad.pro b/leocad.pro index 6b588bfe..3ae26763 100644 --- a/leocad.pro +++ b/leocad.pro @@ -175,6 +175,7 @@ SOURCES += \ common/lc_aboutdialog.cpp \ common/lc_application.cpp \ common/lc_arraydialog.cpp \ + common/lc_blenderpreferences.cpp \ common/lc_bricklink.cpp \ common/lc_category.cpp \ common/lc_categorydialog.cpp \ @@ -244,6 +245,7 @@ HEADERS += \ common/lc_application.h \ common/lc_array.h \ common/lc_arraydialog.h \ + common/lc_blenderpreferences.h \ common/lc_bricklink.h \ common/lc_category.h \ common/lc_categorydialog.h \