diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index b1b6600bc2..c4a4efbf24 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -3238,6 +3238,20 @@ static inline void fill_expolygons_generate_paths( fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow); } +static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) +{ + // Draw the perimeters. + Polylines polylines; + polylines.reserve(expoly.holes.size() + 1); + for (size_t i = 0; i <= expoly.holes.size(); ++ i) { + Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); + pl.points.emplace_back(pl.points.front()); + pl.clip_end(clip_length); + polylines.emplace_back(std::move(pl)); + } + return polylines; +} + static inline void tree_supports_generate_paths( ExtrusionEntitiesPtr &dst, const Polygons &polygons, @@ -3336,7 +3350,20 @@ static inline void tree_supports_generate_paths( const double clip_length = spacing * 0.15; const double anchor_length = spacing * 6.; ClipperLib_Z::Paths anchor_candidates; - for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { + for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { + double area = expoly.area(); + if (area > sqr(scaled(5.))) { + // Make the tree branch stable by adding another perimeter. + ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); + if (level2.size() == 1) { + Polylines polylines; + extrusion_entities_append_paths(dst, draw_perimeters(expoly, clip_length), erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + // Disable reversal of the path, always start with the anchor, always print CCW. + false); + expoly = level2.front(); + } + } + // Try to produce one more perimeter to place the seam anchor. // First genrate a 2nd perimeter loop as a source for anchor candidates. // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. @@ -3466,9 +3493,9 @@ static inline void fill_expolygons_with_sheath_generate_paths( fill_params.density = density; fill_params.dont_adjust = true; - double spacing = flow.scaled_spacing(); + const double spacing = flow.scaled_spacing(); // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. - double clip_length = spacing * 0.15; + const double clip_length = spacing * 0.15; for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { // Don't reorder the skirt and its infills. @@ -3478,16 +3505,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( eec->no_sort = true; } ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t i = 0; i <= expoly.holes.size(); ++ i) { - Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); - pl.points.emplace_back(pl.points.front()); - pl.clip_end(clip_length); - polylines.emplace_back(std::move(pl)); - } - extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); + extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); // Fill in the rest. fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); if (no_sort && ! eec->empty()) diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index fecbef8313..d5758cf07b 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -3534,7 +3534,8 @@ static void draw_branches( for (size_t i = 0; i < projections.size(); ++ i) { const SupportElement &element = *elements_with_link_down[i].first; const int below = elements_with_link_down[i].second; - if (pts[i] != projections[i]) { + const bool locked = below == -1 && element.state.layer_idx > 0; + if (! locked && pts[i] != projections[i]) { // Nudge the circle center away from the collision. Vec3d v{ projections[i].x() - pts[i].x(), projections[i].y() - pts[i].y(), projections[i].z() - pts[i].z() }; double depth = v.norm(); @@ -3555,7 +3556,7 @@ static void draw_branches( } } // Laplacian smoothing - if (! element.parents.empty() && (below != -1 || element.state.layer_idx == 0)) { + if (! locked && ! element.parents.empty()) { Vec2d avg{ 0, 0 }; const SupportElements &above = move_bounds[element.state.layer_idx + 1]; const size_t offset_above = linear_data_layers[element.state.layer_idx + 1]; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 04ac957537..e4db546a1e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2192,6 +2192,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path) ); } else { + notification_manager->close_notification_of_type(NotificationType::ExportFinished); notification_manager->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel, format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path) diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index 5fb8b01321..2189f2ad91 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -10,9 +10,9 @@ #include #include #include - #include - +#include +#include #else // unix, linux & OSX includes #include @@ -72,6 +72,192 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons return current_drives; } +namespace { +// returns the device instance handle of a storage volume or 0 on error +// called from eject_inner, based on https://stackoverflow.com/a/58848961 +DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, WCHAR* dos_device_name) +{ + bool is_floppy = (wcsstr(dos_device_name, L"\\Floppy") != NULL); // TODO: could be tested better? + GUID* guid; + switch (drive_type) { + case DRIVE_REMOVABLE: + if (is_floppy) { + // we are interested only in SD cards or USB sticks + BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Drive is floppy disk."; + return 0; + //guid = (GUID*)&GUID_DEVINTERFACE_FLOPPY; + } else { + guid = (GUID*)&GUID_DEVINTERFACE_DISK; + } + break; + case DRIVE_FIXED: + // we are interested only in SD cards or USB sticks + BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Drive is harddisk."; + return 0; + //guid = (GUID*)&GUID_DEVINTERFACE_DISK; + //break; + case DRIVE_CDROM: + BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Drive is cd-rom."; + // we are interested only in SD cards or USB sticks + return 0; + //guid = (GUID*)&GUID_DEVINTERFACE_CDROM; + //break; + default: + return 0; + } + + // Get device interface info set handle for all devices attached to system + HDEVINFO h_dev_info = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (h_dev_info == INVALID_HANDLE_VALUE) { + BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Invalid dev info handle."; + return 0; + } + + // Retrieve a context structure for a device interface of a device information set + BYTE buf[1024]; + PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)buf; + SP_DEVICE_INTERFACE_DATA spdid; + SP_DEVINFO_DATA spdd; + DWORD size; + + spdid.cbSize = sizeof(spdid); + + // Loop through devices and compare device numbers + for (DWORD index = 0; SetupDiEnumDeviceInterfaces(h_dev_info, NULL, guid, index, &spdid); ++index) { + SetupDiGetDeviceInterfaceDetail(h_dev_info, &spdid, NULL, 0, &size, NULL); + // check the buffer size + if (size == 0 || size > sizeof(buf)) { + continue; + } + // prepare structures + pspdidd->cbSize = sizeof(*pspdidd); + ZeroMemory(&spdd, sizeof(spdd)); + spdd.cbSize = sizeof(spdd); + // fill structures + long res = SetupDiGetDeviceInterfaceDetail(h_dev_info, &spdid, pspdidd, size, &size, &spdd); + if (!res) { + continue; + } + // open the drive with pspdidd->DevicePath to compare device numbers + HANDLE drive_handle = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (drive_handle == INVALID_HANDLE_VALUE) { + continue; + } + // get its device number + STORAGE_DEVICE_NUMBER sdn; + DWORD bytes_returned = 0; + res = DeviceIoControl(drive_handle, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &bytes_returned, NULL); + CloseHandle(drive_handle); + if (!res) { + continue; + } + //compare + if (device_number != (long)sdn.DeviceNumber) { + continue; + } + // this is the drive, return the device instance + SetupDiDestroyDeviceInfoList(h_dev_info); + return spdd.DevInst; + } + + SetupDiDestroyDeviceInfoList(h_dev_info); + BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Enmurating couldn't find the drive."; + return 0; +} + +// Perform eject using CM_Request_Device_EjectW. +// Returns 0 if success. +int eject_inner(const std::string& path) +{ + // Following implementation is based on https://stackoverflow.com/a/58848961 + assert(path.size() > 0); + std::wstring wpath = std::wstring(); + wpath += boost::nowide::widen(path)[0]; // drive letter wide + wpath[0] &= ~0x20; // make sure drive letter is uppercase + assert(wpath[0] >= 'A' && wpath[0] <= 'Z'); + std::wstring root_path = wpath + L":\\"; // for GetDriveType + std::wstring device_path = wpath + L":"; //for QueryDosDevice + std::wstring volume_access_path = L"\\\\.\\" + wpath + L":"; // for CreateFile + long device_number = -1; + + // open the storage volume + HANDLE volume_handle = CreateFileW(volume_access_path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); + if (volume_handle == INVALID_HANDLE_VALUE) { + BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Invalid value of file handle.", path); + return 1; + } + + // get the volume's device number + STORAGE_DEVICE_NUMBER sdn; + DWORD bytes_returned = 0; + long res = DeviceIoControl(volume_handle, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &bytes_returned, NULL); + if (res) { + device_number = sdn.DeviceNumber; + } + CloseHandle(volume_handle); + + if (device_number == -1) { + BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Invalid device number.", path); + return 1; + } + + // get the drive type which is required to match the device numbers correctely + UINT drive_type = GetDriveTypeW(root_path.c_str()); + + // get the dos device name (like \device\floppy0) to decide if it's a floppy or not + WCHAR dos_device_name[MAX_PATH]; + res = QueryDosDeviceW(device_path.c_str(), dos_device_name, MAX_PATH); + if (!res) { + BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Invalid dos device name.", path); + return 1; + } + + // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number + DEVINST dev_inst = get_dev_inst_by_device_number(device_number, drive_type, dos_device_name); + + if (dev_inst == 0) { + BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Invalid device instance handle.", path); + return 1; + } + + PNP_VETO_TYPE veto_type = PNP_VetoTypeUnknown; + WCHAR veto_name[MAX_PATH]; + veto_name[0] = 0; + + // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives! + DEVINST dev_inst_parent = 0; + res = CM_Get_Parent(&dev_inst_parent, dev_inst, 0); + +#if 0 + // loop with several tries and sleep (this is running on main UI thread) + for (int i = 0; i < 3; ++i) { + veto_name[0] = 0; + // CM_Query_And_Remove_SubTree doesn't work for restricted users + //res = CM_Query_And_Remove_SubTreeW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, CM_REMOVE_NO_RESTART); // CM_Query_And_Remove_SubTreeA is not implemented under W2K! + //res = CM_Query_And_Remove_SubTreeW(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART); // with messagebox (W2K, Vista) or balloon (XP) + res = CM_Request_Device_EjectW(dev_inst_parent, &veto_type, veto_name, MAX_PATH, 0); + //res = CM_Request_Device_EjectW(DevInstParent, NULL, NULL, 0, 0); // with messagebox (W2K, Vista) or balloon (XP) + if (res == CR_SUCCESS && veto_type == PNP_VetoTypeUnknown) { + return 0; + } + // Wait for next try. + // This is main thread! + Sleep(500); + } +#endif // 0 + + // perform eject + res = CM_Request_Device_EjectW(dev_inst_parent, &veto_type, veto_name, MAX_PATH, 0); + if (res == CR_SUCCESS && veto_type == PNP_VetoTypeUnknown) { + return 0; + } + + BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Request to eject device has failed.", path); + return 1; +} + +} // Called from UI therefore it blocks the UI thread. // It also blocks updates at the worker thread. // Win32 implementation. @@ -86,6 +272,28 @@ void RemovableDriveManager::eject_drive() BOOST_LOG_TRIVIAL(info) << "Ejecting started"; std::scoped_lock lock(m_drives_mutex); auto it_drive_data = this->find_last_save_path_drive_data(); + if (it_drive_data != m_current_drives.end()) { + if (!eject_inner(m_last_save_path)) { + // success + assert(m_callback_evt_handler); + if (m_callback_evt_handler) + wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true))); + } else { + // failed to eject + // this should not happen, throwing exception might be the way here + assert(m_callback_evt_handler); + if (m_callback_evt_handler) + wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(*it_drive_data, false))); + } + } else { + // drive not found in m_current_drives + assert(m_callback_evt_handler); + if (m_callback_evt_handler) + wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair({"",""}, false))); + } +#if 0 + // Implementation used until 2.5.x version + // Some usb drives does not eject properly (still visible in file explorer). Some even does not write all content and eject. if (it_drive_data != m_current_drives.end()) { // get handle to device std::string mpath = "\\\\.\\" + m_last_save_path; @@ -102,16 +310,16 @@ void RemovableDriveManager::eject_drive() //these 3 commands should eject device safely but they dont, the device does disappear from file explorer but the "device was safely remove" notification doesnt trigger. //sd cards does trigger WM_DEVICECHANGE messege, usb drives dont BOOL e1 = DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); - BOOST_LOG_TRIVIAL(debug) << "FSCTL_LOCK_VOLUME " << e1 << " ; " << deviceControlRetVal << " ; " << GetLastError(); + BOOST_LOG_TRIVIAL(error) << "FSCTL_LOCK_VOLUME " << e1 << " ; " << deviceControlRetVal << " ; " << GetLastError(); BOOL e2 = DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); - BOOST_LOG_TRIVIAL(debug) << "FSCTL_DISMOUNT_VOLUME " << e2 << " ; " << deviceControlRetVal << " ; " << GetLastError(); - // some implemenatations also calls IOCTL_STORAGE_MEDIA_REMOVAL here but it returns error to me + BOOST_LOG_TRIVIAL(error) << "FSCTL_DISMOUNT_VOLUME " << e2 << " ; " << deviceControlRetVal << " ; " << GetLastError(); + // some implemenatations also calls IOCTL_STORAGE_MEDIA_REMOVAL here with FALSE as third parameter, which should set PreventMediaRemoval BOOL error = DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); if (error == 0) { CloseHandle(handle); BOOST_LOG_TRIVIAL(error) << "Ejecting " << mpath << " failed (IOCTL_STORAGE_EJECT_MEDIA)" << deviceControlRetVal << " " << GetLastError(); assert(m_callback_evt_handler); - if (m_callback_evt_handler) + if (m_callback_evt_handler) wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(*it_drive_data, false))); return; } @@ -122,6 +330,7 @@ void RemovableDriveManager::eject_drive() wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true))); m_current_drives.erase(it_drive_data); } +#endif // 0 } std::string RemovableDriveManager::get_removable_drive_path(const std::string &path)