mirror of https://github.com/KLayout/klayout.git
Generic polygon to polygon processors
This commit is contained in:
parent
9fbc926d67
commit
1a126ede45
|
|
@ -48,6 +48,9 @@ class DB_PUBLIC_TEMPLATE shape_collection_processor
|
|||
: public tl::Object
|
||||
{
|
||||
public:
|
||||
typedef Shape shape_type;
|
||||
typedef Result result_type;
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -238,6 +238,220 @@ private:
|
|||
bool m_wants_variants;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// Generic shape processor declarations
|
||||
|
||||
template <class ProcessorBase>
|
||||
class shape_processor_impl
|
||||
: public ProcessorBase
|
||||
{
|
||||
public:
|
||||
typedef typename ProcessorBase::shape_type shape_type;
|
||||
typedef typename ProcessorBase::result_type result_type;
|
||||
|
||||
shape_processor_impl ()
|
||||
{
|
||||
mp_vars = &m_mag_and_orient;
|
||||
m_wants_variants = true;
|
||||
m_requires_raw_input = false;
|
||||
m_result_is_merged = false;
|
||||
m_result_must_not_be_merged = false;
|
||||
}
|
||||
|
||||
// overrides virtual method
|
||||
virtual const db::TransformationReducer *vars () const
|
||||
{
|
||||
return mp_vars;
|
||||
}
|
||||
|
||||
// maybe overrides virtual method
|
||||
virtual bool requires_raw_input () const
|
||||
{
|
||||
return m_requires_raw_input;
|
||||
}
|
||||
|
||||
void set_requires_raw_input (bool f)
|
||||
{
|
||||
m_requires_raw_input = f;
|
||||
}
|
||||
|
||||
// overrides virtual method
|
||||
virtual bool wants_variants () const
|
||||
{
|
||||
return m_wants_variants;
|
||||
}
|
||||
|
||||
void set_wants_variants (bool f)
|
||||
{
|
||||
m_wants_variants = f;
|
||||
}
|
||||
|
||||
// overrides virtual method
|
||||
virtual bool result_is_merged () const
|
||||
{
|
||||
return m_result_is_merged;
|
||||
}
|
||||
|
||||
void set_result_is_merged (bool f)
|
||||
{
|
||||
m_result_is_merged = f;
|
||||
}
|
||||
|
||||
// overrides virtual method
|
||||
virtual bool result_must_not_be_merged () const
|
||||
{
|
||||
return m_result_must_not_be_merged;
|
||||
}
|
||||
|
||||
void set_result_must_not_be_merged (bool f)
|
||||
{
|
||||
m_result_must_not_be_merged = f;
|
||||
}
|
||||
|
||||
void is_isotropic ()
|
||||
{
|
||||
mp_vars = &m_mag;
|
||||
}
|
||||
|
||||
void is_scale_invariant ()
|
||||
{
|
||||
mp_vars = &m_orientation;
|
||||
}
|
||||
|
||||
void is_isotropic_and_scale_invariant ()
|
||||
{
|
||||
mp_vars = 0;
|
||||
}
|
||||
|
||||
virtual void process (const shape_type &shape, std::vector<result_type> &res) const
|
||||
{
|
||||
res = do_process (shape);
|
||||
}
|
||||
|
||||
std::vector<result_type> issue_do_process (const shape_type &) const
|
||||
{
|
||||
return std::vector<result_type> ();
|
||||
}
|
||||
|
||||
std::vector<result_type> do_process (const shape_type &shape) const
|
||||
{
|
||||
if (f_process.can_issue ()) {
|
||||
return f_process.issue<shape_processor_impl, std::vector<result_type>, const shape_type &> (&shape_processor_impl::issue_do_process, shape);
|
||||
} else {
|
||||
return issue_do_process (shape);
|
||||
}
|
||||
}
|
||||
|
||||
gsi::Callback f_process;
|
||||
|
||||
static gsi::Methods method_decls (bool with_merged_options)
|
||||
{
|
||||
gsi::Methods decls =
|
||||
callback ("process", &shape_processor_impl::issue_do_process, &shape_processor_impl::f_process, gsi::arg ("shape"),
|
||||
"@brief Processes a shape\n"
|
||||
"This method is the actual payload. It needs to be reimplemented in a derived class.\n"
|
||||
"If needs to process the input shape and deliver a list of output shapes.\n"
|
||||
"The output list may be empty to entirely discard the input shape. It may also contain more than a single shape.\n"
|
||||
"In that case, the number of total shapes may grow during application of the processor.\n"
|
||||
);
|
||||
|
||||
if (with_merged_options) {
|
||||
decls +=
|
||||
method ("requires_raw_input?", &shape_processor_impl::requires_raw_input,
|
||||
"@brief Gets a value indicating whether the processor needs raw (unmerged) input\n"
|
||||
"See \\requires_raw_input= for details.\n"
|
||||
) +
|
||||
method ("requires_raw_input=", &shape_processor_impl::set_requires_raw_input, gsi::arg ("flag"),
|
||||
"@brief Sets a value indicating whether the processor needs raw (unmerged) input\n"
|
||||
"This flag must be set before using this processor. It tells the processor implementation whether the "
|
||||
"processor wants to have raw input (unmerged). The default value is 'false', meaning that\n"
|
||||
"the processor will receive merged polygons ('merged semantics').\n"
|
||||
"\n"
|
||||
"Setting this value to false potentially saves some CPU time needed for merging the polygons.\n"
|
||||
"Also, raw input means that strange shapes such as dot-like edges, self-overlapping polygons, "
|
||||
"empty or degenerated polygons are preserved."
|
||||
) +
|
||||
method ("result_is_merged?", &shape_processor_impl::result_is_merged,
|
||||
"@brief Gets a value indicating whether the processor delivers merged output\n"
|
||||
"See \\result_is_merged= for details.\n"
|
||||
) +
|
||||
method ("result_is_merged=", &shape_processor_impl::set_result_is_merged, gsi::arg ("flag"),
|
||||
"@brief Sets a value indicating whether the processor delivers merged output\n"
|
||||
"This flag must be set before using this processor. If the processor maintains the merged condition\n"
|
||||
"by design (output is merged if input is), it is a good idea to set this predicate to 'true'.\n"
|
||||
"This will avoid additional merge steps when the resulting collection is used in further operations\n"
|
||||
"that need merged input\n."
|
||||
) +
|
||||
method ("result_must_not_be_merged?", &shape_processor_impl::result_must_not_be_merged,
|
||||
"@brief Gets a value indicating whether the processor's output must not be merged\n"
|
||||
"See \\result_must_not_be_merged= for details.\n"
|
||||
) +
|
||||
method ("result_must_not_be_merged=", &shape_processor_impl::set_result_must_not_be_merged, gsi::arg ("flag"),
|
||||
"@brief Sets a value indicating whether the processor's output must not be merged\n"
|
||||
"This flag must be set before using this processor. The processor can set this flag if it wants to\n"
|
||||
"deliver shapes that must not be merged - e.g. point-like edges or strange or degenerated polygons.\n."
|
||||
);
|
||||
}
|
||||
|
||||
decls +=
|
||||
method ("wants_variants?", &shape_processor_impl::wants_variants,
|
||||
"@brief Gets a value indicating whether the filter prefers cell variants\n"
|
||||
"See \\wants_variants= for details.\n"
|
||||
) +
|
||||
method ("wants_variants=", &shape_processor_impl::set_wants_variants, gsi::arg ("flag"),
|
||||
"@brief Sets a value indicating whether the filter prefers cell variants\n"
|
||||
"This flag must be set before using this filter for hierarchical applications (deep mode). "
|
||||
"It tells the filter implementation whether cell variants should be created (true, the default) "
|
||||
"or shape propagation will be applied (false).\n"
|
||||
"\n"
|
||||
"This decision needs to be made, if the filter indicates that it will deliver different results\n"
|
||||
"for scaled or rotated versions of the shape (see \\is_isotropic and the other hints). If a cell\n"
|
||||
"is present with different qualities - as seen from the top cell - the respective instances\n"
|
||||
"need to be differentiated. Cell variant formation is one way, shape propagation the other way.\n"
|
||||
"Typically, cell variant formation is less expensive, but the hierarchy will be modified."
|
||||
) +
|
||||
method ("is_isotropic", &shape_processor_impl::is_isotropic,
|
||||
"@brief Indicates that the filter has isotropic properties\n"
|
||||
"Call this method before using the filter to indicate that the selection is independent of "
|
||||
"the orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
|
||||
"hierarchical mode.\n"
|
||||
"\n"
|
||||
"Examples for isotropic (polygon) processors are size or shrink operators. Size or shrink is not dependent "
|
||||
"on orientation unless size or shrink needs to be different in x and y direction."
|
||||
) +
|
||||
method ("is_scale_invariant", &shape_processor_impl::is_scale_invariant,
|
||||
"@brief Indicates that the filter is scale invariant\n"
|
||||
"Call this method before using the filter to indicate that the selection is independent of "
|
||||
"the scale of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
|
||||
"hierarchical mode.\n"
|
||||
"\n"
|
||||
"An example for a scale invariant (polygon) processor is the rotation operator. Rotation is not depending on scale, "
|
||||
"but on the original orientation as mirrored versions need to be rotated differently."
|
||||
) +
|
||||
method ("is_isotropic_and_scale_invariant", &shape_processor_impl::is_isotropic_and_scale_invariant,
|
||||
"@brief Indicates that the filter is isotropic and scale invariant\n"
|
||||
"Call this method before using the filter to indicate that the selection is independent of "
|
||||
"the scale and orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
|
||||
"hierarchical mode.\n"
|
||||
"\n"
|
||||
"An example for such a (polygon) processor is the convex decomposition operator. The decomposition of a polygon into "
|
||||
"convex parts is an operation that is not depending on scale nor orientation."
|
||||
);
|
||||
|
||||
return decls;
|
||||
}
|
||||
|
||||
private:
|
||||
const db::TransformationReducer *mp_vars;
|
||||
db::OrientationReducer m_orientation;
|
||||
db::MagnificationReducer m_mag;
|
||||
db::MagnificationAndOrientationReducer m_mag_and_orient;
|
||||
bool m_requires_raw_input;
|
||||
bool m_wants_variants;
|
||||
bool m_result_is_merged;
|
||||
bool m_result_must_not_be_merged;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -127,6 +127,102 @@ Class<gsi::PolygonFilterImpl> decl_PolygonFilterImpl ("db", "PolygonFilter",
|
|||
"This class has been introduced in version 0.29.\n"
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// PolygonProcessor binding
|
||||
|
||||
Class<shape_processor_impl<db::PolygonProcessorBase> > decl_PolygonProcessor ("db", "PolygonProcessor",
|
||||
shape_processor_impl<db::PolygonProcessorBase>::method_decls (true),
|
||||
"@brief A generic polygon processor adaptor\n"
|
||||
"\n"
|
||||
"Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own "
|
||||
"processors class and pass an instance to \\Region#process or \\Region#processed method.\n"
|
||||
"\n"
|
||||
"Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n"
|
||||
"The result of this call is a list of zero to many output polygons derived from the input polygon.\n"
|
||||
"The output region is the sum over all these individual results.\n"
|
||||
"\n"
|
||||
"The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible "
|
||||
"and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You "
|
||||
"need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
|
||||
"before using it.\n"
|
||||
"\n"
|
||||
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
|
||||
"formation which is not always desired and blows up the hierarchy.\n"
|
||||
"\n"
|
||||
"Here is some example that shrinks every polygon to half of the size but does not change the position.\n"
|
||||
"In this example the 'position' is defined by the center of the bounding box:"
|
||||
"\n"
|
||||
"@code\n"
|
||||
"class ShrinkToHalfProcessor < RBA::PolygonProcessor\n"
|
||||
"\n"
|
||||
" # Constructor\n"
|
||||
" def initialize\n"
|
||||
" self.is_isotropic_and_scale_invariant # scale or orientation do not matter\n"
|
||||
" end\n"
|
||||
" \n"
|
||||
" # Shrink to half size\n"
|
||||
" def process(polygon)\n"
|
||||
" shift = polygon.bbox.center - RBA::Point::new # shift vector\n"
|
||||
" return [ (polygon.moved(-shift) * 0.5).moved(shift) ]\n"
|
||||
" end\n"
|
||||
"\n"
|
||||
"end\n"
|
||||
"\n"
|
||||
"region = ... # some Region\n"
|
||||
"shrinked_to_half = region.processed(ShrinkToHalf::new)\n"
|
||||
"@/code\n"
|
||||
"\n"
|
||||
"This class has been introduced in version 0.29.\n"
|
||||
);
|
||||
|
||||
Class<shape_processor_impl<db::PolygonToEdgeProcessorBase> > decl_PolygonToEdgeProcessor ("db", "PolygonToEdgeProcessor",
|
||||
shape_processor_impl<db::PolygonToEdgeProcessorBase>::method_decls (true),
|
||||
"@brief A generic polygon-to-edge processor adaptor\n"
|
||||
"\n"
|
||||
"Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own "
|
||||
"processors class and pass an instance to \\Region#processed method.\n"
|
||||
"\n"
|
||||
"Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n"
|
||||
"The result of this call is a list of zero to many output edges derived from the input polygon.\n"
|
||||
"The output edge collection is the sum over all these individual results.\n"
|
||||
"\n"
|
||||
"The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible "
|
||||
"and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You "
|
||||
"need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
|
||||
"before using it.\n"
|
||||
"\n"
|
||||
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
|
||||
"formation which is not always desired and blows up the hierarchy.\n"
|
||||
"\n"
|
||||
"For a basic example see the \\PolygonProcessor class, with the exception that this incarnation has to deliver edges.\n"
|
||||
"\n"
|
||||
"This class has been introduced in version 0.29.\n"
|
||||
);
|
||||
|
||||
Class<shape_processor_impl<db::PolygonToEdgePairProcessorBase> > decl_PolygonToEdgePairProcessor ("db", "PolygonToEdgePairProcessor",
|
||||
shape_processor_impl<db::PolygonToEdgePairProcessorBase>::method_decls (true),
|
||||
"@brief A generic polygon-to-edge-pair processor adaptor\n"
|
||||
"\n"
|
||||
"Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own "
|
||||
"processors class and pass an instance to \\Region#processed method.\n"
|
||||
"\n"
|
||||
"Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n"
|
||||
"The result of this call is a list of zero to many output edge pairs derived from the input polygon.\n"
|
||||
"The output edge pair collection is the sum over all these individual results.\n"
|
||||
"\n"
|
||||
"The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible "
|
||||
"and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You "
|
||||
"need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
|
||||
"before using it.\n"
|
||||
"\n"
|
||||
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
|
||||
"formation which is not always desired and blows up the hierarchy.\n"
|
||||
"\n"
|
||||
"For a basic example see the \\PolygonProcessor class, with the exception that this incarnation has to deliver edge pairs.\n"
|
||||
"\n"
|
||||
"This class has been introduced in version 0.29.\n"
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// Region binding
|
||||
|
||||
|
|
@ -411,6 +507,26 @@ static void filter (db::Region *r, const PolygonFilterImpl *f)
|
|||
r->filter (*f);
|
||||
}
|
||||
|
||||
static db::Region processed_pp (const db::Region *r, const shape_processor_impl<db::PolygonProcessorBase> *f)
|
||||
{
|
||||
return r->processed (*f);
|
||||
}
|
||||
|
||||
static void process_pp (db::Region *r, const shape_processor_impl<db::PolygonProcessorBase> *f)
|
||||
{
|
||||
r->process (*f);
|
||||
}
|
||||
|
||||
static db::EdgePairs processed_pep (const db::Region *r, const shape_processor_impl<db::PolygonToEdgePairProcessorBase> *f)
|
||||
{
|
||||
return r->processed (*f);
|
||||
}
|
||||
|
||||
static db::Edges processed_pe (const db::Region *r, const shape_processor_impl<db::PolygonToEdgeProcessorBase> *f)
|
||||
{
|
||||
return r->processed (*f);
|
||||
}
|
||||
|
||||
static db::Region with_perimeter1 (const db::Region *r, db::Region::perimeter_type perimeter, bool inverse)
|
||||
{
|
||||
db::RegionPerimeterFilter f (perimeter, perimeter + 1, inverse);
|
||||
|
|
@ -2426,6 +2542,30 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
"\n"
|
||||
"This method has been introduced in version 0.29.\n"
|
||||
) +
|
||||
method_ext ("process", &process_pp, gsi::arg ("process"),
|
||||
"@brief Applies a generic polygon processor in place (replacing the polygons from the Region)\n"
|
||||
"See \\PolygonProcessor for a description of this feature.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.29.\n"
|
||||
) +
|
||||
method_ext ("processed", &processed_pp, gsi::arg ("processed"),
|
||||
"@brief Applies a generic polygon processor and returns a processed copy\n"
|
||||
"See \\PolygonProcessor for a description of this feature.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.29.\n"
|
||||
) +
|
||||
method_ext ("processed", &processed_pep, gsi::arg ("processed"),
|
||||
"@brief Applies a generic polygon-to-edge-pair processor and returns an edge pair collection with the results\n"
|
||||
"See \\PolygonToEdgePairProcessor for a description of this feature.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.29.\n"
|
||||
) +
|
||||
method_ext ("processed", &processed_pe, gsi::arg ("processed"),
|
||||
"@brief Applies a generic polygon-to-edge processor and returns an edge collection with the results\n"
|
||||
"See \\PolygonToEdgeProcessor for a description of this feature.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.29.\n"
|
||||
) +
|
||||
method_ext ("rectangles", &rectangles,
|
||||
"@brief Returns all polygons which are rectangles\n"
|
||||
"This method returns all polygons in self which are rectangles."
|
||||
|
|
|
|||
|
|
@ -44,6 +44,21 @@ class TriangleFilter < RBA::PolygonFilter
|
|||
|
||||
end
|
||||
|
||||
class ShrinkToHalfProcessor < RBA::PolygonProcessor
|
||||
|
||||
# Constructor
|
||||
def initialize
|
||||
self.is_isotropic_and_scale_invariant # scale or orientation do not matter
|
||||
end
|
||||
|
||||
# Shrink to half size
|
||||
def process(polygon)
|
||||
shift = polygon.bbox.center - RBA::Point::new # shift vector
|
||||
return [ (polygon.moved(-shift) * 0.5).moved(shift) ]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class DBRegion_TestClass < TestBase
|
||||
|
||||
# Basics
|
||||
|
|
@ -1212,8 +1227,8 @@ class DBRegion_TestClass < TestBase
|
|||
f.wants_variants = false
|
||||
assert_equal(f.wants_variants?, false)
|
||||
assert_equal(f.requires_raw_input, false)
|
||||
f.requires_raw_input = false
|
||||
assert_equal(f.requires_raw_input, false)
|
||||
f.requires_raw_input = true
|
||||
assert_equal(f.requires_raw_input, true)
|
||||
|
||||
# Smoke test
|
||||
f.is_isotropic
|
||||
|
|
@ -1233,6 +1248,43 @@ class DBRegion_TestClass < TestBase
|
|||
|
||||
end
|
||||
|
||||
# Generic processors
|
||||
def test_generic_processors
|
||||
|
||||
# Some basic tests for the filter class
|
||||
|
||||
f = ShrinkToHalfProcessor::new
|
||||
assert_equal(f.wants_variants?, true)
|
||||
f.wants_variants = false
|
||||
assert_equal(f.wants_variants?, false)
|
||||
assert_equal(f.requires_raw_input, false)
|
||||
f.requires_raw_input = true
|
||||
assert_equal(f.requires_raw_input, true)
|
||||
assert_equal(f.result_is_merged, false)
|
||||
f.result_is_merged = true
|
||||
assert_equal(f.result_is_merged, true)
|
||||
assert_equal(f.result_must_not_be_merged, false)
|
||||
f.result_must_not_be_merged = true
|
||||
assert_equal(f.result_must_not_be_merged, true)
|
||||
|
||||
# Smoke test
|
||||
f.is_isotropic
|
||||
f.is_scale_invariant
|
||||
|
||||
# Some application
|
||||
|
||||
region = RBA::Region::new
|
||||
|
||||
region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]]))
|
||||
region.insert(RBA::Box::new(200, 0, 300, 100))
|
||||
|
||||
assert_equal(region.processed(ShrinkToHalfProcessor::new).to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)")
|
||||
assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)")
|
||||
region.process(ShrinkToHalfProcessor::new)
|
||||
assert_equal(region.to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)")
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
load("test_epilogue.rb")
|
||||
|
|
|
|||
Loading…
Reference in New Issue