WIP: more tests, bug fixes, new feature: deep_reject_odd_polygons, odd_polygons check disabled in deep mode

This commit is contained in:
Matthias Koefferlein 2021-01-10 18:46:01 +01:00
parent e21f14b79d
commit 158ea196ec
16 changed files with 384 additions and 18 deletions

View File

@ -1046,7 +1046,7 @@ public:
virtual const TransformationReducer *vars () const { return mp_proc->vars (); }
virtual bool wants_variants () const { return mp_proc->wants_variants (); }
virtual bool wants_merged () const { return true; }
virtual bool wants_merged () const { return ! mp_proc->requires_raw_input (); }
virtual void do_compute_local (db::Layout *layout, const shape_interactions<db::Polygon, db::Polygon> &interactions, std::vector<std::unordered_set<db::Polygon> > &results, size_t max_vertex_count, double area_ratio) const;
virtual void do_compute_local (db::Layout *layout, const shape_interactions<db::PolygonRef, db::PolygonRef> &interactions, std::vector<std::unordered_set<db::PolygonRef> > &results, size_t max_vertex_count, double area_ratio) const;

View File

@ -288,11 +288,21 @@ struct DeepShapeStore::LayoutHolder
// ----------------------------------------------------------------------------------
DeepShapeStoreState::DeepShapeStoreState ()
: m_threads (1), m_max_area_ratio (3.0), m_max_vertex_count (16), m_text_property_name (), m_text_enlargement (-1)
: m_threads (1), m_max_area_ratio (3.0), m_max_vertex_count (16), m_reject_odd_polygons (false), m_text_property_name (), m_text_enlargement (-1)
{
// .. nothing yet ..
}
void DeepShapeStoreState::set_reject_odd_polygons (bool f)
{
m_reject_odd_polygons = f;
}
bool DeepShapeStoreState::reject_odd_polygons () const
{
return m_reject_odd_polygons;
}
void DeepShapeStoreState::set_text_enlargement (int enl)
{
m_text_enlargement = enl;
@ -437,7 +447,7 @@ DeepLayer DeepShapeStore::create_from_flat (const db::Region &region, bool for_n
// The chain of operators for producing clipped and reduced polygon references
db::PolygonReferenceHierarchyBuilderShapeReceiver refs (&layout (), text_enlargement (), text_property_name ());
db::ReducingHierarchyBuilderShapeReceiver red (&refs, max_area_ratio, max_vertex_count);
db::ReducingHierarchyBuilderShapeReceiver red (&refs, max_area_ratio, max_vertex_count, m_state.reject_odd_polygons ());
// try to maintain the texts on top level - go through shape iterator
std::pair<db::RecursiveShapeIterator, db::ICplxTrans> ii = region.begin_iter ();
@ -631,6 +641,16 @@ double DeepShapeStore::max_area_ratio () const
return m_state.max_area_ratio ();
}
void DeepShapeStore::set_reject_odd_polygons (bool f)
{
m_state.set_reject_odd_polygons (f);
}
bool DeepShapeStore::reject_odd_polygons () const
{
return m_state.reject_odd_polygons ();
}
void DeepShapeStore::set_max_vertex_count (size_t n)
{
m_state.set_max_vertex_count (n);
@ -791,7 +811,7 @@ DeepLayer DeepShapeStore::create_polygon_layer (const db::RecursiveShapeIterator
// The chain of operators for producing clipped and reduced polygon references
db::PolygonReferenceHierarchyBuilderShapeReceiver refs (& layout, text_enlargement (), text_property_name ());
db::ReducingHierarchyBuilderShapeReceiver red (&refs, max_area_ratio, max_vertex_count);
db::ReducingHierarchyBuilderShapeReceiver red (&refs, max_area_ratio, max_vertex_count, m_state.reject_odd_polygons ());
db::ClippingHierarchyBuilderShapeReceiver clip (&red);
// Build the working hierarchy from the recursive shape iterator

View File

@ -252,6 +252,9 @@ public:
void set_text_enlargement (int enl);
int text_enlargement () const;
void set_reject_odd_polygons (bool f);
bool reject_odd_polygons () const;
const std::set<db::cell_index_type> *breakout_cells (unsigned int layout_index) const;
void clear_breakout_cells (unsigned int layout_index);
void set_breakout_cells (unsigned int layout_index, const std::set<db::cell_index_type> &boc);
@ -261,6 +264,7 @@ public:
private:
int m_threads;
double m_max_area_ratio;
bool m_reject_odd_polygons;
size_t m_max_vertex_count;
tl::Variant m_text_property_name;
std::vector<std::set<db::cell_index_type> > m_breakout_cells;
@ -611,6 +615,20 @@ public:
*/
int threads () const;
/**
* @brief Sets a flag indicating whether to reject odd polygons
*
* Some kind of "odd" (e.g. non-orientable) polygons may spoil the functionality
* because they cannot be handled properly. By using this flag, the shape store
* we reject these kind of polygons. The default is "accept" (without warning).
*/
void set_reject_odd_polygons (bool f);
/**
* @brief Gets a flag indicating whether to reject odd polygons
*/
bool reject_odd_polygons () const;
/**
* @brief Sets the maximum vertex count default value
*

View File

@ -557,8 +557,8 @@ ClippingHierarchyBuilderShapeReceiver::insert_clipped (const db::Polygon &poly,
// ---------------------------------------------------------------------------------------------
ReducingHierarchyBuilderShapeReceiver::ReducingHierarchyBuilderShapeReceiver (HierarchyBuilderShapeReceiver *pipe, double area_ratio, size_t max_vertex_count)
: mp_pipe (pipe ? pipe : &def_inserter), m_area_ratio (area_ratio), m_max_vertex_count (max_vertex_count)
ReducingHierarchyBuilderShapeReceiver::ReducingHierarchyBuilderShapeReceiver (HierarchyBuilderShapeReceiver *pipe, double area_ratio, size_t max_vertex_count, bool reject_odd_polygons)
: mp_pipe (pipe ? pipe : &def_inserter), m_area_ratio (area_ratio), m_max_vertex_count (max_vertex_count), m_reject_odd_polygons (reject_odd_polygons)
{
// .. nothing yet ..
}
@ -590,14 +590,24 @@ ReducingHierarchyBuilderShapeReceiver::push (const db::Polygon &shape, const db:
}
void
ReducingHierarchyBuilderShapeReceiver::reduce (const db::Polygon &poly, const db::ICplxTrans &trans, const db::Box &region, const db::RecursiveShapeReceiver::box_tree_type *complex_region, db::Shapes *target)
ReducingHierarchyBuilderShapeReceiver::reduce (const db::Polygon &poly, const db::ICplxTrans &trans, const db::Box &region, const db::RecursiveShapeReceiver::box_tree_type *complex_region, db::Shapes *target, bool check)
{
if (check && m_reject_odd_polygons && is_non_orientable_polygon (poly)) {
// non-orientable polygons generate an error
if (target->cell () && target->cell ()->layout ()) {
throw tl::Exception (tl::to_string (tr ("Non-orientable polygon encountered: %s in cell %s")), poly.to_string (), target->cell ()->layout ()->cell_name (target->cell ()->cell_index ()));
} else {
throw tl::Exception (tl::to_string (tr ("Non-orientable polygon encountered: %s")), poly.to_string ());
}
}
if ((m_max_vertex_count >= 4 && poly.vertices () > m_max_vertex_count) || (m_area_ratio > 2.0 && poly.area_ratio () > m_area_ratio)) {
std::vector <db::Polygon> split_polygons;
db::split_polygon (poly, split_polygons);
for (std::vector <db::Polygon>::const_iterator sp = split_polygons.begin (); sp != split_polygons.end (); ++sp) {
reduce (*sp, trans, region, complex_region, target);
reduce (*sp, trans, region, complex_region, target, false);
}
} else {

View File

@ -124,18 +124,19 @@ class DB_PUBLIC ReducingHierarchyBuilderShapeReceiver
: public HierarchyBuilderShapeReceiver
{
public:
ReducingHierarchyBuilderShapeReceiver (HierarchyBuilderShapeReceiver *pipe = 0, double area_ratio = 3.0, size_t max_vertex_count = 16);
ReducingHierarchyBuilderShapeReceiver (HierarchyBuilderShapeReceiver *pipe = 0, double area_ratio = 3.0, size_t max_vertex_count = 16, bool reject_odd_polygons = false);
virtual void push (const db::Shape &shape, const db::ICplxTrans &trans, const db::Box &, const db::RecursiveShapeReceiver::box_tree_type *, db::Shapes *target);
virtual void push (const db::Box &shape, const db::ICplxTrans &trans, const db::Box &, const db::RecursiveShapeReceiver::box_tree_type *, db::Shapes *target);
virtual void push (const db::Polygon &shape, const db::ICplxTrans &trans, const db::Box &, const db::RecursiveShapeReceiver::box_tree_type *, db::Shapes *target);
private:
void reduce (const db::Polygon &poly, const db::ICplxTrans &trans, const db::Box &region, const db::RecursiveShapeReceiver::box_tree_type *complex_region, db::Shapes *target);
void reduce (const db::Polygon &poly, const db::ICplxTrans &trans, const db::Box &region, const db::RecursiveShapeReceiver::box_tree_type *complex_region, db::Shapes *target, bool check = true);
HierarchyBuilderShapeReceiver *mp_pipe;
double m_area_ratio;
size_t m_max_vertex_count;
bool m_reject_odd_polygons;
};
/**

View File

@ -837,6 +837,107 @@ smooth (const db::Polygon &polygon, db::Coord d)
return new_poly;
}
// -------------------------------------------------------------------------
// Strange polygons
namespace
{
/**
* @brief A helper class to implement the strange polygon detector
*/
struct StrangePolygonInsideFunc
{
inline bool operator() (int wc) const
{
return wc < 0 || wc > 1;
}
};
/**
* @brief A helper class to implement the non-orientable polygon detector
*/
struct NonOrientablePolygonFunc
{
inline bool operator() (int wc) const
{
// As polygon contours are normalized by default to positive wrap count a negative wrap count
// indicates non-orientability
return wc < 0;
}
};
/**
* @brief An exception type indicating a strange polygon
*/
struct OddPolygonException
{
OddPolygonException () { }
};
/**
* @brief An edge processor catching the error
*/
class ErrorCatchingEdgeSink
: public db::EdgeSink
{
// TODO: we should not use exceptions to indicate a condition, but right now, there is no good alternative
// and this is considered an error anyway.
virtual void put (const db::Edge &) { throw OddPolygonException (); }
virtual void crossing_edge (const db::Edge &) { throw OddPolygonException (); }
};
}
template <class F>
bool
check_wrapcount (const db::Polygon &poly, std::vector<db::Polygon> *error_parts)
{
size_t vn = poly.vertices ();
if (vn < 4 || (vn == 4 && poly.is_box ())) {
return false;
}
EdgeProcessor ep;
ep.insert (poly);
F inside;
db::GenericMerge<F> op (inside);
if (error_parts) {
db::PolygonContainer pc (*error_parts, false);
db::PolygonGenerator pg (pc, false, false);
ep.process (pg, op);
return ! error_parts->empty ();
} else {
try {
ErrorCatchingEdgeSink es;
ep.process (es, op);
} catch (OddPolygonException &) {
return true;
}
return false;
}
}
bool
is_strange_polygon (const db::Polygon &poly, std::vector<db::Polygon> *strange_parts)
{
return check_wrapcount<StrangePolygonInsideFunc> (poly, strange_parts);
}
bool
is_non_orientable_polygon (const db::Polygon &poly, std::vector<db::Polygon> *strange_parts)
{
return check_wrapcount<NonOrientablePolygonFunc> (poly, strange_parts);
}
// -------------------------------------------------------------------------
// Rounding tools

View File

@ -465,6 +465,23 @@ void DB_PUBLIC smooth_contour (db::Polygon::polygon_contour_iterator from, db::P
*/
db::Polygon DB_PUBLIC smooth (const db::Polygon &poly, db::Coord d);
/**
* @brief Returns a value indicating whether the polygon is an "strange polygon"
* "strange polygons" are ones which are non-orientable or have self-overlaps, e.g. their wrap
* count after orientation normalization is not 0 or 1.
* If "error_parts" is given it will receive markers indicating the parts which violate
* this wrap count condition.
*/
bool DB_PUBLIC is_strange_polygon (const db::Polygon &poly, std::vector<db::Polygon> *error_parts = 0);
/**
* @brief Returns a value indicating whether the polygon is "non-orientable"
* Such polygons contain loops which cannot be oriented, e.g. "8"-type loops.
* If "error_parts" is given it will receive markers indicating the parts which are
* non-orientable.
*/
bool DB_PUBLIC is_non_orientable_polygon (const db::Polygon &poly, std::vector<db::Polygon> *error_parts = 0);
/**
* @brief A area collector
*

View File

@ -111,6 +111,19 @@ Class<db::DeepShapeStore> decl_dbDeepShapeStore ("db", "DeepShapeStore",
gsi::method ("threads", &db::DeepShapeStore::threads,
"@brief Gets the number of threads.\n"
) +
gsi::method ("reject_odd_polygons=", &db::DeepShapeStore::set_reject_odd_polygons, gsi::arg ("count"),
"@brief Sets a flag indicating whether to reject odd polygons\n"
"\n"
"Some kind of 'odd' (e.g. non-orientable) polygons may spoil the functionality "
"because they cannot be handled properly. By using this flag, the shape store "
"we reject these kind of polygons. The default is 'accept' (without warning).\n"
"\n"
"This attribute has been introduced in version 0.27."
) +
gsi::method ("reject_odd_polygons", &db::DeepShapeStore::reject_odd_polygons,
"@brief Gets a flag indicating whether to reject odd polygons.\n"
"This attribute has been introduced in version 0.27."
) +
gsi::method ("max_vertex_count=", &db::DeepShapeStore::set_max_vertex_count, gsi::arg ("count"),
"@brief Sets the maximum vertex count default value\n"
"\n"

View File

@ -2372,3 +2372,94 @@ TEST(404)
EXPECT_EQ (sp[1].to_string (), "(0,832;176,874;390,925)");
}
}
static db::Polygon str2poly (const std::string &s)
{
db::Polygon poly;
tl::Extractor ex (s.c_str ());
ex.read (poly);
return poly;
}
// self-overlapping, non-orientable check
TEST(405)
{
std::string ps;
std::vector<db::Polygon> parts;
// null polygon
ps = "()";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), false);
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), false);
// triangle
ps = "(0,0;1000,0;1000,1000)";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), false);
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), false);
// rectangle counter-clockwise
ps = "(0,0;1000,0;1000,1000;0,1000)";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), false);
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), false);
// rectangle clockwise
ps = "(0,0;0,1000;1000,1000;1000,0)";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), false);
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), false);
// "8" shape
ps = "(0,0;1000,1000;0,1000;1000,0)";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), true);
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), true);
parts.clear ();
EXPECT_EQ (db::is_strange_polygon (str2poly (ps), &parts), true);
EXPECT_EQ (parts.size (), size_t (1));
if (! parts.empty ()) {
EXPECT_EQ (parts[0].to_string (), "(0,0;500,500;1000,0)");
}
parts.clear ();
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps), &parts), true);
EXPECT_EQ (parts.size (), size_t (1));
if (! parts.empty ()) {
EXPECT_EQ (parts[0].to_string (), "(0,0;500,500;1000,0)");
}
// self-touching
ps = "(0,0;0,2000;1000,2000;1000,1000;3000,1000;3000,3000;1000,3000;1000,2000;0,2000;0,4000;4000,4000;4000,0)";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), false);
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), false);
// self-overlap
ps = "(0,0;0,2500;1000,2500;1000,1000;3000,1000;3000,3000;1000,3000;1000,2000;0,2000;0,4000;4000,4000;4000,0)";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), true);
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), false);
parts.clear ();
EXPECT_EQ (db::is_strange_polygon (str2poly (ps), &parts), true);
EXPECT_EQ (parts.size (), size_t (1));
if (! parts.empty ()) {
EXPECT_EQ (parts[0].to_string (), "(0,2000;0,2500;1000,2500;1000,2000)");
}
// inner loop twisted
ps = "(0,0;0,2000;1000,2000;1000,3000;3000,3000;3000,1000;1000,1000;1000,2000;0,2000;0,4000;4000,4000;4000,0)";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), true);
// This is a double loop, so it's orientable
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), false);
// non-orientable hole
ps = "(0,0;0,4000;4000,4000;4000,0/1000,1000;3000,3000;1000,3000;3000,1000)";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), true);
// NOTE: a non-orientable holes does not generate -1 wrapcount, but just 0. So the polygon is "orientable"
// as a whole. Which isn't good for detecting invalid input polygons, but as those are hull-only for GDS and
// OASIS and most other formats (except DXF), we don't care too much here:
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), false);
// hole outside hull
ps = "(0,0;0,4000;4000,4000;4000,0/1000,1000;5000,1000;5000,3000;1000,3000)";
EXPECT_EQ (db::is_strange_polygon (str2poly (ps)), true);
EXPECT_EQ (db::is_non_orientable_polygon (str2poly (ps)), true);
}

View File

@ -53,10 +53,13 @@ module DRC
dss = RBA::DeepShapeStore::new
@max_area_ratio = dss.max_area_ratio
@max_vertex_count = dss.max_vertex_count
@deep_reject_odd_polygons = dss.reject_odd_polygons
dss._destroy
@verbose = false
@in_context = false
end
def joined
@ -680,6 +683,28 @@ module DRC
self.threads(n)
end
# %DRC%
# @name deep_reject_odd_polygons
# @brief Gets or sets a value indicating whether the reject odd polygons in deep mode
# @synopsis deep_reject_odd_polygons(flag)
# @synopsis deep_reject_odd_polygons
#
# In deep mode, non-orientable (e.g. "8"-shaped) polygons may not be resolved properly.
# By default the interpretation of such polygons is undefined - they may even vanish entirely.
# By setting this flag to true, the deep mode layout processor will reject such polygons with
# an error.
def deep_reject_odd_polygons(*args)
if args.size > 0
@deep_reject_odd_polygons = args[0] ? true : false
end
@deep_reject_odd_polygons
end
def deep_reject_odd_polygons=(flag)
self.deep_reject_odd_polygons(flag)
end
# %DRC%
# @name max_vertex_count
# @brief Gets or sets the maximum vertex count for deep mode fragmentation
@ -1770,18 +1795,29 @@ CODE
end
def _wrapper_context(func, *args, &proc)
in_context_outer = @in_context
begin
@in_context = true
return yield(*args)
rescue => ex
raise("'" + func + "': " + ex.to_s)
ensure
@in_context = in_context_outer
end
end
def _context(func, *args, &proc)
begin
if @in_context
return yield(*args)
rescue => ex
raise("'" + func + "': " + ex.to_s)
else
begin
@in_context = true
return yield(*args)
rescue => ex
raise("'" + func + "': " + ex.to_s)
ensure
@in_context = false
end
end
end
@ -1879,7 +1915,7 @@ CODE
end
# enable progress
# disable progress again
if obj.is_a?(RBA::Region)
obj.disable_progress
end
@ -1934,7 +1970,7 @@ CODE
end
# enable progress
# disable progress again
if obj.is_a?(RBA::Region)
obj.disable_progress
end
@ -1977,7 +2013,7 @@ CODE
end
# enable progress
# disable progress again
if obj.is_a?(RBA::Region)
obj.disable_progress
end
@ -2322,6 +2358,7 @@ CODE
# object which keeps the DSS.
@dss.text_property_name = "LABEL"
@dss.text_enlargement = 1
@dss.reject_odd_polygons = @deep_reject_odd_polygons
@dss.max_vertex_count = @max_vertex_count
@dss.max_area_ratio = @max_area_ratio

View File

@ -1233,11 +1233,19 @@ CODE
# @synopsis layer.odd_polygons
# Returns the parts of the polygons which are not orientable (i.e. "8" configuration) or self-overlapping.
# Merged semantics does not apply for this method. Always the raw polygons are taken (see \raw).
#
# The odd_polygons check is not available in deep mode currently. See \deep_reject_odd_polygons for
# an alternative.
def odd_polygons
@engine._context("odd_polygons") do
requires_region
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :strange_polygon_check))
if is_deep?
@engine.error("'odd_polygons' is not performing any check in deep mode - use 'deep_reject_odd_polygons' instead")
return @engine.polygons
else
requires_region
return DRCLayer::new(@engine, @engine._vcmd(self.data, :strange_polygon_check))
end
end
end

View File

@ -168,3 +168,13 @@ TEST(10d)
{
run_test (_this, "10", true);
}
TEST(11)
{
run_test (_this, "11", false);
}
TEST(11d)
{
run_test (_this, "11", true);
}

40
testdata/drc/drcGenericTests_11.drc vendored Normal file
View File

@ -0,0 +1,40 @@
source $drc_test_source
target $drc_test_target
if $drc_test_deep
deep
threads(0) # easier to debug
end
if $drc_test_deep
begin
deep_reject_odd_polygons(true)
l1 = input(1, 0)
raise("Test failed: input should throw and error because of odd polygons")
rescue => ex
if ex.to_s != "'input': Non-orientable polygon encountered: (1500,5000;6000,5000;1500,9500;6000,9500) in cell TOP in Region::initialize"
raise("Test failed: unexpected error message")
end
end
# This one should work:
deep_reject_odd_polygons(false)
l1 = input(1, 0)
else
l1 = input(1, 0)
end
# special functions
l1.output(1, 0)
# classical "odd_polygons" check for reference
l1.odd_polygons.output(10, 0)
l1.drc(smoothed(0.5)).output(100, 0)
l1.drc(odd_polygons).output(101, 0)
l1.drc(rounded_corners(1.0, 0.5, 8)).output(102, 0)

BIN
testdata/drc/drcGenericTests_11.gds vendored Normal file

Binary file not shown.

BIN
testdata/drc/drcGenericTests_au11.gds vendored Normal file

Binary file not shown.

BIN
testdata/drc/drcGenericTests_au11d.gds vendored Normal file

Binary file not shown.