#include "ShrinkwrapManager.h" #include #include #include #include #include #include #include #include #define NOMINMAX #include // 取消Windows API宏定义,避免与OTK方法冲突 #ifdef GetCurrentDirectory #undef GetCurrentDirectory #endif #include #include #include // For _access on Windows ShrinkwrapManager* ShrinkwrapManager::instance = nullptr; ShrinkwrapManager& ShrinkwrapManager::Instance() { if (instance == nullptr) { instance = new ShrinkwrapManager(); } return *instance; } SessionInfo ShrinkwrapManager::GetSessionInfo() { SessionInfo info; info.is_valid = false; try { info.session = pfcGetProESession(); if (info.session) { info.wSession = wfcWSession::cast(info.session); if (info.wSession) { info.is_valid = true; try { xstring version_xstr = pfcGetProEVersion(); if (version_xstr != xstringnil && !version_xstr.IsEmpty()) { info.version = StringToStdString(version_xstr); } else { info.version = "Unknown"; } } catch (...) { info.version = "Unknown"; } } } } catch (...) { info.is_valid = false; } return info; } std::string ShrinkwrapManager::StringToStdString(const xstring& xstr) { try { if (xstr.IsNull()) { return ""; } std::wstring wstr(xstr); if (wstr.empty()) { return ""; } std::string result(wstr.begin(), wstr.end()); return result; } catch (...) { return ""; } } xstring ShrinkwrapManager::StringToXString(const std::string& str) { try { std::wstring wstr(str.begin(), str.end()); return xstring(wstr.c_str()); } catch (...) { return xstringnil; } } std::string ShrinkwrapManager::GetCurrentTimeString() { auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); std::stringstream ss; ss << std::put_time(std::localtime(&time_t), "%Y-%m-%dT%H:%M:%S"); return ss.str(); } std::string ShrinkwrapManager::GenerateSafePartName(pfcModel_ptr source_model) { if (!source_model) { // Fallback to timestamp-based name auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); std::stringstream ss; ss << "shrinkwrap_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S"); return ss.str(); } try { // Get source model name xstring name_xstr = source_model->GetFileName(); std::string base_name = StringToStdString(name_xstr); // Remove extension size_t dot_pos = base_name.find_last_of("."); if (dot_pos != std::string::npos) { base_name = base_name.substr(0, dot_pos); } // Clean non-ASCII characters, keep only alphanumeric and underscore std::string safe_name; safe_name.reserve(base_name.length()); for (char c : base_name) { if (std::isalnum(static_cast(c)) || c == '_') { safe_name += c; } else if (!safe_name.empty() && safe_name.back() != '_') { safe_name += '_'; } } // Remove trailing underscores while (!safe_name.empty() && safe_name.back() == '_') { safe_name.pop_back(); } // Ensure name is not empty if (safe_name.empty()) { safe_name = "model"; } // Limit length (Creo has 31 character limit, reserve space for suffix) if (safe_name.length() > 20) { safe_name = safe_name.substr(0, 20); } // Add shrinkwrap suffix return safe_name + "_shrink"; } catch (...) { // Fallback to timestamp-based name auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); std::stringstream ss; ss << "shrinkwrap_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S"); return ss.str(); } } std::string ShrinkwrapManager::CalculateFileSize(const std::string& file_path) { try { HANDLE hFile = CreateFileA(file_path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { LARGE_INTEGER fileSize; if (GetFileSizeEx(hFile, &fileSize)) { CloseHandle(hFile); double size_mb = static_cast(fileSize.QuadPart) / (1024.0 * 1024.0); std::stringstream ss; ss << std::fixed << std::setprecision(1) << size_mb << "MB"; return ss.str(); } CloseHandle(hFile); } } catch (...) { // File access error } return "0.0MB"; } bool ShrinkwrapManager::ValidateRequest(const ShrinkwrapShellRequest& request, std::string& error_message) { if (request.software_type != "creo") { error_message = "Invalid software_type, must be 'creo'"; return false; } if (request.quality < 1 || request.quality > 10) { error_message = "Quality must be between 1 and 10"; return false; } if (request.small_surface_percentage < 0.0 || request.small_surface_percentage > 100.0) { error_message = "Small surface percentage must be between 0.0 and 100.0"; return false; } return true; } bool ShrinkwrapManager::IsCreoSessionAvailable() { SessionInfo session_info = GetSessionInfo(); return session_info.is_valid; } std::string ShrinkwrapManager::GetSupportedFormats() { return "Assembly models only - outputs Part (.prt) files"; } void ShrinkwrapManager::ApplyPresetParameters(ShrinkwrapShellRequest& request) { if (request.preset == "fast") { // Fast档:最快速生成,最保守设置 request.quality = 3; request.chord_height = 0.3; request.ignore_small_surfaces = false; // 关闭小面过滤 request.small_surface_percentage = 0.0; // 不过滤 request.fill_holes = false; // 不填孔 request.output_type = "solid_surface"; } else if (request.preset == "balanced") { // Balanced档:平衡设置,适合大多数情况 request.quality = 5; request.chord_height = 0.15; request.ignore_small_surfaces = false; // 仍然关闭小面过滤避免卡死 request.small_surface_percentage = 0.0; request.fill_holes = false; // 暂时不填孔 request.output_type = "solid_surface"; } else if (request.preset == "tight") { // Tight档:相对高精度,但仍然保守 request.quality = 5; // 限制在5以内避免卡死 request.chord_height = 0.1; request.ignore_small_surfaces = false; // 不过滤小面 request.small_surface_percentage = 0.0; request.fill_holes = false; // 暂时不填孔,等稳定后再开启 request.output_type = "solid_surface"; } // 空字符串或其他值保持默认参数 } double ShrinkwrapManager::CalculateModelVolume(pfcModel_ptr model) { if (!model) return 0.0; try { pfcSolid_ptr solid = pfcSolid::cast(model); if (solid) { pfcMassProperty_ptr mass_props = solid->GetMassProperty(nullptr); if (mass_props) { return mass_props->GetVolume(); } } } catch (...) { // Volume calculation failed } return 0.0; } bool ShrinkwrapManager::FileExists(const std::string& filepath) { // Use _access for Windows platform return (_access(filepath.c_str(), 0) == 0); } std::string ShrinkwrapManager::GenerateUniqueFilePath(const std::string& base_path, const std::string& file_name) { // Construct full path std::string full_path = base_path + "\\" + file_name; // Return original path if file doesn't exist if (!FileExists(full_path)) { return full_path; } // Extract name and extension size_t dot_pos = file_name.find_last_of('.'); std::string name_without_ext = (dot_pos != std::string::npos) ? file_name.substr(0, dot_pos) : file_name; std::string extension = (dot_pos != std::string::npos) ? file_name.substr(dot_pos) : ""; // Try different suffixes int counter = 1; const int max_attempts = 999; // Limit maximum attempts while (counter <= max_attempts) { std::string new_file_name = name_without_ext + "_" + std::to_string(counter) + extension; full_path = base_path + "\\" + new_file_name; if (!FileExists(full_path)) { return full_path; } counter++; } // Use timestamp as fallback if all attempts exhausted auto now = std::chrono::system_clock::now(); auto timestamp = std::chrono::duration_cast(now.time_since_epoch()).count(); std::string timestamp_file_name = name_without_ext + "_" + std::to_string(timestamp) + extension; return base_path + "\\" + timestamp_file_name; } pfcModel_ptr ShrinkwrapManager::FindComponentByPath(wfcWAssembly_ptr assembly, const std::string& component_path, const SessionInfo& session_info) { if (!assembly || component_path.empty()) return nullptr; try { // Parse path: split by '/' or '\' std::vector path_segments; std::string path = component_path; std::replace(path.begin(), path.end(), '\\', '/'); std::istringstream iss(path); std::string segment; while (std::getline(iss, segment, '/')) { if (!segment.empty()) { path_segments.push_back(segment); } } if (path_segments.empty()) return nullptr; // Get current assembly name to check if first segment is the top-level xstring asm_name_xstr = pfcModel::cast(assembly)->GetFileName(); std::string asm_name = StringToStdString(asm_name_xstr); // If first segment matches top-level assembly name, skip it size_t start_index = 0; if (!path_segments.empty() && !asm_name.empty()) { // Case-insensitive comparison std::string seg_lower = path_segments[0]; std::string asm_lower = asm_name; std::transform(seg_lower.begin(), seg_lower.end(), seg_lower.begin(), ::tolower); std::transform(asm_lower.begin(), asm_lower.end(), asm_lower.begin(), ::tolower); if (seg_lower == asm_lower) { start_index = 1; } } // If nothing left after skipping top-level, return the assembly itself if (start_index >= path_segments.size()) { return pfcModel::cast(assembly); } // Recursively search for target component wfcWAssembly_ptr current_asm = assembly; for (size_t i = start_index; i < path_segments.size(); i++) { std::string target_name = path_segments[i]; std::string target_lower = target_name; std::transform(target_lower.begin(), target_lower.end(), target_lower.begin(), ::tolower); pfcFeatures_ptr features = current_asm->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT); if (!features) return nullptr; bool found = false; int features_count = features->getarraysize(); for (int j = 0; j < features_count; j++) { try { pfcFeature_ptr feature = features->get(j); if (!feature) continue; pfcComponentFeat_ptr comp_feat = pfcComponentFeat::cast(feature); if (!comp_feat) continue; auto model_descr = comp_feat->GetModelDescr(); if (!model_descr) continue; xstring comp_name_xstr = model_descr->GetFileName(); std::string comp_name = StringToStdString(comp_name_xstr); std::string comp_lower = comp_name; std::transform(comp_lower.begin(), comp_lower.end(), comp_lower.begin(), ::tolower); if (comp_lower == target_lower) { // Found matching component pfcModel_ptr comp_model = session_info.session->GetModelFromDescr(model_descr); if (!comp_model) { comp_model = session_info.session->RetrieveModel(model_descr); } if (!comp_model) continue; // If this is the last segment, return the model if (i == path_segments.size() - 1) { return comp_model; } // Otherwise, continue searching in this sub-assembly if (comp_model->GetType() == pfcMDL_ASSEMBLY) { current_asm = wfcWAssembly::cast(comp_model); if (current_asm) { found = true; break; } } } } catch (...) { continue; } } if (!found) return nullptr; } return nullptr; } catch (...) { return nullptr; } } pfcModel_ptr ShrinkwrapManager::ExecuteOTKShrinkwrap(pfcModel_ptr source_model, const ShrinkwrapShellRequest& request, std::string& error_detail) { if (!source_model) { error_detail = "Source model is null"; return nullptr; } SessionInfo session_info = GetSessionInfo(); if (!session_info.is_valid) { error_detail = "Creo session is not valid"; return nullptr; } try { // Step 1: Cast to Solid pfcSolid_ptr source_solid = nullptr; try { source_solid = pfcSolid::cast(source_model); } catch (...) { error_detail = "Exception when casting source model to Solid"; return nullptr; } if (!source_solid) { error_detail = "Failed to cast source model to Solid. Model type may not support Shrinkwrap."; return nullptr; } // Step 2: Generate safe Part name std::string base_name; try { base_name = GenerateSafePartName(source_model); } catch (...) { error_detail = "Exception when generating safe part name"; return nullptr; } // Step 3: Ensure unique name by checking existing models std::string unique_filename = base_name; int counter = 1; xstring test_name = StringToXString(unique_filename); while (true) { try { pfcModel_ptr existing_model = session_info.session->GetModel(test_name, pfcMDL_PART); if (existing_model) { unique_filename = base_name + "_" + std::to_string(counter); test_name = StringToXString(unique_filename); counter++; } else { break; } } catch (...) { break; } } // Step 4: Create output Part with unique name // Get working directory first to check for existing files std::string working_dir; try { xstring working_dir_xstr = session_info.session->GetCurrentDirectory(); working_dir = StringToStdString(working_dir_xstr); } catch (...) { working_dir = ""; } // Generate unique filename by checking both session and disk int name_counter = 0; const int max_name_attempts = 100; std::string final_filename = base_name; while (name_counter < max_name_attempts) { if (name_counter > 0) { final_filename = base_name + "_" + std::to_string(name_counter); } // Ensure filename is not too long (Creo limit is 31 characters) if (final_filename.length() > 31) { final_filename = final_filename.substr(0, 25) + "_" + std::to_string(name_counter); } // Check if file exists on disk std::string file_path = working_dir + "\\" + final_filename + ".prt"; bool file_exists_on_disk = FileExists(file_path); // Check if model exists in session bool model_exists_in_session = false; try { xstring check_name = StringToXString(final_filename); pfcModel_ptr existing_model = session_info.session->GetModel(check_name, pfcMDL_PART); if (existing_model) { model_exists_in_session = true; // Try to erase from session try { existing_model->Erase(); model_exists_in_session = false; } catch (...) { // Erase failed } } } catch (...) { // GetModel throws exception if model doesn't exist } if (!file_exists_on_disk && !model_exists_in_session) { break; // Found a unique name } name_counter++; } if (name_counter >= max_name_attempts) { error_detail = "Could not find unique filename after " + std::to_string(max_name_attempts) + " attempts"; return nullptr; } xstring output_name = StringToXString(final_filename); pfcPart_ptr output_part = nullptr; try { output_part = session_info.session->CreatePart(output_name); } catch (...) { error_detail = "Exception when creating output Part: " + final_filename + ". Working dir: " + working_dir; return nullptr; } if (!output_part) { error_detail = "Failed to create output Part: " + final_filename; return nullptr; } pfcModel_ptr output_model = pfcModel::cast(output_part); if (!output_model) { error_detail = "Failed to cast output Part to Model"; return nullptr; } // Step 5: Create Shrinkwrap instructions pfcShrinkwrapSurfaceSubsetInstructions_ptr instructions = nullptr; try { instructions = pfcShrinkwrapSurfaceSubsetInstructions::Create(output_model); } catch (...) { error_detail = "Exception when creating ShrinkwrapSurfaceSubsetInstructions"; return nullptr; } if (!instructions) { error_detail = "Failed to create ShrinkwrapSurfaceSubsetInstructions"; return nullptr; } // Step 6: Set parameters try { instructions->SetQuality(request.quality); instructions->SetAutoHoleFilling(request.fill_holes); instructions->SetIgnoreSmallSurfaces(request.ignore_small_surfaces); instructions->SetSmallSurfPercentage(request.small_surface_percentage); instructions->SetIgnoreQuilts(request.ignore_quilts); instructions->SetIgnoreSkeleton(request.ignore_skeleton); instructions->SetAssignMassProperties(request.assign_mass_properties); } catch (...) { error_detail = "Exception when setting Shrinkwrap parameters"; return nullptr; } // Step 7: Cast to export instructions pfcShrinkwrapExportInstructions_ptr export_instructions = pfcShrinkwrapExportInstructions::cast(instructions); if (!export_instructions) { error_detail = "Failed to cast instructions to ShrinkwrapExportInstructions"; return nullptr; } // Step 8: Execute ExportShrinkwrap try { source_solid->ExportShrinkwrap(export_instructions); } catch (...) { error_detail = "Exception during ExportShrinkwrap execution - model may be too complex"; return nullptr; } try { if (output_model) { // Get current working directory xstring current_dir_xstr = session_info.session->GetCurrentDirectory(); std::string save_path = StringToStdString(current_dir_xstr); // Get model file name with extension xstring model_name_xstr = output_model->GetFileName(); std::string file_name = StringToStdString(model_name_xstr); // Add .prt extension if not present if (file_name.find(".prt") == std::string::npos && file_name.find(".PRT") == std::string::npos) { file_name += ".prt"; } // Check for existing file and generate unique path std::string unique_file_path = GenerateUniqueFilePath(save_path, file_name); // Extract new file name if path was modified size_t last_slash = unique_file_path.find_last_of('\\'); if (last_slash != std::string::npos) { std::string new_file_name = unique_file_path.substr(last_slash + 1); // Remove extension to get model name size_t dot_pos = new_file_name.find_last_of('.'); if (dot_pos != std::string::npos) { new_file_name = new_file_name.substr(0, dot_pos); } // Rename model if name changed std::string original_name = StringToStdString(model_name_xstr); size_t orig_dot_pos = original_name.find_last_of('.'); if (orig_dot_pos != std::string::npos) { original_name = original_name.substr(0, orig_dot_pos); } if (new_file_name != original_name) { xstring new_name_xstr = StringToXString(new_file_name); output_model->Rename(new_name_xstr); } } // Save model to disk output_model->Save(); } } catch (...) { // Save failed but return model anyway // Model exists in session even if save failed } return pfcModel::cast(output_part); } catch (const pfcXToolkitError& e) { error_detail = "OTK Toolkit Error during ExportShrinkwrap"; return nullptr; } catch (const pfcXBadArgument& e) { error_detail = "Bad Argument Error: Invalid parameter passed to Shrinkwrap API"; return nullptr; } catch (const pfcXToolkitOutOfMemory& e) { error_detail = "Out of Memory Error during Shrinkwrap operation"; return nullptr; } catch (const pfcXToolkitGeneralError& e) { error_detail = "General OTK Error during ExportShrinkwrap - model may be too complex or have invalid geometry"; return nullptr; } catch (const std::bad_alloc& e) { error_detail = "System memory allocation failed"; return nullptr; } catch (const std::exception& e) { error_detail = std::string("Standard exception: ") + e.what(); return nullptr; } catch (...) { error_detail = "Unknown exception during Shrinkwrap operation"; return nullptr; } } ShrinkwrapShellResult ShrinkwrapManager::ExecuteShrinkwrapShell(const ShrinkwrapShellRequest& request) { ShrinkwrapShellResult result; // Apply preset parameters (if specified) ShrinkwrapShellRequest processed_request = request; if (!processed_request.preset.empty()) { ApplyPresetParameters(processed_request); } // Validate request std::string validation_error; if (!ValidateRequest(processed_request, validation_error)) { result.success = false; result.error_message = validation_error; return result; } // Check Creo session SessionInfo session_info = GetSessionInfo(); if (!session_info.is_valid) { result.success = false; result.error_message = "Creo session not available"; return result; } try { // Get current model pfcModel_ptr current_model = session_info.session->GetCurrentModel(); if (!current_model) { result.success = false; result.error_message = "No current model loaded"; return result; } // Check model type - Shrinkwrap works for both assembly and part if (current_model->GetType() != pfcMDL_ASSEMBLY && current_model->GetType() != pfcMDL_PART) { result.success = false; result.error_message = "Shrinkwrap shell export requires a part or assembly model. Current model type is not supported."; return result; } // Determine target model for shrinkwrap pfcModel_ptr target_model = current_model; // If component_path is specified, find the sub-assembly if (!processed_request.component_path.empty()) { if (current_model->GetType() != pfcMDL_ASSEMBLY) { result.success = false; result.error_message = "component_path can only be used when current model is an assembly."; return result; } wfcWAssembly_ptr current_assembly = wfcWAssembly::cast(current_model); if (!current_assembly) { result.success = false; result.error_message = "Failed to cast current model to assembly."; return result; } // Parse component path and find target component target_model = FindComponentByPath(current_assembly, processed_request.component_path, session_info); if (!target_model) { result.success = false; result.error_message = "Component not found at path: " + processed_request.component_path; return result; } // Verify target is a solid (part or assembly) if (target_model->GetType() != pfcMDL_ASSEMBLY && target_model->GetType() != pfcMDL_PART) { result.success = false; result.error_message = "Target component must be a part or assembly for shrinkwrap."; return result; } } // Execute Shrinkwrap on target model pfcModel_ptr shrinkwrap_model = nullptr; std::string shrinkwrap_error_detail; try { shrinkwrap_model = ExecuteOTKShrinkwrap(target_model, processed_request, shrinkwrap_error_detail); } catch (const pfcXToolkitError& e) { result.success = false; result.error_message = "OTK Toolkit Error: Creo operation failed. This may indicate model corruption or invalid geometry."; return result; } catch (const pfcXBadArgument& e) { result.success = false; result.error_message = "Invalid Parameters: One or more shrinkwrap parameters are invalid. Please check quality, chord_height and other settings."; return result; } catch (const pfcXToolkitOutOfMemory& e) { result.success = false; result.error_message = "Memory Error: Insufficient memory to process the model. Try reducing quality or using a simpler preset."; return result; } catch (const std::bad_alloc& e) { result.success = false; result.error_message = "System Memory Error: Out of memory. Please close other applications and try again."; return result; } catch (...) { result.success = false; result.error_message = "Unknown Error: An unexpected error occurred during shrinkwrap processing."; return result; } if (!shrinkwrap_model) { result.success = false; result.error_message = "Shrinkwrap Processing Failed: " + shrinkwrap_error_detail; return result; } // Get output file information try { xstring current_dir = session_info.session->GetCurrentDirectory(); std::string actual_save_path = StringToStdString(current_dir); xstring model_name = shrinkwrap_model->GetFileName(); std::string actual_filename = StringToStdString(model_name); // Set result parameters result.parameters.output_file_path = actual_save_path + "\\" + actual_filename; result.parameters.output_file_size = CalculateFileSize(result.parameters.output_file_path); result.parameters.shrinkwrap_time = GetCurrentTimeString(); result.parameters.method = "creo_native_shrinkwrap"; result.parameters.quality = processed_request.quality; result.parameters.fill_holes = processed_request.fill_holes; result.parameters.ignore_small_surfaces = processed_request.ignore_small_surfaces; result.parameters.small_surface_percentage = processed_request.small_surface_percentage; result.parameters.ignore_quilts = processed_request.ignore_quilts; result.parameters.ignore_skeleton = processed_request.ignore_skeleton; result.parameters.assign_mass_properties = processed_request.assign_mass_properties; result.parameters.preset_used = processed_request.preset; result.success = true; result.message = "Shrinkwrap shell export completed successfully"; } catch (...) { // Failed to get file info, but shrinkwrap succeeded result.parameters.method = "creo_native_shrinkwrap"; result.parameters.quality = processed_request.quality; result.success = true; result.message = "Shrinkwrap completed but file info unavailable"; } } catch (const std::exception& e) { result.success = false; result.error_message = "Standard error: " + std::string(e.what()); } catch (...) { result.success = false; result.error_message = "Unknown error during shrinkwrap operation"; } return result; }