Item 37. AUTO_PTR

本文探讨了C++标准库中auto_ptr的正确用法,强调了其在资源管理和异常安全性方面的重要性,并介绍了const auto_ptr的使用技巧。

Difficulty: 8

This Item covers basics about how you can use the standard auto_ptr safely and effectively.

Historical note: The original simpler form of this Item, appearing as a Special Edition of Guru of the Week, was first published in honor of the voting out of the Final Draft International Standard for Programming Language C++. It was known/suspected that auto_ptr would change one last time at the final meeting, where the standard was to be voted complete (Morristown, New Jersey, November 1997), so this problem was posted the day before the meeting began. The solution, freshly updated to reflect the prior day's changes to the standard, became the first published treatment of the standard auto_ptr.

Many thanks from all of us to Bill Gibbons, Greg Colvin, Steve Rumsby, and others who worked hard on the final refinement of auto_ptr. Greg, in particular, has labored over auto_ptr and related smart pointer classes for many years to satisfy various committee concerns and requirements, and deserves public recognition for that work.

This problem, now with a considerably more comprehensive and refined solution, illustrates the reasons for the eleventh-hour changes that were made, and it shows how you can make the best possible use of auto_ptr.

Comment on the following code: What's good, what's safe, what's legal, and what's not?

auto_ptr<T> source() 
{
  return auto_ptr<T>( new T(1) );
}
void sink( auto_ptr<T> pt ) { }
void f()
{
  auto_ptr<T> a( source() );
  sink( source() );
  sink( auto_ptr<T>( new T(1) ) );
  vector< auto_ptr<T> > v;
  v.push_back( auto_ptr<T>( new T(3) ) );
  v.push_back( auto_ptr<T>( new T(4) ) );
  v.push_back( auto_ptr<T>( new T(1) ) );
  v.push_back( a );
  v.push_back( auto_ptr<T>( new T(2) ) );
  sort( v.begin(), v.end() );
  cout << a->Value();
}
class C
{
public:    /*...*/
protected: /*...*/
private:   /*...*/
  auto_ptr<CImpl> pimpl_;
};

 

Solution

graphics/bulb_icon.gif

Most people have heard of the standard auto_ptr smart pointer facility, but not everyone uses it daily. That's a shame, because it turns out that auto_ptr neatly solves common C++ coding problems, and using it well can lead to more-robust code. This article shows how to use auto_ptr correctly to make your code safer梐nd how to avoid the dangerous but common abuses of auto_ptr that create intermittent and hard-to-diagnose bugs.

Why "Auto" Pointer?

auto_ptr is just one of a wide array of possible smart pointers. Many commercial libraries provide more-sophisticated kinds of smart pointers that can do wild and wonderful things, from managing reference counts to providing advanced proxy services. Think of the standard C++ auto_ptr as the Ford Escort of smart pointers梐 simple, general-purpose smart pointer that doesn't have all the gizmos and luxuries of special-purpose or high-performance smart pointers, but does many common things well and is perfectly suitable for regular use.

What auto_ptr does is own a dynamically allocated object and perform automatic cleanup when the object is no longer needed. Here's a simple example of code that's unsafe without auto_ptr.

// Example 1(a): Original code 
//
void f()
{
  T* pt( new T );
  /*...more code...*/
  delete pt;
}

Most of us write code like this every day. If f() is a three-line function that doesn't do anything exceptional, this may be fine. But if f() never executes the delete statement, either because of an early return or because of an exception thrown during execution of the function body, then the allocated object is not deleted and we have a classic memory leak.

