feat: 完善一级关系参数化建模与参数导出
This commit is contained in:
+155
-31
@@ -47,11 +47,59 @@ def _safe_int_or_none(value: object) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
def _empty_edge_polydata():
|
||||
polydata = vtk.vtkPolyData()
|
||||
polydata.SetPoints(vtk.vtkPoints())
|
||||
polydata.SetLines(vtk.vtkCellArray())
|
||||
edge_arr = vtk.vtkIntArray()
|
||||
edge_arr.SetName("edge_id")
|
||||
part_arr = vtk.vtkIntArray()
|
||||
part_arr.SetName("part_id")
|
||||
polydata.GetCellData().AddArray(edge_arr)
|
||||
polydata.GetCellData().AddArray(part_arr)
|
||||
return polydata
|
||||
|
||||
|
||||
def _timing_summary(timings: dict[str, object] | None, *, limit: int = 4) -> str:
|
||||
if not isinstance(timings, dict) or not timings:
|
||||
return ""
|
||||
labels = {
|
||||
"step_load": "STEP读取",
|
||||
"stats": "统计",
|
||||
"display_faces": "面显示",
|
||||
"display_edges": "边线",
|
||||
"apply_loaded": "界面刷新",
|
||||
"total": "总计",
|
||||
}
|
||||
rows: list[tuple[str, float]] = []
|
||||
for key, value in timings.items():
|
||||
if key == "total":
|
||||
continue
|
||||
try:
|
||||
seconds = float(value)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if seconds >= 0.05:
|
||||
rows.append((labels.get(str(key), str(key)), seconds))
|
||||
rows.sort(key=lambda item: item[1], reverse=True)
|
||||
parts = [f"{label} {seconds:.1f}s" for label, seconds in rows[:limit]]
|
||||
try:
|
||||
total = float(timings.get("total"))
|
||||
except (TypeError, ValueError):
|
||||
total = 0.0
|
||||
if total >= 0.05:
|
||||
parts.append(f"总计 {total:.1f}s")
|
||||
return ",".join(parts)
|
||||
|
||||
|
||||
class WindowCoreMixin:
|
||||
@Slot(object)
|
||||
def _run_ui_task(self, callback) -> None:
|
||||
callback()
|
||||
|
||||
def _empty_edge_polydata(self):
|
||||
return _empty_edge_polydata()
|
||||
|
||||
def showEvent(self, event) -> None:
|
||||
super().showEvent(event)
|
||||
if getattr(self, "first_show_handled", False):
|
||||
@@ -62,7 +110,7 @@ class WindowCoreMixin:
|
||||
@Slot()
|
||||
def _after_first_show(self) -> None:
|
||||
self._ensure_vtk_interactor_started()
|
||||
if getattr(self, "auto_load_on_show", False):
|
||||
if getattr(self, "auto_load_on_show", False) and getattr(self, "step_path", None) is not None:
|
||||
self.auto_load_on_show = False
|
||||
self.load_step(self.step_path, background=True)
|
||||
|
||||
@@ -927,23 +975,41 @@ class WindowCoreMixin:
|
||||
deflection: float,
|
||||
show_internal_edges: bool,
|
||||
build_polydata: bool = True,
|
||||
build_edges: bool = True,
|
||||
) -> dict[str, object]:
|
||||
timings: dict[str, float] = {}
|
||||
total_started = time.perf_counter()
|
||||
started = time.perf_counter()
|
||||
new_model = StepModel.load(path)
|
||||
timings["step_load"] = time.perf_counter() - started
|
||||
started = time.perf_counter()
|
||||
stats = new_model.stats()
|
||||
timings["stats"] = time.perf_counter() - started
|
||||
result = {
|
||||
"path": path,
|
||||
"model": new_model,
|
||||
"stats": stats,
|
||||
"deflection": deflection,
|
||||
"show_internal_edges": show_internal_edges,
|
||||
"timings": timings,
|
||||
}
|
||||
if build_polydata:
|
||||
started = time.perf_counter()
|
||||
face_polydata = new_model.build_face_polydata(deflection=deflection)
|
||||
result["model_polydata"] = _smooth_surface_polydata(face_polydata)
|
||||
result["edge_polydata"] = new_model.build_edge_polydata(
|
||||
deflection=deflection,
|
||||
show_same_domain_internal_edges=show_internal_edges,
|
||||
)
|
||||
timings["display_faces"] = time.perf_counter() - started
|
||||
if build_edges:
|
||||
started = time.perf_counter()
|
||||
result["edge_polydata"] = new_model.build_edge_polydata(
|
||||
deflection=deflection,
|
||||
show_same_domain_internal_edges=show_internal_edges,
|
||||
)
|
||||
timings["display_edges"] = time.perf_counter() - started
|
||||
else:
|
||||
result["edge_polydata"] = _empty_edge_polydata()
|
||||
result["edge_polydata_deferred"] = True
|
||||
timings["display_edges"] = 0.0
|
||||
timings["total"] = time.perf_counter() - total_started
|
||||
return result
|
||||
|
||||
def _load_step_background_or_sync(self, path: str | Path, *, background: bool) -> None:
|
||||
@@ -995,6 +1061,7 @@ class WindowCoreMixin:
|
||||
deflection: float,
|
||||
show_internal_edges: bool,
|
||||
status_prefix: str,
|
||||
defer_edges: bool = True,
|
||||
) -> bool:
|
||||
self.statusBar().showMessage(f"{status_prefix} {new_path.name}...")
|
||||
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
||||
@@ -1004,6 +1071,7 @@ class WindowCoreMixin:
|
||||
new_path,
|
||||
deflection=deflection,
|
||||
show_internal_edges=show_internal_edges,
|
||||
build_edges=not defer_edges,
|
||||
)
|
||||
result["display"] = "ready"
|
||||
except Exception as exc:
|
||||
@@ -1013,7 +1081,9 @@ class WindowCoreMixin:
|
||||
finally:
|
||||
QApplication.restoreOverrideCursor()
|
||||
self._apply_loaded_model_result(result, reset_camera=True)
|
||||
self.statusBar().showMessage(f"Loaded {self.step_path.name}")
|
||||
timing_text = _timing_summary(result.get("timings") if isinstance(result, dict) else None)
|
||||
suffix = f";{timing_text}" if timing_text else ""
|
||||
self.statusBar().showMessage(f"Loaded {self.step_path.name}{suffix}")
|
||||
return True
|
||||
|
||||
@Slot(object)
|
||||
@@ -1026,6 +1096,7 @@ class WindowCoreMixin:
|
||||
deflection=self.preview_load_deflection,
|
||||
show_internal_edges=self._show_same_domain_internal_edges(),
|
||||
status_prefix="读取 STEP 可视化网格",
|
||||
defer_edges=True,
|
||||
)
|
||||
finally:
|
||||
self._end_load_task()
|
||||
@@ -1047,6 +1118,7 @@ class WindowCoreMixin:
|
||||
return detached
|
||||
|
||||
def _apply_loaded_model_result(self, result: dict[str, object], *, reset_camera: bool) -> None:
|
||||
apply_started = time.perf_counter()
|
||||
new_path = Path(result["path"])
|
||||
stats = result["stats"]
|
||||
self.model = result["model"]
|
||||
@@ -1062,7 +1134,8 @@ class WindowCoreMixin:
|
||||
self._reset_selection()
|
||||
model_polydata = result.get("model_polydata")
|
||||
edge_polydata = result.get("edge_polydata")
|
||||
if model_polydata is None or edge_polydata is None:
|
||||
edge_deferred = bool(result.get("edge_polydata_deferred"))
|
||||
if model_polydata is None or (edge_polydata is None and not edge_deferred):
|
||||
deflection = float(result.get("deflection", 0.8))
|
||||
show_internal_edges = bool(result.get("show_internal_edges", self._show_same_domain_internal_edges()))
|
||||
model_polydata = self.model.build_face_polydata(deflection=deflection)
|
||||
@@ -1070,6 +1143,8 @@ class WindowCoreMixin:
|
||||
deflection=deflection,
|
||||
show_same_domain_internal_edges=show_internal_edges,
|
||||
)
|
||||
elif edge_polydata is None:
|
||||
edge_polydata = _empty_edge_polydata()
|
||||
self._rebuild_scene_from_polydata(
|
||||
model_polydata,
|
||||
edge_polydata,
|
||||
@@ -1077,18 +1152,28 @@ class WindowCoreMixin:
|
||||
)
|
||||
self._clear_editable_candidates()
|
||||
self._clear_cylinder_candidates()
|
||||
self.set_info(
|
||||
{
|
||||
"file": str(self.step_path),
|
||||
"parts": stats.parts,
|
||||
"solids": stats.solids,
|
||||
"faces": stats.faces,
|
||||
"edges": stats.edges,
|
||||
"vertices": stats.vertices,
|
||||
"display": result.get("display", "quick preview" if self.load_in_progress else "ready"),
|
||||
}
|
||||
)
|
||||
timings = result.get("timings")
|
||||
if isinstance(timings, dict):
|
||||
timings["apply_loaded"] = time.perf_counter() - apply_started
|
||||
display_state = result.get("display", "quick preview" if self.load_in_progress else "ready")
|
||||
if edge_deferred:
|
||||
display_state = f"{display_state}; edge display pending"
|
||||
info_payload = {
|
||||
"file": str(self.step_path),
|
||||
"parts": stats.parts,
|
||||
"solids": stats.solids,
|
||||
"faces": stats.faces,
|
||||
"edges": stats.edges,
|
||||
"vertices": stats.vertices,
|
||||
"display": display_state,
|
||||
}
|
||||
timing_text = _timing_summary(timings if isinstance(timings, dict) else None)
|
||||
if timing_text:
|
||||
info_payload["load_performance"] = timing_text
|
||||
self.set_info(info_payload)
|
||||
self._update_action_states()
|
||||
if edge_deferred:
|
||||
QTimer.singleShot(80, self._rebuild_deferred_edge_display)
|
||||
|
||||
@Slot(object)
|
||||
def _finish_initial_load(self, result: object) -> None:
|
||||
@@ -1177,10 +1262,12 @@ class WindowCoreMixin:
|
||||
def open_step(self) -> None:
|
||||
if self._edit_busy("请等待当前编辑完成后再打开文件。"):
|
||||
return
|
||||
current_path = getattr(self, "step_path", None)
|
||||
start_dir = current_path.parent if isinstance(current_path, Path) else Path.cwd()
|
||||
path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"打开 STEP 文件",
|
||||
str(self.step_path.parent if self.step_path else Path.cwd()),
|
||||
str(start_dir),
|
||||
"STEP 文件 (*.step *.stp);;所有文件 (*.*)",
|
||||
)
|
||||
if path:
|
||||
@@ -1200,6 +1287,9 @@ class WindowCoreMixin:
|
||||
return
|
||||
path_text = self.path_label.text().strip() if hasattr(self, "path_label") else ""
|
||||
path = Path(path_text) if path_text else self.step_path
|
||||
if path is None:
|
||||
self.statusBar().showMessage("请先点击“导入几何模型”选择 STEP 文件。")
|
||||
return
|
||||
self._ensure_vtk_interactor_started()
|
||||
self.load_step(path)
|
||||
|
||||
@@ -1620,6 +1710,50 @@ class WindowCoreMixin:
|
||||
edge_id = int(self.edge_id_array.GetValue(cell_id))
|
||||
self.edge_cell_ids_by_edge.setdefault(edge_id, []).append(cell_id)
|
||||
|
||||
def _install_edge_polydata(self, edge_polydata, *, render: bool = True) -> None:
|
||||
if edge_polydata is None:
|
||||
edge_polydata = _empty_edge_polydata()
|
||||
old_actor = getattr(self, "edge_actor", None)
|
||||
if old_actor is not None:
|
||||
try:
|
||||
self.renderer.RemoveActor(old_actor)
|
||||
except Exception:
|
||||
pass
|
||||
self.edge_polydata = edge_polydata
|
||||
self.edge_id_array = self.edge_polydata.GetCellData().GetArray("edge_id")
|
||||
self._rebuild_edge_polydata_cell_index()
|
||||
edge_mapper = vtk.vtkPolyDataMapper()
|
||||
edge_mapper.SetInputData(self.edge_polydata)
|
||||
_prepare_static_mapper(edge_mapper)
|
||||
self.edge_actor = vtk.vtkActor()
|
||||
self.edge_actor.SetMapper(edge_mapper)
|
||||
self.edge_actor.GetProperty().SetColor(0.08, 0.09, 0.1)
|
||||
self.edge_actor.GetProperty().SetLineWidth(1.0)
|
||||
self.edge_actor.GetProperty().LightingOff()
|
||||
self.renderer.AddActor(self.edge_actor)
|
||||
self.edge_overlay_polydata_cache.clear()
|
||||
if render:
|
||||
self.render_window.Render()
|
||||
|
||||
@Slot()
|
||||
def _rebuild_deferred_edge_display(self) -> None:
|
||||
if self.model is None or self.operation_in_progress or self.load_in_progress:
|
||||
return
|
||||
started = time.perf_counter()
|
||||
self.statusBar().showMessage("模型已显示,正在补充边线...")
|
||||
QApplication.processEvents()
|
||||
try:
|
||||
edge_polydata = self.model.build_edge_polydata(
|
||||
deflection=float(getattr(self, "preview_load_deflection", 0.35) or 0.35),
|
||||
show_same_domain_internal_edges=self._show_same_domain_internal_edges(),
|
||||
)
|
||||
self._install_edge_polydata(edge_polydata, render=True)
|
||||
except Exception as exc:
|
||||
self.statusBar().showMessage(f"模型已显示;边线补充失败:{exc}")
|
||||
return
|
||||
elapsed = time.perf_counter() - started
|
||||
self.statusBar().showMessage(f"模型边线已补充,用时 {elapsed:.1f}s")
|
||||
|
||||
def _remember_overlay_cache_item(self, cache: dict, key: object, value: object) -> object:
|
||||
if len(cache) >= self.overlay_cache_limit:
|
||||
try:
|
||||
@@ -1744,18 +1878,8 @@ class WindowCoreMixin:
|
||||
self.model_actor.SetBackfaceProperty(backface_property)
|
||||
self.renderer.AddActor(self.model_actor)
|
||||
|
||||
self.edge_polydata = edge_polydata
|
||||
self.edge_id_array = self.edge_polydata.GetCellData().GetArray("edge_id")
|
||||
self._rebuild_edge_polydata_cell_index()
|
||||
edge_mapper = vtk.vtkPolyDataMapper()
|
||||
edge_mapper.SetInputData(self.edge_polydata)
|
||||
_prepare_static_mapper(edge_mapper)
|
||||
self.edge_actor = vtk.vtkActor()
|
||||
self.edge_actor.SetMapper(edge_mapper)
|
||||
self.edge_actor.GetProperty().SetColor(0.08, 0.09, 0.1)
|
||||
self.edge_actor.GetProperty().SetLineWidth(1.0)
|
||||
self.edge_actor.GetProperty().LightingOff()
|
||||
self.renderer.AddActor(self.edge_actor)
|
||||
self.edge_actor = None
|
||||
self._install_edge_polydata(edge_polydata, render=False)
|
||||
|
||||
self.highlight_actor = None
|
||||
self.edge_highlight_actor = None
|
||||
|
||||
Reference in New Issue
Block a user