leocad/common/lc_blenderpreferences.cpp
2024-05-13 19:59:37 -07:00

3748 lines
144 KiB
C++

#include <QFormLayout>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QLineEdit>
#include <QCheckBox>
#include <QPushButton>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QLabel>
#include <QProgressBar>
#include <QProcess>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <zlib.h>
#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 "lc_zipfile.h"
#include "lc_file.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, for interesting reflections")},
/* 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", "1", 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 leeway around the model in the rendered image")},
/*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 */ {"realgapwidth", "0.0002", QObject::tr("Gap Width"), QObject::tr("Amount of space between each part (default 0.2mm)")},
/*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 */ {"realscale", "1.0", QObject::tr("Image Scale"), QObject::tr("Sets a scale for the model (1.0 = real life scale)")},
/*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")},
#ifdef Q_OS_LINUX
/* 03 LBL_CASE_SENSITIVE_FILESYSTEM */ {"casesensitivefilesystem", "1", QObject::tr("Case-sensitive Filesystem"),QObject::tr("Filesystem is case sensitive. Defaults to true on Linux.")},
#else
/* 03 LBL_CASE_SENSITIVE_FILESYSTEM */ {"casesensitivefilesystem", "0", QObject::tr("Case-sensitive Filesystem"),QObject::tr("Filesystem case sensitive defaults to false Windows and MacOS. Set true if LDraw path set to case-sensitive on case-insensitive filesystem.")},
#endif
/* 04 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")},
/* 05 LBL_DISPLAY_LOGO */ {"displaylogo", "1", QObject::tr("Display Logo"), QObject::tr("Display the logo on the stud")},
/* 06 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)},
/* 07 LBL_IMPORT_EDGES */ {"importedges", "0", QObject::tr("Import Edges"), QObject::tr("Import LDraw edges as edges")},
/* 08 LBL_IMPORT_LIGHTS_MM */ {"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)},
/* 09 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")},
/* 10 LBL_MAKE_GAPS */ {"makegaps", "1", QObject::tr("Make Gaps"), QObject::tr("Make small gaps between bricks. A small gap is more realistic")},
/* 11 LBL_META_BFC */ {"metabfc", "1", QObject::tr("BFC"), QObject::tr("Process LDraw Back Face Culling meta commands")},
/* 12 LBL_META_CLEAR */ {"metaclear", "0", QObject::tr("CLEAR Command"), QObject::tr("Hides all parts in the timeline up to where this command is encountered")},
/* 13 LBL_META_GROUP */ {"metagroup", "1", QObject::tr("GROUP Command"), QObject::tr("Process GROUP meta commands")},
/* 14 LBL_META_PAUSE */ {"metapause", "0", QObject::tr("PAUSE Command"), QObject::tr("Not implemented")},
/* 15 LBL_META_PRINT_WRITE */ {"metaprintwrite", "0", QObject::tr("PRINT/WRITE Command"), QObject::tr("Prints PRINT/WRITE META commands to the system console.")},
/* 16 LBL_META_SAVE */ {"metasave", "0", QObject::tr("SAVE Command"), QObject::tr("Not implemented")},
/* 17 LBL_META_STEP */ {"metastep", "0", QObject::tr("STEP Command"), QObject::tr("Adds a keyframe that shows the part at the moment in the timeline")},
/* 18 LBL_META_STEP_GROUPS */ {"metastepgroups", "0", QObject::tr("STEP Groups"), QObject::tr("Create collection for individual steps")},
/* 19 LBL_META_TEXMAP */ {"metatexmap", "1", QObject::tr("TEXMAP and DATA Command"), QObject::tr("Process TEXMAP and DATA meta commands")},
/* 20 LBL_NO_STUDS */ {"nostuds", "0", QObject::tr("No Studs"), QObject::tr("Don't import studs")},
/* 21 LBL_OVERWRITE_IMAGE_MM */ {"overwriteimage", "1", QObject::tr("Overwrite Image"), QObject::tr("Specify whether to overwrite an existing rendered image file")},
/* 22 LBL_POSITION_CAMERA_MM */ {"positioncamera", "1", QObject::tr("Position Camera"), QObject::tr("Position the camera to show the whole model")},
/* 23 LBL_PARENT_TO_EMPTY */ {"parenttoempty", "1", QObject::tr("Parent To Empty"), QObject::tr("Parent the model to an empty")},
/* 24 LBL_PREFER_STUDIO */ {"preferstudio", "0", QObject::tr("Prefer Stud.io Library"), QObject::tr("Search for parts in Stud.io library first")},
/* 25 LBL_PREFER_UNOFFICIAL */ {"preferunofficial", "0", QObject::tr("Prefer Unofficial Parts"), QObject::tr("Search for unofficial parts first")},
/* 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_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")},
/* 36 LBL_TRIANGULATE */ {"triangulate", "0", QObject::tr("Triangulate Faces"), QObject::tr("Triangulate all faces")},
/* 37 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")},
/* 38 LBL_USE_FREESTYLE_EDGES */ {"usefreestyleedges", "0", QObject::tr("Use Freestyle Edges"), QObject::tr("Render LDraw edges using freestyle")},
/* 39 LBL_VERBOSE_MM */ {"verbose", "1", QObject::tr("Verbose output"), QObject::tr("Output all messages while working, else only show warnings and errors")},
/* 40/00 LBL_BEVEL_SEGMENTS */ {"bevelsegments", "4", QObject::tr("Bevel Segments"), QObject::tr("Bevel segments")},
/* 41/01 LBL_BEVEL_WEIGHT */ {"bevelweight", "0.3", QObject::tr("Bevel Weight"), QObject::tr("Bevel weight")},
/* 42/02 LBL_BEVEL_WIDTH_MM */ {"bevelwidth", "0.3", QObject::tr("Bevel Width"), QObject::tr("Width of the bevelled edges")},
/* 43/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")},
/* 44/04 LBL_FRAMES_PER_STEP */ {"framesperstep", "3", QObject::tr("Frames Per Step"), QObject::tr("Frames per step")},
/* 45/05 LBL_GAP_SCALE */ {"gapscale", "0.997", QObject::tr("Gap Scale"), QObject::tr("Scale individual parts by this much to create the gap")},
/* 46/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")},
/* 47/07 LBL_MERGE_DISTANCE */ {"mergedistance", "0.05", QObject::tr("Merge Distance"), QObject::tr("Maximum distance between elements to merge")},
/* 48/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.")},
/* 49/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.")},
/* 50/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.")},
/* 51/11 LBL_STARTING_STEP_FRAME */ {"startingstepframe", "1", QObject::tr("Starting Step Frame"), QObject::tr("Frame to add the first STEP meta command")},
/* 52/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")},
/* 53/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)")},
/* 54/02 LBL_COLOUR_STRATEGY */ {"colorstrategy", "material", QObject::tr("How To Color Parts"), QObject::tr("Colour strategy options - Material (Default - use if exporting), Vertex colors (slightly quicker to import)")},
/* 55/03 LBL_RESOLUTION_MM */ {"resolution", "Standard", QObject::tr("Resolution"), QObject::tr("Resolution of part primitives, ie. how much geometry they have")},
/* 56/04 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 */ {"logo3|logo4|logo5", QObject::tr("Raised flattened logo geometry(3)|Raised rounded logo geometry(4)|Subtle rounded logo geometry(5)")},
/* 01 LBL_COLOUR_SCHEME_MM */ {"lgeo|ldraw|alt|custom", QObject::tr("Realistic Colours|Original LDraw Colours|Alternate LDraw Colours|Custom Colours")},
/* 02 LBL_COLOUR_STRATEGY */ {"material|vertex_colors", QObject::tr("Material|Vertex Colors")},
/* 03 LBL_RESOLUTION_MM */ {"Low|Standard|High", QObject::tr("Low Resolution Primitives|Standard Primitives|High Resolution Primitives")},
/* 04 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()
{
const QString 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);
QCheckBox* AddonVersionCheck = new QCheckBox(tr("Prompt to download new addon version when available"), BlenderAddonVersionBox);
AddonVersionCheck->setChecked(lcGetProfileInt(LC_PROFILE_BLENDER_ADDON_VERSION_CHECK));
QObject::connect(AddonVersionCheck, &QCheckBox::stateChanged, [](int State)
{
const bool VersionCheck = static_cast<Qt::CheckState>(State) == Qt::CheckState::Checked;
lcSetProfileInt(LC_PROFILE_BLENDER_ADDON_VERSION_CHECK, (int)VersionCheck);
});
mAddonGridLayout->addWidget(AddonVersionCheck,0,0,1,4);
mAddonVersionLabel = new QLabel(BlenderAddonVersionBox);
mAddonGridLayout->addWidget(mAddonVersionLabel,1,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,1,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,1,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,1,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,2,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
{
const QString 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));
const QString 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));
const QString 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;
const QString 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
};
const QString BlenderDir = QDir::toNativeSeparators(QString("%1/Blender").arg(mDataDir));
const QString BlenderConfigDir = QString("%1/setup/addon_setup/config").arg(BlenderDir);
const QString BlenderAddonDir = QDir::toNativeSeparators(QString("%1/addons").arg(BlenderDir));
const QString BlenderExeCompare = QDir::toNativeSeparators(lcGetProfileString(LC_PROFILE_BLENDER_PATH)).toLower();
const QString BlenderInstallFile = QDir::toNativeSeparators(QString("%1/%2").arg(BlenderDir).arg(LC_BLENDER_ADDON_INSTALL_FILE));
const QString 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()));
const QString& 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()));
const QString 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;
const QString 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
{
const QString& Title = tr ("%1 Blender LDraw Addon").arg(LC_PRODUCTNAME_STR);
const QString& Header = tr ("Blender test failed.");
ShowMessage(Header, Title, Message);
return;
}
} // Test Blender
if (!mBlenderVersion.isEmpty() && !mImportMMActBox->isChecked() && !mImportActBox->isChecked())
{
const QString& Title = tr ("%1 Blender LDraw Addon Modules").arg(LC_PRODUCTNAME_STR);
const QString& Header = tr ("No import module enabled. If you continue, the default import module (Import TN) will be used.<br>If you select No, all addon modules will be disabled.");
const QString& 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)
{
const QString 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 (const QString& 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<br>%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..."));
const QString BlenderAddonFile = QDir::toNativeSeparators(QString("%1/%2").arg(BlenderDir).arg(LC_BLENDER_ADDON_FILE));
QString Result;
bool Success = gAddonPreferences->ExtractAddon(BlenderAddonFile, Result);
if (!Success)
{
QString Message = tr("Failed to extract %1 to %2").arg(LC_BLENDER_ADDON_FILE).arg(BlenderDir);
if (Result.size())
Message.append(" "+Result);
ShowMessage(Message, 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
};
const QString BlenderAddonDir = QDir::toNativeSeparators(QString("%1/addons").arg(BlenderDir));
const QString BlenderAddonFile = QDir::toNativeSeparators(QString("%1/%2").arg(BlenderDir).arg(LC_BLENDER_ADDON_FILE));
const QString 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 = [&]()
{
lcHttpManager* HttpManager = new lcHttpManager(gAddonPreferences);
connect(HttpManager, SIGNAL(DownloadFinished(lcHttpReply*)), gAddonPreferences, SLOT(DownloadFinished(lcHttpReply*)));
gAddonPreferences->mHttpReply = HttpManager->DownloadFile(QLatin1String(LC_BLENDER_ADDON_LATEST_URL));
while (gAddonPreferences->mHttpReply)
QApplication::processEvents();
if (!gAddonPreferences->mData.isEmpty())
{
QJsonDocument Json = QJsonDocument::fromJson(gAddonPreferences->mData);
OnlineVersion = Json.object()["tag_name"].toString();
gAddonPreferences->mData.clear();
}
else
{
ShowMessage(tr("Check latest addon version failed."), tr("Latest Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning);
return true; // Reload existing archive
}
QByteArray Ba;
if (!ExtractedAddon)
{
const char* VersionFile = "addons/" LC_BLENDER_ADDON_FOLDER_STR "/__version__.py";
lcZipFile ZipFile;
if (!ZipFile.OpenRead(BlenderAddonFile))
{
ShowMessage(tr("Cannot open addon archive file: %1.").arg(BlenderAddonFile));
return false;
}
lcMemFile File;
if (!ZipFile.ExtractFile(VersionFile, File))
{
ShowMessage(tr("Cannot extract addon archive version file: %1.").arg(VersionFile));
return false;
}
Ba = QByteArray::fromRawData((const char*)File.mBuffer, (int)File.GetLength());
if (Ba.isEmpty())
{
ShowMessage(tr("Cannot read addon archive version file: %1.").arg(VersionFile));
return false; // Download new archive
}
}
else
{
QFile File(AddonVersionFile);
if (!File.open(QIODevice::ReadOnly))
{
ShowMessage(tr("Cannot read addon version file: [%1]<br>%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(")","");
}
}
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; // Download new archive
}
return true; // Reload existing archive
};
if (BlenderAddonExists)
{
if (GetBlenderAddonVersionMatch())
{
AddonAction = ADDON_RELOAD;
}
else if (gMainWindow)
{
if (lcGetProfileInt(LC_PROFILE_BLENDER_ADDON_VERSION_CHECK))
{
if (LocalVersion.isEmpty())
LocalVersion = gAddonPreferences->mAddonVersion;
const QString& Title = tr ("%1 Blender LDraw Addon").arg(LC_PRODUCTNAME_STR);
const QString& Header = tr ("Detected %1 Blender LDraw addon %2. A newer version %3 exists.").arg(LC_PRODUCTNAME_STR).arg(LocalVersion).arg(OnlineVersion);
const QString& 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;
}
}
else
{
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;
lcHttpManager* HttpManager = new lcHttpManager(gAddonPreferences);
connect(HttpManager, SIGNAL(DownloadFinished(lcHttpReply*)), gAddonPreferences, SLOT(DownloadFinished(lcHttpReply*)));
gAddonPreferences->mHttpReply = HttpManager->DownloadFile(QLatin1String(LC_BLENDER_ADDON_URL));
while (gAddonPreferences->mHttpReply)
QApplication::processEvents();
if (!gAddonPreferences->mData.isEmpty())
{
if (QFileInfo(BlenderAddonFile).exists())
{
QDir Dir(BlenderDir);
if (!Dir.remove(BlenderAddonFile))
ShowMessage(tr("Failed to remove Blender addon archive:<br>%1").arg(BlenderAddonFile));
}
QFile File(BlenderAddonFile);
if (File.open(QIODevice::WriteOnly))
{
File.write(gAddonPreferences->mData);
File.close();
BlenderAddonExists = true;
gAddonPreferences->mData.clear();
}
else
ShowMessage(tr("Failed to open Blender addon file:<br>%1:<br>%2").arg(BlenderAddonFile).arg(File.errorString()));
}
else
ShowMessage(tr("Failed to download Blender addon archive:<br>%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;
const QString 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)
{
const QString 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;
const QString& Title = tr ("%1 Blender Addon Install").arg(LC_PRODUCTNAME_STR);
const QString& Header = "<b>" + tr ("Addon install failed.") + "</b>";
const QString& Body = tr ("LDraw addon install encountered one or more errors. See Show Details...");
ShowMessage(Header, Title, Body, StdErrLog, MBB_OK, QMessageBox::Warning);
}
else
{
const QString 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<QLineEdit*>(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<QComboBox*>(sender());
if (ComboBox)
{
LblIdx = ComboBox->property("ControlID").toInt();
Item = ComboBox->itemData(Index).toString();
}
}
else
{
QCheckBox* CheckBox = qobject_cast<QCheckBox*>(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<QLineEdit*>(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()
{
const QString 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)
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QRegularExpression RxInfo("^INFO: ");
QRegularExpression RxData("^DATA: ");
QRegularExpression RxError("^(?:\\w)*ERROR: ", QRegularExpression::CaseInsensitiveOption);
QRegularExpression RxWarning("^(?:\\w)*WARNING: ", QRegularExpression::CaseInsensitiveOption);
QRegularExpression RxAddonVersion("^ADDON VERSION: ", QRegularExpression::CaseInsensitiveOption);
QStringList StdOutLines = StdOutput.split(QRegularExpression("\n|\r\n|\r"));
#else
QRegExp RxInfo("^INFO: ");
QRegExp RxData("^DATA: ");
QRegExp RxError("^(?:\\w)*ERROR: ", Qt::CaseInsensitive);
QRegExp RxWarning("^(?:\\w)*WARNING: ", Qt::CaseInsensitive);
QRegExp RxAddonVersion("^ADDON VERSION: ", Qt::CaseInsensitive);
QStringList StdOutLines = StdOutput.split(QRegExp("\n|\r\n|\r"));
#endif
bool ErrorEncountered = false;
QStringList Items, ErrorList;
const QString SaveAddonVersion = mAddonVersion;
const QString SaveVersion = mBlenderVersion;
int EditListItems = mPathLineEditList.size();
for (const QString& 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() + "<br>";
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()
{
const QString& StdOut = QString(mProcess->readAllStandardOutput());
mStdOutList.append(StdOut);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QRegularExpression RxInfo("^INFO: ");
QRegularExpression RxError("(?:\\w)*ERROR: ", QRegularExpression::CaseInsensitiveOption);
QRegularExpression RxWarning("(?:\\w)*WARNING: ", QRegularExpression::CaseInsensitiveOption);
#else
QRegExp RxInfo("^INFO: ");
QRegExp RxError("(?:\\w)*ERROR: ", Qt::CaseInsensitive);
QRegExp RxWarning("(?:\\w)*WARNING: ", Qt::CaseInsensitive);
#endif
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())
{
const QString StdOutLog = QDir::toNativeSeparators(QString("<br>- See %1/Blender/stdout-blender-addon-install")
.arg(mDataDir));
QMessageBox::Icon Icon = QMessageBox::Warning;
const QString& Items = Error ? tr("errors%1").arg(Warning ? tr(" and warnings") : "") : Warning ? tr("warnings") : "";
const QString& Title = tr ("%1 Blender Addon Install").arg(LC_PRODUCTNAME_STR);
const QString& Header = "<b>" + tr ("Addon install standard output.") + "</b>";
const QString& 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() + "<br>";
};
Error = false;
QStringList ReturnLines;
const QString BlenderDir = QString("%1/Blender").arg(mDataDir);
QFile File(QString("%1/stderr-blender-addon-install").arg(BlenderDir));
if ( ! File.open(QFile::ReadOnly | QFile::Text))
{
const 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())
{
const QString& Line = In.readLine(0);
ReturnLines << CleanLine(Line);
if (!Error)
Error = !Line.isEmpty();
}
return ReturnLines.join(" ");
}
void lcBlenderPreferences::WriteStdOut()
{
const QString 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)
{
const QString& Title = tr ("Cancel %1 Addon Install").arg(LC_PRODUCTNAME_STR);
const QString& Header = "<b>" + tr("Are you sure you want to cancel the add on install ?") + "</b>";
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;
const QString 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;
const QString 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;
const QString 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::AppLocalDataLocation);
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())
{
const QString 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;
const QString& Key = QString("%1/%2").arg(LC_BLENDER_ADDON, mBlenderPaths[LblIdx].key);
const QString& 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;
const QString& Key = QString("%1/%2").arg(LC_BLENDER_ADDON_MM, mBlenderPaths[LblIdx].key_mm);
const QString& Value = Settings.value(Key, QString()).toString();
if (QFileInfo(Value).exists())
{
mBlenderPaths[LblIdx].value = QDir::toNativeSeparators(Value);
}
}
for (int LblIdx = 0; LblIdx < NumSettings(); LblIdx++)
{
const QString& Key = QString("%1/%2").arg(LC_BLENDER_ADDON, mBlenderSettings[LblIdx].key);
const QString& 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)
{
const QString& Label = mDefaultSettings[LblIdx].label;
mBlenderSettings[LblIdx].label = QString("%1 - Setting (%2)").arg(Label).arg(Value);
}
}
for (int LblIdx = 0; LblIdx < NumSettingsMM(); LblIdx++)
{
const QString& Key = QString("%1/%2").arg(LC_BLENDER_ADDON_MM, mBlenderSettingsMM[LblIdx].key);
const QString& 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)
{
const QString& Label = mDefaultSettingsMM[LblIdx].label;
mBlenderSettingsMM[LblIdx].label = QString("%1 - Setting (%2)").arg(Label).arg(Value);
}
}
}
else
{
const QString 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();
const QString 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);
if (Settings.contains(Key))
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;
if (Settings.contains(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);
if (Settings.contains(Key))
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;
if (Settings.contains(Key))
Settings.setValue(Key, QVariant(Value));
}
searchDirectoriesKey = QLatin1String("additionalSearchPaths");
concludeSettingsGroup();
const QString 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);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QScreen* Screen = screen();
const QRect DesktopGeom = Screen ? Screen->geometry() : QRect();
#else
const QRect DesktopGeom = QApplication::desktop()->geometry();
#endif
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))
{
const QString 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())
{
const QString 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&)));
const QString 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&)));
const QString 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);
const QString 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<NativeImage> 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])
{
const QString& Title = tr ("LDraw Render Settings Conflict");
const QString& Header = "<b>" + tr ("Crop image configuration settings conflict were resolved.") + "</b>";
const QString& Body = QString("%1%2%3").arg(Conflict[0] ? tr("Keep aspect ratio set to false.<br>") : "").arg(Conflict[1] ? tr("Add environment (backdrop and base plane) set to false.<br>") : "").arg(Conflict[2] ? tr("Transparent background set to true.<br>") : "");
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&)));
const QString Width = QString::number(CropImage ? ImageWidth : mImageWidth);
const QString 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<QComboBox*>(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;
const QString& Title = tr ("Custom LDraw Colours");
const QString& Header = "<b>" + tr ("Colour scheme 'custom' cannot be enabled. Custom LDConfig file not found.") + "</b>";
const QString& Body = tr ("Colour scheme 'custom' selected but no LDConfig file was specified.<br>The default colour scheme '%1' will be used.<br>").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()
{
const QString& Title = tr ("Render Settings Modified");
const QString& Header = "<b>" + tr("Do you want to accept the modified settings before quitting ?") + "</b>";
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()
{
const QString BlenderConfigDir = QString("%1/Blender/setup/addon_setup/config").arg(gAddonPreferences->mDataDir);
const QString 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:<br>%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;
const QString& Title = tr ("Replace Existing File");
const QString Header = "<b>" + QMessageBox::tr ("Existing file %1 detected.").arg(fileInfo.fileName()) + "</b>";
const QString Body = QMessageBox::tr ("\"%1\"<br>This file already exists.<br>Replace existing file?").arg(fileInfo.fileName());
int Exec = ShowMessage(Header, Title, Body, QString(), MBB_YES, QMessageBox::NoIcon);
return (Exec == QMessageBox::Yes);
}
int lcBlenderPreferences::ShowMessage(const QString& Header, const QString& Title, const QString& 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<QMessageBox::Icon>(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("<br>", "\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.mid(Index,1).isEmpty())
FixedWidth = Body.indexOf(" ", Index);
}
else if (FixedWidth < MinimumWidth)
FixedWidth = MinimumWidth;
TextWidget->setFixedWidth(FixedWidth);
}
}
const bool DownloadRequest = Body.startsWith(tr("Do you want to download version "));
if (DownloadRequest){
QCheckBox* AddonVersionCheck = new QCheckBox(tr("Do not show download new addon version message again."));
Box.setCheckBox(AddonVersionCheck);
QObject::connect(AddonVersionCheck, &QCheckBox::stateChanged, [](int State)
{
bool VersionCheck = true;
if (static_cast<Qt::CheckState>(State) == Qt::CheckState::Checked)
VersionCheck = false;
lcSetProfileInt(LC_PROFILE_BLENDER_ADDON_VERSION_CHECK, (int)VersionCheck);
});
}
return Box.exec();
}
void lcBlenderPreferences::DownloadFinished(lcHttpReply* Reply)
{
if (!Reply->error())
mData = Reply->readAll();
else
ShowMessage(tr("Addon download failed."));
mHttpReply = nullptr;
Reply->deleteLater();
}
namespace WindowsFileAttributes
{
enum
{
Dir = 0x10, // FILE_ATTRIBUTE_DIRECTORY
File = 0x80, // FILE_ATTRIBUTE_NORMAL
TypeMask = 0x90,
ReadOnly = 0x01, // FILE_ATTRIBUTE_READONLY
PermMask = 0x01
};
}
namespace UnixFileAttributes
{
enum
{
Dir = 0040000, // __S_IFDIR
File = 0100000, // __S_IFREG
SymLink = 0120000, // __S_IFLNK
TypeMask = 0170000, // __S_IFMT
ReadUser = 0400, // __S_IRUSR
WriteUser = 0200, // __S_IWUSR
ExeUser = 0100, // __S_IXUSR
ReadGroup = 0040, // __S_IRGRP
WriteGroup = 0020, // __S_IWGRP
ExeGroup = 0010, // __S_IXGRP
ReadOther = 0004, // __S_IROTH
WriteOther = 0002, // __S_IWOTH
ExeOther = 0001, // __S_IXOTH
PermMask = 0777
};
}
bool lcBlenderPreferences::ExtractAddon(const QString FileName, QString& Result)
{
enum ZipHostOS
{
HostFAT = 0,
HostUnix = 3,
HostHPFS = 6, // filesystem used by OS/2 (and NT 3.x)
HostNTFS = 11, // filesystem used by Windows NT
HostVFAT = 14, // filesystem used by Windows 95, NT
HostOSX = 19
};
struct ZipFileInfo
{
ZipFileInfo(lcZipFileInfo& FileInfo) noexcept
: ZipInfo(FileInfo), isDir(false), isFile(false), isSymLink(false)
{
}
bool IsValid() const noexcept { return isDir || isFile || isSymLink; }
lcZipFileInfo& ZipInfo;
QString filePath;
uint isDir : 1;
uint isFile : 1;
uint isSymLink : 1;
QFile::Permissions permissions;
};
auto ModeToPermissions = [](quint32 Mode)
{
QFile::Permissions Permissions;
if (Mode& UnixFileAttributes::ReadUser)
Permissions |= QFile::ReadOwner | QFile::ReadUser;
if (Mode& UnixFileAttributes::WriteUser)
Permissions |= QFile::WriteOwner | QFile::WriteUser;
if (Mode& UnixFileAttributes::ExeUser)
Permissions |= QFile::ExeOwner | QFile::ExeUser;
if (Mode& UnixFileAttributes::ReadGroup)
Permissions |= QFile::ReadGroup;
if (Mode& UnixFileAttributes::WriteGroup)
Permissions |= QFile::WriteGroup;
if (Mode& UnixFileAttributes::ExeGroup)
Permissions |= QFile::ExeGroup;
if (Mode& UnixFileAttributes::ReadOther)
Permissions |= QFile::ReadOther;
if (Mode& UnixFileAttributes::WriteOther)
Permissions |= QFile::WriteOther;
if (Mode& UnixFileAttributes::ExeOther)
Permissions |= QFile::ExeOther;
return Permissions;
};
lcZipFile ZipFile;
if (!ZipFile.OpenRead(FileName))
return false;
const QString DestinationDir = QFileInfo(FileName).absolutePath();
bool Ok = true;
int Extracted = 0;
for (quint32 FileIdx = 0; FileIdx < ZipFile.mFiles.size(); FileIdx++)
{
ZipFileInfo FileInfo(ZipFile.mFiles[FileIdx]);
quint32 Mode = FileInfo.ZipInfo.external_fa;
const ZipHostOS HostOS = ZipHostOS(FileInfo.ZipInfo.version >> 8);
switch (HostOS)
{
case HostOSX:
case HostUnix:
Mode = (Mode >> 16)& 0xffff;
switch (Mode& UnixFileAttributes::TypeMask)
{
case UnixFileAttributes::SymLink:
FileInfo.isSymLink = true;
break;
case UnixFileAttributes::Dir:
FileInfo.isDir = true;
break;
case UnixFileAttributes::File:
default:
FileInfo.isFile = true;
break;
}
FileInfo.permissions = ModeToPermissions(Mode);
break;
case HostFAT:
case HostNTFS:
case HostHPFS:
case HostVFAT:
switch (Mode& WindowsFileAttributes::TypeMask)
{
case WindowsFileAttributes::Dir:
FileInfo.isDir = true;
break;
case WindowsFileAttributes::File:
default:
FileInfo.isFile = true;
break;
}
FileInfo.permissions |= QFile::ReadOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther;
if ((Mode& WindowsFileAttributes::ReadOnly) == 0)
FileInfo.permissions |= QFile::WriteOwner | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther;
if (FileInfo.isDir)
FileInfo.permissions |= QFile::ExeOwner | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther;
break;
default:
ShowMessage(tr("ZipFile entry format (HostOS %1) at index %2 is not supported. Extract terminated.").arg(HostOS).arg(FileIdx), tr("Extract Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning);
Ok = false;
}
if (!Ok || !(Ok = FileInfo.IsValid()))
break;
// Check the file path - if broken, convert separators, eat leading and trailing ones
FileInfo.filePath = QDir::fromNativeSeparators(FileInfo.ZipInfo.file_name);
QString FilePathRef(FileInfo.filePath);
while (FilePathRef.startsWith(QLatin1Char('.')) || FilePathRef.startsWith(QLatin1Char('/')))
FilePathRef = FilePathRef.mid(1);
while (FilePathRef.endsWith(QLatin1Char('/')))
FilePathRef.chop(1);
FileInfo.filePath = FilePathRef;
const QString AbsPath = QDir::fromNativeSeparators(DestinationDir + QDir::separator() + FileInfo.filePath);
// directories
if (FileInfo.isDir)
{
QDir BaseDir(DestinationDir);
if (!(Ok = BaseDir.mkpath(FileInfo.filePath)))
break;
if (!(Ok = QFile::setPermissions(AbsPath, FileInfo.permissions)))
break;
Extracted++;
continue;
}
lcMemFile MemFile;
ZipFile.ExtractFile(FileIdx, MemFile);
QByteArray const& ByteArray = QByteArray::fromRawData((const char*)MemFile.mBuffer, (int)MemFile.GetLength());
// symlinks
if (FileInfo.isSymLink)
{
QString Destination = QFile::decodeName(ByteArray);
if (!(Ok = !Destination.isEmpty()))
break;
QFileInfo LinkFileInfo(AbsPath);
if (!QFile::exists(LinkFileInfo.absolutePath()))
QDir::root().mkpath(LinkFileInfo.absolutePath());
if (!(Ok = QFile::link(Destination, AbsPath)))
break;
Extracted++;
continue;
}
// files
if (FileInfo.isFile)
{
QFile File(AbsPath);
if(!(Ok = File.open(QIODevice::WriteOnly)))
break;
File.write(ByteArray);
File.setPermissions(FileInfo.permissions);
File.close();
Extracted++;
}
}
if (!Ok)
Result = tr("%1 of %2 files extracted.").arg(Extracted).arg(ZipFile.mFiles.size());
return Ok;
}