A simple way to make Example 1(a) safe is to wrap the pointer in a "smarter," pointer-like object that owns the pointer and that, when destroyed, deletes the pointed-at object automatically. Because this smart pointer is simply used as an automatic object (that is, one that's destroyed automatically when it goes out of scope), it's reasonably called an "auto" pointer:

// Example 1(b): Safe code, with auto_ptr 
//
void f()
{
  auto_ptr<T> pt( new T );
  /*...more code...*/
} // cool: pt's destructor is called as it goes out
  // of scope, and the object is deleted automatically

Now the code will not leak the T object, no matter whether the function exits normally or by means of an exception, because pt's destructor will always be called during stack unwinding. The cleanup happens automatically.

Finally, using an auto_ptr is just about as easy as using a builtin pointer, and to "take back" the resource and assume manual ownership again, we just call release().

// Example 2: Using an auto_ptr 
//
void g()
{
  T* pt1 = new T;
  // right now, we own the allocated object
  // pass ownership to an auto_ptr
  auto_ptr<T> pt2( pt1 );
  // use the auto_ptr the same way
  // we'd use a simple pointer
  *pt2 = 12;       // same as "*pt1 = 12;"
  pt2->SomeFunc(); // same as "pt1->SomeFunc();"
  // use get() to see the pointer value
  assert( pt1 == pt2.get() );
  // use release() to take back ownership
  T* pt3 = pt2.release();
  // delete the object ourselves, since now
  // no auto_ptr owns it any more
  delete pt3;
} // pt2 doesn't own any pointer, and so won't
  // try to delete it... OK, no double delete

Finally, we can use auto_ptr's reset() function to reset the auto_ptr to own a different object. If the auto_ptr already owns an object, though, it first deletes the already-owned object, so calling reset() is much the same as destroying the auto_ptr and creating a new one that owns the new object.

// Example 3: Using reset() 
//
void h()
{
  auto_ptr<T> pt( new T(1) );
  pt.reset( new T(2) );
    // deletes the first T that was
    // allocated with "new T(1)"
} // finally, pt goes out of scope and
  // the second T is also deleted
Wrapping Pointer Data Members

Similarly, auto_ptr can be used to safely wrap pointer data members. Consider the following common example that uses the Pimpl (or compiler-firewall) Idiom.[2]

[2] The Pimpl Idiom is useful for reducing project build times because it prevents wide-ranging recompilations of client code whenever the private portions of C change. For more about the Pimpl Idiom and how best to deploy compiler firewalls, see Items 26 through 30.

// Example 4(a): A typical Pimpl 
//

// file c.h
//
class C
{
public:
  C();
  ~C();
  /*...*/
private:
  struct CImpl; // forward declaration
  CImpl* pimpl_;
};

// file c.cpp
//
struct C::CImpl { /*...*/ };
C::C() : pimpl_( new CImpl ) { }
C::~C() { delete pimpl_; }

In brief, C's private details are split off into a separate implementation object that's hidden behind an opaque pointer. The idea is that C's constructor is responsible for allocating the private helper Pimpl object that contains the class's hidden internals, and C's destructor is responsible for deallocating it. Using auto_ptr, however, we find an easier way:

// Example 4(b): A safer Pimpl, using auto_ptr 
//
// file c.h
//
class C
{
public:
  C();
  ~C();
  /*...*/
private:
  struct CImpl; // forward declaration
  auto_ptr<CImpl> pimpl_;
  C& operator = ( const C& );
  C( const C& );
};
// file c.cpp
//
struct C::CImpl { /*...*/ };
C::C() : pimpl_( new CImpl ) { }
C::~C() {}

Now the destructor doesn't need to worry about deleting the pimpl_ pointer, because the auto_ptr will handle it automatically. Better still, it means C::C() has to do less work to detect and recover from constructor failures because pimpl_ is always automatically cleaned up. This can be easier than managing the pointer manually, and it follows the good practice of wrapping resource ownership in objects梐 job that auto_ptr is well suited to do. We'll revisit this example again at the end.

Ownership, Sources, and Sinks

This is nifty stuff all by itself, but it gets better. It's also very useful to pass auto_ptrs to and from functions, as function parameters and return values.

To see why, first consider what happens when you copy an auto_ptr: An auto_ptr owns the object that it holds a pointer to, and only one auto_ptr may own an object at a time. When you copy an auto_ptr, you automatically transfer ownership from the source auto_ptr to the target auto_ptr; if the target auto_ptr already owns an object, that object is first freed. After the copy, only the target auto_ptr owns the pointer and will delete it in due time, while the source is set back to a null state and can no longer be used to refer to the owned object.

For example:

// Example 5: Transferring ownership from 
//            one auto_ptr to another
//
void f()
{
  auto_ptr<T> pt1( new T );
  auto_ptr<T> pt2;
  pt1->DoSomething(); // OK
  pt2 = pt1;  // now pt2 owns the pointer,
              // and pt1 does not
  pt2->DoSomething(); // OK
} // as we go out of scope, pt2's destructor
  // deletes the pointer, but pt1's does nothing

But be careful to avoid the pitfall of trying to use a nonowning auto_ptr:

// Example 6: Never try to do work through 
//            a non-owning auto_ptr
//
void f()
{
  auto_ptr<T> pt1( new T );
  auto_ptr<T> pt2;
  pt2 = pt1;  // now pt2 owns the pointer, and
              // pt1 does not
  pt1->DoSomething();
              // error: following a null pointer
}

With that in mind, we start to see how well auto_ptr works with sources and sinks. A "source" is a function or other operation that creates a new resource, and then it typically hands off and relinquishes ownership of the resource. A "sink" is a function that does the reverse梟amely, that takes ownership of an existing object (and typically disposes of it). Instead of just having sources and sinks return and take bald pointers, though, it's usually better to return or take a smart pointer that owns the resource.

This gets us to the first part of the Item's code:

auto_ptr<T> source() 
{
  return auto_ptr<T>( new T(1) );
}
void sink( auto_ptr<T> pt ) { }

This is both legal and safe. Note the elegance of what's going on here:

  1. source() allocates a new object and returns it to the caller in a completely safe way, by letting the caller assume ownership of the pointer. Even if the caller ignores the return value (of course, you would never write code that ignores return values, right?), the allocated object will always be safely deleted.

    See also Item 19, which demonstrates why this is an important idiom, since returning a result by wrapping it in something like an auto_ptr is sometimes the only way to make a function strongly exception-safe.

  2. sink() takes an auto_ptr by value and therefore assumes ownership of it. When sink() is done, the deletion is performed as the local auto_ptr object goes out of scope (as long as sink() itself hasn't handed off ownership to someone else). Because the sink() function as written above doesn't actually do anything with the parameter, calling "sink( pt );" is a fancy way of writing "pt.reset(0);".

    The next piece of code shows source() and sink() in action.

    void f() 
    {
      auto_ptr<T> a( source() );
    

This is again both legal and safe. Here f() takes ownership of the pointer received from source(), and (ignoring some problems later in f()) it will delete it automatically when the automatic variable goes out of scope. This is fine, and it's exactly how passing back an auto_ptr by value is meant to work.

sink( source() ); 

Once again, this is both legal and safe. Given the trivial (that is, empty) definitions of source() and sink() here, this is just a fancy way of writing "delete new T(1);". So is it really useful? Well, if you imagine source() as a nontrivial factory function and sink() as a nontrivial consumer, then yes, it makes a lot of sense and crops up regularly in real-world programming.

sink( auto_ptr<T>( new T(1) ) ); 

Still legal and safe. This is another fancy way of writing "delete new T(1);", and it's a useful idiom when sink() is a nontrivial consumer function that takes ownership of the pointed-to object.

But beware: Never use auto_ptrs except in one of the ways I've just described. I have seen many programmers try to use auto_ptrs in other ways, just as they would use any other object. The problem with this is that auto_ptrs are most assuredly not like any other object. Here's the fundamental issue, and I'll highlight it to make sure it stands out.

 

For auto_ptr, copies are NOT equivalent.

 

It turns out that this has important effects when you try to use auto_ptrs with generic code that does make copies and isn't necessarily aware that copies aren't equivalent (after all, copies usually are). Consider the following code that I regularly see posted on the C++ newsgroups:

vector< auto_ptr<T> > v; 

This is not legal, and it sure isn't safe! Beware梩his road is paved with good intentions.

It is never safe to put auto_ptrs into standard containers. Some people will tell you that their compiler and library compiles this fine, and others will tell you that they've seen exactly this example recommended in the documentation of a certain popular compiler. Don't listen to them.

The problem is that auto_ptr does not quite meet the requirements of a type you can put into containers, because copies of auto_ptrs are not equivalent. For one thing, there's nothing that says a vector can't just decide to up and make an "extra" internal copy of some object it contains. But hold on, because it's about to get worse.

v.push_back( auto_ptr<T>( new T(3) ) ); 
v.push_back( auto_ptr<T>( new T(4) ) );
v.push_back( auto_ptr<T>( new T(1) ) );
v.push_back( a );

(Note that copying a into v means that the a object no longer owns the pointer it's carrying. More on that in a moment.)

v.push_back( auto_ptr<T>( new T(2) ) ); 
sort( v.begin(), v.end() );

Illegal. Unsafe. When you call generic functions that will copy elements, like sort() does, the functions have to be able to assume that copies are going to be equivalent. At least one popular sort internally takes a copy of a "pivot" element, and if you try to make it work on auto_ptrs, it will merrily take a copy of the pivot auto_ptr object (thereby taking ownership and putting it in a temporary auto_ptr on the side), do the rest of its work on the sequence (including taking further copies of the now-nonowning auto_ptr that was picked as a pivot value), and when the sort is over, the pivot is destroyed and you have a problem. At least one auto_ptr in the sequence no longer owns the pointer it once held, and in fact the pointer it held has already been deleted.

So the standards committee bent over backward to do everything it could to help you out. The standard auto_ptr was deliberately and specifically designed to break if you try to use it with the standard containers (or, at least, to break with most natural implementations of the standard library). To do this, the committee used a trick: auto_ptr's copy constructor and copy assignment operator take references to non-const to the right-hand-side object. Most implementations of the standard containers' single-element insert() functions take a reference to const, and hence won't work with auto_ptrs.

The Scoop on Nonowning auto_ptrs
    // (after having copied a to another auto_ptr) 
  cout << a->Value();
}

(We'll assume that a was copied, but that its pointer wasn't deleted by the vector or the sort.) Copying an auto_ptr not only transfers ownership, but resets the source auto_ptr to null. This is done specifically to avoid letting anyone do anything through a nonowning auto_ptr. Using a nonowning auto_ptr like this is not legal, and trying to dereference a null pointer like this will result in undefined behavior (typically, a core dump or memory access exception on most systems).

This brings us to the last common usage of auto_ptr.

Wrapping Pointer Members
class C 
{
public:    /*...*/
protected: /*...*/
private:   /*...*/
  auto_ptr<CImpl> pimpl_;
};

Possible issue: One of the /*...*/ areas (whether public, protected, or private) had better include at least declarations for copy construction and copy assignment.

auto_ptrs are useful for encapsulating pointing member variables. This works very much like our motivating example at the beginning of this Item, except that instead of saving us the trouble of doing cleanup at the end of a function, it now saves us the trouble of doing cleanup in C's destructor.

There is still a caveat, of course: Just as if you were using a bald pointer data member instead of an auto_ptr member, you will have to supply your own destructor and copy constructor and copy assignment operator for the class (even if you disable them by making them private and undefined), because the default ones will do the wrong thing.

auto_ptr and Exception Safety

Finally, auto_ptr is sometimes essential to writing exception-safe code. Consider the following function:

// Exception-safe? 
//
String f()
{
    String result;
    result = "some value";
    cout << "some output";
    return result;
}

This function has two visible side effects: It emits some output, and it returns a String. A detailed examination of exception safety is beyond the scope of this Item, but the goal we want to achieve is the strong exception-safety guarantee, which boils down to ensuring that the function acts atomically梕ven if there are exceptions, either all side effects will happen or none of them will.

Although the above code comes pretty close to achieving the strong exception-safety guarantee, there's still one minor quibble, as illustrated by the following calling code:

String theName; 
theName = f();

The String copy constructor is invoked because the result is returned by value, and the copy assignment operator is invoked to copy the result into theName. If either copy fails, then f() has completed all of its work and all of its side effects (good), but the result has been irretrievably lost (oops).

Can we do better, and perhaps avoid the problem by avoiding the copy? For example, we could let the function take a non-const String reference parameter and place the return value in that:

// Better? 
//
void f( String& result )
{
  cout << "some output";
  result = "some value";
}

This may look better, but it isn't, because the assignment to result might still fail which leaves us with one side effect complete and the other incomplete. Bottom line, this attempt doesn't really buy us much.

One way to solve the problem is to return a pointer to a dynamically allocated String, but the best solution is to go a step farther and return the pointer in an auto_ptr:

// Correct (finally!) 
//
auto_ptr<String> f()
{
  auto_ptr<String> result = new String;
  •result = "some value";
  cout << "some output";
  return result;
      // rely on transfer of
      // ownership; this can't throw
}

This does the trick, since we have effectively hidden all of the work to construct the second side effect (the return value) while ensuring that it can be safely returned to the caller using only nonthrowing operations after the first side effect has completed (the printing of the message). We know that, once the cout is complete, the returned value will make it successfully into the hands of the caller, and be correctly cleaned up in all cases: If the caller accepts the returned value, the act of accepting a copy of the auto_ptr causes the caller to take ownership; and if the caller does not accept the returned value, say by ignoring the return value, the allocated String will be automatically cleaned up as the temporary auto_ptr holding it is destroyed. The price for this extra safety? As often happens when implementing strong exception safety, the strong safety comes at the (usually minor) cost of some efficiency梙ere, the extra dynamic memory allocation. But, when it comes to trading off efficiency for correctness, we usually ought to prefer the latter!

Make a habit of using smart pointers like auto_ptr in your daily work. auto_ptr neatly solves common problems and will make your code safer and more robust, especially when it comes to preventing resource leaks and ensuring strong exception safety. Because it's standard, it's portable across libraries and platforms, and so it will be right there with you wherever you take your code.

The const auto_ptr Idiom

Now that we've waded through the deeper stuff, here's a technique you'll find interesting. Among its other benefits, the refinement to auto_ptr also means that const auto_ptrs never lose ownership. Copying a const auto_ptr is illegal, and in fact the only things you can do with a const auto_ptr are dereference it with operator*() or operator->() or call get() to inquire about the value of the contained pointer. This means that we have a clear and concise idiom to express that an auto_ptr can never lose ownership.

const auto_ptr<T> pt1( new T ); 
    // making pt1 const guarantees that pt1 can
    // never be copied to another auto_ptr, and
    // so is guaranteed to never lose ownership
auto_ptr<T> pt2( pt1 ); // illegal
auto_ptr<T> pt3;
pt3 = pt1;              // illegal
pt1.release();          // illegal
pt1.reset( new T );     // illegal

Now that's what I call const. So if you want to declare to the world that an auto_ptr can never be changed and will always delete what it owns, this is the way to do it. The const auto_ptr idiom is a useful and common technique, and one you should keep in mind. The originally posted solution to this Guru of the Week issue concluded with the following words: "This const auto_ptr idiom is one of those things that's likely to become a commonly used technique, and now you can say that you knew about it since the beginning."

templat_editor.py文件已修改,但现在我的main_window.py的完整代码如下:#主窗口 - PyQt6 版本 import os import cv2 import numpy as np import sys import logging import faulthandler # 用于捕获崩溃信息 import ctypes # Windows API 调用支持 from typing import Optional from pyzbar.pyzbar import decode from PyQt6.QtGui import QPainter, QPolygonF, QPixmap from PyQt6.QtCore import QPointF from PyQt6.QtCore import PYQT_VERSION_STR, QRectF from PyQt6.QtGui import QAction from PyQt6.QtWidgets import QDockWidget, QWidget,QMainWindow, QStatusBar, \ QTabWidget, QToolBar, QApplication, QMessageBox, QFileDialog, QDoubleSpinBox from PyQt6.QtWidgets import ( QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QGroupBox, QLineEdit, QFormLayout, QSpinBox, QColorDialog, QRadioButton, QButtonGroup, QCheckBox, QTextEdit, QFontComboBox ) from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtGui import QColor, QFont, QTextCharFormat from ui.template_editor import TemplateCanvas # 启用故障处理程序,帮助调试崩溃问题 faulthandler.enable() # 设置日志系统,记录运行时信息 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("app_debug.log"), # 输出到文件 logging.StreamHandler() # 输出到控制台 ] ) logger = logging.getLogger(__name__) logger.info("应用程序启动") logger.info(f"Python 路径: {sys.executable}") logger.info(f"参数: {sys.argv}") logger.info(f"系统路径: {sys.path}") logger.info(f"工作目录: {os.getcwd()}") logger.info(f"PyQt6 版本: {'未导入' if 'PYQT_VERSION_STR' not in globals() else PYQT_VERSION_STR}") # 检查是否运行在虚拟环境中 if not hasattr(sys, 'real_prefix') and not hasattr(sys, 'base_prefix'): logging.warning("警告: 未在虚拟环境中运行,建议使用虚拟环境") # 设置调试标志和环境变量 os.environ['QT_DEBUG_PLUGINS'] = '1' os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = '--disable-gpu --no-sandbox' # Windows 错误报告设置 if sys.platform == 'win32': ctypes.windll.kernel32.SetErrorMode.argtypes = [ctypes.c_uint] ctypes.windll.kernel32.SetErrorMode(ctypes.c_uint(0x0001 | 0x0002)) # 设置堆栈保护 os.environ['QT_FATAL_CRITICALS'] = '1' os.environ['QT_FATAL_WARNINGS'] = '1' class BasePropertiesPanel(QWidget): """属性面板基类,定义通用信号与接口""" propertyChanged = pyqtSignal(str, object) # 属性变化信号 def __init__(self, parent=None): super().__init__(parent) self.region = None self.setup_ui() def setup_ui(self): pass def set_region(self, region): """绑定当前编辑区域对象""" self.region = region self.update_ui_from_region() def update_ui_from_region(self): """根据区域数据更新 UI""" pass def update_region_from_ui(self): """根据 UI 数据更新区域属性""" pass class TextPropertiesPanel(BasePropertiesPanel): """文本属性面板 UI 实现 """ # 定义信号用于与画布通信 textChanged = pyqtSignal(str) fontChanged = pyqtSignal(QFont) colorChanged = pyqtSignal(QColor) alignmentChanged = pyqtSignal(Qt.AlignmentFlag) positionChanged = pyqtSignal(float, float) sizeChanged = pyqtSignal(float, float) def __init__(self, parent=None): super().__init__(parent) self.current_font = QFont() self.current_color = QColor(0, 0, 0) # 默认黑色 self.current_alignment = Qt.AlignmentFlag.AlignLeft def setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(10) # 文本内容编辑区域 content_group = QGroupBox("文本内容") content_layout = QVBoxLayout(content_group) self.text_edit = QTextEdit() self.text_edit.setPlaceholderText("输入文本内容...") self.text_edit.setMinimumHeight(80) self.text_edit.textChanged.connect(self.on_text_changed) content_layout.addWidget(self.text_edit) layout.addWidget(content_group) # 字体属性区域 font_group = QGroupBox("字体属性") font_layout = QFormLayout(font_group) # 字体选择 self.font_combo = QFontComboBox() self.font_combo.currentFontChanged.connect(self.on_font_changed) # 字号选择 self.font_size_spin = QSpinBox() self.font_size_spin.setRange(6, 120) self.font_size_spin.setValue(12) self.font_size_spin.valueChanged.connect(self.on_font_size_changed) # 字体样式 style_layout = QHBoxLayout() self.bold_check = QCheckBox("粗体") self.italic_check = QCheckBox("斜体") self.underline_check = QCheckBox("下划线") self.bold_check.toggled.connect(self.on_style_changed) self.italic_check.toggled.connect(self.on_style_changed) self.underline_check.toggled.connect(self.on_style_changed) style_layout.addWidget(self.bold_check) style_layout.addWidget(self.italic_check) style_layout.addWidget(self.underline_check) # 字体颜色 color_layout = QHBoxLayout() self.color_label = QLabel() self.color_label.setFixedSize(20, 20) self.color_label.setStyleSheet("background-color: black; border: 1px solid gray;") self.color_btn = QPushButton("选择颜色...") self.color_btn.clicked.connect(self.choose_color) color_layout.addWidget(QLabel("颜色:")) color_layout.addWidget(self.color_label) color_layout.addWidget(self.color_btn) color_layout.addStretch() font_layout.addRow("字体:", self.font_combo) font_layout.addRow("字号:", self.font_size_spin) font_layout.addRow("样式:", style_layout) font_layout.addRow(color_layout) layout.addWidget(font_group) # 对齐方式 align_group = QGroupBox("对齐方式") align_layout = QHBoxLayout(align_group) self.align_group = QButtonGroup(self) self.align_left = QRadioButton("左对齐") self.align_center = QRadioButton("居中对齐") self.align_right = QRadioButton("右对齐") self.align_justify = QRadioButton("两端对齐") self.align_group.addButton(self.align_left, 1) self.align_group.addButton(self.align_center, 2) self.align_group.addButton(self.align_right, 3) self.align_group.addButton(self.align_justify, 4) self.align_left.setChecked(True) self.align_group.buttonToggled.connect(self.on_alignment_changed) align_layout.addWidget(self.align_left) align_layout.addWidget(self.align_center) align_layout.addWidget(self.align_right) align_layout.addWidget(self.align_justify) layout.addWidget(align_group) # 位置和尺寸 position_group = QGroupBox("位置和尺寸") position_layout = QFormLayout(position_group) self.pos_x_spin = QDoubleSpinBox() self.pos_x_spin.setRange(0, 1000) self.pos_x_spin.setSuffix(" mm") self.pos_x_spin.valueChanged.connect(self.on_position_changed) self.pos_y_spin = QDoubleSpinBox() self.pos_y_spin.setRange(0, 1000) self.pos_y_spin.setSuffix(" mm") self.pos_y_spin.valueChanged.connect(self.on_position_changed) self.width_spin = QDoubleSpinBox() self.width_spin.setRange(10, 500) self.width_spin.setSuffix(" mm") self.width_spin.setValue(100) self.width_spin.valueChanged.connect(self.on_size_changed) self.height_spin = QDoubleSpinBox() self.height_spin.setRange(10, 500) self.height_spin.setSuffix(" mm") self.height_spin.setValue(30) self.height_spin.valueChanged.connect(self.on_size_changed) position_layout.addRow("X 位置:", self.pos_x_spin) position_layout.addRow("Y 位置:", self.pos_y_spin) position_layout.addRow("宽度:", self.width_spin) position_layout.addRow("高度:", self.height_spin) layout.addWidget(position_group) self.setLayout(layout) def set_text_properties(self, text, font, color, alignment, x, y, width, height): """设置文本属性值""" # 文本内容 self.text_edit.setPlainText(text) # 字体 self.current_font = font self.font_combo.setCurrentFont(font) self.font_size_spin.setValue(font.pointSize()) self.bold_check.setChecked(font.bold()) self.italic_check.setChecked(font.italic()) self.underline_check.setChecked(font.underline()) # 颜色 self.current_color = color self.color_label.setStyleSheet(f"background-color: {color.name()}; border: 1px solid gray;") # 对齐方式 self.current_alignment = alignment if alignment == Qt.AlignmentFlag.AlignLeft: self.align_left.setChecked(True) elif alignment == Qt.AlignmentFlag.AlignHCenter: self.align_center.setChecked(True) elif alignment == Qt.AlignmentFlag.AlignRight: self.align_right.setChecked(True) elif alignment == Qt.AlignmentFlag.AlignJustify: self.align_justify.setChecked(True) # 位置和尺寸 self.pos_x_spin.setValue(x) self.pos_y_spin.setValue(y) self.width_spin.setValue(width) self.height_spin.setValue(height) def on_text_changed(self): """文本内容变化处理""" text = self.text_edit.toPlainText() self.textChanged.emit(text) def on_font_changed(self, font): """字体变化处理""" self.current_font.setFamily(font.family()) self.fontChanged.emit(self.current_font) def on_font_size_changed(self, size): """字号变化处理""" self.current_font.setPointSize(size) self.fontChanged.emit(self.current_font) def on_style_changed(self, checked): """字体样式变化处理""" self.current_font.setBold(self.bold_check.isChecked()) self.current_font.setItalic(self.italic_check.isChecked()) self.current_font.setUnderline(self.underline_check.isChecked()) self.fontChanged.emit(self.current_font) def choose_color(self): """选择字体颜色""" color = QColorDialog.getColor() if color.isValid(): self.current_color = color self.color_label.setStyleSheet(f"background-color: {color.name()}; border: 1px solid gray;") self.colorChanged.emit(color) def on_alignment_changed(self, button, checked): """对齐方式变化处理""" if not checked: return align_map = { 1: Qt.AlignmentFlag.AlignLeft, 2: Qt.AlignmentFlag.AlignHCenter, 3: Qt.AlignmentFlag.AlignRight, 4: Qt.AlignmentFlag.AlignJustify } align = align_map.get(self.align_group.checkedId(), Qt.AlignmentFlag.AlignLeft) self.current_alignment = align self.alignmentChanged.emit(align) def on_position_changed(self): """位置变化处理""" x = self.pos_x_spin.value() y = self.pos_y_spin.value() self.positionChanged.emit(x, y) def on_size_changed(self): """尺寸变化处理""" width = self.width_spin.value() height = self.height_spin.value() self.sizeChanged.emit(width, height) class QRPropertiesPanel(BasePropertiesPanel): """二维码属性面板 UI 实现,包含自动识别功能""" def __init__(self, parent=None): super().__init__(parent) self.qr_position_x = None self.qr_correction_combo = None self.qr_content_edit = None self.qr_position_y = None self.qr_fixed_check = None self.apply_btn = None self.qr_size_spin = None self.qr_type_combo = None self.qr_data_label = None self.detection_status = None self.qr_selector = None self.qr_preview = None self.detect_btn = None self.detected_qr_codes = [] # 存储检测到的二维码信息 self.current_qr_index = -1 # 当前选中的二维码索引 def setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(10) # 自动检测区域 auto_detect_group = QGroupBox("二维码自动检测") auto_layout = QVBoxLayout(auto_detect_group) self.detect_btn = QPushButton("自动识别二维码") self.detect_btn.clicked.connect(self.auto_detect_qr_codes) self.detect_btn.setStyleSheet("background-color: #4CAF50; color: white;") self.detection_status = QLabel("就绪") self.detection_status.setAlignment(Qt.AlignmentFlag.AlignCenter) auto_layout.addWidget(self.detect_btn) auto_layout.addWidget(self.detection_status) # 二维码选择器 self.qr_selector = QComboBox() self.qr_selector.currentIndexChanged.connect(self.select_qr_code) auto_layout.addWidget(QLabel("检测到的二维码:")) auto_layout.addWidget(self.qr_selector) # 预览区域 preview_layout = QHBoxLayout() self.qr_preview = QLabel() self.qr_preview.setFixedSize(150, 150) self.qr_preview.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;") self.qr_preview.setAlignment(Qt.AlignmentFlag.AlignCenter) self.qr_data_label = QLabel("二维码数据将显示在这里") self.qr_data_label.setWordWrap(True) self.qr_data_label.setStyleSheet("background-color: #f8f8f8; padding: 5px;") preview_layout.addWidget(self.qr_preview) preview_layout.addWidget(self.qr_data_label, 1) auto_layout.addLayout(preview_layout) layout.addWidget(auto_detect_group) # 二维码属性编辑区域 props_group = QGroupBox("二维码属性") form_layout = QFormLayout(props_group) self.qr_type_combo = QComboBox() self.qr_type_combo.addItems(["QR Code", "Data Matrix", "PDF417", "Aztec"]) self.qr_content_edit = QLineEdit() self.qr_content_edit.setPlaceholderText("输入二维码内容") self.qr_size_spin = QDoubleSpinBox() self.qr_size_spin.setRange(5, 100) self.qr_size_spin.setValue(30) self.qr_size_spin.setSuffix(" mm") self.qr_correction_combo = QComboBox() self.qr_correction_combo.addItems(["L (低)", "M (中)", "Q (高)", "H (最高)"]) self.qr_correction_combo.setCurrentIndex(1) self.qr_position_x = QDoubleSpinBox() self.qr_position_x.setRange(0, 1000) self.qr_position_x.setSuffix(" mm") self.qr_position_y = QDoubleSpinBox() self.qr_position_y.setRange(0, 1000) self.qr_position_y.setSuffix(" mm") self.qr_fixed_check = QCheckBox("固定位置") self.qr_fixed_check.setChecked(True) form_layout.addRow("类型:", self.qr_type_combo) form_layout.addRow("内容:", self.qr_content_edit) form_layout.addRow("尺寸:", self.qr_size_spin) form_layout.addRow("纠错级别:", self.qr_correction_combo) form_layout.addRow("位置 X:", self.qr_position_x) form_layout.addRow("位置 Y:", self.qr_position_y) form_layout.addRow(self.qr_fixed_check) # 应用按钮 self.apply_btn = QPushButton("应用更改") self.apply_btn.clicked.connect(self.apply_qr_properties) form_layout.addRow(self.apply_btn) layout.addWidget(props_group) layout.addStretch(1) self.setLayout(layout) # 连接信号 self.qr_content_edit.textChanged.connect(self.update_qr_preview) self.qr_size_spin.valueChanged.connect(self.update_qr_preview) self.qr_correction_combo.currentIndexChanged.connect(self.update_qr_preview) def auto_detect_qr_codes(self): """自动检测二维码""" if not self.region or not self.region.template_image: self.detection_status.setText("错误: 没有可用的模板图像") self.detection_status.setStyleSheet("color: red;") return try: # 转换图像为OpenCV格式 qimage = self.region.template_image width, height = qimage.width(), qimage.height() ptr = qimage.bits() ptr.setsize(qimage.sizeInBytes()) img_np = np.array(ptr).reshape(height, width, 4) # RGBA格式 # 转换为BGR格式并转为灰度图 img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGBA2BGR) gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) # 使用pyzbar检测二维码 self.detected_qr_codes = decode(gray) if not self.detected_qr_codes: self.detection_status.setText("未检测到二维码") self.detection_status.setStyleSheet("color: orange;") return # 更新UI显示结果 self.detection_status.setText(f"检测到 {len(self.detected_qr_codes)} 个二维码") self.detection_status.setStyleSheet("color: green;") # 更新二维码选择器 self.qr_selector.clear() for i, qr in enumerate(self.detected_qr_codes): self.qr_selector.addItem(f"二维码 {i + 1}") # 自动选择第一个二维码 if self.detected_qr_codes: self.qr_selector.setCurrentIndex(0) except Exception as e: self.detection_status.setText(f"检测失败: {str(e)}") self.detection_status.setStyleSheet("color: red;") logger.error(f"二维码检测失败: {str(e)}", exc_info=True) def select_qr_code(self, index): """选择检测到的二维码""" if index < 0 or index >= len(self.detected_qr_codes): return self.current_qr_index = index qr = self.detected_qr_codes[index] # 显示二维码数据 qr_data = qr.data.decode('utf-8') self.qr_data_label.setText(f"类型: {qr.type}\n\n数据:\n{qr_data}") # 更新属性编辑器 self.qr_content_edit.setText(qr_data) # 计算二维码位置 (转换为毫米,假设300dpi) points = qr.polygon if len(points) >= 4: # 计算中心点 center_x = sum([p.x for p in points]) / 4 center_y = sum([p.y for p in points]) / 4 # 转换为毫米 (300dpi: 1英寸=25.4mm, 300像素=25.4mm) mm_per_pixel = 25.4 / 300 self.qr_position_x.setValue(center_x * mm_per_pixel) self.qr_position_y.setValue(center_y * mm_per_pixel) # 计算尺寸 width = max(p.x for p in points) - min(p.x for p in points) height = max(p.y for p in points) - min(p.y for p in points) size_mm = max(width, height) * mm_per_pixel self.qr_size_spin.setValue(size_mm) # 更新预览 self.update_qr_preview() # 在模板画布上高亮显示选中的二维码 self.highlight_selected_qr(qr) def highlight_selected_qr(self, qr): """在模板画布上高亮显示选中的二维码""" if not self.region or not self.region.template_image: return # 创建原始图像的副本 image = self.region.template_image.copy() # 创建QPainter绘制高亮框 painter = QPainter(image) painter.setPen(QColor(255, 0, 0, 200)) # 半透明红色 painter.setBrush(Qt.BrushStyle.NoBrush) # 绘制二维码边界框 points = qr.polygon if len(points) == 4: polygon = QPolygonF() for point in points: polygon.append(QPointF(point.x, point.y)) painter.drawPolygon(polygon) # 绘制中心点 center_x = sum([p.x for p in points]) / 4 center_y = sum([p.y for p in points]) / 4 painter.setBrush(QColor(255, 0, 0, 150)) painter.drawEllipse(QPointF(center_x, center_y), 5, 5) painter.end() # 更新画布显示 self.region.setPixmap(QPixmap.fromImage(image)) def update_qr_preview(self): """更新二维码预览图像""" # 在实际应用中,这里应该生成二维码预览图 # 为了简化,我们只显示一个占位符 pixmap = QPixmap(150, 150) pixmap.fill(QColor(240, 240, 240)) painter = QPainter(pixmap) painter.setPen(Qt.GlobalColor.darkGray) painter.drawRect(10, 10, 130, 130) # 显示二维码类型和内容摘要 qr_type = self.qr_type_combo.currentText() content = self.qr_content_edit.text()[:15] + "..." if len( self.qr_content_edit.text()) > 15 else self.qr_content_edit.text() painter.drawText(QRectF(0, 0, 150, 150), Qt.AlignmentFlag.AlignCenter, f"{qr_type}\n\n{content}") painter.end() self.qr_preview.setPixmap(pixmap) def apply_qr_properties(self): """应用二维码属性更改""" if not self.region: return # 在实际应用中,这里应该更新二维码对象的属性 qr_type = self.qr_type_combo.currentText() content = self.qr_content_edit.text() size = self.qr_size_spin.value() position_x = self.qr_position_x.value() position_y = self.qr_position_y.value() logger.info( f"更新二维码属性: 类型={qr_type}, 内容={content[:20]}..., 尺寸={size}mm, 位置=({position_x},{position_y})mm") # 这里应该调用画布更新二维码显示 # self.region.update_qr_display() def update_ui_from_region(self): """根据区域数据更新 UI""" if not self.region: return # 在实际应用中,这里应该从区域对象加载二维码属性 # 现在只是设置一些示例值 self.qr_content_edit.setText("https://example.com/product/12345") self.qr_size_spin.setValue(25.0) self.qr_position_x.setValue(50.0) self.qr_position_y.setValue(30.0) self.update_qr_preview() def update_region_from_ui(self): """根据 UI 数据更新区域属性""" if not self.region: return # 在实际应用中,这里应该更新二维码对象的属性 content = self.qr_content_edit.text() size = self.qr_size_spin.value() position_x = self.qr_position_x.value() position_y = self.qr_position_y.value() self.region.set_content(content) self.region.set_size(size) self.region.set_position(position_x, position_y) logger.info(f"二维码区域属性已更新") class LogoPropertiesPanel(BasePropertiesPanel): """Logo属性面板 UI 实现""" def setup_ui(self): layout = QVBoxLayout(self) layout.addWidget(QLabel("Logo属性面板")) self.setLayout(layout) class MainWindow(QMainWindow): def __init__(self, data_path: Optional[str] = None): super().__init__() self.run_test_add_regions = None self.logger = logging.getLogger(__name__) self.logger.info("主窗口初始化开始") self.setWindowTitle("自动打印系统") self.resize(800, 600) self.data_path = data_path or os.path.join(os.path.dirname(__file__), "../../../data") # 初始化所有实例属性为 None self.left_layout = None self.btn_upload = None self.btn_add_text = None self.btn_add_qr = None self.template_canvas = None self.props_tabs = None self.text_props_panel = None self.qr_props_panel = None self.logo_props_panel = None self.right_layout = None self.btn_import_data = None self.btn_save_config = None self.data_mapper = None self.field_mapping_widget = None self.history_combo = None self.btn_history = None self.btn_generate = None self.btn_rerun = None self.project_manager = None self.font_manager = None self.compositor = None # 初始化控件 self.init_components() try: # 创建项目管理器 from src.auto_print_system.core.project_manager import ProjectManager self.project_manager = ProjectManager(self.data_path) # 创建字体管理器 from src.auto_print_system.core.font_manager import FontManager self.font_manager = FontManager(self.data_path) # 创建合成引擎 from src.auto_print_system.core.file_compositor import FileCompositor self.compositor = FileCompositor(self.data_path, self.font_manager) except ImportError as e: self.logger.error(f"初始化核心组件失败: {str(e)}", exc_info=True) QMessageBox.critical(self, "错误", f"初始化核心组件失败:\n{str(e)}") # 设置状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪") self.current_project = None self.logo_path = None self.spot_color_path = None # 加载样式 self.apply_styles() def init_components(self) -> None: """初始化所有界面组件""" self.logger.info("开始初始化组件") central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 左侧模板编辑区 left_dock = QDockWidget("模板编辑区", self) left_widget = QWidget() self.left_layout = QVBoxLayout(left_widget) btn_layout = QHBoxLayout() self.btn_upload = QPushButton("上传模板") self.btn_add_text = QPushButton("添加文本框") self.btn_add_qr = QPushButton("添加二维码区") self.btn_upload.clicked.connect(self.upload_template) self.btn_add_text.clicked.connect(self.add_text_region) self.btn_add_qr.clicked.connect(self.add_qr_region) btn_layout.addWidget(self.btn_upload) btn_layout.addWidget(self.btn_add_text) btn_layout.addWidget(self.btn_add_qr) self.left_layout.addLayout(btn_layout) try: from src.auto_print_system.ui.template_editor import TemplateCanvas self.template_canvas = TemplateCanvas(data_path=self.data_path) self.template_canvas.setAlignment(Qt.AlignmentFlag.AlignCenter) self.template_canvas.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;") self.left_layout.addWidget(self.template_canvas) except ImportError as e: self.logger.error(f"初始化模板画布失败: {str(e)}", exc_info=True) self.template_canvas = None # 属性面板 self.props_tabs = QTabWidget() self.text_props_panel = TextPropertiesPanel() self.qr_props_panel = QRPropertiesPanel() self.logo_props_panel = LogoPropertiesPanel() self.props_tabs.addTab(self.text_props_panel, "文本属性") self.props_tabs.addTab(self.qr_props_panel, "二维码属性") self.props_tabs.addTab(self.logo_props_panel, "Logo属性") self.left_layout.addWidget(self.props_tabs) left_dock.setWidget(left_widget) self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, left_dock) # 右侧数据映射区 right_dock = QDockWidget("数据映射区", self) right_widget = QWidget() self.right_layout = QVBoxLayout(right_widget) data_btn_layout = QHBoxLayout() self.btn_import_data = QPushButton("导入数据") self.btn_save_config = QPushButton("保存配置") self.btn_import_data.clicked.connect(self.import_data) self.btn_save_config.clicked.connect(self.save_config) data_btn_layout.addWidget(self.btn_import_data) data_btn_layout.addWidget(self.btn_save_config) self.right_layout.addLayout(data_btn_layout) try: from src.auto_print_system.ui.data_mapper import DataMapperWidget self.data_mapper = DataMapperWidget(self) self.right_layout.addWidget(self.data_mapper, 1) except ImportError as e: self.logger.error(f"初始化数据映射器失败: {str(e)}", exc_info=True) self.field_mapping_widget = QLabel("拖拽字段到模板区域进行映射") self.field_mapping_widget.setStyleSheet("min-height: 100px; background-color: #fff; border: 1px dashed #999;") self.right_layout.addWidget(self.field_mapping_widget) right_dock.setWidget(right_widget) self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, right_dock) # 底部操作区 bottom_widget = QWidget() bottom_layout = QHBoxLayout(bottom_widget) self.history_combo = QComboBox() self.btn_history = QPushButton("管理") self.btn_generate = QPushButton("开始合成") self.btn_rerun = QPushButton("执行翻单") self.btn_rerun.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; padding: 8px 16px;") self.btn_rerun.clicked.connect(self.rerun_project) self.btn_rerun.setEnabled(False) # 设置按钮初始状态为不可点击 bottom_layout.addWidget(QLabel("翻单历史:")) bottom_layout.addWidget(self.history_combo) bottom_layout.addWidget(self.btn_history) bottom_layout.addSpacing(20) bottom_layout.addWidget(self.btn_generate) bottom_layout.addWidget(self.btn_rerun) main_layout.addWidget(bottom_widget) # 翻单按钮状态初始化 self.create_menus() self.create_toolbar() self.update_rerun_button_state() # 连接文本属性面板信号到画布 if self.template_canvas: # 文本内容变化 self.text_props_panel.textChanged.connect( self.template_canvas.update_selected_text_region_text ) # 字体变化 self.text_props_panel.fontChanged.connect( self.template_canvas.update_selected_text_region_font ) # 颜色变化 self.text_props_panel.colorChanged.connect( self.template_canvas.update_selected_text_region_color ) # 对齐方式变化 self.text_props_panel.alignmentChanged.connect( self.template_canvas.update_selected_text_region_alignment ) # 位置变化 self.text_props_panel.positionChanged.connect( self.template_canvas.update_selected_text_region_position ) # 尺寸变化 self.text_props_panel.sizeChanged.connect( self.template_canvas.update_selected_text_region_size ) # ===== 功能槽函数 ===== def update_rerun_button_state(self) -> None: """根据项目状态更新翻单按钮的可用性""" if not hasattr(self, 'template_canvas') or not hasattr(self, 'data_mapper'): self.logger.warning("模板画布或数据映射器未初始化") return can_rerun = ( self.template_canvas is not None and self.template_canvas.template_item is not None and self.data_mapper is not None ) if self.btn_rerun: self.btn_rerun.setEnabled(can_rerun) else: self.logger.warning("翻单按钮未初始化,无法更新状态") def rerun_project(self) -> None: """执行翻单操作""" if self.btn_rerun is not None: self.btn_rerun.setEnabled(False) # 防止多次点击 else: logging.error("btn_rerun 控件未正确初始化") return # ... 其他业务逻辑 ... def create_menus(self) -> None: """创建菜单栏""" menubar = self.menuBar() file_menu = menubar.addMenu('文件') new_action = QAction('新建项目', self) open_action = QAction('打开项目', self) save_action = QAction('保存项目', self) exit_action = QAction('退出', self) exit_action.triggered.connect(self.close) file_menu.addAction(new_action) file_menu.addAction(open_action) file_menu.addAction(save_action) file_menu.addSeparator() file_menu.addAction(exit_action) edit_menu = menubar.addMenu('编辑') edit_menu.addAction('撤销') edit_menu.addAction('重做') def create_toolbar(self) -> None: """创建工具栏""" toolbar = QToolBar("主工具栏") self.addToolBar(toolbar) toolbar.addAction("打开模板", self.upload_template) toolbar.addAction("导入数据", self.import_data) toolbar.addSeparator() toolbar.addAction("添加文本", self.add_text_region) toolbar.addAction("添加二维码", self.add_qr_region) toolbar.addSeparator() toolbar.addAction("开始合成", self.generate_output) def apply_styles(self) -> None: """应用基本样式表""" self.setStyleSheet(""" QMainWindow { background-color: #f5f5f5; } QPushButton { padding: 5px 10px; border: 1px solid #ccc; border-radius: 4px; } QPushButton:hover { background-color: #e9e9e9; } """) def upload_template(self) -> None: """上传模板文件""" self.logger.debug("上传模板操作被触发") file_dialog = QFileDialog(self) file_dialog.setNameFilter("模板文件 (*.pdf *.png *.jpg *.jpeg)") file_dialog.setWindowTitle("选择模板文件") if file_dialog.exec(): template_path = file_dialog.selectedFiles()[0] self.logger.info(f"选择了模板文件: {template_path}") try: if self.template_canvas: success = self.template_canvas.load_template(template_path) if success: self.status_bar.showMessage(f"模板加载成功: {os.path.basename(template_path)}") else: self.status_bar.showMessage("模板加载失败,请检查文件格式") self.logger.error(f"模板加载失败: {template_path}") else: self.logger.error("模板画布未初始化") self.status_bar.showMessage("模板画布未初始化,无法加载模板") except Exception as e: self.logger.error(f"模板加载失败: {str(e)}", exc_info=True) self.status_bar.showMessage("模板加载失败,请检查文件格式或权限") def add_text_region(self) -> None: """添加文本区域""" self.logger.debug("添加文本区域操作被触发") if self.template_canvas: try: self.template_canvas.set_mode(TemplateCanvas.MODE_TEXT) self.status_bar.showMessage("已切换到【添加文本区域】模式") except Exception as e: self.logger.error(f"设置文本区域模式失败: {str(e)}", exc_info=True) self.status_bar.showMessage("设置模式失败,请查看日志") else: self.logger.error("模板画布未初始化") self.status_bar.showMessage("模板画布未初始化,无法添加文本区域") def add_qr_region(self) -> None: """添加二维码区域""" self.logger.debug("添加二维码区域操作被触发") if self.template_canvas: try: self.template_canvas.set_mode(TemplateCanvas.MODE_QR) self.status_bar.showMessage("添加二维码区域模式激活") except Exception as e: self.logger.error(f"设置二维码区域模式失败: {str(e)}", exc_info=True) self.status_bar.showMessage("设置二维码区域模式失败,请重试") else: self.logger.error("模板画布未初始化") self.status_bar.showMessage("模板画布未初始化,无法添加二维码区域") def import_data(self) -> None: """导入数据文件""" self.logger.debug("导入数据操作被触发") file_dialog = QFileDialog(self) file_dialog.setNameFilter("数据文件 (*.csv *.xlsx *.xls)") file_dialog.setWindowTitle("选择数据文件") if file_dialog.exec(): data_path = file_dialog.selectedFiles()[0] self.logger.info(f"选择了数据文件: {data_path}") try: if self.data_mapper: self.data_mapper.import_data() self.status_bar.showMessage(f"数据文件已导入: {os.path.basename(data_path)}") else: self.logger.error("数据映射器未初始化") self.status_bar.showMessage("数据映射器未初始化,无法导入数据文件") except Exception as e: self.logger.error(f"导入数据失败: {str(e)}", exc_info=True) self.status_bar.showMessage("导入数据失败,请检查文件格式或权限") if self.data_mapper: self.data_mapper.import_data() self.update_rerun_button_state() #更新按钮状态 def save_config(self) -> None: """保存配置文件""" self.logger.debug("保存配置操作被触发") try: if self.data_mapper: mapping_config = self.data_mapper.get_mapping() if mapping_config: # 假设配置文件保存到 JSON 文件 config_path = os.path.join(self.data_path, "config.json") with open(config_path, "w", encoding="utf-8") as config_file: import json json.dump(mapping_config, config_file, ensure_ascii=False, indent=4) self.logger.info(f"配置文件已保存至: {config_path}") self.status_bar.showMessage("配置文件保存成功") else: self.logger.warning("无映射配置可保存") self.status_bar.showMessage("无映射配置可保存") else: self.logger.error("数据映射器未初始化") self.status_bar.showMessage("数据映射器未初始化,无法保存配置") except Exception as e: self.logger.error(f"保存配置失败: {str(e)}", exc_info=True) self.status_bar.showMessage("保存配置失败,请检查文件权限") def generate_output(self) -> None: """生成输出文件""" self.logger.debug("开始合成操作被触发") if not self.current_project: self.logger.error("未选择项目,无法生成输出文件") self.status_bar.showMessage("未选择项目,无法生成输出文件") return try: # 检查是否有有效的模板和数据映射 if not self.template_canvas or not self.template_canvas.template_item: self.logger.error("无有效模板,无法生成输出文件") self.status_bar.showMessage("无有效模板,无法生成输出文件") return if not self.data_mapper or not self.data_mapper.data_frame: self.logger.error("无有效数据,无法生成输出文件") self.status_bar.showMessage("无有效数据,无法生成输出文件") return # 开始合成操作 self.status_bar.showMessage("正在生成输出文件...") self.logger.info("开始合成输出文件") output_path = os.path.join(self.data_path, "output.pdf") self.compositor.compose(output_path) self.status_bar.showMessage("输出文件生成成功") self.logger.info(f"输出文件已生成至: {output_path}") except Exception as e: self.logger.error(f"生成输出文件失败: {str(e)}", exc_info=True) self.status_bar.showMessage("生成输出文件失败,请检查设置") def rerun_project(self) -> None: """执行翻单操作""" self.logger.debug("执行翻单操作被触发") if self.btn_rerun is not None: self.btn_rerun.setEnabled(False) # 防止多次点击 else: self.logger.error("btn_rerun 控件未正确初始化") return if not self.current_project: self.logger.error("未选择项目,无法执行翻单") self.status_bar.showMessage("未选择项目,无法执行翻单") return try: # 检查是否有有效的模板和数据映射 if not self.template_canvas or not self.template_canvas.template_item: self.logger.error("无有效模板,无法执行翻单") self.status_bar.showMessage("无有效模板,无法执行翻单") return if not self.data_mapper or not self.data_mapper.data_frame: self.logger.error("无有效数据,无法执行翻单") self.status_bar.showMessage("无有效数据,无法执行翻单") return # 执行翻单操作 self.status_bar.showMessage("正在执行翻单...") self.logger.info("开始执行翻单") output_path = os.path.join(self.data_path, "rerun_output.pdf") self.compositor.compose(output_path) self.status_bar.showMessage("翻单执行成功") self.logger.info(f"翻单输出文件已生成至: {output_path}") # 恢复按钮状态 self.btn_rerun.setEnabled(True) except Exception as e: self.logger.error(f"翻单执行失败: {str(e)}", exc_info=True) self.status_bar.showMessage("翻单执行失败,请检查设置") # 恢复按钮状态 self.btn_rerun.setEnabled(True) # 测试运行 if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec()) 根据以上修改建议,请帮我输出完整的修改后的main_window.py的代码
07-16
#pragma once #include <fstream> #include <iomanip> #include <QString> #include <QCoreApplication> #include <QVariantMap> #include "Loger.h" #include <nlohmann/json.hpp> // 需要引入 nlohmann/json.hpp using std::string; using json = nlohmann::ordered_json; extern Loger m_Loger; // 全局Log类 class ConfigItem { public: string Alias; string Description; string Value; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ConfigItem, Alias, Description, Value) ConfigItem() {}; bool toBool() const { return string2Bool(QString::fromLocal8Bit(Value.c_str())); } static bool string2Bool(const QString& str) { QString cleanStr = str.trimmed().toLower(); if (cleanStr == "true" || cleanStr == "1" || cleanStr == "on" || cleanStr == "yes") { return true; } return false; } }; class BasicTree { public: std::unique_ptr<ConfigItem> m_time; std::unique_ptr<ConfigItem> m_trayName; std::unique_ptr<ConfigItem> m_configFile; std::unique_ptr<ConfigItem> m_throughFocusData; std::unique_ptr<ConfigItem> m_position; std::unique_ptr<ConfigItem> m_driverPos; std::unique_ptr<ConfigItem> m_defocus; BasicTree() { initItems(); } BasicTree(const BasicTree& other) { initItems(); copyFrom(other); } BasicTree& operator=(const BasicTree& other) { if (this != &other) { copyFrom(other); } return *this; } // 添加 JSON 序列化支持 void fromJson(const json& j) { if (j.contains("time")) assign(j["time"], m_time); if (j.contains("trayName")) assign(j["trayName"], m_trayName); if (j.contains("configFile")) assign(j["configFile"], m_configFile); if (j.contains("throughFocusData")) assign(j["throughFocusData"], m_throughFocusData); if (j.contains("position")) assign(j["position"], m_position); if (j.contains("driverPos")) assign(j["driverPos"], m_driverPos); if (j.contains("defocus")) assign(j["defocus"], m_defocus); } private: void initItems() { m_time = std::make_unique<ConfigItem>(); m_trayName = std::make_unique<ConfigItem>(); m_configFile = std::make_unique<ConfigItem>(); m_throughFocusData = std::make_unique<ConfigItem>(); m_position = std::make_unique<ConfigItem>(); m_driverPos = std::make_unique<ConfigItem>(); m_defocus = std::make_unique<ConfigItem>(); } void copyFrom(const BasicTree& other) { m_time->m_value = other.m_time->m_value; m_trayName->m_value = other.m_trayName->m_value; m_configFile->m_value = other.m_configFile->m_value; m_throughFocusData->m_value = other.m_throughFocusData->m_value; m_position->m_value = other.m_position->m_value; m_driverPos->m_value = other.m_driverPos->m_value; m_defocus->m_value = other.m_defocus->m_value; } void assign(const json& j, std::unique_ptr<ConfigItem>& item) { if (j.is_object()) { if (j.contains("alias")) j["alias"].get_to(item->m_alias); if (j.contains("description")) j["description"].get_to(item->m_description); if (j.contains("value")) j["value"].get_to(item->m_value); } } }; class ResultTree { public: std::unique_ptr<ConfigItem> m_final; std::unique_ptr<ConfigItem> m_mtf; std::unique_ptr<ConfigItem> m_dof; std::unique_ptr<ConfigItem> m_focalShift; std::unique_ptr<ConfigItem> m_astigmatism; std::unique_ptr<ConfigItem> m_fov; std::unique_ptr<ConfigItem> m_average; std::unique_ptr<ConfigItem> m_aggregate; std::unique_ptr<ConfigItem> m_multiFrqMtf; std::unique_ptr<ConfigItem> m_defocus; std::unique_ptr<ConfigItem> m_failReason; ResultTree() { initItems(); } ResultTree(const ResultTree& other) { initItems(); copyFrom(other); } ResultTree& operator=(const ResultTree& other) { if (this != &other) { copyFrom(other); } return *this; } private: void initItems() { m_final = std::make_unique<ConfigItem>(); m_mtf = std::make_unique<ConfigItem>(); m_dof = std::make_unique<ConfigItem>(); m_focalShift = std::make_unique<ConfigItem>(); m_astigmatism = std::make_unique<ConfigItem>(); m_fov = std::make_unique<ConfigItem>(); m_average = std::make_unique<ConfigItem>(); m_aggregate = std::make_unique<ConfigItem>(); m_multiFrqMtf = std::make_unique<ConfigItem>(); m_defocus = std::make_unique<ConfigItem>(); m_failReason = std::make_unique<ConfigItem>(); } void copyFrom(const ResultTree& other) { m_final->m_value = other.m_final->m_value; m_mtf->m_value = other.m_mtf->m_value; m_dof->m_value = other.m_dof->m_value; m_focalShift->m_value = other.m_focalShift->m_value; m_astigmatism->m_value = other.m_astigmatism->m_value; m_fov->m_value = other.m_fov->m_value; m_average->m_value = other.m_average->m_value; m_aggregate->m_value = other.m_aggregate->m_value; m_multiFrqMtf->m_value = other.m_multiFrqMtf->m_value; m_defocus->m_value = other.m_defocus->m_value; m_failReason->m_value = other.m_failReason->m_value; } }; // 配置文件项类 class ConfigFileItem { public: std::unique_ptr<ConfigItem> m_barcode; std::unique_ptr<ConfigItem> m_cameraCounts; std::unique_ptr<ConfigItem> m_groupDefine; std::unique_ptr<BasicTree> m_basic; std::unique_ptr<ResultTree> m_result; //std::unique_ptr<NormaltTree> m_normal; std::unique_ptr<ConfigItem> m_defocus; ConfigFileItem() { initItems(); } ConfigFileItem(const ConfigFileItem& other) { initItems(); copyFrom(other); } ConfigFileItem& operator=(const ConfigFileItem& other) { if (this != &other) { copyFrom(other); } return *this; } ~ConfigFileItem() { clearItems(); } void serializeToFile(const QString& filePath = "") { QString path = filePath.isEmpty() ? QCoreApplication::applicationDirPath() + "/Porject/config.json" : filePath; } bool deserializeFromFile(const QString& filePath = "") { QString path = filePath.isEmpty() ? QCoreApplication::applicationDirPath() + "/Porject/config.json" : filePath; // 使用 QFile 读取(Qt 文件操作) QFile file(path); if (!file.open(QIODevice::ReadOnly)) { m_Loger.RecordLogError("Failed to open file for reading: " + static_cast<string>(path.toLocal8Bit())); return false; } // 使用UTF-8编码读取 QTextStream in(&file); in.setCodec("UTF-8"); QString jsonString = in.readAll(); file.close(); try { std::string utf8Str = jsonString.toUtf8().toStdString(); json j = json::parse(utf8Str); // 反序列化所有字段 if (j.contains("Barcode")) fromJson(j["Barcode"], *m_barcode); if (j.contains("cameraCounts")) fromJson(j["cameraCounts"], *m_cameraCounts); if (j.contains("groupDefine")) fromJson(j["groupDefine"], *m_groupDefine); if (j.contains("basic")) m_basic->fromJson(j["basic"]); // 嵌套反序列化 //if (j.contains("result")) fromJson(j["result"], *m_result); if (j.contains("defocus")) fromJson(j["defocus"], *m_defocus); return true; } catch (const std::exception& e) { m_Loger.RecordLogError("JSON parse error: " + static_cast<string>(e.what())); return false; } } void copyToTestData(TestData& testData) { testData.m_bBarcode = m_barcode->toBool(); testData.m_keyBarcode = QString::fromLocal8Bit(m_barcode->m_alias.c_str()); // 复制 BasicTree 数据 testData.Basic.m_bConfigFile = m_basic->m_configFile->toBool(); testData.Basic.m_keyConfigFile = QString::fromLocal8Bit(m_basic->m_configFile->m_alias.c_str()); testData.Basic.m_bDefocus = m_basic->m_defocus->toBool(); testData.Basic.m_keyDefocus = QString::fromLocal8Bit(m_basic->m_defocus->m_alias.c_str()); testData.Basic.m_bDriverPos = m_basic->m_driverPos->toBool(); testData.Basic.m_keyDriverPos = QString::fromLocal8Bit(m_basic->m_driverPos->m_alias.c_str()); testData.Basic.m_bPosition = m_basic->m_position->toBool(); testData.Basic.m_keyPosition = QString::fromLocal8Bit(m_basic->m_position->m_alias.c_str()); testData.Basic.m_bThroughFocusData = m_basic->m_throughFocusData->toBool(); testData.Basic.m_keyThroughFocusData = QString::fromLocal8Bit(m_basic->m_throughFocusData->m_alias.c_str()); testData.Basic.m_bTime = m_basic->m_time->toBool(); testData.Basic.m_keyTime = QString::fromLocal8Bit(m_basic->m_time->m_alias.c_str()); testData.Basic.m_bTrayName = m_basic->m_trayName->toBool(); testData.Basic.m_keyTrayName = QString::fromLocal8Bit(m_basic->m_trayName->m_alias.c_str()); testData.m_bCameraCounts = m_cameraCounts->toBool(); testData.m_keyCameraCounts = QString::fromLocal8Bit(m_cameraCounts->m_alias.c_str()); testData.m_bDefocus = m_defocus->toBool(); testData.m_keyDefocus = QString::fromLocal8Bit(m_defocus->m_alias.c_str()); testData.m_bGroupDefine = m_groupDefine->toBool(); testData.m_keyGroupDefine = QString::fromLocal8Bit(m_groupDefine->m_alias.c_str()); } private: void initItems() { m_barcode = std::make_unique <ConfigItem>(); m_cameraCounts = std::make_unique <ConfigItem>(); m_groupDefine = std::make_unique <ConfigItem>(); m_basic = std::make_unique <BasicTree>(); m_result = std::make_unique <ResultTree>(); //m_normal = new NormaltTree(this); m_defocus = std::make_unique <ConfigItem>(); } void clearItems() { // 所有对象由父对象管理 } void copyFrom(const ConfigFileItem& other) { // 复制简单值 m_barcode->m_value = other.m_barcode->m_value; m_cameraCounts->m_value = other.m_cameraCounts->m_value; m_groupDefine->m_value = other.m_groupDefine->m_value; m_defocus->m_value = other.m_defocus->m_value; // 复制嵌套对象 *m_basic = *other.m_basic; *m_result = *other.m_result; //*m_normal = *other.m_normal; } // JSON 序列化辅助函数 static json toJson(const ConfigItem& item) { return { {"alias", item.m_alias}, {"description", item.m_description}, {"value", item.m_value} }; } // JSON 反序列化辅助函数 static void fromJson(const json& j, ConfigItem& item) { if (j.contains("Alias")) j["Alias"].get_to(item.m_alias); if (j.contains("Description")) j["Description"].get_to(item.m_description); if (j.contains("Value")) j["Value"].get_to(item.m_value); } // BasicTree 的 JSON 序列化 static json toJson(const BasicTree& tree) { return { {"time", toJson(*tree.m_time)}, {"trayName", toJson(*tree.m_trayName)}, {"configFile", toJson(*tree.m_configFile)}, {"throughFocusData", toJson(*tree.m_throughFocusData)}, {"position", toJson(*tree.m_position)}, {"driverPos", toJson(*tree.m_driverPos)}, {"defocus", toJson(*tree.m_defocus)} }; } };使用NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE重构
09-23
private: // nodehandle params ros::NodeHandle nh_; BiasSampler sampler_; // for informed sampling Eigen::Vector3d trans_, scale_; Eigen::Matrix3d rot_; bool use_informed_sampling_; double steer_length_; double search_radius_; double search_time_; int max_tree_node_nums_; int valid_tree_node_nums_; double first_path_use_time_; double final_path_use_time_; double cost_best_; std::vector<TreeNode *> nodes_pool_; TreeNode *start_node_; TreeNode *goal_node_; vector<Eigen::Vector3d> final_path_; vector<vector<Eigen::Vector3d>> path_list_; vector<std::pair<double, double>> solution_cost_time_pair_list_; // environment env::OccMap::Ptr map_ptr_; std::shared_ptr<visualization::Visualization> vis_ptr_; void reset() { final_path_.clear(); path_list_.clear(); cost_best_ = DBL_MAX; solution_cost_time_pair_list_.clear(); for (int i = 0; i < valid_tree_node_nums_; i++) { nodes_pool_[i]->parent = nullptr; nodes_pool_[i]->children.clear(); } valid_tree_node_nums_ = 0; } double calDist(const Eigen::Vector3d &p1, const Eigen::Vector3d &p2) { return (p1 - p2).norm(); } RRTNode3DPtr addTreeNode(RRTNode3DPtr &parent, const Eigen::Vector3d &state, const double &cost_from_start, const double &cost_from_parent) { RRTNode3DPtr new_node_ptr = nodes_pool_[valid_tree_node_nums_]; valid_tree_node_nums_++; new_node_ptr->parent = parent; parent->children.push_back(new_node_ptr); new_node_ptr->x = state; new_node_ptr->cost_from_start = cost_from_start; new_node_ptr->cost_from_parent = cost_from_parent; return new_node_ptr; } void changeNodeParent(RRTNode3DPtr &node, RRTNode3DPtr &parent, const double &cost_from_parent) { if (node->parent) node->parent->children.remove(node); //DON'T FORGET THIS, remove it form its parent's children list node->parent = parent; node->cost_from_parent = cost_from_parent; node->cost_from_start = parent->cost_from_start + cost_from_parent; parent->children.push_back(node); // for all its descedants, change the cost_from_start and tau_from_start; RRTNode3DPtr descendant(node); std::queue<RRTNode3DPtr> Q; Q.push(descendant); while (!Q.empty()) { descendant = Q.front(); Q.pop(); for (const auto &leafptr : descendant->children) { leafptr->cost_from_start = leafptr->cost_from_parent + descendant->cost_from_start; Q.push(leafptr); } } } void fillPath(const RRTNode3DPtr &node_A, const RRTNode3DPtr &node_B, vector<Eigen::Vector3d> &path) { path.clear(); RRTNode3DPtr node_ptr = node_A; while (node_ptr->parent) { path.push_back(node_ptr->x); node_ptr = node_ptr->parent; } path.push_back(start_node_->x); std::reverse(std::begin(path), std::end(path)); node_ptr = node_B; while (node_ptr->parent) { path.push_back(node_ptr->x); node_ptr = node_ptr->parent; } path.push_back(goal_node_->x); } inline void sortNbrSet( Neighbour &nbrSet, Eigen::Vector3d &x_rand ) { std::sort(nbrSet.nearing_nodes.begin(), nbrSet.nearing_nodes.end(), [&x_rand](NodeWithStatus &node1, NodeWithStatus &node2){ return node1.node_ptr->cost_from_start + (node1.node_ptr->x - x_rand).norm() < node2.node_ptr->cost_from_start + (node2.node_ptr->x - x_rand).norm(); }); } inline void rewireTree( Neighbour &nbrSet, RRTNode3DPtr &new_node, const Eigen::Vector3d &x_target) { for(auto curr_node : nbrSet.nearing_nodes) { double dist_to_potential_child = calDist(new_node->x, curr_node.node_ptr->x); bool not_consistent = new_node->cost_from_start + dist_to_potential_child < curr_node.node_ptr->cost_from_start ? true : false; bool promising = new_node->cost_from_start + dist_to_potential_child + calDist(curr_node.node_ptr->x, x_target) < cost_best_ ? true : false; if( not_consistent && promising ) { bool connected(false); if (curr_node.is_checked) connected = curr_node.is_valid; else connected = map_ptr_->isSegmentValid(new_node->x, curr_node.node_ptr->x); if(connected) changeNodeParent(curr_node.node_ptr, new_node, dist_to_potential_child); } } } inline void chooseBestNode( Neighbour &nbrSet, const Eigen::Vector3d &x_rand, RRTNode3DPtr &min_node, double &cost_start, double &cost_parent) { for( auto &curr_node : nbrSet.nearing_nodes) { curr_node.is_checked = true; if(map_ptr_->isSegmentValid(curr_node.node_ptr->x, x_rand)) { curr_node.is_valid = true; min_node = curr_node.node_ptr; cost_parent = calDist(min_node->x, x_rand); cost_start = min_node->cost_from_start + cost_parent; break; }else{ curr_node.is_valid = false; continue; } } } bool brrt_star(const Eigen::Vector3d &s, const Eigen::Vector3d &g) { ros::Time rrt_start_time = ros::Time::now(); bool tree_connected = false; double c_square = (g - s).squaredNorm() / 4.0; /* kd tree init */ kdtree *treeA = kd_create(3); kdtree *treeB = kd_create(3); //Add start and goal nodes to kd trees kd_insert3(treeA, start_node_->x[0], start_node_->x[1], start_node_->x[2], start_node_); kd_insert3(treeB, goal_node_->x[0], goal_node_->x[1], goal_node_->x[2], goal_node_); /* main loop */ int idx = 0; for (idx = 0; (ros::Time::now() - rrt_start_time).toSec() < search_time_ && valid_tree_node_nums_ < max_tree_node_nums_; ++idx) { bool check_connect = false; bool selectTreeA = true; /* random sampling */ Eigen::Vector3d x_rand; sampler_.samplingOnce(x_rand); if (!map_ptr_->isStateValid(x_rand)) { continue; } /* Search neighbors in both treeA and treeB */ Neighbour neighbour_nodesA, neighbour_nodesB; neighbour_nodesA.nearing_nodes.reserve(80); neighbour_nodesB.nearing_nodes.reserve(80); neighbour_nodesB.center = neighbour_nodesA.center = x_rand; struct kdres *nbr_setA = kd_nearest_range3(treeA, x_rand[0], x_rand[1], x_rand[2], search_radius_); struct kdres *nbr_setB = kd_nearest_range3(treeB, x_rand[0], x_rand[1], x_rand[2], search_radius_); if ( nbr_setA == nullptr ) // TreeA { struct kdres *p_nearest = kd_nearest3(treeA, x_rand[0], x_rand[1], x_rand[2]); if (p_nearest == nullptr) { ROS_ERROR("nearest query error"); continue; } RRTNode3DPtr nearest_node = (RRTNode3DPtr)kd_res_item_data(p_nearest); kd_res_free(p_nearest); neighbour_nodesA.nearing_nodes.emplace_back(nearest_node, false, false); }else{ check_connect = true; while (!kd_res_end(nbr_setA)){ RRTNode3DPtr curr_node = (RRTNode3DPtr)kd_res_item_data(nbr_setA); neighbour_nodesA.nearing_nodes.emplace_back(curr_node, false, false); // store range query result so that we dont need to query again for rewire; kd_res_next(nbr_setA); //go to next in kd tree range query result } } kd_res_free(nbr_setA); //reset kd tree range query if ( nbr_setB == nullptr )// TreeB { struct kdres *p_nearest = kd_nearest3(treeB, x_rand[0], x_rand[1], x_rand[2]); if (p_nearest == nullptr) { ROS_ERROR("nearest query error"); continue; } RRTNode3DPtr nearest_node = (RRTNode3DPtr)kd_res_item_data(p_nearest); kd_res_free(p_nearest); neighbour_nodesB.nearing_nodes.emplace_back(nearest_node, false, false); }else{ check_connect = true; while (!kd_res_end(nbr_setB)){ RRTNode3DPtr curr_node = (RRTNode3DPtr)kd_res_item_data(nbr_setB); neighbour_nodesB.nearing_nodes.emplace_back(curr_node, false, false); // store range query result so that we dont need to query again for rewire; kd_res_next(nbr_setB); //go to next in kd tree range query result } } kd_res_free(nbr_setB); //reset kd tree range query /* Sort two neighbor sets */ sortNbrSet(neighbour_nodesA, x_rand); sortNbrSet(neighbour_nodesB, x_rand); /* Get the best parent node in each tree */ RRTNode3DPtr min_node_A(nullptr), min_node_B(nullptr); double min_cost_start_A(DBL_MAX), min_cost_start_B(DBL_MAX); double cost_parent_A(DBL_MAX), cost_parent_B(DBL_MAX); chooseBestNode(neighbour_nodesA, x_rand, min_node_A, min_cost_start_A, cost_parent_A); chooseBestNode(neighbour_nodesB, x_rand, min_node_B, min_cost_start_B, cost_parent_B); /* Select the best tree, insert the node and rewire the tree */ RRTNode3DPtr new_node(nullptr); if( (min_node_A != nullptr) || (min_node_B != nullptr) ) { if( min_cost_start_A < min_cost_start_B ){ if(min_cost_start_A + calDist(x_rand, goal_node_->x) >= cost_best_) continue; // Sampling rejection selectTreeA = true; new_node = addTreeNode(min_node_A, x_rand, min_cost_start_A, cost_parent_A); kd_insert3(treeA, x_rand[0], x_rand[1], x_rand[2], new_node); rewireTree(neighbour_nodesA, new_node, goal_node_->x); }else{ if(min_cost_start_B + calDist(x_rand, start_node_->x) >= cost_best_) continue; // Sampling rejection selectTreeA = false; new_node = addTreeNode(min_node_B, x_rand, min_cost_start_B, cost_parent_B); kd_insert3(treeB, x_rand[0], x_rand[1], x_rand[2], new_node); rewireTree(neighbour_nodesB, new_node, start_node_->x); } } if( (min_node_A == nullptr) || (min_node_B == nullptr) ) check_connect = false; // No possible connection /* Check connection */ if( check_connect ) { /* Accept connection if achieve better cost */ double cost_curr = min_cost_start_A + min_cost_start_B; if(cost_curr < cost_best_) { cost_best_ = cost_curr; tree_connected = true; vector<Eigen::Vector3d> curr_best_path; if( selectTreeA ) fillPath(new_node, min_node_B, curr_best_path); else fillPath(min_node_A, new_node, curr_best_path); path_list_.emplace_back(curr_best_path); solution_cost_time_pair_list_.emplace_back(cost_best_, (ros::Time::now() - rrt_start_time).toSec()); if(use_informed_sampling_) { /* Update informed set */ scale_[0] = cost_best_ / 2.0; scale_[1] = sqrt(scale_[0] * scale_[0] - c_square); scale_[2] = scale_[1]; sampler_.setInformedSacling(scale_); std::vector<visualization::ELLIPSOID> ellps; ellps.emplace_back(trans_, scale_, rot_); vis_ptr_->visualize_ellipsoids(ellps, "informed_set", visualization::yellow, 0.2); } } } }//End of one sampling iteration if (tree_connected) { final_path_use_time_ = (ros::Time::now() - rrt_start_time).toSec(); ROS_INFO_STREAM("[BRRT*]: find_path_use_time: " << solution_cost_time_pair_list_.front().second << ", length: " << solution_cost_time_pair_list_.front().first); visualizeWholeTree(); final_path_ = path_list_.back(); } else if (valid_tree_node_nums_ == max_tree_node_nums_) { visualizeWholeTree(); ROS_ERROR_STREAM("[BRRT*]: NOT CONNECTED TO GOAL after " << max_tree_node_nums_ << " nodes added to rrt-tree"); } else { ROS_ERROR_STREAM("[BRRT*]: NOT CONNECTED TO GOAL after " << (ros::Time::now() - rrt_start_time).toSec() << " seconds"); } return tree_connected; } 注释上述代码
09-19
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值