Renaming of 'processor' to 'operator' to avoid name clash with EdgeProcessor, added edge operators.

This commit is contained in:
Matthias Koefferlein 2024-01-26 15:19:44 +01:00
parent bbb535a0bf
commit b7277631c3
4 changed files with 284 additions and 32 deletions

View File

@ -123,6 +123,102 @@ Class<gsi::EdgeFilterImpl> decl_EdgeFilterImpl ("db", "EdgeFilter",
"This class has been introduced in version 0.29.\n"
);
// ---------------------------------------------------------------------------------
// EdgeProcessor binding
Class<shape_processor_impl<db::EdgeProcessorBase> > decl_EdgeProcessorBase ("db", "EdgeOperator",
shape_processor_impl<db::EdgeProcessorBase>::method_decls (true),
"@brief A generic edge-to-polygon operator\n"
"\n"
"Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own "
"operator class and pass an instance to \\Edges#processed method.\n"
"\n"
"Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output edges derived from the input edge.\n"
"The output edge collection is the sum over all these individual results.\n"
"\n"
"The magic happens when deep mode edge collections 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 operator behaves. You "
"need to configure the operator 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 edge to half of the size, but does not change the position.\n"
"In this example the 'position' is defined by the center of the edge:"
"\n"
"@code\n"
"class ShrinkToHalf < RBA::EdgeOperator\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(edge)\n"
" shift = edge.bbox.center - RBA::Point::new # shift vector\n"
" return [ (edge.moved(-shift) * 0.5).moved(shift) ]\n"
" end\n"
"\n"
"end\n"
"\n"
"edges = ... # some Edges collection\n"
"shrinked_to_half = edges.processed(ShrinkToHalf::new)\n"
"@/code\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
Class<shape_processor_impl<db::EdgeToPolygonProcessorBase> > decl_EdgeToPolygonProcessor ("db", "EdgeToPolygonOperator",
shape_processor_impl<db::EdgeToPolygonProcessorBase>::method_decls (true),
"@brief A generic edge-to-polygon operator\n"
"\n"
"Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own "
"operator class and pass an instance to \\Edges#processed method.\n"
"\n"
"Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output polygons derived from the input edge.\n"
"The output region is the sum over all these individual results.\n"
"\n"
"The magic happens when deep mode edge collections 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 operator behaves. You "
"need to configure the operator 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 \\EdgeOperator 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::EdgeToEdgePairProcessorBase> > decl_EdgeToEdgePairProcessor ("db", "EdgeToEdgePairOperator",
shape_processor_impl<db::EdgeToEdgePairProcessorBase>::method_decls (true),
"@brief A generic edge-to-edge-pair operator\n"
"\n"
"Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own "
"operator class and pass an instance to \\Edges#processed method.\n"
"\n"
"Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output edge pairs derived from the input edge.\n"
"The output edge pair collection is the sum over all these individual results.\n"
"\n"
"The magic happens when deep mode edge collections 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 operator behaves. You "
"need to configure the operator 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 \\EdgeOperator class, with the exception that this incarnation has to deliver edge pairs.\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
// ---------------------------------------------------------------------------------
// Edges binding
@ -303,6 +399,28 @@ static void filter (db::Edges *r, const EdgeFilterImpl *f)
r->filter (*f);
}
static db::Edges processed_ee (const db::Edges *r, const shape_processor_impl<db::EdgeProcessorBase> *f)
{
return r->processed (*f);
}
static void process_ee (db::Edges *r, const shape_processor_impl<db::EdgeProcessorBase> *f)
{
r->process (*f);
}
static db::EdgePairs processed_eep (const db::Edges *r, const shape_processor_impl<db::EdgeToEdgePairProcessorBase> *f)
{
return r->processed (*f);
}
static db::Region processed_ep (const db::Edges *r, const shape_processor_impl<db::EdgeToPolygonProcessorBase> *f)
{
db::Region out;
r->processed (out, *f);
return out;
}
static db::Edges with_length1 (const db::Edges *r, db::Edges::distance_type length, bool inverse)
{
db::EdgeLengthFilter f (length, length + 1, inverse);
@ -732,6 +850,30 @@ Class<db::Edges> decl_Edges (decl_dbShapeCollection, "db", "Edges",
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("process", &process_ee, gsi::arg ("process"),
"@brief Applies a generic edge processor in place (replacing the edges from the Edges collection)\n"
"See \\EdgeProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("processed", &processed_ee, gsi::arg ("processed"),
"@brief Applies a generic edge processor and returns a processed copy\n"
"See \\EdgeProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("processed", &processed_eep, gsi::arg ("processed"),
"@brief Applies a generic edge-to-edge-pair processor and returns an edge pair collection with the results\n"
"See \\EdgeToEdgePairProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("processed", &processed_ep, gsi::arg ("processed"),
"@brief Applies a generic edge-to-polygon processor and returns an edge collection with the results\n"
"See \\EdgeToPolygonProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("with_length", with_length1, gsi::arg ("length"), gsi::arg ("inverse"),
"@brief Filters the edges by length\n"
"Filters the edges in the edge collection by length. If \"inverse\" is false, only "

View File

@ -130,20 +130,20 @@ Class<gsi::PolygonFilterImpl> decl_PolygonFilterImpl ("db", "PolygonFilter",
// ---------------------------------------------------------------------------------
// PolygonProcessor binding
Class<shape_processor_impl<db::PolygonProcessorBase> > decl_PolygonProcessor ("db", "PolygonProcessor",
Class<shape_processor_impl<db::PolygonProcessorBase> > decl_PolygonOperator ("db", "PolygonOperator",
shape_processor_impl<db::PolygonProcessorBase>::method_decls (true),
"@brief A generic polygon processor adaptor\n"
"@brief A generic polygon operator\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"
"Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own "
"operator 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"
"Conceptually, these methods take each polygon from the region and present it to the operators' '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 "
"and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the operator 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 "
@ -153,7 +153,7 @@ Class<shape_processor_impl<db::PolygonProcessorBase> > decl_PolygonProcessor ("d
"In this example the 'position' is defined by the center of the bounding box:"
"\n"
"@code\n"
"class ShrinkToHalfProcessor < RBA::PolygonProcessor\n"
"class ShrinkToHalf < RBA::PolygonOperator\n"
"\n"
" # Constructor\n"
" def initialize\n"
@ -175,50 +175,50 @@ Class<shape_processor_impl<db::PolygonProcessorBase> > decl_PolygonProcessor ("d
"This class has been introduced in version 0.29.\n"
);
Class<shape_processor_impl<db::PolygonToEdgeProcessorBase> > decl_PolygonToEdgeProcessor ("db", "PolygonToEdgeProcessor",
Class<shape_processor_impl<db::PolygonToEdgeProcessorBase> > decl_PolygonToEdgeProcessor ("db", "PolygonToEdgeOperator",
shape_processor_impl<db::PolygonToEdgeProcessorBase>::method_decls (true),
"@brief A generic polygon-to-edge processor adaptor\n"
"@brief A generic polygon-to-edge operator\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"
"Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own "
"operator 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"
"Conceptually, these methods take each polygon from the region and present it to the operator'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 "
"and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the operator 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"
"For a basic example see the \\PolygonOperator 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",
Class<shape_processor_impl<db::PolygonToEdgePairProcessorBase> > decl_PolygonToEdgePairProcessor ("db", "PolygonToEdgePairOperator",
shape_processor_impl<db::PolygonToEdgePairProcessorBase>::method_decls (true),
"@brief A generic polygon-to-edge-pair processor adaptor\n"
"@brief A generic polygon-to-edge-pair operator\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"
"Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own "
"operator 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"
"Conceptually, these methods take each polygon from the region and present it to the operator'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 "
"and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the operator 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"
"For a basic example see the \\PolygonOperator class, with the exception that this incarnation has to deliver edge pairs.\n"
"\n"
"This class has been introduced in version 0.29.\n"
);

View File

@ -45,6 +45,49 @@ class ParallelFilter < RBA::EdgeFilter
end
class ShrinkToHalfEdgeOperator < RBA::EdgeOperator
# Constructor
def initialize
self.is_isotropic_and_scale_invariant # scale or orientation do not matter
end
# Shrink to half size
def process(edge)
shift = edge.bbox.center - RBA::Point::new # shift vector
return [ (edge.moved(-shift) * 0.5).moved(shift) ]
end
end
class SomeEdgeToEdgePairOperator < RBA::EdgeToEdgePairOperator
# Constructor
def initialize
self.is_isotropic_and_scale_invariant # scale or orientation do not matter
end
def process(edge)
box = edge.bbox
return [ RBA::EdgePair::new([ box.left, box.bottom, box.left, box.top ], [ box.right, box.bottom, box.right, box.top ]) ]
end
end
class SomeEdgeToPolygonOperator < RBA::EdgeToPolygonOperator
# Constructor
def initialize
self.is_isotropic_and_scale_invariant # scale or orientation do not matter
end
def process(edge)
box = edge.bbox
return [ RBA::Polygon::new(box) ]
end
end
class DBEdges_TestClass < TestBase
# Basics
@ -796,6 +839,73 @@ class DBEdges_TestClass < TestBase
end
# Generic processors
def test_generic_processors_ee
# Some basic tests for the processor class
f = ShrinkToHalfEdgeOperator::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
edges = RBA::Edges::new
edges.insert(RBA::Edge::new(0, 0, 100, 100))
edges.insert(RBA::Edge::new(200, 300, 200, 500))
assert_equal(edges.processed(ShrinkToHalfEdgeOperator::new).to_s, "(25,25;75,75);(200,350;200,450)")
assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)")
edges.process(ShrinkToHalfEdgeOperator::new)
assert_equal(edges.to_s, "(25,25;75,75);(200,350;200,450)")
end
# Generic processors
def test_generic_processors_eep
p = SomeEdgeToEdgePairOperator::new
edges = RBA::Edges::new
edges.insert(RBA::Edge::new(0, 0, 100, 100))
edges.insert(RBA::Edge::new(200, 300, 200, 500))
assert_equal(edges.processed(p).to_s, "(0,0;0,100)/(100,0;100,100);(200,300;200,500)/(200,300;200,500)")
assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)")
end
# Generic processors
def test_generic_processors_ep
p = SomeEdgeToPolygonOperator::new
edges = RBA::Edges::new
edges.insert(RBA::Edge::new(0, 0, 100, 100))
edges.insert(RBA::Edge::new(200, 300, 200, 500))
assert_equal(edges.processed(p).to_s, "(0,0;0,100;100,100;100,0);(200,300;200,300;200,500;200,500)")
assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)")
end
end
load("test_epilogue.rb")

View File

@ -44,7 +44,7 @@ class TriangleFilter < RBA::PolygonFilter
end
class ShrinkToHalfProcessor < RBA::PolygonProcessor
class ShrinkToHalfOperator < RBA::PolygonOperator
# Constructor
def initialize
@ -59,7 +59,7 @@ class ShrinkToHalfProcessor < RBA::PolygonProcessor
end
class SomePolygonToEdgePairProcessor < RBA::PolygonToEdgePairProcessor
class SomePolygonToEdgePairOperator < RBA::PolygonToEdgePairOperator
# Constructor
def initialize
@ -73,7 +73,7 @@ class SomePolygonToEdgePairProcessor < RBA::PolygonToEdgePairProcessor
end
class SomePolygonToEdgeProcessor < RBA::PolygonToEdgeProcessor
class SomePolygonToEdgeOperator < RBA::PolygonToEdgeOperator
# Constructor
def initialize
@ -1281,7 +1281,7 @@ class DBRegion_TestClass < TestBase
# Some basic tests for the processor class
f = ShrinkToHalfProcessor::new
f = ShrinkToHalfOperator::new
assert_equal(f.wants_variants?, true)
f.wants_variants = false
assert_equal(f.wants_variants?, false)
@ -1306,9 +1306,9 @@ class DBRegion_TestClass < TestBase
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.processed(ShrinkToHalfOperator::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)
region.process(ShrinkToHalfOperator::new)
assert_equal(region.to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)")
end
@ -1316,7 +1316,7 @@ class DBRegion_TestClass < TestBase
# Generic processors
def test_generic_processors_pep
p = SomePolygonToEdgePairProcessor::new
p = SomePolygonToEdgePairOperator::new
region = RBA::Region::new
@ -1331,7 +1331,7 @@ class DBRegion_TestClass < TestBase
# Generic processors
def test_generic_processors_pe
p = SomePolygonToEdgeProcessor::new
p = SomePolygonToEdgeOperator::new
region = RBA::Region::new