Merge branch 'wip2'

This commit is contained in:
Matthias Koefferlein 2024-02-18 22:27:17 +01:00
commit 890b389102
24 changed files with 597 additions and 162 deletions

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>668</width>
<height>410</height>
<width>745</width>
<height>438</height>
</rect>
</property>
<property name="windowTitle">
@ -845,7 +845,12 @@
</item>
<item>
<property name="text">
<string>Angle measurement (three mouse clicks)</string>
<string>Auto measure along edge (points will be set automatically)</string>
</property>
</item>
<item>
<property name="text">
<string>Angle or radius measurement (three mouse clicks)</string>
</property>
</item>
<item>

View File

@ -247,6 +247,8 @@ RulerModeConverter::to_string (ant::Template::ruler_mode_type m)
return "single_click";
} else if (m == ant::Template::RulerAutoMetric) {
return "auto_metric";
} else if (m == ant::Template::RulerAutoMetricEdge) {
return "auto_metric_edge";
} else if (m == ant::Template::RulerMultiSegment) {
return "multi_segment";
} else if (m == ant::Template::RulerThreeClicks) {
@ -266,6 +268,8 @@ RulerModeConverter::from_string (const std::string &s, ant::Template::ruler_mode
a = ant::Template::RulerSingleClick;
} else if (t == "auto_metric") {
a = ant::Template::RulerAutoMetric;
} else if (t == "auto_metric_edge") {
a = ant::Template::RulerAutoMetricEdge;
} else if (t == "multi_segment") {
a = ant::Template::RulerMultiSegment;
} else if (t == "angle") {

View File

@ -60,6 +60,9 @@ static std::vector<ant::Template> make_standard_templates ()
templates.push_back (ant::Template (tl::to_string (tr ("Measure")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_measure"));
templates.back ().set_mode (ant::Template::RulerAutoMetric);
templates.push_back (ant::Template (tl::to_string (tr ("Measure edge")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_measure_edge"));
templates.back ().set_mode (ant::Template::RulerAutoMetricEdge);
templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "", "", "$(sprintf('%.5g',G))°", ant::Object::STY_line, ant::Object::OL_angle, true, lay::AC_Global, "_angle"));
templates.back ().set_mode (ant::Template::RulerThreeClicks);

View File

@ -1056,8 +1056,18 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view)
m_drawing (false), m_current (),
m_move_mode (MoveNone),
m_seg_index (0),
m_current_template (0)
{
m_current_template (0),
m_hover (false),
m_hover_wait (false),
m_hover_buttons (0),
m_mouse_in_window (false)
{
#if defined(HAVE_QT)
m_timer.setInterval (100 /*hover time*/);
m_timer.setSingleShot (true);
connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timeout ()));
#endif
mp_view->annotations_changed_event.add (this, &Service::annotations_changed);
}
@ -1809,9 +1819,36 @@ Service::mouse_double_click_event (const db::DPoint & /*p*/, unsigned int button
return false;
}
lay::TwoPointSnapToObjectResult
Service::auto_measure (const db::DPoint &p, lay::angle_constraint_type ac, const ant::Template &tpl)
{
// for auto-metric we need some cutline constraint - any or global won't do.
if (ac == lay::AC_Global) {
ac = tpl.angle_constraint ();
}
if (ac == lay::AC_Global) {
ac = m_snap_mode;
}
if (ac == lay::AC_Global) {
ac = lay::AC_Diagonal;
}
db::DVector g;
if (m_grid_snap) {
g = db::DVector (m_grid, m_grid);
}
double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (m_snap_range);
snap_range *= 0.5;
return lay::obj_snap2 (mp_view, p, g, ac, snap_range, snap_range * 1000.0);
}
bool
Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
hover_reset ();
if (prio && (buttons & lay::LeftButton) != 0) {
const ant::Template &tpl = current_template ();
@ -1852,27 +1889,7 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio
} else if (tpl.mode () == ant::Template::RulerAutoMetric) {
// for auto-metric we need some cutline constraint - any or global won't do.
lay::angle_constraint_type ac = ac_from_buttons (buttons);
if (ac == lay::AC_Global) {
ac = tpl.angle_constraint ();
}
if (ac == lay::AC_Global) {
ac = m_snap_mode;
}
if (ac == lay::AC_Global) {
ac = lay::AC_Diagonal;
}
db::DVector g;
if (m_grid_snap) {
g = db::DVector (m_grid, m_grid);
}
double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (m_snap_range);
snap_range *= 0.5;
lay::TwoPointSnapToObjectResult ee = lay::obj_snap2 (mp_view, p, g, ac, snap_range, snap_range * 1000.0);
lay::TwoPointSnapToObjectResult ee = auto_measure (p, ac_from_buttons (buttons), tpl);
if (ee.any) {
// begin the transaction
@ -1893,6 +1910,29 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio
}
} else if (tpl.mode () == ant::Template::RulerAutoMetricEdge) {
lay::PointSnapToObjectResult snap_details = snap1_details (p, true);
if (snap_details.object_snap == lay::PointSnapToObjectResult::ObjectEdge) {
// begin the transaction
if (manager ()) {
tl_assert (! manager ()->transacting ());
manager ()->transaction (tl::to_string (tr ("Create ruler")));
}
m_current = ant::Object (snap_details.object_ref.p1 (), snap_details.object_ref.p2 (), 0, tpl);
show_message ();
insert_ruler (m_current, true);
// end the transaction
if (manager ()) {
manager ()->commit ();
}
}
} else {
m_p1 = snap1 (p, m_obj_snap && tpl.snap ()).second;
@ -1968,21 +2008,33 @@ Service::create_measure_ruler (const db::DPoint &pt, lay::angle_constraint_type
bool
Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
if (prio) {
if (! prio) {
return false;
}
lay::PointSnapToObjectResult snap_details;
if (m_drawing) {
snap_details = snap2_details (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons));
} else {
const ant::Template &tpl = current_template ();
snap_details = snap1_details (p, m_obj_snap && tpl.snap ());
}
if (! m_drawing && m_mouse_in_window && view ()->transient_selection_mode ()) {
mouse_cursor_from_snap_details (snap_details);
// Restart hover timer
m_hover_wait = true;
#if defined(HAVE_QT)
m_timer.start ();
#endif
m_hover_point = p;
m_hover_buttons = buttons;
}
if (m_drawing && prio) {
lay::PointSnapToObjectResult snap_details;
if (m_drawing) {
snap_details = snap2_details (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons));
} else {
const ant::Template &tpl = current_template ();
snap_details = snap1_details (p, m_obj_snap && tpl.snap () && (tpl.mode () != ant::Template::RulerAutoMetricEdge || ! view ()->transient_selection_mode ()));
}
mouse_cursor_from_snap_details (snap_details);
if (m_drawing) {
set_cursor (lay::Cursor::cross);
@ -2284,6 +2336,84 @@ Service::click_proximity (const db::DPoint &pos, lay::Editable::SelectionMode mo
}
}
bool
Service::enter_event (bool /*prio*/)
{
m_mouse_in_window = true;
return false;
}
bool
Service::leave_event (bool)
{
m_mouse_in_window = false;
hover_reset ();
return false;
}
void
Service::hover_reset ()
{
if (m_hover_wait) {
#if defined(HAVE_QT)
m_timer.stop ();
#endif
m_hover_wait = false;
}
if (m_hover) {
// as we use the transient selection for the hover ruler, we have to remove it here
clear_transient_selection ();
m_hover = false;
}
}
#if defined(HAVE_QT)
void
Service::timeout ()
{
m_hover_wait = false;
m_hover = true;
// as we use the transient selection for the hover ruler, we have to remove it here
clear_transient_selection ();
// transiently create an auto-metric ruler if requested
ant::Object *ruler = 0;
const ant::Template &tpl = current_template ();
if (tpl.mode () == ant::Template::RulerAutoMetric) {
lay::TwoPointSnapToObjectResult ee = auto_measure (m_hover_point, ac_from_buttons (m_hover_buttons), tpl);
if (ee.any) {
m_current = ant::Object (ee.first, ee.second, 0, tpl);
ruler = &m_current;
}
} else if (tpl.mode () == ant::Template::RulerAutoMetricEdge) {
lay::PointSnapToObjectResult snap_details = snap1_details (m_hover_point, true);
if (snap_details.object_snap == lay::PointSnapToObjectResult::ObjectEdge) {
m_current = ant::Object (snap_details.object_ref.p1 (), snap_details.object_ref.p2 (), 0, tpl);
ruler = &m_current;
}
}
if (ruler) {
// HINT: there is no special style for "transient selection on rulers"
mp_transient_ruler = new ant::View (this, ruler, true /*not selected*/);
if (! editables ()->has_selection ()) {
display_status (true);
}
}
}
#endif
bool
Service::transient_select (const db::DPoint &pos)
{

View File

@ -39,6 +39,11 @@
#include <map>
#include <vector>
#if defined (HAVE_QT)
# include <QTimer>
# include <QObject>
#endif
namespace ant {
class LayoutViewBase;
@ -177,12 +182,19 @@ private:
// -------------------------------------------------------------
class ANT_PUBLIC Service
: public lay::EditorServiceBase,
class ANT_PUBLIC Service :
#if defined (HAVE_QT)
public QObject,
#endif
public lay::EditorServiceBase,
public lay::Drawing,
public db::Object
{
public:
#if defined (HAVE_QT)
Q_OBJECT
#endif
public:
typedef lay::AnnotationShapes::iterator obj_iterator;
/**
@ -341,6 +353,21 @@ public:
*/
virtual db::DBox selection_bbox ();
/**
* @brief Implementation of the editables API
*/
virtual bool enter_event (bool);
/**
* @brief Implementation of the editables API
*/
virtual bool leave_event (bool);
/**
* @brief Implementation of the editables API
*/
virtual void hover_reset ();
/**
* @brief Transform the selection (reimplementation of lay::Editable interface)
*/
@ -506,6 +533,11 @@ public:
*/
tl::Event annotation_selection_changed_event;
#if defined (HAVE_QT)
public slots:
void timeout ();
#endif
private:
// Ruler display and snapping configuration
tl::Color m_color;
@ -551,10 +583,22 @@ private:
std::vector<ant::Template> m_ruler_templates;
unsigned int m_current_template;
// Hover detector
bool m_hover;
bool m_hover_wait;
db::DPoint m_hover_point;
unsigned int m_hover_buttons;
#if defined (HAVE_QT)
QTimer m_timer;
#endif
bool m_mouse_in_window;
std::pair<bool, db::DPoint> snap1 (const db::DPoint &p, bool obj_snap);
lay::PointSnapToObjectResult snap1_details (const db::DPoint &p, bool obj_snap);
std::pair<bool, db::DPoint> snap2 (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac);
lay::PointSnapToObjectResult snap2_details (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac);
lay::TwoPointSnapToObjectResult auto_measure (const db::DPoint &p, lay::angle_constraint_type ac, const ant::Template &tpl);
const ant::Template &current_template () const;

View File

@ -64,15 +64,20 @@ public:
*/
RulerAutoMetric = 2,
/**
* @brief The ruler is auto-metric along an edge: a single click will place a ruler and the ruler will extend to the edge below
*/
RulerAutoMetricEdge = 3,
/**
* @brief The ruler an angle type (two segments, three mouse clicks) for angle and circle radius measurements
*/
RulerThreeClicks = 3,
RulerThreeClicks = 4,
/**
* @brief The ruler is a multi-segment type
*/
RulerMultiSegment = 4
RulerMultiSegment = 5
};
/**

View File

@ -434,6 +434,11 @@ static int ruler_mode_auto_metric ()
return ant::Template::RulerAutoMetric;
}
static int ruler_mode_auto_metric_edge ()
{
return ant::Template::RulerAutoMetricEdge;
}
static int ruler_mode_three_clicks ()
{
return ant::Template::RulerThreeClicks;
@ -525,6 +530,12 @@ gsi::Class<AnnotationRef> decl_Annotation (decl_BasicAnnotation, "lay", "Annotat
"\n"
"This constant has been introduced in version 0.25"
) +
gsi::method ("RulerModeAutoMetricEdge", &gsi::ruler_mode_auto_metric_edge,
"@brief Specifies edge-sensitive auto-metric ruler mode for the \\register_template method\n"
"In auto-metric mode, a ruler can be placed with a single click and p1/p2 will be determined from the edge it is placed on.\n"
"\n"
"This constant has been introduced in version 0.29"
) +
gsi::method ("RulerThreeClicks", &gsi::ruler_mode_three_clicks,
"@brief Specifies three-click ruler mode for the \\register_template method\n"
"In this ruler mode, two segments are created for angle and circle radius measurements. Three mouse clicks are required.\n"

View File

@ -154,6 +154,10 @@ CommonReaderBase::rename_cell (db::Layout &layout, size_t id, const std::string
common_reader_error (tl::sprintf (tl::to_string (tr ("Cell named %s with ID %ld was already given name %s")), cn, id, iid->second.first));
}
if (iname != m_name_map.end () && iname->second.first != null_id && iname->second.first != id) {
common_reader_error (tl::sprintf (tl::to_string (tr ("Same cell name %s, but different IDs: %ld and %ld")), cn, id, iname->second.first));
}
if (iid != m_id_map.end () && iname != m_name_map.end ()) {
if (iname->second.second != iid->second.second) {

View File

@ -1566,23 +1566,26 @@ compute_rounded (const db::DPolygon &polygon, double rinner, double router, unsi
}
// -------------------------------------------------------------------------
// Implementation of AreaMap
// Implementation of area_map
AreaMap::AreaMap ()
template <class C>
area_map<C>::area_map ()
: m_nx (0), m_ny (0)
{
mp_av = 0;
}
AreaMap::AreaMap (const AreaMap &other)
template <class C>
area_map<C>::area_map (const area_map &other)
: m_nx (0), m_ny (0)
{
mp_av = 0;
operator= (other);
}
AreaMap &
AreaMap::operator= (const AreaMap &other)
template <class C>
area_map<C> &
area_map<C>::operator= (const area_map &other)
{
if (this != &other) {
// TODO: this could be copy on write
@ -1594,21 +1597,24 @@ AreaMap::operator= (const AreaMap &other)
return *this;
}
AreaMap::AreaMap (const db::Point &p0, const db::Vector &d, size_t nx, size_t ny)
template <class C>
area_map<C>::area_map (const area_map::point_type &p0, const area_map::vector_type &d, size_t nx, size_t ny)
: m_p0 (p0), m_d (d), m_p (d), m_nx (nx), m_ny (ny)
{
mp_av = new area_type [nx * ny];
clear ();
}
AreaMap::AreaMap (const db::Point &p0, const db::Vector &d, const db::Vector &p, size_t nx, size_t ny)
template <class C>
area_map<C>::area_map (const area_map::point_type &p0, const area_map::vector_type &d, const area_map::vector_type &p, size_t nx, size_t ny)
: m_p0 (p0), m_d (d), m_p (std::min (d.x (), p.x ()), std::min (d.y (), p.y ())), m_nx (nx), m_ny (ny)
{
mp_av = new area_type [nx * ny];
clear ();
}
AreaMap::~AreaMap ()
template <class C>
area_map<C>::~area_map ()
{
if (mp_av) {
delete[] mp_av;
@ -1616,18 +1622,20 @@ AreaMap::~AreaMap ()
mp_av = 0;
}
template <class C>
void
AreaMap::reinitialize (const db::Point &p0, const db::Vector &d, size_t nx, size_t ny)
area_map<C>::reinitialize (const area_map::point_type &p0, const area_map::vector_type &d, size_t nx, size_t ny)
{
reinitialize (p0, d, d, nx, ny);
}
template <class C>
void
AreaMap::reinitialize (const db::Point &p0, const db::Vector &d, const db::Vector &p, size_t nx, size_t ny)
area_map<C>::reinitialize (const area_map::point_type &p0, const area_map::vector_type &d, const area_map::vector_type &p, size_t nx, size_t ny)
{
m_p0 = p0;
m_d = d;
m_p = db::Vector (std::min (d.x (), p.x ()), std::min (d.y (), p.y ()));
m_p = vector_type (std::min (d.x (), p.x ()), std::min (d.y (), p.y ()));
if (nx != m_nx || ny != m_ny) {
@ -1645,8 +1653,9 @@ AreaMap::reinitialize (const db::Point &p0, const db::Vector &d, const db::Vecto
clear ();
}
template <class C>
void
AreaMap::clear ()
area_map<C>::clear ()
{
if (mp_av) {
area_type *a = mp_av;
@ -1656,8 +1665,9 @@ AreaMap::clear ()
}
}
template <class C>
void
AreaMap::swap (AreaMap &other)
area_map<C>::swap (area_map &other)
{
std::swap (m_p0, other.m_p0);
std::swap (m_d, other.m_d);
@ -1667,8 +1677,9 @@ AreaMap::swap (AreaMap &other)
std::swap (mp_av, other.mp_av);
}
AreaMap::area_type
AreaMap::total_area () const
template <class C>
typename area_map<C>::area_type
area_map<C>::total_area () const
{
area_type asum = 0;
if (mp_av) {
@ -1680,16 +1691,21 @@ AreaMap::total_area () const
return asum;
}
db::Box
AreaMap::bbox () const
template <class C>
typename area_map<C>::box_type
area_map<C>::bbox () const
{
if (m_nx == 0 || m_ny == 0) {
return db::Box ();
return box_type ();
} else {
return db::Box (m_p0, m_p0 + db::Vector (db::Coord (m_nx - 1) * m_d.x () + m_p.x (), db::Coord (m_ny - 1) * m_d.y () + m_p.y ()));
return box_type (m_p0, m_p0 + vector_type (C (m_nx - 1) * m_d.x () + m_p.x (), C (m_ny - 1) * m_d.y () + m_p.y ()));
}
}
// explicit instantiations
template class area_map<db::Coord>;
template class area_map<db::DCoord>;
// -------------------------------------------------------------------------
// Implementation of rasterize
@ -1707,29 +1723,69 @@ static bool edge_is_partially_left_of (const db::Edge &e, const db::Edge &e_orig
}
}
bool
rasterize (const db::Polygon &polygon, db::AreaMap &am)
static bool edge_is_partially_left_of (const db::DEdge &e, const db::DEdge &e_original, db::DCoord x)
{
typedef db::AreaMap::area_type area_type;
db::Box box = am.bbox ();
db::Box pbox = polygon.box ();
DCoord xmin = db::edge_xmin (e);
if (db::coord_traits<db::DCoord>::less (xmin, x)) {
return true;
} else if (db::coord_traits<db::DCoord>::equal (xmin, x) && ! db::coord_traits<db::DCoord>::equal (e_original.dx (), 0)) {
// the skew edge is cut partially rendering a straight vertical line (due to rounding)
// which we will count as "left of"
return true;
} else {
return false;
}
}
static size_t npixels_floor (db::Coord d, db::Coord p)
{
return size_t (std::max (db::Coord (0), d / p));
}
static size_t npixels_ceil (db::Coord d, db::Coord p)
{
return size_t (std::max (db::Coord (0), (d + p - 1) / p));
}
static size_t npixels_floor (db::DCoord d, db::DCoord p)
{
return size_t (std::max (db::DCoord (0), floor (d / p + db::epsilon)));
}
static size_t npixels_ceil (db::DCoord d, db::DCoord p)
{
return size_t (std::max (db::DCoord (0), ceil (d / p - db::epsilon)));
}
template <class C>
static
bool
rasterize_impl (const db::polygon<C> &polygon, db::area_map<C> &am)
{
typedef typename db::area_map<C>::area_type area_type;
typedef db::box<C> box_type;
typedef db::edge<C> edge_type;
box_type box = am.bbox ();
box_type pbox = polygon.box ();
// check if the polygon overlaps the rasterization area. Otherwise, we simply do nothing.
if (! pbox.overlaps (box)) {
return false;
}
db::Coord ymin = box.bottom (), ymax = box.top ();
db::Coord dy = am.d ().y (), dx = am.d ().x ();
db::Coord py = am.p ().y (), px = am.p ().x ();
db::Coord y0 = am.p0 ().y (), x0 = am.p0 ().x ();
C ymin = box.bottom (), ymax = box.top ();
C dy = am.d ().y (), dx = am.d ().x ();
C py = am.p ().y (), px = am.p ().x ();
C y0 = am.p0 ().y (), x0 = am.p0 ().x ();
size_t ny = am.ny (), nx = am.nx ();
size_t iy0 = std::min (ny, size_t (std::max (db::Coord (0), (pbox.bottom () - am.p0 ().y ()) / am.d ().y ())));
size_t iy1 = std::min (ny, size_t (std::max (db::Coord (0), (pbox.top () - am.p0 ().y () + am.d ().y () - 1) / am.d ().y ())));
size_t iy0 = std::min (ny, npixels_floor (pbox.bottom () - am.p0 ().y (), am.d ().y ()));
size_t iy1 = std::min (ny, npixels_ceil (pbox.top () - am.p0 ().y (), am.d ().y ()));
size_t ix0 = std::min (nx, size_t (std::max (db::Coord (0), (pbox.left () - am.p0 ().x ()) / am.d ().x ())));
size_t ix1 = std::min (nx, size_t (std::max (db::Coord (0), (pbox.right () - am.p0 ().x () + am.d ().x () - 1) / am.d ().x ())));
size_t ix0 = std::min (nx, npixels_floor (pbox.left () - am.p0 ().x (), am.d ().x ()));
size_t ix1 = std::min (nx, npixels_ceil (pbox.right () - am.p0 ().x (), am.d ().x ()));
// no scanning required (i.e. degenerated polygon) -> do nothing
if (iy0 == iy1 || ix0 == ix1) {
@ -1738,26 +1794,26 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am)
// collect edges
size_t n = 0;
for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) {
for (typename db::polygon<C>::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) {
if ((*e).dy () != 0 && db::edge_ymax (*e) > ymin && db::edge_ymin (*e) < ymax) {
++n;
}
}
std::vector <db::Edge> edges;
std::vector <edge_type> edges;
edges.reserve (n);
for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) {
for (typename db::polygon<C>::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) {
if ((*e).dy () != 0 && db::edge_ymax (*e) > ymin && db::edge_ymin (*e) < ymax) {
edges.push_back (*e);
}
}
// sort edges
std::sort (edges.begin (), edges.end (), db::edge_ymin_compare<db::Coord> ());
std::sort (edges.begin (), edges.end (), db::edge_ymin_compare<C> ());
std::vector <db::Edge>::iterator c = edges.begin ();
typename std::vector <edge_type>::iterator c = edges.begin ();
db::Coord y = y0 + dy * db::Coord (iy0);
C y = y0 + dy * C (iy0);
while (c != edges.end () && db::edge_ymax (*c) <= y) {
++c;
@ -1767,36 +1823,36 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am)
return false;
}
std::vector <db::Edge>::iterator f = c;
typename std::vector <edge_type>::iterator f = c;
for (size_t iy = iy0; iy < iy1; ++iy) {
db::Coord yy = y + py;
C yy = y + py;
while (f != edges.end () && db::edge_ymin (*f) < yy) {
++f;
}
std::sort (c, f, db::edge_xmin_compare <db::Coord> ());
std::sort (c, f, db::edge_xmin_compare <C> ());
db::Coord x = x0 + dx * db::Coord (ix0);
db::Coord xl = pbox.left ();
C x = x0 + dx * C (ix0);
C xl = pbox.left ();
area_type a = 0;
std::vector <db::Edge>::iterator cc = c;
typename std::vector <edge_type>::iterator cc = c;
while (cc != edges.end () && cc != f && db::edge_xmax (*cc) <= x) {
db::Coord y1 = std::max (y, std::min (yy, cc->p1 ().y ()));
db::Coord y2 = std::max (y, std::min (yy, cc->p2 ().y ()));
C y1 = std::max (y, std::min (yy, cc->p1 ().y ()));
C y2 = std::max (y, std::min (yy, cc->p2 ().y ()));
a += area_type (px) * area_type (y2 - y1);
++cc;
}
std::vector <db::Edge>::iterator ff = cc;
typename std::vector <edge_type>::iterator ff = cc;
for (size_t ix = ix0; ix < ix1; ++ix) {
db::Coord xx = x + px;
db::Coord xxx = x + dx;
C xx = x + px;
C xxx = x + dx;
// TODO: edge_xmin_at_interval(y, yy) and edge_xmax.. would be more efficient in the
// all-angle case. However, it is crucial that the edge clipping produces
@ -1807,7 +1863,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am)
++ff;
}
std::vector <db::Edge>::iterator fff = ff;
typename std::vector <edge_type>::iterator fff = ff;
if (xx < xxx) {
while (fff != f && db::edge_xmin (*fff) < xxx) {
@ -1818,11 +1874,11 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am)
if (xl < x) {
// consider all edges or parts of those left of the first cell
db::Box left (xl, y, x, yy);
box_type left (xl, y, x, yy);
for (std::vector <db::Edge>::iterator e = cc; e != ff; ++e) {
for (typename std::vector <edge_type>::iterator e = cc; e != ff; ++e) {
std::pair<bool, db::Edge> ec = e->clipped (left);
std::pair<bool, edge_type> ec = e->clipped (left);
if (ec.first && edge_is_partially_left_of (ec.second, *e, x)) {
a += area_type (ec.second.dy ()) * area_type (px);
}
@ -1835,11 +1891,11 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am)
if (dx == py) {
db::Box cell (x, y, xx, yy);
box_type cell (x, y, xx, yy);
for (std::vector <db::Edge>::iterator e = cc; e != ff; ++e) {
for (typename std::vector <edge_type>::iterator e = cc; e != ff; ++e) {
std::pair<bool, db::Edge> ec = e->clipped (cell);
std::pair<bool, edge_type> ec = e->clipped (cell);
if (ec.first && edge_is_partially_left_of (ec.second, *e, xx)) {
aa += (area_type (ec.second.dy ()) * area_type (2 * xx - (ec.second.p2 ().x () + ec.second.p1 ().x ()))) / 2;
a += area_type (ec.second.dy ()) * area_type (px);
@ -1849,22 +1905,22 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am)
} else {
db::Box cell (x, y, xx, yy);
box_type cell (x, y, xx, yy);
for (std::vector <db::Edge>::iterator e = cc; e != ff; ++e) {
for (typename std::vector <edge_type>::iterator e = cc; e != ff; ++e) {
std::pair<bool, db::Edge> ec = e->clipped (cell);
std::pair<bool, edge_type> ec = e->clipped (cell);
if (ec.first && edge_is_partially_left_of (ec.second, *e, xx)) {
aa += (area_type (ec.second.dy ()) * area_type (2 * xx - (ec.second.p2 ().x () + ec.second.p1 ().x ()))) / 2;
}
}
db::Box wide_cell (x, y, x + dx, yy);
box_type wide_cell (x, y, x + dx, yy);
for (std::vector <db::Edge>::iterator e = cc; e != fff; ++e) {
for (typename std::vector <edge_type>::iterator e = cc; e != fff; ++e) {
std::pair<bool, db::Edge> wide_ec = e->clipped (wide_cell);
std::pair<bool, edge_type> wide_ec = e->clipped (wide_cell);
if (wide_ec.first && edge_is_partially_left_of (wide_ec.second, *e, x + dx)) {
a += area_type (wide_ec.second.dy ()) * area_type (px);
}
@ -1880,7 +1936,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am)
ff = fff;
for (std::vector <db::Edge>::iterator ccx = cc; ccx != ff; ++ccx) {
for (typename std::vector <edge_type>::iterator ccx = cc; ccx != ff; ++ccx) {
if (db::edge_xmax (*ccx) <= x) {
std::swap (*ccx, *cc);
++cc;
@ -1898,7 +1954,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am)
y = yy;
for (std::vector <db::Edge>::iterator cx = c; cx != f; ++cx) {
for (typename std::vector <edge_type>::iterator cx = c; cx != f; ++cx) {
if (db::edge_ymax (*cx) <= y) {
std::swap (*cx, *c);
++c;
@ -1910,6 +1966,18 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am)
return true;
}
bool
rasterize (const db::Polygon &polygon, db::AreaMap &am)
{
return rasterize_impl<db::Coord> (polygon, am);
}
bool
rasterize (const db::DPolygon &polygon, db::DAreaMap &am)
{
return rasterize_impl<db::DCoord> (polygon, am);
}
// -------------------------------------------------------------------------
// Implementation of Minkowski sum

View File

@ -493,55 +493,59 @@ bool DB_PUBLIC is_non_orientable_polygon (const db::Polygon &poly, std::vector<d
* It is used for example by the rasterize function to collect area values
* on a per-pixel basis.
*/
class DB_PUBLIC AreaMap
template <class C>
class DB_PUBLIC area_map
{
public:
typedef db::coord_traits<db::Coord>::area_type area_type;
typedef typename db::coord_traits<C>::area_type area_type;
typedef db::point<C> point_type;
typedef db::vector<C> vector_type;
typedef db::box<C> box_type;
/**
* @brief Constructor
*/
AreaMap ();
area_map ();
/**
* @brief Copy constructor
*/
AreaMap (const AreaMap &);
area_map (const area_map &);
/**
* @brief Constructor
*/
AreaMap (const db::Point &p0, const db::Vector &d, size_t nx, size_t ny);
area_map (const point_type &p0, const vector_type &d, size_t nx, size_t ny);
/**
* @brief Constructor with pixel size
*/
AreaMap (const db::Point &p0, const db::Vector &d, const db::Vector &p, size_t nx, size_t ny);
area_map (const point_type &p0, const vector_type &d, const vector_type &p, size_t nx, size_t ny);
/**
* @brief Destructor
*/
~AreaMap ();
~area_map ();
/**
* @brief Assignment
*/
AreaMap &operator= (const AreaMap &);
area_map &operator= (const area_map &);
/**
* @brief Reinitialize
*/
void reinitialize (const db::Point &p0, const db::Vector &d, size_t nx, size_t ny);
void reinitialize (const point_type &p0, const vector_type &d, size_t nx, size_t ny);
/**
* @brief Reinitialize with pixel size
*/
void reinitialize (const db::Point &p0, const db::Vector &d, const db::Vector &p, size_t nx, size_t ny);
void reinitialize (const point_type &p0, const vector_type &d, const vector_type &p, size_t nx, size_t ny);
/**
* @brief Swap of two maps
*/
void swap (AreaMap &other);
void swap (area_map &other);
/**
* @brief Get the area of one pixel
@ -578,7 +582,7 @@ public:
/**
* @brief The origin
*/
const db::Point &p0 () const
const point_type &p0 () const
{
return m_p0;
}
@ -586,7 +590,7 @@ public:
/**
* @brief Move the origin
*/
void move (const db::Vector &d)
void move (const vector_type &d)
{
m_p0 += d;
}
@ -594,7 +598,7 @@ public:
/**
* @brief The per-pixel displacement vector (pixel size)
*/
const db::Vector &d () const
const vector_type &d () const
{
return m_d;
}
@ -602,7 +606,7 @@ public:
/**
* @brief The pixel size (must be less than d)
*/
const db::Vector &p () const
const vector_type &p () const
{
return m_p;
}
@ -610,7 +614,7 @@ public:
/**
* @brief Compute the bounding box of the area map
*/
db::Box bbox () const;
box_type bbox () const;
/**
* @brief Compute the total area
@ -632,12 +636,15 @@ public:
private:
area_type *mp_av;
db::Point m_p0;
db::Vector m_d;
db::Vector m_p;
point_type m_p0;
vector_type m_d;
vector_type m_p;
size_t m_nx, m_ny;
};
typedef area_map<db::Coord> AreaMap;
typedef area_map<db::DCoord> DAreaMap;
/**
* @brief Rasterize the polygon into the given area map
*
@ -648,6 +655,16 @@ private:
*/
bool DB_PUBLIC rasterize (const db::Polygon &polygon, db::AreaMap &am);
/**
* @brief Rasterize the polygon into the given area map (double version)
*
* This will decompose the polygon and produce per-pixel area values for the given
* polygon. The area contributions will be added to the given area map.
*
* Returns a value indicating whether the map will be non-empty.
*/
bool DB_PUBLIC rasterize (const db::DPolygon &polygon, db::DAreaMap &am);
/**
* @brief Minkowski sum of an edge and a polygon
*/

View File

@ -788,6 +788,40 @@ size_dvm (db::Region *region, const db::Vector &dv, unsigned int mode)
return *region;
}
static std::vector<std::vector<double> >
rasterize2 (const db::Region *region, const db::Point &origin, const db::Vector &pixel_distance, const db::Vector &pixel_size, unsigned int nx, unsigned int ny)
{
db::DAreaMap am (db::DPoint (origin), db::DVector (pixel_distance), db::DVector (pixel_size), nx, ny);
auto pi = region->begin ();
pi = pi.confined (db::Box (am.bbox ()), false /*not overlapping*/);
while (! pi.at_end ()) {
db::DPolygon dp (*pi);
db::rasterize (dp, am);
++pi;
}
std::vector<std::vector<double> > result;
result.reserve (ny);
for (unsigned int y = 0; y < ny; ++y) {
result.push_back (std::vector<double> ());
std::vector<double> &row = result.back ();
row.reserve (nx);
for (unsigned int x = 0; x < nx; ++x) {
row.push_back (am.get (x, y));
}
}
return result;
}
static std::vector<std::vector<double> >
rasterize1 (const db::Region *region, const db::Point &origin, const db::Vector &pixel_size, unsigned int nx, unsigned int ny)
{
return rasterize2 (region, origin, pixel_size, pixel_size, nx, ny);
}
static db::Point default_origin;
// provided by gsiDeclDbPolygon.cc:
@ -3095,6 +3129,36 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"@brief Converts the region to a string\n"
"This version allows specification of the maximum number of polygons contained in the string."
) +
method_ext ("rasterize", &rasterize1, gsi::arg ("origin"), gsi::arg ("pixel_size"), gsi::arg ("nx"), gsi::arg ("ny"),
"@brief A grayscale rasterizer delivering the area covered per pixel\n"
"@param origin The lower-left corner of the lowest-left pixel\n"
"@param pixel_size The dimension of each pixel (the x component gives the width, the y component the height)\n"
"@param nx The number of pixels in horizontal direction\n"
"@param ny The number of pixels in vertical direction\n"
"The method will create a grayscale, high-resolution density map of a rectangular region.\n"
"The scan region is defined by the origin, the pixel size and the number of pixels in horizontal (nx) and\n"
"vertical (ny) direction. The resulting array will contain the area covered by polygons from the region\n"
"in square database units.\n"
"\n"
"For non-overlapping polygons, the maximum density value is px*py. Overlapping polygons are counted multiple\n"
"times, so the actual values may be larger. If you want overlaps removed, you have to\n"
"merge the region before. Merge semantics does not apply for the 'rasterize' method.\n"
"\n"
"The resulting area values are precise within the limits of double-precision floating point arithmetics.\n"
"\n"
"A second version exists that allows specifying an active pixel size which is smaller than the\n"
"pixel distance hence allowing pixels samples that do not cover the full area, but leave gaps between the pixels.\n"
"\n"
"This method has been added in version 0.29.\n"
) +
method_ext ("rasterize", &rasterize2, gsi::arg ("origin"), gsi::arg ("pixel_distance"), gsi::arg ("pixel_size"), gsi::arg ("nx"), gsi::arg ("ny"),
"@brief A version of 'rasterize' that allows a pixel step distance which is larger than the pixel size\n"
"This version behaves like the first variant of 'rasterize', but the pixel distance (pixel-to-pixel step raster)\n"
"can be specified separately from the pixel size. Currently, the pixel size must be equal or smaller than the\n"
"pixel distance - i.e. the pixels must not overlap.\n"
"\n"
"This method has been added in version 0.29.\n"
) +
method ("enable_progress", &db::Region::enable_progress, gsi::arg ("label"),
"@brief Enable progress reporting\n"
"After calling this method, the region will report the progress through a progress bar while "

View File

@ -828,7 +828,7 @@ The following example selects all shapes which are rectangles and
whose area is larger than 0.5 square micrometers:
</p><p>
<pre>
out = in.drc(if_all(area &gt; 0.5, rectangle))
out = in.drc(if_all(area &gt; 0.5, rectangles))
</pre>
</p><p>
The condition expressions may be of any type (edges, edge pairs and polygons).

View File

@ -555,7 +555,7 @@ module DRC
# whose area is larger than 0.5 square micrometers:
#
# @code
# out = in.drc(if_all(area > 0.5, rectangle))
# out = in.drc(if_all(area > 0.5, rectangles))
# @/code
#
# The condition expressions may be of any type (edges, edge pairs and polygons).

View File

@ -105,15 +105,22 @@ MouseTracker::mouse_move_event (const db::DPoint &p, unsigned int /*buttons*/, b
double max_coord = 1e30; // big enough I guess
mp_markers.push_back (new lay::DMarker (mp_view));
mp_markers.back ()->set_line_style (m_cursor_line_style);
mp_markers.back ()->set_color (m_cursor_color);
mp_markers.back ()->set (db::DEdge (db::DPoint (tp.x (), -max_coord), db::DPoint (tp.x (), max_coord)));
for (int i = 0; i < 2; ++i) {
mp_markers.push_back (new lay::DMarker (mp_view));
mp_markers.back ()->set_line_style (m_cursor_line_style);
mp_markers.back ()->set_color (m_cursor_color);
mp_markers.back ()->set (db::DEdge (db::DPoint (-max_coord, tp.y ()), db::DPoint (max_coord, tp.y ())));
mp_markers.push_back (new lay::DMarker (mp_view));
mp_markers.back ()->set_line_style (m_cursor_line_style);
mp_markers.back ()->set_line_width (1);
mp_markers.back ()->set_halo (false);
mp_markers.back ()->set_dither_pattern (1);
mp_markers.back ()->set_color (m_cursor_color.is_valid () ? m_cursor_color : mp_view->canvas ()->foreground_color ());
if (i == 0) {
mp_markers.back ()->set (db::DEdge (db::DPoint (tp.x (), -max_coord), db::DPoint (tp.x (), max_coord)));
} else {
mp_markers.back ()->set (db::DEdge (db::DPoint (-max_coord, tp.y ()), db::DPoint (max_coord, tp.y ())));
}
}
}

View File

@ -305,10 +305,7 @@ MoveService::handle_click (const db::DPoint &p, unsigned int buttons, bool drag_
if (mp_editables->begin_move (p, ac_from_buttons (buttons))) {
lay::SelectionService *selector = mp_view->selection_service ();
if (selector) {
selector->hover_reset ();
}
ui ()->hover_reset ();
mp_view->clear_transient_selection ();

View File

@ -69,14 +69,7 @@ public:
virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio);
virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio);
virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio);
/**
* @brief Reset the hover timer for the transient selection
*
* This method may be used by other services (in particular Move) to avoid the transient to
* be triggered from a move operation.
*/
void hover_reset ();
virtual void hover_reset ();
#if defined (HAVE_QT)
public slots:

View File

@ -1051,7 +1051,15 @@ ViewObjectUI::drag_cancel ()
}
}
namespace
void
ViewObjectUI::hover_reset ()
{
for (service_iterator svc = begin_services (); svc != end_services (); ++svc) {
(*svc)->hover_reset ();
}
}
namespace
{
struct z_order_compare_f
{

View File

@ -147,6 +147,17 @@ public:
virtual bool drop_event (const db::DPoint & /*p*/, const DragDropDataBase * /*data*/) { return false; }
#endif
/**
* @brief Hover reset request
*
* This event is issued for services providing some "hover" mode - i.e. capture
* mouse move events and start a timer on them.
*
* The implementation of this event should cancel this timer and
* not raise a hover condition.
*/
virtual void hover_reset () { }
/**
* @brief Mouse press event handler
*
@ -605,6 +616,11 @@ public:
*/
void drag_cancel ();
/**
* @brief Calls hover_reset on all services
*/
void hover_reset ();
/**
* @brief CanvasPlane rendering
*

View File

@ -55,9 +55,6 @@
</property>
<item row="1" column="1">
<widget class="lay::ColorButton" name="color_pb">
<property name="toolTip">
<string>The color in which the rulers are drawn</string>
</property>
<property name="text">
<string/>
</property>
@ -151,9 +148,6 @@
</item>
<item row="0" column="1">
<widget class="lay::ColorButton" name="color_chc">
<property name="toolTip">
<string>The color in which the rulers are drawn</string>
</property>
<property name="text">
<string/>
</property>

View File

@ -972,17 +972,31 @@ SimpleColorButton::set_color_internal (QColor c)
m_color = c;
QFontMetrics fm (font (), this);
QRect rt (fm.boundingRect (QObject::tr ("Auto"))); // dummy text to be compliant with the other color button
QPixmap pxmp (rt.width () + 24, rt.height ());
QRect rt (fm.boundingRect (QObject::tr ("XXXXXXX")));
#if QT_VERSION >= 0x050000
double dpr = devicePixelRatio ();
#else
double dpr = 1.0;
#endif
QPixmap pxmp (rt.width () * dpr, rt.height () * dpr);
#if QT_VERSION >= 0x050000
pxmp.setDevicePixelRatio (dpr);
#endif
QPainter pxpainter (&pxmp);
QColor text_color = palette ().color (QPalette::Active, QPalette::Text);
pxpainter.setPen (QPen (text_color));
pxpainter.setBrush (QBrush (c.isValid () ? c : QColor (128, 128, 128)));
QRect r (0, 0, pxmp.width () - 1, pxmp.height () - 1);
QPen frame_pen (text_color);
frame_pen.setWidthF (1.0);
frame_pen.setJoinStyle (Qt::MiterJoin);
pxpainter.setPen (frame_pen);
int dpri = int (dpr);
QRectF r ((dpri / 2) / dpr, (dpri / 2) / dpr, rt.width () - 1.0, rt.height () - 1.0);
pxpainter.drawRect (r);
setIconSize (pxmp.size ());
setIconSize (QSize (rt.width (), rt.height ()));
setIcon (QIcon (pxmp));
}
@ -1216,22 +1230,25 @@ ColorButton::set_color_internal (QColor c)
#if QT_VERSION >= 0x50000
pixmap.setDevicePixelRatio (dpr);
#endif
pixmap.fill (QColor (0, 0, 0, 0));
QColor text_color = palette ().color (QPalette::Active, QPalette::Text);
QPainter pxpainter (&pixmap);
pxpainter.setPen (QPen (text_color));
QPen frame_pen (text_color);
frame_pen.setWidthF (1.0);
frame_pen.setJoinStyle (Qt::MiterJoin);
pxpainter.setPen (frame_pen);
int dpri = int (dpr);
QRectF r ((dpri / 2) / dpr, (dpri / 2) / dpr, rt.width () - 1.0, rt.height () - 1.0);
if (! m_color.isValid ()) {
pxpainter.setFont (font ());
QRectF r (0, 0, rt.width () - pxpainter.pen ().widthF (), rt.height () - pxpainter.pen ().widthF ());
pxpainter.drawText (r, Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine, QObject::tr ("Auto"));
} else {
pxpainter.setBrush (QBrush (c));
QRectF r (0, 0, rt.width () - pxpainter.pen ().widthF (), rt.height () - pxpainter.pen ().widthF ());
pxpainter.drawRect (r);
}

View File

@ -602,3 +602,21 @@ TEST(Bug_1474)
EXPECT_EQ (ex.msg (), "Cell named ADDHX2 with ID 4 was already given name SEDFFTRX2 (position=763169, cell=)");
}
}
TEST(DuplicateCellname)
{
db::Manager m (false);
db::Layout layout (&m);
try {
tl::InputStream file (tl::testdata () + "/oasis/duplicate_cellname.oas");
db::OASISReader reader (file);
reader.read (layout);
EXPECT_EQ (false, true);
} catch (tl::CancelException &ex) {
// Seen when private test data is not installed
throw;
} catch (tl::Exception &ex) {
EXPECT_EQ (ex.msg (), "Same cell name TOP, but different IDs: 3 and 0 (position=1070, cell=)");
}
}

BIN
testdata/oasis/duplicate_cellname.oas vendored Normal file

Binary file not shown.

View File

@ -1226,6 +1226,36 @@ class DBRegion_TestClass < TestBase
end
# rasterize
def test_rasterize
r = RBA::Region::new()
r.insert(RBA::Polygon::new([[0, 0], [100, 100], [150, 0]]))
r.insert(RBA::Polygon::new(RBA::Box::new([0, 200], [100, 300])))
pd = RBA::Vector::new(50, 50)
ps = RBA::Vector::new(25, 25)
sum = 0
2.times do |ix|
2.times do |iy|
am = r.rasterize(RBA::Point::new(-50 + ix * ps.x, -20 + iy * ps.y), pd, ps, 7, 7)
sum += am.collect { |r| r.inject(:+) }.inject(:+)
end
end
assert_equal("%.12g" % sum, "%.12g" % (7.0 * pd.x * pd.y))
tot = 0.0
pd = RBA::Vector::new(50, 50)
am = r.rasterize(RBA::Point::new(-50, -20), pd, 7, 7)
sum = am.collect { |r| r.inject(:+) }.inject(:+)
assert_equal("%.12g" % sum, "%.12g" % (7.0 * pd.x * pd.y))
end
end
load("test_epilogue.rb")