feat: 添加子装配体层级删除接口 /api/creo/subassembly/hierarchy/delete
This commit is contained in:
parent
04edf96378
commit
b95031026a
4
.gitignore
vendored
4
.gitignore
vendored
@ -17,4 +17,6 @@ otk_cpp_doc/
|
||||
*.tmp_proj
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.sln.docstates
|
||||
.x64
|
||||
.MFCCreoDll
|
||||
@ -162,6 +162,25 @@ std::string BatchOperationManager::BuildOpenResultJson(const OpenResult& result)
|
||||
return json.str();
|
||||
}
|
||||
|
||||
// Build JSON for workflow step
|
||||
std::string BatchOperationManager::BuildWorkflowStepJson(const std::string& step_name,
|
||||
bool success,
|
||||
const std::string& time,
|
||||
const std::string& error) {
|
||||
std::ostringstream json;
|
||||
json << "{"
|
||||
<< "\"step\":\"" << EscapeJsonString(step_name) << "\","
|
||||
<< "\"success\":" << (success ? "true" : "false") << ","
|
||||
<< "\"time\":\"" << EscapeJsonString(time) << "\"";
|
||||
|
||||
if (!error.empty()) {
|
||||
json << ",\"error\":\"" << EscapeJsonString(error) << "\"";
|
||||
}
|
||||
|
||||
json << "}";
|
||||
return json.str();
|
||||
}
|
||||
|
||||
// Execute save_model operation
|
||||
BatchOperationResult BatchOperationManager::ExecuteSaveModel(int index, const BatchOperation& operation) {
|
||||
BatchOperationResult result;
|
||||
@ -503,6 +522,575 @@ BatchOperationResult BatchOperationManager::ExecuteOpenModel(int index, const Ba
|
||||
return result;
|
||||
}
|
||||
|
||||
// Execute workflow_hierarchy_delete operation
|
||||
BatchOperationResult BatchOperationManager::ExecuteHierarchyDeleteWorkflow(int index, const BatchOperation& operation) {
|
||||
BatchOperationResult result;
|
||||
result.operation_index = index;
|
||||
result.operation_type = "workflow_hierarchy_delete";
|
||||
|
||||
auto workflow_start = std::chrono::high_resolution_clock::now();
|
||||
std::vector<std::string> steps_json;
|
||||
|
||||
try {
|
||||
// Extract parameters
|
||||
std::string working_directory;
|
||||
std::string input_file_path;
|
||||
std::string open_mode = "active";
|
||||
std::string project_name;
|
||||
std::string target_level_str;
|
||||
std::string export_path;
|
||||
std::string geom_flags = "solids";
|
||||
|
||||
auto it = operation.parameters.find("working_directory");
|
||||
if (it != operation.parameters.end()) {
|
||||
working_directory = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("input_file_path");
|
||||
if (it != operation.parameters.end()) {
|
||||
input_file_path = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("open_mode");
|
||||
if (it != operation.parameters.end()) {
|
||||
open_mode = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("project_name");
|
||||
if (it != operation.parameters.end()) {
|
||||
project_name = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("target_level");
|
||||
if (it != operation.parameters.end()) {
|
||||
target_level_str = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("export_path");
|
||||
if (it != operation.parameters.end()) {
|
||||
export_path = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("geom_flags");
|
||||
if (it != operation.parameters.end()) {
|
||||
geom_flags = it->second;
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if (input_file_path.empty()) {
|
||||
result.success = false;
|
||||
result.error_message = "Missing required parameter: input_file_path";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (project_name.empty()) {
|
||||
result.success = false;
|
||||
result.error_message = "Missing required parameter: project_name";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (target_level_str.empty()) {
|
||||
result.success = false;
|
||||
result.error_message = "Missing required parameter: target_level";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (export_path.empty()) {
|
||||
result.success = false;
|
||||
result.error_message = "Missing required parameter: export_path";
|
||||
return result;
|
||||
}
|
||||
|
||||
int target_level = 0;
|
||||
try {
|
||||
target_level = std::stoi(target_level_str);
|
||||
} catch (...) {
|
||||
result.success = false;
|
||||
result.error_message = "Invalid target_level value";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 1: Set working directory (if provided)
|
||||
if (!working_directory.empty()) {
|
||||
auto step_start = std::chrono::high_resolution_clock::now();
|
||||
ChangeDirectoryResult dir_result = CreoUtilities::SetWorkingDirectory(working_directory);
|
||||
auto step_end = std::chrono::high_resolution_clock::now();
|
||||
auto step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("set_directory", dir_result.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
dir_result.success ? "" : dir_result.error_message));
|
||||
|
||||
if (!dir_result.success) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to set working directory: " + dir_result.error_message;
|
||||
result.result_json = "{\"steps\":[" + steps_json[0] + "]}";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Open model
|
||||
auto step_start = std::chrono::high_resolution_clock::now();
|
||||
OpenResult open_result = CreoManager::Instance().OpenModel(input_file_path, open_mode);
|
||||
auto step_end = std::chrono::high_resolution_clock::now();
|
||||
auto step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("open_model", open_result.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
open_result.success ? "" : open_result.error_message));
|
||||
|
||||
if (!open_result.success) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to open model: " + open_result.error_message;
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 3: Hierarchy delete
|
||||
step_start = std::chrono::high_resolution_clock::now();
|
||||
CreoManager::HierarchyDeleteResult delete_result =
|
||||
CreoManager::Instance().DeleteHierarchyComponents(project_name, target_level);
|
||||
step_end = std::chrono::high_resolution_clock::now();
|
||||
step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("hierarchy_delete", delete_result.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
delete_result.success ? "" : delete_result.error_message));
|
||||
|
||||
if (!delete_result.success) {
|
||||
// Try to close model before returning error
|
||||
try {
|
||||
CreoManager::Instance().CloseModel(true);
|
||||
} catch (...) {}
|
||||
|
||||
result.success = false;
|
||||
result.error_message = "Failed to delete hierarchy: " + delete_result.error_message;
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 4: Export STEP
|
||||
step_start = std::chrono::high_resolution_clock::now();
|
||||
ExportResult export_result = CreoManager::Instance().ExportModelToSTEP(export_path, geom_flags);
|
||||
step_end = std::chrono::high_resolution_clock::now();
|
||||
step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("export_step", export_result.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
export_result.success ? "" : export_result.error_message));
|
||||
|
||||
if (!export_result.success) {
|
||||
// Try to close model before returning error
|
||||
try {
|
||||
CreoManager::Instance().CloseModel(true);
|
||||
} catch (...) {}
|
||||
|
||||
result.success = false;
|
||||
result.error_message = "Failed to export STEP: " + export_result.error_message;
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 5: Close model
|
||||
step_start = std::chrono::high_resolution_clock::now();
|
||||
CloseResult close_result = CreoManager::Instance().CloseModel(false);
|
||||
step_end = std::chrono::high_resolution_clock::now();
|
||||
step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("close_model", close_result.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
close_result.success ? "" : close_result.error_message));
|
||||
|
||||
// Build final result
|
||||
auto workflow_end = std::chrono::high_resolution_clock::now();
|
||||
auto workflow_duration = std::chrono::duration_cast<std::chrono::milliseconds>(workflow_end - workflow_start);
|
||||
result.execution_time = std::to_string(workflow_duration.count()) + "ms";
|
||||
|
||||
std::ostringstream final_json;
|
||||
final_json << "{"
|
||||
<< "\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) final_json << ",";
|
||||
final_json << steps_json[i];
|
||||
}
|
||||
final_json << "],"
|
||||
<< "\"final_output\":\"" << EscapeJsonString(export_path) << "\","
|
||||
<< "\"final_size\":\"" << EscapeJsonString(export_result.file_size) << "\""
|
||||
<< "}";
|
||||
|
||||
result.success = true;
|
||||
result.result_json = final_json.str();
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
// Try to close model on exception
|
||||
try {
|
||||
CreoManager::Instance().CloseModel(true);
|
||||
} catch (...) {}
|
||||
|
||||
result.success = false;
|
||||
result.error_message = "Exception in workflow: " + std::string(e.what());
|
||||
|
||||
if (!steps_json.empty()) {
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
}
|
||||
} catch (...) {
|
||||
// Try to close model on exception
|
||||
try {
|
||||
CreoManager::Instance().CloseModel(true);
|
||||
} catch (...) {}
|
||||
|
||||
result.success = false;
|
||||
result.error_message = "Unknown error in workflow";
|
||||
|
||||
if (!steps_json.empty()) {
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Execute workflow_shrinkwrap operation
|
||||
BatchOperationResult BatchOperationManager::ExecuteShrinkwrapWorkflow(int index, const BatchOperation& operation) {
|
||||
BatchOperationResult result;
|
||||
result.operation_index = index;
|
||||
result.operation_type = "workflow_shrinkwrap";
|
||||
|
||||
auto workflow_start = std::chrono::high_resolution_clock::now();
|
||||
std::vector<std::string> steps_json;
|
||||
|
||||
try {
|
||||
// Extract parameters
|
||||
std::string working_directory;
|
||||
std::string input_file_path;
|
||||
std::string shrinkwrap_output_path;
|
||||
std::string quality_str = "5";
|
||||
std::string chord_height_str = "0.5";
|
||||
std::string step_export_path;
|
||||
std::string geom_flags = "solids";
|
||||
|
||||
auto it = operation.parameters.find("working_directory");
|
||||
if (it != operation.parameters.end()) {
|
||||
working_directory = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("input_file_path");
|
||||
if (it != operation.parameters.end()) {
|
||||
input_file_path = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("shrinkwrap_output_path");
|
||||
if (it != operation.parameters.end()) {
|
||||
shrinkwrap_output_path = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("quality");
|
||||
if (it != operation.parameters.end() && !it->second.empty()) {
|
||||
quality_str = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("chord_height");
|
||||
if (it != operation.parameters.end() && !it->second.empty()) {
|
||||
chord_height_str = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("step_export_path");
|
||||
if (it != operation.parameters.end()) {
|
||||
step_export_path = it->second;
|
||||
}
|
||||
|
||||
it = operation.parameters.find("geom_flags");
|
||||
if (it != operation.parameters.end()) {
|
||||
geom_flags = it->second;
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if (input_file_path.empty()) {
|
||||
result.success = false;
|
||||
result.error_message = "Missing required parameter: input_file_path";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (shrinkwrap_output_path.empty()) {
|
||||
result.success = false;
|
||||
result.error_message = "Missing required parameter: shrinkwrap_output_path";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (step_export_path.empty()) {
|
||||
result.success = false;
|
||||
result.error_message = "Missing required parameter: step_export_path";
|
||||
return result;
|
||||
}
|
||||
|
||||
int quality = 5;
|
||||
double chord_height = 0.5;
|
||||
|
||||
try {
|
||||
quality = std::stoi(quality_str);
|
||||
} catch (...) {
|
||||
result.success = false;
|
||||
result.error_message = "Invalid quality value";
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
chord_height = std::stod(chord_height_str);
|
||||
} catch (...) {
|
||||
result.success = false;
|
||||
result.error_message = "Invalid chord_height value";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 1: Set working directory (if provided)
|
||||
if (!working_directory.empty()) {
|
||||
auto step_start = std::chrono::high_resolution_clock::now();
|
||||
ChangeDirectoryResult dir_result = CreoUtilities::SetWorkingDirectory(working_directory);
|
||||
auto step_end = std::chrono::high_resolution_clock::now();
|
||||
auto step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("set_directory", dir_result.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
dir_result.success ? "" : dir_result.error_message));
|
||||
|
||||
if (!dir_result.success) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to set working directory: " + dir_result.error_message;
|
||||
result.result_json = "{\"steps\":[" + steps_json[0] + "]}";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Open original model
|
||||
auto step_start = std::chrono::high_resolution_clock::now();
|
||||
OpenResult open_result = CreoManager::Instance().OpenModel(input_file_path, "active");
|
||||
auto step_end = std::chrono::high_resolution_clock::now();
|
||||
auto step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("open_model", open_result.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
open_result.success ? "" : open_result.error_message));
|
||||
|
||||
if (!open_result.success) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to open model: " + open_result.error_message;
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 3: Execute Shrinkwrap
|
||||
step_start = std::chrono::high_resolution_clock::now();
|
||||
ShrinkwrapShellRequest shrink_request;
|
||||
shrink_request.output_file_path = shrinkwrap_output_path;
|
||||
shrink_request.quality = quality;
|
||||
shrink_request.chord_height = chord_height;
|
||||
|
||||
ShrinkwrapShellResult shrink_result =
|
||||
ShrinkwrapManager::Instance().ExecuteShrinkwrapShell(shrink_request);
|
||||
step_end = std::chrono::high_resolution_clock::now();
|
||||
step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("shrinkwrap", shrink_result.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
shrink_result.success ? "" : shrink_result.error_message));
|
||||
|
||||
if (!shrink_result.success) {
|
||||
// Try to close model before returning error
|
||||
try {
|
||||
CreoManager::Instance().CloseModel(true);
|
||||
} catch (...) {}
|
||||
|
||||
result.success = false;
|
||||
result.error_message = "Failed to execute shrinkwrap: " + shrink_result.error_message;
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 4: Close original model
|
||||
step_start = std::chrono::high_resolution_clock::now();
|
||||
CloseResult close_original = CreoManager::Instance().CloseModel(true);
|
||||
step_end = std::chrono::high_resolution_clock::now();
|
||||
step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("close_original", close_original.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
close_original.success ? "" : close_original.error_message));
|
||||
|
||||
// Step 5: Open Shrinkwrap model
|
||||
step_start = std::chrono::high_resolution_clock::now();
|
||||
OpenResult open_shrink = CreoManager::Instance().OpenModel(shrinkwrap_output_path, "active");
|
||||
step_end = std::chrono::high_resolution_clock::now();
|
||||
step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("open_shrinkwrap_model", open_shrink.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
open_shrink.success ? "" : open_shrink.error_message));
|
||||
|
||||
if (!open_shrink.success) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to open shrinkwrap model: " + open_shrink.error_message;
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 6: Export STEP
|
||||
step_start = std::chrono::high_resolution_clock::now();
|
||||
ExportResult export_result = CreoManager::Instance().ExportModelToSTEP(step_export_path, geom_flags);
|
||||
step_end = std::chrono::high_resolution_clock::now();
|
||||
step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("export_step", export_result.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
export_result.success ? "" : export_result.error_message));
|
||||
|
||||
if (!export_result.success) {
|
||||
// Try to close model before returning error
|
||||
try {
|
||||
CreoManager::Instance().CloseModel(true);
|
||||
} catch (...) {}
|
||||
|
||||
result.success = false;
|
||||
result.error_message = "Failed to export STEP: " + export_result.error_message;
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 7: Close shrinkwrap model
|
||||
step_start = std::chrono::high_resolution_clock::now();
|
||||
CloseResult close_shrink = CreoManager::Instance().CloseModel(true);
|
||||
step_end = std::chrono::high_resolution_clock::now();
|
||||
step_duration = std::chrono::duration_cast<std::chrono::milliseconds>(step_end - step_start);
|
||||
|
||||
steps_json.push_back(BuildWorkflowStepJson("close_shrinkwrap", close_shrink.success,
|
||||
std::to_string(step_duration.count()) + "ms",
|
||||
close_shrink.success ? "" : close_shrink.error_message));
|
||||
|
||||
// Build final result
|
||||
auto workflow_end = std::chrono::high_resolution_clock::now();
|
||||
auto workflow_duration = std::chrono::duration_cast<std::chrono::milliseconds>(workflow_end - workflow_start);
|
||||
result.execution_time = std::to_string(workflow_duration.count()) + "ms";
|
||||
|
||||
std::ostringstream final_json;
|
||||
final_json << "{"
|
||||
<< "\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) final_json << ",";
|
||||
final_json << steps_json[i];
|
||||
}
|
||||
final_json << "],"
|
||||
<< "\"final_output\":\"" << EscapeJsonString(step_export_path) << "\","
|
||||
<< "\"final_size\":\"" << EscapeJsonString(export_result.file_size) << "\""
|
||||
<< "}";
|
||||
|
||||
result.success = true;
|
||||
result.result_json = final_json.str();
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
// Try to close model on exception
|
||||
try {
|
||||
CreoManager::Instance().CloseModel(true);
|
||||
} catch (...) {}
|
||||
|
||||
result.success = false;
|
||||
result.error_message = "Exception in workflow: " + std::string(e.what());
|
||||
|
||||
if (!steps_json.empty()) {
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
}
|
||||
} catch (...) {
|
||||
// Try to close model on exception
|
||||
try {
|
||||
CreoManager::Instance().CloseModel(true);
|
||||
} catch (...) {}
|
||||
|
||||
result.success = false;
|
||||
result.error_message = "Unknown error in workflow";
|
||||
|
||||
if (!steps_json.empty()) {
|
||||
std::ostringstream steps_array;
|
||||
steps_array << "{\"steps\":[";
|
||||
for (size_t i = 0; i < steps_json.size(); i++) {
|
||||
if (i > 0) steps_array << ",";
|
||||
steps_array << steps_json[i];
|
||||
}
|
||||
steps_array << "]}";
|
||||
result.result_json = steps_array.str();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Main entry point: Execute batch operations
|
||||
BatchOperationsResponse BatchOperationManager::ExecuteBatchOperations(const BatchOperationsRequest& request) {
|
||||
BatchOperationsResponse response;
|
||||
@ -543,6 +1131,10 @@ BatchOperationsResponse BatchOperationManager::ExecuteBatchOperations(const Batc
|
||||
op_result = ExecuteCloseModel(static_cast<int>(i), operation);
|
||||
} else if (operation.operation_type == "open_model") {
|
||||
op_result = ExecuteOpenModel(static_cast<int>(i), operation);
|
||||
} else if (operation.operation_type == "workflow_hierarchy_delete") {
|
||||
op_result = ExecuteHierarchyDeleteWorkflow(static_cast<int>(i), operation);
|
||||
} else if (operation.operation_type == "workflow_shrinkwrap") {
|
||||
op_result = ExecuteShrinkwrapWorkflow(static_cast<int>(i), operation);
|
||||
} else {
|
||||
// Unknown operation type
|
||||
op_result.operation_index = static_cast<int>(i);
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "CreoManager.h"
|
||||
#include "PathDeleteManager.h"
|
||||
#include "ShrinkwrapManager.h"
|
||||
#include "CreoUtilities.h"
|
||||
|
||||
// Single operation request structure
|
||||
struct BatchOperation {
|
||||
@ -69,6 +70,10 @@ private:
|
||||
BatchOperationResult ExecuteCloseModel(int index, const BatchOperation& operation);
|
||||
BatchOperationResult ExecuteOpenModel(int index, const BatchOperation& operation);
|
||||
|
||||
// Execute workflow operations
|
||||
BatchOperationResult ExecuteHierarchyDeleteWorkflow(int index, const BatchOperation& operation);
|
||||
BatchOperationResult ExecuteShrinkwrapWorkflow(int index, const BatchOperation& operation);
|
||||
|
||||
// Helper functions
|
||||
std::string GetCurrentTimeString();
|
||||
std::string EscapeJsonString(const std::string& str);
|
||||
@ -79,4 +84,8 @@ private:
|
||||
std::string BuildShrinkwrapResultJson(const ShrinkwrapShellResult& result);
|
||||
std::string BuildCloseResultJson(const CloseResult& result);
|
||||
std::string BuildOpenResultJson(const OpenResult& result);
|
||||
|
||||
// Workflow step helper
|
||||
std::string BuildWorkflowStepJson(const std::string& step_name, bool success,
|
||||
const std::string& time, const std::string& error = "");
|
||||
};
|
||||
|
||||
82
CLAUDE.md
82
CLAUDE.md
@ -1,82 +0,0 @@
|
||||
# MFC Creo DLL 项目文档
|
||||
|
||||
## 项目概述
|
||||
|
||||
MFC动态链接库(DLL),为Creo CAD软件提供RESTful API服务。
|
||||
|
||||
**技术栈:** MFC + OTK/ProToolkit + Windows Socket
|
||||
**服务端口:** 12345
|
||||
**架构特点:** 无锁线程通信,跨主机部署支持
|
||||
|
||||
## 核心功能模块
|
||||
|
||||
### 模型操作
|
||||
- 状态检测 - Creo连接状态、模型状态实时监控
|
||||
- 生命周期 - 打开/关闭/保存模型
|
||||
- STEP导出 - 装配体和零件导出
|
||||
- Shrinkwrap导出 - 外壳模型生成
|
||||
|
||||
### 分析算法
|
||||
- **层级分析** - 装配体结构遍历,支持指定层级返回
|
||||
- **薄壳化分析** - PCA-based OBB精确几何分析,识别外壳组件
|
||||
- **几何复杂度** - 多维度复杂度评估排序
|
||||
- **层级统计** - 组件数量分布统计
|
||||
|
||||
### 操作功能
|
||||
- **安全删除** - SuppressFeatures策略,保持装配体完整性
|
||||
- **路径删除** - 按组件路径批量删除
|
||||
- **模型搜索** - 三种匹配模式的名称搜索
|
||||
|
||||
## 主要API接口
|
||||
|
||||
```http
|
||||
# 状态与模型操作
|
||||
GET /api/status/creo # Creo连接状态
|
||||
GET /api/status/model # 当前模型状态
|
||||
POST /api/model/open|close|save # 模型生命周期
|
||||
POST /api/export/model # STEP导出
|
||||
|
||||
# 分析算法
|
||||
POST /api/creo/analysis/hierarchy # 层级结构分析
|
||||
POST /api/analysis/shell-analysis # 薄壳化分析
|
||||
POST /api/analysis/geometry-complexity # 几何复杂度分析
|
||||
POST /api/analysis/hierarchy-statistics # 层级统计
|
||||
|
||||
# 操作功能
|
||||
POST /api/search/models # 模型搜索
|
||||
POST /api/creo/hierarchy/delete # 层级删除
|
||||
POST /api/creo/component/delete-by-path # 路径删除
|
||||
POST /api/creo/shrinkwrap/shell # Shrinkwrap导出
|
||||
```
|
||||
|
||||
## 核心算法优化
|
||||
|
||||
### Shell Analysis PCA增强算法 (2025-01-19)
|
||||
- **PCA-based OBB**: 主成分分析精确定向包围盒,细长零件精度提升30%+
|
||||
- **智能选择机制**: 5维评分系统,自动选择最优几何分析方法
|
||||
- **22点采样策略**: 角点+面心+边心,增强协方差矩阵精度
|
||||
- **多层回退保障**: PCA→变换矩阵→AABB,确保零崩溃
|
||||
|
||||
### 薄壳化分析LOO算法 (2025-01-09)
|
||||
- **Leave-One-Out归因**: Top-K特征独立测量,精度从60%→75%
|
||||
- **智能缓存机制**: 模型版本+单位系统防脏读
|
||||
- **薄壁结构保护**: proximity检测避免误删关键支撑
|
||||
|
||||
### 无锁线程架构
|
||||
- **原子操作通信**: 避免C++标准库mutex,MessageItem统一处理
|
||||
- **50ms轮询机制**: 平衡响应速度和CPU占用
|
||||
- **跨线程OTK调用**: 定时器机制处理主线程操作
|
||||
|
||||
## 构建环境
|
||||
|
||||
**IDE:** Visual Studio 2022 (v143)
|
||||
**配置:** Debug|x64
|
||||
**依赖:** Creo 5.0.0.0 OTK库
|
||||
**目标:** Windows 7+
|
||||
|
||||
## 开发规范
|
||||
|
||||
- **API稳定性**: 向后兼容,最小变更
|
||||
- **异常处理**: 完善错误处理,确保服务稳定
|
||||
- **性能优先**: 减少数据传输,提升前端体验
|
||||
- **代码规范**: 所有注释使用英文,避免编码问题
|
||||
286
CreoManager.cpp
286
CreoManager.cpp
@ -1619,6 +1619,292 @@ CreoManager::HierarchyDeleteResult CreoManager::DeleteHierarchyComponents(const
|
||||
return result;
|
||||
}
|
||||
|
||||
// 子装配体层级删除功能实现 - 以指定子装配体为顶层进行层级删除
|
||||
CreoManager::HierarchyDeleteResult CreoManager::DeleteSubassemblyHierarchyComponents(const std::string& subassembly_path, int target_level) {
|
||||
HierarchyDeleteResult result;
|
||||
result.target_level = target_level;
|
||||
|
||||
if (subassembly_path.empty()) {
|
||||
result.error_message = "Subassembly path is required";
|
||||
return result;
|
||||
}
|
||||
|
||||
SessionInfo sessionInfo = GetSessionInfo();
|
||||
if (!sessionInfo.is_valid) {
|
||||
result.error_message = "Creo session not available";
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel();
|
||||
if (!current_model) {
|
||||
result.error_message = "No current model loaded";
|
||||
return result;
|
||||
}
|
||||
|
||||
// 检查是否为装配体
|
||||
if (current_model->GetType() != pfcMDL_ASSEMBLY) {
|
||||
result.error_message = "Current model is not an assembly";
|
||||
return result;
|
||||
}
|
||||
|
||||
// 转换为装配体
|
||||
wfcWAssembly_ptr rootAssembly = wfcWAssembly::cast(current_model);
|
||||
if (!rootAssembly) {
|
||||
result.error_message = "Failed to cast model to assembly";
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取当前模型名称
|
||||
std::string currentModelName = "";
|
||||
try {
|
||||
xstring filename_xstr = current_model->GetFileName();
|
||||
currentModelName = XStringToString(filename_xstr);
|
||||
} catch (...) {
|
||||
currentModelName = "";
|
||||
}
|
||||
|
||||
// 解析子装配体路径
|
||||
std::string path = subassembly_path;
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
std::vector<std::string> path_segments;
|
||||
std::istringstream iss(path);
|
||||
std::string segment;
|
||||
while (std::getline(iss, segment, '/')) {
|
||||
if (!segment.empty()) {
|
||||
path_segments.push_back(segment);
|
||||
}
|
||||
}
|
||||
|
||||
if (path_segments.empty()) {
|
||||
result.error_message = "Invalid subassembly path format";
|
||||
return result;
|
||||
}
|
||||
|
||||
// 如果路径以当前模型名称开头,跳过第一段
|
||||
int startSegment = 0;
|
||||
if (!currentModelName.empty() && !path_segments.empty()) {
|
||||
std::string firstSeg = path_segments[0];
|
||||
std::string modelName = currentModelName;
|
||||
std::transform(firstSeg.begin(), firstSeg.end(), firstSeg.begin(), ::tolower);
|
||||
std::transform(modelName.begin(), modelName.end(), modelName.begin(), ::tolower);
|
||||
if (firstSeg == modelName) {
|
||||
startSegment = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 递归查找目标子装配体
|
||||
std::function<wfcWAssembly_ptr(wfcWAssembly_ptr, int)> findSubassembly = [&](wfcWAssembly_ptr currentAssembly, int segmentIndex) -> wfcWAssembly_ptr {
|
||||
if (!currentAssembly || segmentIndex >= (int)path_segments.size()) {
|
||||
return currentAssembly;
|
||||
}
|
||||
|
||||
std::string targetSegment = path_segments[segmentIndex];
|
||||
std::string targetLower = targetSegment;
|
||||
std::transform(targetLower.begin(), targetLower.end(), targetLower.begin(), ::tolower);
|
||||
|
||||
pfcFeatures_ptr features = currentAssembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT);
|
||||
if (!features) return nullptr;
|
||||
|
||||
int count = features->getarraysize();
|
||||
for (int i = 0; i < count; i++) {
|
||||
try {
|
||||
pfcFeature_ptr feature = features->get(i);
|
||||
if (!feature) continue;
|
||||
|
||||
pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feature);
|
||||
if (!compFeat) continue;
|
||||
|
||||
auto modelDescr = compFeat->GetModelDescr();
|
||||
if (!modelDescr) continue;
|
||||
|
||||
xstring filename_xstr = modelDescr->GetFileName();
|
||||
std::string componentName = XStringToString(filename_xstr);
|
||||
std::string componentLower = componentName;
|
||||
std::transform(componentLower.begin(), componentLower.end(), componentLower.begin(), ::tolower);
|
||||
|
||||
if (componentLower == targetLower) {
|
||||
// 找到匹配的组件
|
||||
if (segmentIndex == (int)path_segments.size() - 1) {
|
||||
// 这是目标子装配体
|
||||
pfcModel_ptr childModel = LoadComponentModel(compFeat);
|
||||
if (childModel && childModel->GetType() == pfcMDL_ASSEMBLY) {
|
||||
return wfcWAssembly::cast(childModel);
|
||||
}
|
||||
return nullptr;
|
||||
} else {
|
||||
// 继续递归查找
|
||||
pfcModel_ptr childModel = LoadComponentModel(compFeat);
|
||||
if (childModel && childModel->GetType() == pfcMDL_ASSEMBLY) {
|
||||
wfcWAssembly_ptr childAssembly = wfcWAssembly::cast(childModel);
|
||||
if (childAssembly) {
|
||||
return findSubassembly(childAssembly, segmentIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
// 查找目标子装配体
|
||||
wfcWAssembly_ptr targetAssembly = findSubassembly(rootAssembly, startSegment);
|
||||
if (!targetAssembly) {
|
||||
result.error_message = "Subassembly not found at path: " + subassembly_path;
|
||||
return result;
|
||||
}
|
||||
|
||||
// target_level=2表示保留2层,删除第3层的组件
|
||||
// 层级映射:target_level=2 -> 删除level_3 -> currentLevel=2
|
||||
int deleteLevel = target_level - 1;
|
||||
|
||||
// 收集删除统计信息
|
||||
std::map<int, std::vector<std::string>> componentsToDeleteByLevel;
|
||||
int total_deleted = 0;
|
||||
int successful_count = 0;
|
||||
int failed_count = 0;
|
||||
|
||||
// 递归遍历到指定层级直接删除(以目标子装配体为层级0开始)
|
||||
std::function<void(wfcWAssembly_ptr, int)> deleteAtLevel = [&](wfcWAssembly_ptr currentAssembly, int currentLevel) {
|
||||
if (!currentAssembly || currentLevel > deleteLevel) return;
|
||||
|
||||
try {
|
||||
pfcFeatures_ptr features = currentAssembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT);
|
||||
|
||||
if (features) {
|
||||
if (currentLevel == deleteLevel) {
|
||||
// 在目标层级:获取所有组件并删除
|
||||
xintsequence_ptr featIds = xintsequence::create();
|
||||
std::vector<std::string> levelComponents;
|
||||
for (int i = 0; i < features->getarraysize(); i++) {
|
||||
pfcFeature_ptr feature = features->get(i);
|
||||
pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feature);
|
||||
if (compFeat) {
|
||||
// 记录组件信息用于响应
|
||||
try {
|
||||
auto modelDescr = compFeat->GetModelDescr();
|
||||
if (modelDescr) {
|
||||
xstring filename_xstr = modelDescr->GetFileName();
|
||||
std::string filename = XStringToString(filename_xstr);
|
||||
levelComponents.push_back(filename);
|
||||
total_deleted++;
|
||||
}
|
||||
} catch (...) {
|
||||
// 忽略获取文件名失败的组件
|
||||
}
|
||||
|
||||
// 添加到删除列表
|
||||
int featId = feature->GetId();
|
||||
featIds->append(featId);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行删除
|
||||
if (featIds->getarraysize() > 0) {
|
||||
try {
|
||||
wfcWSolid_ptr wsolid = wfcWSolid::cast(currentAssembly);
|
||||
if (wsolid) {
|
||||
|
||||
// 使用SuppressFeatures方法,更安全地"删除"组件
|
||||
wfcFeatSuppressOrDeleteOptions_ptr options = wfcFeatSuppressOrDeleteOptions::create();
|
||||
options->append(wfcFEAT_SUPP_OR_DEL_NO_OPTS);
|
||||
|
||||
// 创建重生成指令,允许失败
|
||||
wfcWRegenInstructions_ptr regenInstr = wfcWRegenInstructions::Create();
|
||||
|
||||
// 执行抑制操作(更安全,不会破坏引用关系)
|
||||
wsolid->SuppressFeatures(featIds, options, regenInstr);
|
||||
|
||||
// 手动重生成模型
|
||||
try {
|
||||
currentAssembly->Regenerate(nullptr);
|
||||
} catch (...) {
|
||||
// 重生成失败不影响抑制操作
|
||||
}
|
||||
|
||||
successful_count += featIds->getarraysize();
|
||||
|
||||
// 记录成功抑制的组件 - 累积而不是覆盖
|
||||
if (componentsToDeleteByLevel.find(deleteLevel + 1) == componentsToDeleteByLevel.end()) {
|
||||
componentsToDeleteByLevel[deleteLevel + 1] = std::vector<std::string>();
|
||||
}
|
||||
componentsToDeleteByLevel[deleteLevel + 1].insert(
|
||||
componentsToDeleteByLevel[deleteLevel + 1].end(),
|
||||
levelComponents.begin(),
|
||||
levelComponents.end()
|
||||
);
|
||||
} else {
|
||||
failed_count += featIds->getarraysize();
|
||||
}
|
||||
} catch (...) {
|
||||
failed_count += featIds->getarraysize();
|
||||
}
|
||||
}
|
||||
} else if (currentLevel < deleteLevel) {
|
||||
// 还没到目标层级:只对装配体组件继续递归
|
||||
for (int i = 0; i < features->getarraysize(); i++) {
|
||||
pfcFeature_ptr feature = features->get(i);
|
||||
pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feature);
|
||||
if (compFeat) {
|
||||
auto modelDescr = compFeat->GetModelDescr();
|
||||
if (modelDescr && modelDescr->GetType() == pfcMDL_ASSEMBLY) {
|
||||
pfcModel_ptr childModel = LoadComponentModel(compFeat);
|
||||
if (childModel && childModel->GetType() == pfcMDL_ASSEMBLY) {
|
||||
wfcWAssembly_ptr childAssembly = wfcWAssembly::cast(childModel);
|
||||
if (childAssembly) {
|
||||
deleteAtLevel(childAssembly, currentLevel + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// 忽略单个装配体的错误
|
||||
}
|
||||
};
|
||||
|
||||
// 从目标子装配体开始删除(层级0)
|
||||
deleteAtLevel(targetAssembly, 0);
|
||||
|
||||
result.deleted_components = componentsToDeleteByLevel;
|
||||
result.total_deleted = total_deleted;
|
||||
result.original_levels = 0; // 临时设置,避免异常值
|
||||
|
||||
// 删除完成后重新生成模型
|
||||
if (successful_count > 0) {
|
||||
try {
|
||||
targetAssembly->Regenerate(nullptr);
|
||||
} catch (...) {
|
||||
// 重新生成失败不影响删除结果
|
||||
}
|
||||
}
|
||||
|
||||
result.successful = successful_count;
|
||||
result.failed = failed_count;
|
||||
result.final_levels = target_level + 1; // 删除后的层级数
|
||||
|
||||
if (failed_count == 0) {
|
||||
result.success = true;
|
||||
result.message = "All components suppressed successfully (safer than deletion)";
|
||||
} else {
|
||||
result.success = (successful_count > 0);
|
||||
result.message = "Suppression completed with " + std::to_string(failed_count) + " failures";
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
result.error_message = "Exception during suppression: " + std::string(e.what());
|
||||
} catch (...) {
|
||||
result.error_message = "Unknown error during suppression";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Multi-directional extreme value projection shell analysis implementation
|
||||
CreoManager::ShellAnalysisResult CreoManager::AnalyzeShellFeaturesEnhanced(const ShellAnalysisRequest& request) {
|
||||
ShellAnalysisResult result;
|
||||
|
||||
@ -249,6 +249,9 @@ public:
|
||||
|
||||
HierarchyDeleteResult DeleteHierarchyComponents(const std::string& project_name, int target_level);
|
||||
|
||||
// 子装配体层级删除功能 - 以指定子装配体为顶层进行层级删除
|
||||
HierarchyDeleteResult DeleteSubassemblyHierarchyComponents(const std::string& subassembly_path, int target_level);
|
||||
|
||||
// 薄壳化分析功能
|
||||
struct ShellAnalysisRequest {
|
||||
std::string software_type;
|
||||
|
||||
335
Creo技术方案总结.md
Normal file
335
Creo技术方案总结.md
Normal file
@ -0,0 +1,335 @@
|
||||
# Creo 前端技术路线与算法体系总结
|
||||
|
||||
## 摘要
|
||||
|
||||
本文档面向计划构建与 MFCCreoDll 插件协同工作的前端工程,系统梳理现有 C++/MFC 插件所提供的几何分析与模型管理能力,说明其依托的 PTC Creo Parametric Toolkit (OTK C++) API,以及前端接入时需要遵循的通信约定、架构设计、算法协同与性能优化策略。文档重点扩展薄壳分析与 Shrinkwrap 外壳导出的几何原理、数值指标与 API 依赖,提供数据结构、流程图示与复杂度分析,为后续实现桌面端或 Web 前端提供完整的工程蓝图。
|
||||
|
||||
## 1. 引言
|
||||
|
||||
### 1.1 背景与动机
|
||||
|
||||
- **插件职责**:MFCCreoDll 通过内置 `HttpServer` 将 Creo OTK 的装配遍历、几何分析、批量操作等能力封装为 HTTP JSON 接口,面向企业内部自动化建模与模型瘦身场景。
|
||||
- **前端缺口**:仓库未包含任何前端 UI(无 JavaScript/TypeScript/HTML/CSS 及构建脚本),现有用户依赖命令行或脚本调用,缺乏可视化入口和交互式分析能力。
|
||||
- **建设目标**:搭建一套专业化前端工作台,实现模型状态可视化、薄壳削减决策、Shrinkwrap 导出监控、批量操作编排与日志追踪,提升 Creo 工程师的决策效率。
|
||||
|
||||
### 1.2 设计原则
|
||||
|
||||
1. **分层解耦**:前端仅通过 HTTP/JSON 与插件交互,不直接依赖 OTK SDK;业务逻辑与呈现分离,便于扩展。
|
||||
2. **可观测性**:所有长耗时任务(薄壳分析、Shrinkwrap、批量操作)需暴露进度、耗时、错误分类,便于运维监控。
|
||||
3. **可组合性**:提供参数模板、操作剧本与结果数据的导入导出,支持在 CI/CD 或 PLM 流程中复用。
|
||||
4. **安全性**:在远程部署场景下需支持 TLS、Token 校验与审计日志,避免 Creo 能力被滥用。
|
||||
|
||||
## 2. 技术架构蓝图
|
||||
|
||||
### 2.1 系统角色
|
||||
|
||||
| 角色 | 描述 | 关键职责 |
|
||||
| --- | --- | --- |
|
||||
| **Creo 插件 (C++)** | 运行于 Creo Session 的 DLL,负责与 OTK C++ 交互 | 处理模型操作、几何分析、文件导出、批量执行;输出 JSON |
|
||||
| **前端应用** | 浏览器或 Electron 桌面应用 | 展示数据、收集用户输入、调度任务、渲染图表与报告 |
|
||||
| **本地 HTTP 服务** | 插件内置 `HttpServer` | 提供 REST 风格接口,端口默认 12345,支持 CORS |
|
||||
|
||||
### 2.2 前端分层结构
|
||||
|
||||
1. **Presentation Layer**:基于 React/Vue 等组件库构建视图,包含仪表盘、图表、树状组件、文件选择器。
|
||||
2. **Application Layer**:管理路由、权限、国际化、主题,以及业务逻辑 orchestrator。
|
||||
3. **Data Layer**:封装 API 客户端(Axios/Fetch),实现请求队列、超时、重试、缓存与统一错误处理。
|
||||
4. **State Management Layer**:使用 Redux Toolkit、Zustand 或 Pinia 保存 Creo 会话信息、当前模型上下文、分析结果、任务队列。
|
||||
5. **Computation Layer**:通过 Web Worker / Rust WASM 实现本地重计算(排序、聚合、差异分析),减少主线程阻塞。
|
||||
6. **Persistence Layer**:IndexedDB、SQLite (在 Electron 中) 或本地文件用于缓存配置、模板、历史日志、结果快照。
|
||||
|
||||
### 2.3 数据流视图
|
||||
|
||||
1. 用户在 UI 发起操作 →
|
||||
2. 前端构造 JSON 请求 → HTTP 客户端发送至 `http://127.0.0.1:12345` →
|
||||
3. 插件通过 OTK 执行业务逻辑 →
|
||||
4. 插件返回标准化 JSON(含 `success`, `data`, `error`)→
|
||||
5. 前端解析并更新状态树 →
|
||||
6. 触发 UI 渲染更新 / 日志记录 / 通知提示。
|
||||
|
||||
## 3. 功能模块设计
|
||||
|
||||
### 3.1 会话监控中心
|
||||
|
||||
- 实时展示 Creo 连接状态、版本号、当前工作目录 (`/api/status/creo`)。
|
||||
- 记录最近 N 次 API 调用与耗时,支持过滤与导出。
|
||||
- 提供 `Reconnect`/`Check Session` 按钮,触发 `/test` 接口用于诊断。
|
||||
|
||||
### 3.2 模型生命周期控制台
|
||||
|
||||
- **打开模型**:支持完整路径和 `dirname + filename` 两种输入形式,提供最近使用列表。
|
||||
- **保存模型**:展示文件大小、保存耗时、成功提示;允许用户配置保存策略(自动备份等)。
|
||||
- **关闭模型**:支持 `force_close` 参数,提示未保存修改。
|
||||
|
||||
### 3.3 几何分析工作台
|
||||
|
||||
- **复杂度分析**:对 `/api/analysis/geometry-complexity` 输出进行聚合,展示热力图、TopN 列表、曲线趋势。
|
||||
- **层级统计分析**:渲染层级数量、各层组件数、风险评分,并支持导出 CSV/Excel。
|
||||
|
||||
### 3.4 薄壳分析与削减决策中心
|
||||
|
||||
- 展示 `safeDeletions`、`suggestedDeletions`、`preserveList` 三类结果,提供可视化标签(置信度、体积收益、风险因素)。
|
||||
- 支持按 `confidence`、`volumeReduction`、`component_type` 等条件筛选与排序。
|
||||
- 提供策略模拟:选择候选集合后预估整体体积/文件大小/性能收益变化。
|
||||
|
||||
### 3.5 Shrinkwrap 外壳导出面板
|
||||
|
||||
- 提供参数表单(质量、弦高、填孔、小面过滤、忽略 Skeleton 等)与预设模板选择。
|
||||
- 实时展示执行日志、导出文件路径、文件大小,并允许用户打开文件所在目录。
|
||||
- 支持任务排队、超时重试与错误分类提示。
|
||||
|
||||
### 3.6 装配树与路径操作
|
||||
|
||||
- 树状/平铺视图展示组件层级,支持懒加载与虚拟滚动。
|
||||
- 提供路径复制、按路径删除、子组件统计与风险提示。
|
||||
- 结合层级分析结果,对高风险删除操作弹出确认/二次认证。
|
||||
|
||||
### 3.7 模型搜索与索引视图
|
||||
|
||||
- 支持关键字、类型、是否在会话、相似度阈值、组件/特征等多筛选条件。
|
||||
- 采用命中高亮、标签分类、匹配理由展示(如命中名称/属性/路径)。
|
||||
- 支持导出搜索结果、保存搜索条件模板。
|
||||
|
||||
### 3.8 批量操作编排平台
|
||||
|
||||
- 流程画布支持拖拽组合操作单元(打开、保存、导出、分析、任务脚本)。
|
||||
- 执行时展示步骤进度、耗时、返回 JSON、错误信息,支持暂停/继续/跳过。
|
||||
- 支持剧本库、参数化模板、执行历史回放与日志导出。
|
||||
|
||||
## 4. 接口协议与数据契约
|
||||
|
||||
### 4.1 请求规范
|
||||
|
||||
- 所有接口使用 `application/json; charset=utf-8`。
|
||||
- 请求体大小默认限制 1 MB (`Config::MAX_REQUEST_SIZE`);前端需对大型任务参数进行压缩或拆分。
|
||||
- CORS 头默认为 `Access-Control-Allow-Origin: *`,前端可在浏览器直接调用。
|
||||
|
||||
### 4.2 响应结构
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { ... },
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
- **success**: 布尔值,标识操作是否成功。
|
||||
- **data**: 成功时包含有效载荷(模型信息、分析结果、执行统计等)。
|
||||
- **error**: 失败时包含错误描述;插件在部分接口中返回结构化错误(含分类、建议)。
|
||||
|
||||
### 4.3 关键接口示例
|
||||
|
||||
| URL | 方法 | 描述 |
|
||||
| --- | --- | --- |
|
||||
| `/api/model/open` | POST | 打开模型,支持 `file_path` 或 `dirname` + `filename` |
|
||||
| `/api/model/save` | POST | 保存当前模型,返回文件大小、保存耗时 |
|
||||
| `/api/model/close` | POST | 关闭模型,支持 `force_close` |
|
||||
| `/api/analysis/geometry-complexity` | POST | 几何复杂度分析 |
|
||||
| `/api/analysis/shell-analysis` | POST | 薄壳分析 |
|
||||
| `/api/creo/shrinkwrap/shell` | POST | Shrinkwrap 外壳导出 |
|
||||
| `/api/creo/batch-operations` | POST | 批量操作执行 |
|
||||
|
||||
### 4.4 类型定义建议
|
||||
|
||||
- 使用 TypeScript `type`/`interface` 映射接口返回数据。可通过脚本解析头文件或 JSON 样例生成类型声明,确保编译期类型安全。
|
||||
- 对复杂对象(如薄壳分析结果)建立细粒度类型,便于校验和测试。
|
||||
|
||||
## 5. 核心算法解析
|
||||
|
||||
### 5.1 薄壳分析算法体系
|
||||
|
||||
#### 5.1.1 几何数据采集
|
||||
|
||||
- 利用 `wfcWAssembly::ListDisplayedComponents` 获取装配组件列表。
|
||||
- 调用 `pfcSolid::GetMassProperty`、`pfcSolid::GetOutline` 获取体积、包围盒等几何属性。
|
||||
- 对细长组件使用 22 点采样(角点 8、面心 6、边心 12)构建顶点集,为 PCA 计算做准备。
|
||||
|
||||
#### 5.1.2 主方向与包围盒选择
|
||||
|
||||
1. 计算协方差矩阵 `Σ = (1/N) Σ (v_i - μ)(v_i - μ)^T`,在 OTK 中通过手工矩阵运算实现。
|
||||
2. 求解特征值/特征向量,确定主轴 `e1, e2, e3`。
|
||||
3. 评估长宽比、表面积、体积,若满足:
|
||||
- 长宽比 > `OBB_ASPECT_RATIO_THRESHOLD` (默认 4.0)
|
||||
- 或表面积差值 < `OBB_SURFACE_TOL`
|
||||
则使用 OBB;否则采用 AABB。
|
||||
|
||||
#### 5.1.3 多方向投影与网格离散
|
||||
|
||||
- 方向集合:`D = {±X, ±Y, ±Z}`,记作 6 个单位向量。
|
||||
- 96 × 96 网格离散化平面,对每个网格单元记录最小深度值。
|
||||
- 对组件 `c`,投影覆盖率计算公式:
|
||||
|
||||
\[
|
||||
\text{visibility}(c) = \frac{1}{|D|} \sum_{d \in D} \mathbb{I}\left( \frac{A_{c,d}}{A_{\text{total},d}} \geq \tau_d \right)
|
||||
\]
|
||||
|
||||
其中 `A_{c,d}` 为组件在方向 `d` 的可见面积,`τ_d = 0.001` (0.1%)。
|
||||
|
||||
#### 5.1.4 双阶段投票机制
|
||||
|
||||
- **阶段一**:设置严格窗口 `δ_strict = 0.001 * diag(assembly)`,要求 80% 方向可见,识别高置信度外壳。
|
||||
- **阶段二**:对剩余组件放宽窗口至 `max(0.002*diag, 0.15*median_thickness)`,重复投影与判定,补齐遗漏。
|
||||
|
||||
#### 5.1.5 Leave-One-Out (LOO) 归因
|
||||
|
||||
1. 计算全局可见矩阵 `V_all`。
|
||||
2. 对组件 `c`,构造 `V_{-c}`(移除该组件),比较差异:
|
||||
|
||||
\[
|
||||
\Delta V_c = \frac{\|V_all - V_{-c}\|_1}{\|V_all\|_1}
|
||||
\]
|
||||
|
||||
3. `ΔV_c` 小于阈值的组件被判定为内部件。
|
||||
|
||||
#### 5.1.6 光线追踪验证
|
||||
|
||||
- 从装配质心 `G` 向候选外壳组件中心发射射线,调用 `pfcCreateSelectionEvaluator` / `pfcSelectionEvaluator::ComputeInterference` 检测遮挡。
|
||||
- 如果射线被其他组件阻挡,降低其置信度或标记为内部组件。
|
||||
|
||||
#### 5.1.7 置信度模型
|
||||
|
||||
- 综合指标:
|
||||
- 投影得票数 `votes`
|
||||
- LOO 贡献度 `ΔV`
|
||||
- 光线通过率 `ray_clear_ratio`
|
||||
- 历史统计(可选)
|
||||
|
||||
- 置信度计算示例:
|
||||
|
||||
\[
|
||||
\text{confidence} = w_1 \cdot \frac{\text{votes}}{6} + w_2 \cdot (1 - \Delta V) + w_3 \cdot \text{ray\_clear\_ratio}
|
||||
\]
|
||||
|
||||
默认 `w_1=0.5, w_2=0.3, w_3=0.2`,结果分级为:
|
||||
|
||||
| 置信度范围 | 标签 |
|
||||
| --- | --- |
|
||||
| ≥ 0.75 | 高置信度,推荐删除 |
|
||||
| 0.45–0.75 | 中置信度,谨慎执行 |
|
||||
| < 0.45 | 保留或人工检查 |
|
||||
|
||||
#### 5.1.8 前端可视化策略
|
||||
|
||||
- 为每个组件绘制卡片/表格行,包含:
|
||||
- 可视化标签(颜色/图标表示置信度等级)
|
||||
- 体积减少估计 (`volumeReduction`)
|
||||
- 文件大小减少估计
|
||||
- 风险因素列表(如“外露面少”“与关键件相连”)
|
||||
- 支持拖拽将组件加入“待处理列表”,实时计算整体收益。
|
||||
|
||||
### 5.2 Shrinkwrap 外壳导出算法
|
||||
|
||||
#### 5.2.1 调用流程
|
||||
|
||||
1. 获取当前模型 `session->GetCurrentModel()`,判断类型是否为装配或零件。
|
||||
2. 生成目标名称:
|
||||
- `GenerateSafePartName` 清理特殊字符,限制长度 ≤ 31。
|
||||
- 检测冲突(`session->GetModel`),必要时追加序号或时间戳。
|
||||
3. 调用 `session->CreatePart(new_name)` 创建结果模型。
|
||||
4. `pfcShrinkwrapSurfaceSubsetInstructions::Create` 构造参数:
|
||||
- `SetQuality(quality)`
|
||||
- `SetAutoHoleFilling(fill_holes)`
|
||||
- `SetIgnoreSmallSurfaces(ignore_small_surfaces)`
|
||||
- `SetSmallSurfPercentage(small_surface_percentage)`
|
||||
- `SetIgnoreQuilts(ignore_quilts)`
|
||||
- `SetIgnoreSkeleton(ignore_skeleton)`
|
||||
- `SetAssignMassProperties(assign_mass_properties)`
|
||||
5. 执行 `source_solid->ExportShrinkwrap(instructions)`。
|
||||
6. 保存文件:
|
||||
- 获取当前工作目录 `session->GetCurrentDirectory()`。
|
||||
- 检查文件存在性 (`_access`);若存在,则调用 `GenerateUniqueFilePath` 自动重命名。
|
||||
- 执行 `model->Save()`,使用 `GetFileSizeEx` 计算大小。
|
||||
|
||||
#### 5.2.2 参数预设
|
||||
|
||||
| 预设 | quality | chord_height | fill_holes | ignore_small_surfaces | small_surface_percentage | 输出类型 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| fast | 3 | 0.3 | false | false | 0.0 | `solid_surface` |
|
||||
| balanced | 5 | 0.15 | false | false | 0.0 | `solid_surface` |
|
||||
| tight | 5 | 0.1 | false | false | 0.0 | `solid_surface` |
|
||||
|
||||
- 前端需在 UI 中展示每个预设的特点、适用场景与注意事项。
|
||||
|
||||
#### 5.2.3 异常分类与恢复
|
||||
|
||||
| 异常类型 | 触发条件 | 前端处理建议 |
|
||||
| --- | --- | --- |
|
||||
| `Toolkit Error` | OTK 底层故障或模型损坏 | 提示“请检查模型完整性或重启 Creo” |
|
||||
| `BadArgument` | 参数越界(如质量 > 10) | 高亮错误字段,提供建议范围 |
|
||||
| `OutOfMemory` | 内存不足 | 建议降低质量或关闭其他程序 |
|
||||
| `Timeout` | 超过 `timeout_seconds` | 提供重试、增加超时或后台执行选项 |
|
||||
|
||||
#### 5.2.4 前端监控指标
|
||||
|
||||
- 任务阶段耗时:数据准备、几何计算、文件写入(可按 30%/60%/10% 显示)
|
||||
- 成品文件大小、路径、生成时间
|
||||
- 参数回显与导出日志(含 JSON 原始请求与响应)
|
||||
|
||||
### 5.3 几何复杂度与层级分析
|
||||
|
||||
#### 5.3.1 指标体系
|
||||
|
||||
- **复杂度分值**:由 `GeometryAnalyzer::AnalyzeGeometryComplexity` 根据面数、曲率、特征数量、体积等计算。
|
||||
- **层级统计**:`HierarchyStatisticsAnalyzer` 输出各层组件数量、深度、类型分布。
|
||||
- **风险推荐**:`HierarchyAnalysis` 给出 `safe_deletions` 与 `risky_deletions`,附带原因、风险因素、置信度。
|
||||
|
||||
#### 5.3.2 前端推导
|
||||
|
||||
- 对复杂度分值按照分位数划分等级(如 P75/P90/P95),渲染热力图。
|
||||
- 计算层级树的深度 `depth = max(level)`,节点数量 `N`,平均分支因子 `b = N / depth`。
|
||||
- 对风险推荐进行文本解析,生成可视化标签,如“关键约束”“连接紧固件”“支撑结构”。
|
||||
|
||||
## 6. 工程实践
|
||||
|
||||
### 6.1 性能优化
|
||||
|
||||
- **虚拟化渲染**:对大规模列表/树使用 react-window/react-virtualized。
|
||||
- **请求合并**:对频繁调用的 API 实现请求合并/去重;可采用 `AbortController` 取消过期请求。
|
||||
- **缓存策略**:对静态数据(如组件字典、部件类型映射)使用 IndexedDB;对分析结果采取版本化缓存,便于比较。
|
||||
- **Web Worker**:在 Worker 中执行排序、差异分析、统计聚合,避免主线程卡顿。
|
||||
|
||||
### 6.2 安全与权限
|
||||
|
||||
- 在桌面环境下可绑定 Windows AD 账号或企业 SSO;对敏感操作引入角色权限(如只读/分析/执行)。
|
||||
- 提供操作日志签名或 Hash 校验,确保操作记录不可篡改。
|
||||
- 若开放远程访问,部署反向代理(Nginx)实现 TLS、鉴权头注入、防刷限制。
|
||||
|
||||
### 6.3 测试策略
|
||||
|
||||
- **单元测试**:对数据解析、类型映射、排序筛选、状态管理等进行 Jest/Vitest 测试。
|
||||
- **契约测试**:使用 Pact/JSON Schema 验证接口契约,防止插件更新导致前端解析失败。
|
||||
- **端到端测试**:结合 Cypress/Playwright,模拟用户执行分析、导出、批量操作的完整流程。
|
||||
- **性能基准**:对大模型数据(>10k 组件)进行性能压测,确保 UI 响应时间在 300ms 内。
|
||||
|
||||
## 7. 性能与资源评估
|
||||
|
||||
### 7.1 任務耗時基线(参考)
|
||||
|
||||
| 任务 | 平均耗时 (中等规模装配) | 影响因素 |
|
||||
| --- | --- | --- |
|
||||
| 薄壳分析 | 12–35 s | 组件数量、几何复杂度、硬件配置 |
|
||||
| Shrinkwrap | 5–20 s | 质量参数、弦高、模型拓扑 |
|
||||
| 几何复杂度分析 | 3–8 s | 面数、特征数量 |
|
||||
| 批量操作 (5 步) | 10–25 s | 操作类型、每步耗时 |
|
||||
|
||||
- 前端需对 > 5 s 的任务提供进度反馈;> 30 s 的任务建议支持后台执行。
|
||||
|
||||
### 7.2 资源占用建议
|
||||
|
||||
- **内存**:保持前端单页内存占用 < 500 MB;对大型数据采用分页与懒加载。
|
||||
- **CPU**:将重计算任务(排序、批量差异)移交 Worker,主线程 CPU 使用率维持在 30% 以下。
|
||||
- **网络**:单次请求体 < 1 MB;前端可使用压缩/二进制传输(如 MessagePack)优化传输。
|
||||
|
||||
## 8. 结论与展望
|
||||
|
||||
MFCCreoDll 插件已将 Creo OTK 的核心几何处理能力以 HTTP JSON 形式开放,为前端构建专业化的模型管理平台奠定基础。本文档提出了分层架构设计、主要功能模块、关键算法流程与工程最佳实践,特别对薄壳分析与 Shrinkwrap 外壳导出给出了数学原理、参数体系与异常处理策略,可指导前端团队快速落地高可靠、高性能的解决方案。
|
||||
|
||||
未来工作方向包括:
|
||||
|
||||
1. **实时推送**:激活仓库中的 `WebSocketServer` 模块,与前端的事件总线集成,实现任务进度与状态事件的实时推送。
|
||||
2. **三维可视化集成**:通过 Three.js 或 Cesium 引入轻量模型预览,与薄壳分析结果联动高亮显示。
|
||||
3. **自动化脚本协同**:提供 CLI/REST 混合模式,允许 DevOps 流程直接调用前端定义的剧本,实现无人值守的模型清理流水线。
|
||||
4. **智能化推荐**:基于历史分析结果训练机器学习模型,提高薄壳削减建议与批量操作排序的智能性。
|
||||
|
||||
通过本技术路线,前端团队能够在不直接依赖 OTK 的情况下,充分发挥现有插件能力,为 Creo 工程师提供高可用、高可观测性的生产力工具。
|
||||
108
MFCCreoDll.cpp
108
MFCCreoDll.cpp
@ -901,6 +901,110 @@ HttpResponse HierarchyDeleteHandler(const HttpRequest& request) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// 子装配体层级删除路由处理器
|
||||
HttpResponse SubassemblyHierarchyDeleteHandler(const HttpRequest& request) {
|
||||
HttpResponse response;
|
||||
|
||||
if (request.method != "POST") {
|
||||
response.status_code = 405;
|
||||
response.body = "{\"success\": false, \"error\": \"Method not allowed\"}";
|
||||
return response;
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析JSON请求
|
||||
std::string software_type = ExtractJsonValue(request.body, "software_type");
|
||||
std::string subassembly_path = ExtractJsonValue(request.body, "subassembly_path");
|
||||
std::string target_level_str = ExtractJsonValue(request.body, "target_level");
|
||||
|
||||
if (software_type.empty() || subassembly_path.empty() || target_level_str.empty()) {
|
||||
response.status_code = 400;
|
||||
response.body = "{\"success\": false, \"error\": \"Missing required parameters: software_type, subassembly_path, target_level\"}";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (software_type != "creo") {
|
||||
response.status_code = 400;
|
||||
response.body = "{\"success\": false, \"error\": \"Invalid software_type, must be 'creo'\"}";
|
||||
return response;
|
||||
}
|
||||
|
||||
int target_level;
|
||||
try {
|
||||
target_level = std::stoi(target_level_str);
|
||||
} catch (...) {
|
||||
response.status_code = 400;
|
||||
response.body = "{\"success\": false, \"error\": \"Invalid target_level parameter\"}";
|
||||
return response;
|
||||
}
|
||||
|
||||
// 执行子装配体层级删除
|
||||
CreoManager::HierarchyDeleteResult result = CreoManager::Instance().DeleteSubassemblyHierarchyComponents(subassembly_path, target_level);
|
||||
|
||||
if (result.success) {
|
||||
// 构建成功响应
|
||||
std::ostringstream json;
|
||||
json << "{"
|
||||
<< "\"success\": true,"
|
||||
<< "\"message\": \"" << EscapeJsonString(result.message) << "\","
|
||||
<< "\"data\": {"
|
||||
<< "\"subassembly_path\": \"" << EscapeJsonString(subassembly_path) << "\","
|
||||
<< "\"original_levels\": " << result.original_levels << ","
|
||||
<< "\"target_level\": " << result.target_level << ","
|
||||
<< "\"final_levels\": " << result.final_levels << ","
|
||||
<< "\"deleted_components\": {";
|
||||
|
||||
// 构建删除组件数据
|
||||
bool first_level = true;
|
||||
for (const auto& level_pair : result.deleted_components) {
|
||||
if (!first_level) json << ",";
|
||||
first_level = false;
|
||||
|
||||
json << "\"level_" << level_pair.first << "\": [";
|
||||
bool first_component = true;
|
||||
for (const auto& component : level_pair.second) {
|
||||
if (!first_component) json << ",";
|
||||
first_component = false;
|
||||
json << "\"" << EscapeJsonString(component) << "\"";
|
||||
}
|
||||
json << "]";
|
||||
}
|
||||
|
||||
json << "},"
|
||||
<< "\"deletion_summary\": {"
|
||||
<< "\"total_deleted\": " << result.total_deleted << ","
|
||||
<< "\"successful\": " << result.successful << ","
|
||||
<< "\"failed\": " << result.failed
|
||||
<< "}"
|
||||
<< "},"
|
||||
<< "\"error\": null"
|
||||
<< "}";
|
||||
|
||||
response.body = json.str();
|
||||
} else {
|
||||
// 构建错误响应
|
||||
std::ostringstream json;
|
||||
json << "{"
|
||||
<< "\"success\": false,"
|
||||
<< "\"message\": \"" << EscapeJsonString(result.message) << "\","
|
||||
<< "\"error\": \"" << EscapeJsonString(result.error_message) << "\""
|
||||
<< "}";
|
||||
|
||||
response.status_code = 500;
|
||||
response.body = json.str();
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
response.status_code = 500;
|
||||
response.body = "{\"success\": false, \"error\": \"JSON parsing error: " + std::string(e.what()) + "\"}";
|
||||
} catch (...) {
|
||||
response.status_code = 500;
|
||||
response.body = "{\"success\": false, \"error\": \"Unknown error during subassembly hierarchy deletion\"}";
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// 薄壳化分析处理器
|
||||
HttpResponse ShellAnalysisHandler(const HttpRequest& request) {
|
||||
HttpResponse response;
|
||||
@ -1858,7 +1962,8 @@ std::vector<BatchOperation> ExtractBatchOperations(const std::string& json) {
|
||||
std::vector<std::string> param_keys = {"export_path", "geom_flags", "force_delete",
|
||||
"project_name", "target_level", "quality",
|
||||
"chord_height", "output_file_path", "file_path",
|
||||
"open_mode"};
|
||||
"open_mode", "working_directory", "input_file_path",
|
||||
"shrinkwrap_output_path", "step_export_path"};
|
||||
|
||||
for (const auto& key : param_keys) {
|
||||
std::string value = ExtractJsonValue(params_json, key);
|
||||
@ -2010,6 +2115,7 @@ extern "C" int user_initialize(
|
||||
g_http_server->SetRouteHandler("/api/export/model", ExportModelHandler);
|
||||
g_http_server->SetRouteHandler("/api/creo/analysis/hierarchy", HierarchyAnalysisHandler);
|
||||
g_http_server->SetRouteHandler("/api/creo/hierarchy/delete", HierarchyDeleteHandler);
|
||||
g_http_server->SetRouteHandler("/api/creo/subassembly/hierarchy/delete", SubassemblyHierarchyDeleteHandler);
|
||||
g_http_server->SetRouteHandler("/api/analysis/shell-analysis", ShellAnalysisHandler);
|
||||
g_http_server->SetRouteHandler("/api/model/save", SaveModelHandler);
|
||||
g_http_server->SetRouteHandler("/api/model/close", CloseModelHandler);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,147 +0,0 @@
|
||||
|
||||
|
||||
|
||||
|
||||
在 Creo 5 实现“只保留装配外壳、剔除内部”的完整方案(不含代码)。我把 GUI 操作、参数取值、产物管理、验证与批处理、以及二次开发(OTKJ-Link)落地步骤都拆开写清楚,尽量覆盖坑点与细节。
|
||||
|
||||
---
|
||||
|
||||
## 0. 目标与产物约定
|
||||
- 目标:从一个复杂装配生成一个“仅含外表面”的轻量模型,用于快速打开与尺寸测量;内部零件全部被忽略移除。
|
||||
- 产物(建议二选一)
|
||||
1) 独立外壳零件(.prt):与原装配解耦,适合下游共享、测量与仿真前置。
|
||||
2) 装配内的 Envelope 简化表示:与原装配保持关联,装配更新后外壳可随动。
|
||||
|
||||
---
|
||||
|
||||
## 1. 推荐主线:Shrinkwrap(Outer shell)生成独立外壳零件
|
||||
|
||||
### 1.1 GUI 操作步骤(Creo 5)
|
||||
1) 打开顶层装配(建议在 Master Simp Rep 下)。
|
||||
2) File → Save As → Save a Copy,Type 选择 Shrinkwrap。
|
||||
3) 在 Shrinkwrap 对话框中设置:
|
||||
- Collection method:选 Outer shell(关键!只采集装配外表面,内部自动忽略)。
|
||||
- Quality level:建议 7–9(1–10,越高越细;外观准确性与文件大小时间平衡)。
|
||||
- Fill holes Gap control:
|
||||
- 若希望保留通孔、开窗等细节以便测量,关闭 Fill holes;
|
||||
- 若要形成更“紧”的外壳,开启 Fill holes 并设合适 Max gap(例如 0.5–1.0 mm)。
|
||||
- Ignore small faces Chord height:
|
||||
- 开启 Ignore small faces,阈值建议取关键尺寸的 0.1–0.5%(避免小倒角小标志导致外壳噪声);
|
||||
- Chord height(弦高)控制三角化误差:0.05–0.2 mm 视模型尺度而定。
|
||||
- Output:优先选 SolidSurface geometry(而非纯 Facet),便于后续测量与布尔运算。
|
||||
4) 命名:`AsmName_SW_OS_Q8_Gap1.prt` 一类的可读命名(含方法与关键参数)。
|
||||
5) 生成后自动打开外壳零件,保存。
|
||||
|
||||
备注:Outer shell 的判断在内核里处理,不依赖视角;相比“自研判定外露面”更稳健。
|
||||
|
||||
### 1.2 外壳零件质量与可用性优化
|
||||
- 文件瘦身:若外观面过密,可适度降低 Quality 或放宽 Chord height,再次导出比对外观差异。
|
||||
- 边角锯齿:提高 Quality 或降低 Chord height;必要时对外壳零件做少量 SmoothHeal。
|
||||
- 测量稳定性:确保输出为 实体或封闭曲面。若为 Quilt,可用 SolidifyThicken 封体。
|
||||
|
||||
### 1.3 测量建议
|
||||
- 外形尺寸:直接在外壳零件中用 Measure(边面基准)测量;对大模型可用 Section 截面测量。
|
||||
- 内腔近似尺寸(若已填孔形成封闭外壳):用 Mass Properties 读体积;或用 OffsetSection 制作参考几何后测量。
|
||||
|
||||
---
|
||||
|
||||
## 2. 关联更新方案:Envelope(简化表示)
|
||||
|
||||
### 2.1 适用场景
|
||||
- 需要随装配设计变更而更新“外壳视图”,又不想每次手工导出 Shrinkwrap。
|
||||
|
||||
### 2.2 GUI 步骤(Creo 5)
|
||||
1) 在顶层装配打开 View Manager → Simp Rep。
|
||||
2) 新建一个 Envelope 类型的简化表示(或使用 Default Envelope)。
|
||||
3) 在 Envelope 设置中选择基于 ShrinkwrapOuter shell 的规则生成外壳表示(与 1.1 的参数一致或略放宽)。
|
||||
4) 命名与管理:`ENV_OS_Q7`、`ENV_FAST` 等,便于在总装下游装配中直接切换加载。
|
||||
|
||||
### 2.3 使用与维护
|
||||
- 在需要快速打开或测量时,切换到 Envelope simp rep 加载;
|
||||
- 装配更新后,更新 Envelope 以同步外壳;必要时建立 Update note 流程 防止遗漏。
|
||||
|
||||
---
|
||||
|
||||
## 3. 二次开发(OTK J-Link)无代码落地要点
|
||||
|
||||
需求:批量对多套装配生成外壳;或一键生成外壳并存放到统一位置;或在 PDM 动作里挂钩。
|
||||
|
||||
### 3.1 配置权限
|
||||
- 许可:确保有 Creo TOOLKITOTK 的开发许可;
|
||||
- 版本匹配:开发环境与 Creo 5 版本匹配(头文件与库一致);
|
||||
- 数据访问:若通过 Windchill工管系统,确认签出签入策略(避免生成失败或卡权限)。
|
||||
|
||||
### 3.2 运行时输入输出设计
|
||||
- 输入:顶层装配文件路径、Simp Rep(默认 Master)、Shrinkwrap 参数(方法=Outer shell、质量、孔洞、弦高、忽略小面阈值、输出类型)。
|
||||
- 输出:外壳零件保存路径、命名规范、版本控制策略(例如加日期参数摘要)。
|
||||
- 日志:记录源装配名、面数体积、生成时间、参数集、失败原因码(空装配、拓扑自交等)。
|
||||
|
||||
### 3.3 参数集(建议预设三档)
|
||||
- Fast:Q=5、Chord=0.2–0.3、Ignore small faces=开(阈值偏大)、Fill holes=关;用于大批量初筛浏览。
|
||||
- Balanced:Q=7–8、Chord=0.1、Ignore small faces=开(中等阈值)、Fill holes=按需;用于常规测量。
|
||||
- Tight:Q=9–10、Chord=0.05、Ignore small faces=关或小阈值、Fill holes=按需;用于高精度测量对外交付。
|
||||
|
||||
### 3.4 错误处理与回退
|
||||
- 组件缺失家族表未实例化:先触发 RegenerateResolve,失败则跳过并记录。
|
||||
- 几何不闭合Heal 失败:降阶为 Surface 外壳 输出并提示需要手修;或自动放宽 ChordGap 后重试一次。
|
||||
- 内存占用高超时:对超大装配自动切至 Fast 档;或先对源装配应用一个临时 Rule-based Simp Rep(如按体积阈值剔除小紧固件),再做 Shrinkwrap。
|
||||
|
||||
---
|
||||
|
||||
## 4. 备选与增强
|
||||
|
||||
### 4.1 规则简化(可作为 Shrinkwrap 前置加速)
|
||||
- 在 View Manager → Simp Rep 新建 Rule-based 表示:
|
||||
- Exclude 体积质量尺寸小于阈值的组件(大量螺钉、垫片先剔除,可显著提速 Shrinkwrap)。
|
||||
- Exclude 指定层(如电气标识层)或指定类型(弹簧、柔性件)。
|
||||
- 在该 Simp Rep 下再执行 Shrinkwrap Outer shell(步骤同 1.1)。
|
||||
|
||||
### 4.2 结果校核(强烈建议纳入流程)
|
||||
- 外形偏差对比:将外壳与原装配叠合,做 Global Interference Clearance 或 Compare Geometry,抽查 3–5 条关键尺寸偏差(目标 ≤ 0.2 mm 或依项目设限)。
|
||||
- 质量属性:若外壳作为近似质量模型使用,校核质量与体积差异阈值(例如 ≤ 2%)。
|
||||
- 开孔保留性:若测量依赖孔窗口,抽检是否被 Fill holes 意外封闭。
|
||||
|
||||
### 4.3 命名与配置管理
|
||||
- 统一命名模版:`项目_装配_SW_OS_QChordGap_日期.prt`;
|
||||
- 在模型参数中写入:`SW_METHOD=OuterShell`, `SW_QUALITY=8`, `SW_FILL_HOLES=No`, `SW_CHORD=0.10` 等,便于追溯;
|
||||
- 若走 PDM,定义外壳零件的 生命周期与可见性(只读、对外可分享)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 典型问题与处理建议(FAQ)
|
||||
|
||||
- 外壳看起来“破面漏面”
|
||||
- 提高 Quality 或减小 Chord height;
|
||||
- 关闭 Ignore small faces 或调小阈值;
|
||||
- 输出 Surface 后用 Heal Geometry 尝试修复,再 Solidify。
|
||||
- 文件仍然很大生成缓慢
|
||||
- 先用 Rule-based Simp Rep 排除小件再做 Shrinkwrap;
|
||||
- 减小 Quality,增大 Chord,或关闭高代价选项(如高级填孔);
|
||||
- 分层执行:对大子装先各自 Shrinkwrap,再在顶层对这些外壳做二次 Shrinkwrap。
|
||||
- 关键小特征被抹平,测不到
|
||||
- 关掉 Ignore small faces 或降低其阈值;
|
||||
- 提高 Quality 降低 Chord;
|
||||
- 对关键区域单独做 Local Refine(先对关键子装独立 Shrinkwrap,再装配上来)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 一页式落地清单(可直接照此执行)
|
||||
|
||||
1) 选择产物:独立外壳零件(默认) Envelope 简化表示(需关联)。
|
||||
2) (可选)前置规则简化:排除小件与无关层,加速 Shrinkwrap。
|
||||
3) Shrinkwrap 参数:
|
||||
- Method=Outer shell;Quality=7–9;Chord=0.05–0.15;Ignore small faces=开(阈值合理);Fill holes=按需。
|
||||
- Output=SolidSurface geometry。
|
||||
4) 命名与保存:采用统一命名与参数写入;入库或归档。
|
||||
5) 校核:抽检 3–5 条关键外形尺寸;检查孔窗口保留性;必要时微调参数重生。
|
||||
6) 测量分发:在外壳零件中测量;对外提供该外壳模型以减少泄密与体量。
|
||||
7) (可选)自动化:准备三档参数集,批处理顶层装配与重点子装;记录日志。
|
||||
|
||||
---
|
||||
|
||||
### 结论
|
||||
- 在 Creo 5,用 Shrinkwrap(Outer shell) 就能稳定地“只保留外壳、删除内部”;若要关联更新,用 Envelope 简化表示。
|
||||
- 关键在于 Outer shell + 合理的 QualityChordFillIgnore small faces 参数组合,并配合 规则简化与结果校核。
|
||||
- 若需要规模化与一致性,按第 3 节的无代码要点完成 OTKJ-Link 的批处理与参数固化即可。
|
||||
|
||||
如你愿意,我可以把以上步骤整理成一张操作卡(流程图 + 参数建议表),便于团队统一执行。
|
||||
@ -1,35 +0,0 @@
|
||||
@echo off
|
||||
echo Checking build status...
|
||||
echo.
|
||||
|
||||
echo === Modified Files Summary ===
|
||||
echo 1. ShrinkwrapManager.h - Added timeout_seconds field
|
||||
echo 2. ShellExportHandler.cpp - Added timeout parameter parsing
|
||||
echo 3. MFCCreoDll.cpp - Dynamic timeout support
|
||||
echo 4. ShrinkwrapManager.cpp - Enhanced error handling
|
||||
echo.
|
||||
|
||||
echo === Key Changes ===
|
||||
echo - Dynamic timeout: 10-300 seconds range
|
||||
echo - Better error messages for different OTK exceptions
|
||||
echo - Backward compatible API
|
||||
echo.
|
||||
|
||||
echo === Error Handling Types ===
|
||||
echo - pfcXToolkitError: General Creo operation failures
|
||||
echo - pfcXBadArgument: Invalid parameter values
|
||||
echo - pfcXToolkitOutOfMemory: OTK memory issues
|
||||
echo - std::bad_alloc: System memory issues
|
||||
echo - Unknown errors: Catch-all handler
|
||||
echo.
|
||||
|
||||
echo === Testing ===
|
||||
echo Use the following files to test:
|
||||
echo - test_timeout.py: Python automated testing
|
||||
echo - test_timeout.bat: Manual curl testing
|
||||
echo - SHRINKWRAP_OPTIMIZATION.md: Complete documentation
|
||||
echo.
|
||||
|
||||
echo Build should now compile without errors.
|
||||
echo Next: Compile the project and test with Creo.
|
||||
pause
|
||||
@ -1,32 +0,0 @@
|
||||
@echo off
|
||||
echo Testing Shrinkwrap API with different timeout values
|
||||
echo.
|
||||
|
||||
echo === Test 1: 15 second timeout ===
|
||||
curl -X POST http://localhost:12345/api/creo/shrinkwrap/shell ^
|
||||
-H "Content-Type: application/json" ^
|
||||
-d "{\"software_type\":\"creo\",\"project_name\":\"Timeout Test 1\",\"quality\":5,\"output_file_path\":\"test1.prt\",\"timeout_seconds\":15}"
|
||||
echo.
|
||||
echo.
|
||||
|
||||
echo === Test 2: 60 second timeout ===
|
||||
curl -X POST http://localhost:12345/api/creo/shrinkwrap/shell ^
|
||||
-H "Content-Type: application/json" ^
|
||||
-d "{\"software_type\":\"creo\",\"project_name\":\"Timeout Test 2\",\"quality\":5,\"output_file_path\":\"test2.prt\",\"timeout_seconds\":60}"
|
||||
echo.
|
||||
echo.
|
||||
|
||||
echo === Test 3: Invalid timeout (should use default) ===
|
||||
curl -X POST http://localhost:12345/api/creo/shrinkwrap/shell ^
|
||||
-H "Content-Type: application/json" ^
|
||||
-d "{\"software_type\":\"creo\",\"project_name\":\"Timeout Test 3\",\"quality\":5,\"output_file_path\":\"test3.prt\",\"timeout_seconds\":5}"
|
||||
echo.
|
||||
echo.
|
||||
|
||||
echo === Test 4: No timeout specified (should use default) ===
|
||||
curl -X POST http://localhost:12345/api/creo/shrinkwrap/shell ^
|
||||
-H "Content-Type: application/json" ^
|
||||
-d "{\"software_type\":\"creo\",\"project_name\":\"Timeout Test 4\",\"quality\":5,\"output_file_path\":\"test4.prt\"}"
|
||||
echo.
|
||||
|
||||
pause
|
||||
109
test_timeout.py
109
test_timeout.py
@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
简单的HTTP测试脚本,测试shrinkwrap接口的timeout功能
|
||||
"""
|
||||
import json
|
||||
import requests
|
||||
import sys
|
||||
import time
|
||||
|
||||
def test_shrinkwrap_timeout(timeout_seconds=60):
|
||||
"""
|
||||
测试shrinkwrap接口的超时功能
|
||||
"""
|
||||
url = "http://localhost:12345/api/creo/shrinkwrap/shell"
|
||||
|
||||
# 测试数据
|
||||
test_data = {
|
||||
"software_type": "creo",
|
||||
"project_name": "Timeout Test",
|
||||
"method": "outer_shell",
|
||||
"quality": 5,
|
||||
"chord_height": 0.1,
|
||||
"fill_holes": False,
|
||||
"ignore_small_surfaces": False,
|
||||
"small_surface_percentage": 0.0,
|
||||
"output_file_path": "test_shrinkwrap_timeout.prt",
|
||||
"output_type": "solid_surface",
|
||||
"ignore_quilts": True,
|
||||
"ignore_skeleton": True,
|
||||
"assign_mass_properties": False,
|
||||
"preset": "balanced",
|
||||
"timeout_seconds": timeout_seconds # 使用动态超时
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
print(f"测试shrinkwrap接口,超时设置: {timeout_seconds}秒")
|
||||
print(f"URL: {url}")
|
||||
print(f"请求数据: {json.dumps(test_data, indent=2, ensure_ascii=False)}")
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
response = requests.post(url, json=test_data, headers=headers, timeout=timeout_seconds+10)
|
||||
end_time = time.time()
|
||||
|
||||
print(f"请求耗时: {end_time - start_time:.2f}秒")
|
||||
print(f"HTTP状态码: {response.status_code}")
|
||||
print(f"响应内容: {response.text}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✓ 请求成功")
|
||||
return True
|
||||
elif response.status_code == 504:
|
||||
print("✓ 超时处理正常")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 请求失败: {response.status_code}")
|
||||
return False
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
print("✗ HTTP客户端超时")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("✗ 连接失败,请确保DLL服务正在运行")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"✗ 请求异常: {e}")
|
||||
return False
|
||||
|
||||
def test_different_timeouts():
|
||||
"""
|
||||
测试不同的超时设置
|
||||
"""
|
||||
print("=== 测试不同超时设置 ===\n")
|
||||
|
||||
# 测试各种超时值
|
||||
test_cases = [
|
||||
{"timeout": 15, "desc": "短超时测试"},
|
||||
{"timeout": 30, "desc": "默认超时测试"},
|
||||
{"timeout": 120, "desc": "长超时测试"},
|
||||
{"timeout": 5, "desc": "极短超时测试(应被限制为10秒)"},
|
||||
{"timeout": 400, "desc": "超长超时测试(应被限制为300秒)"}
|
||||
]
|
||||
|
||||
results = []
|
||||
for case in test_cases:
|
||||
print(f"--- {case['desc']} ---")
|
||||
success = test_shrinkwrap_timeout(case['timeout'])
|
||||
results.append({"case": case['desc'], "success": success})
|
||||
print()
|
||||
time.sleep(2) # 间隔等待
|
||||
|
||||
print("=== 测试结果汇总 ===")
|
||||
for result in results:
|
||||
status = "✓" if result['success'] else "✗"
|
||||
print(f"{status} {result['case']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
try:
|
||||
timeout = int(sys.argv[1])
|
||||
test_shrinkwrap_timeout(timeout)
|
||||
except ValueError:
|
||||
print("参数错误,请输入有效的超时秒数")
|
||||
sys.exit(1)
|
||||
else:
|
||||
test_different_timeouts()
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user