mirror of https://github.com/KLayout/klayout.git
commit
ac7e17ffcb
|
|
@ -533,7 +533,7 @@ EdgesDelegate *
|
|||
AsIfFlatEdges::boolean (const Edges *other, EdgeBoolOp op) const
|
||||
{
|
||||
std::auto_ptr<FlatEdges> output (new FlatEdges (true));
|
||||
EdgeBooleanClusterCollector<db::Shapes> cluster_collector (&output->raw_edges (), op);
|
||||
EdgeBooleanClusterCollectorToShapes cluster_collector (&output->raw_edges (), op);
|
||||
|
||||
db::box_scanner<db::Edge, size_t> scanner (report_progress (), progress_desc ());
|
||||
scanner.reserve (size () + (other ? other->size () : 0));
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include "dbCommon.h"
|
||||
#include "dbBoxScanner.h"
|
||||
#include "dbEdgesDelegate.h"
|
||||
#include "dbEdgeBoolean.h"
|
||||
#include "dbBoxScanner.h"
|
||||
#include "dbPolygonTools.h"
|
||||
|
||||
|
|
@ -138,6 +139,11 @@ public:
|
|||
return boolean (&other, EdgeOr);
|
||||
}
|
||||
|
||||
virtual EdgesDelegate *intersections (const Edges &other) const
|
||||
{
|
||||
return boolean (&other, EdgeIntersections);
|
||||
}
|
||||
|
||||
virtual EdgesDelegate *add_in_place (const Edges &other)
|
||||
{
|
||||
return add (other);
|
||||
|
|
|
|||
|
|
@ -137,6 +137,21 @@ struct box_scanner_receiver
|
|||
* terminate the scan process early if the outcome is known.
|
||||
*/
|
||||
bool stop () const { return false; }
|
||||
|
||||
/**
|
||||
* @brief Pre-scanning operations
|
||||
*
|
||||
* This method is called before the scanning starts.
|
||||
*/
|
||||
void initialize () { }
|
||||
|
||||
/**
|
||||
* @brief Post-scanning operations
|
||||
*
|
||||
* This method is called after the scan has finished (without exception). The argument is the
|
||||
* return value (false if "stop" stopped the process).
|
||||
*/
|
||||
void finalize (bool) { }
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -265,6 +280,22 @@ public:
|
|||
*/
|
||||
template <class Rec, class BoxConvert>
|
||||
bool process (Rec &rec, typename BoxConvert::box_type::coord_type enl, const BoxConvert &bc = BoxConvert ())
|
||||
{
|
||||
rec.initialize ();
|
||||
bool ret = do_process (rec, enl, bc);
|
||||
rec.finalize (ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
container_type m_pp;
|
||||
double m_fill_factor;
|
||||
size_t m_scanner_thr;
|
||||
bool m_report_progress;
|
||||
std::string m_progress_desc;
|
||||
|
||||
template <class Rec, class BoxConvert>
|
||||
bool do_process (Rec &rec, typename BoxConvert::box_type::coord_type enl, const BoxConvert &bc = BoxConvert ())
|
||||
{
|
||||
typedef typename BoxConvert::box_type box_type;
|
||||
typedef typename box_type::coord_type coord_type;
|
||||
|
|
@ -420,13 +451,6 @@ public:
|
|||
return true;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
container_type m_pp;
|
||||
double m_fill_factor;
|
||||
size_t m_scanner_thr;
|
||||
bool m_report_progress;
|
||||
std::string m_progress_desc;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -469,6 +493,21 @@ struct box_scanner_receiver2
|
|||
* terminate the scan process early if the outcome is known.
|
||||
*/
|
||||
bool stop () const { return false; }
|
||||
|
||||
/**
|
||||
* @brief Pre-scanning operations
|
||||
*
|
||||
* This method is called before the scanning starts.
|
||||
*/
|
||||
void initialize () { }
|
||||
|
||||
/**
|
||||
* @brief Post-scanning operations
|
||||
*
|
||||
* This method is called after the scan has finished (without exception). The argument is the
|
||||
* return value (false if "stop" stopped the process).
|
||||
*/
|
||||
void finalize (bool) { }
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -613,6 +652,23 @@ public:
|
|||
*/
|
||||
template <class Rec, class BoxConvert1, class BoxConvert2>
|
||||
bool process (Rec &rec, typename BoxConvert1::box_type::coord_type enl, const BoxConvert1 &bc1 = BoxConvert1 (), const BoxConvert2 &bc2 = BoxConvert2 ())
|
||||
{
|
||||
rec.initialize ();
|
||||
bool ret = do_process (rec, enl, bc1, bc2);
|
||||
rec.finalize (ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
container_type1 m_pp1;
|
||||
container_type2 m_pp2;
|
||||
double m_fill_factor;
|
||||
size_t m_scanner_thr;
|
||||
bool m_report_progress;
|
||||
std::string m_progress_desc;
|
||||
|
||||
template <class Rec, class BoxConvert1, class BoxConvert2>
|
||||
bool do_process (Rec &rec, typename BoxConvert1::box_type::coord_type enl, const BoxConvert1 &bc1 = BoxConvert1 (), const BoxConvert2 &bc2 = BoxConvert2 ())
|
||||
{
|
||||
typedef typename BoxConvert1::box_type box_type; // must be same as BoxConvert2::box_type
|
||||
typedef typename box_type::coord_type coord_type;
|
||||
|
|
@ -845,14 +901,6 @@ public:
|
|||
return true;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
container_type1 m_pp1;
|
||||
container_type2 m_pp2;
|
||||
double m_fill_factor;
|
||||
size_t m_scanner_thr;
|
||||
bool m_report_progress;
|
||||
std::string m_progress_desc;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ public:
|
|||
// .. and run the merge operation
|
||||
|
||||
s->second.clear ();
|
||||
EdgeBooleanClusterCollector<db::Shapes> cluster_collector (&s->second, EdgeOr);
|
||||
EdgeBooleanClusterCollectorToShapes cluster_collector (&s->second, EdgeOr);
|
||||
m_scanner.process (cluster_collector, 1, db::box_convert<db::Edge> ());
|
||||
|
||||
return s->second;
|
||||
|
|
@ -751,11 +751,11 @@ EdgesDelegate *DeepEdges::merged () const
|
|||
}
|
||||
|
||||
DeepLayer
|
||||
DeepEdges::and_or_not_with (const DeepEdges *other, bool and_op) const
|
||||
DeepEdges::and_or_not_with (const DeepEdges *other, EdgeBoolOp op) const
|
||||
{
|
||||
DeepLayer dl_out (m_deep_layer.derived ());
|
||||
|
||||
db::EdgeBoolAndOrNotLocalOperation op (and_op);
|
||||
db::EdgeBoolAndOrNotLocalOperation local_op (op);
|
||||
|
||||
db::local_processor<db::Edge, db::Edge, db::Edge> proc (const_cast<db::Layout *> (&m_deep_layer.layout ()), const_cast<db::Cell *> (&m_deep_layer.initial_cell ()), &other->deep_layer ().layout (), &other->deep_layer ().initial_cell ());
|
||||
proc.set_base_verbosity (base_verbosity ());
|
||||
|
|
@ -763,7 +763,7 @@ DeepEdges::and_or_not_with (const DeepEdges *other, bool and_op) const
|
|||
proc.set_area_ratio (m_deep_layer.store ()->max_area_ratio ());
|
||||
proc.set_max_vertex_count (m_deep_layer.store ()->max_vertex_count ());
|
||||
|
||||
proc.run (&op, m_deep_layer.layer (), other->deep_layer ().layer (), dl_out.layer ());
|
||||
proc.run (&local_op, m_deep_layer.layer (), other->deep_layer ().layer (), dl_out.layer ());
|
||||
|
||||
return dl_out;
|
||||
}
|
||||
|
|
@ -786,6 +786,26 @@ DeepEdges::edge_region_op (const DeepRegion *other, bool outside, bool include_b
|
|||
return dl_out;
|
||||
}
|
||||
|
||||
EdgesDelegate *DeepEdges::intersections (const Edges &other) const
|
||||
{
|
||||
const DeepEdges *other_deep = dynamic_cast <const DeepEdges *> (other.delegate ());
|
||||
|
||||
if (empty () || other.empty ()) {
|
||||
|
||||
// Nothing to do
|
||||
return new EmptyEdges ();
|
||||
|
||||
} else if (! other_deep) {
|
||||
|
||||
return AsIfFlatEdges::intersections (other);
|
||||
|
||||
} else {
|
||||
|
||||
return new DeepEdges (and_or_not_with (other_deep, EdgeIntersections));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
EdgesDelegate *DeepEdges::and_with (const Edges &other) const
|
||||
{
|
||||
const DeepEdges *other_deep = dynamic_cast <const DeepEdges *> (other.delegate ());
|
||||
|
|
@ -801,7 +821,7 @@ EdgesDelegate *DeepEdges::and_with (const Edges &other) const
|
|||
|
||||
} else {
|
||||
|
||||
return new DeepEdges (and_or_not_with (other_deep, true));
|
||||
return new DeepEdges (and_or_not_with (other_deep, EdgeAnd));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -851,7 +871,7 @@ EdgesDelegate *DeepEdges::not_with (const Edges &other) const
|
|||
|
||||
} else {
|
||||
|
||||
return new DeepEdges (and_or_not_with (other_deep, false));
|
||||
return new DeepEdges (and_or_not_with (other_deep, EdgeNot));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -903,8 +923,8 @@ EdgesDelegate *DeepEdges::xor_with (const Edges &other) const
|
|||
|
||||
// Implement XOR as (A-B)+(B-A) - only this implementation
|
||||
// is compatible with the local processor scheme
|
||||
DeepLayer n1 (and_or_not_with (other_deep, false));
|
||||
DeepLayer n2 (other_deep->and_or_not_with (this, false));
|
||||
DeepLayer n1 (and_or_not_with (other_deep, EdgeNot));
|
||||
DeepLayer n2 (other_deep->and_or_not_with (this, EdgeNot));
|
||||
|
||||
n1.add_from (n2);
|
||||
return new DeepEdges (n1);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "dbAsIfFlatEdges.h"
|
||||
#include "dbDeepShapeStore.h"
|
||||
#include "dbEdgeBoolean.h"
|
||||
#include "dbEdgePairs.h"
|
||||
|
||||
namespace db {
|
||||
|
|
@ -132,6 +133,8 @@ public:
|
|||
virtual EdgesDelegate *add_in_place (const Edges &other);
|
||||
virtual EdgesDelegate *add (const Edges &other) const;
|
||||
|
||||
virtual EdgesDelegate *intersections (const Edges &other) const;
|
||||
|
||||
virtual EdgesDelegate *inside_part (const Region &other) const;
|
||||
virtual EdgesDelegate *outside_part (const Region &other) const;
|
||||
|
||||
|
|
@ -168,7 +171,7 @@ private:
|
|||
void init ();
|
||||
void ensure_merged_edges_valid () const;
|
||||
const DeepLayer &merged_deep_layer () const;
|
||||
DeepLayer and_or_not_with(const DeepEdges *other, bool and_op) const;
|
||||
DeepLayer and_or_not_with(const DeepEdges *other, EdgeBoolOp op) const;
|
||||
DeepLayer edge_region_op (const DeepRegion *other, bool outside, bool include_borders) const;
|
||||
EdgePairsDelegate *run_check (db::edge_relation_type rel, const Edges *other, db::Coord d, bool whole_edges, metrics_type metrics, double ignore_angle, distance_type min_projection, distance_type max_projection) const;
|
||||
virtual EdgesDelegate *pull_generic (const Edges &edges) const;
|
||||
|
|
|
|||
|
|
@ -24,14 +24,20 @@
|
|||
#define HDR_dbEdgeBoolean
|
||||
|
||||
#include "dbEdge.h"
|
||||
#include "dbEdgesDelegate.h"
|
||||
#include "dbHash.h"
|
||||
#include "dbBoxScanner.h"
|
||||
#include "dbShapes.h"
|
||||
|
||||
#include "tlIntervalMap.h"
|
||||
|
||||
namespace db
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A common definition for the boolean operations available on edges
|
||||
*/
|
||||
enum EdgeBoolOp { EdgeOr, EdgeNot, EdgeXor, EdgeAnd, EdgeIntersections };
|
||||
|
||||
struct OrJoinOp
|
||||
{
|
||||
void operator() (int &v, int n)
|
||||
|
|
@ -216,7 +222,15 @@ struct EdgeBooleanClusterCollector
|
|||
: public db::cluster_collector<db::Edge, size_t, EdgeBooleanCluster<OutputContainer> >
|
||||
{
|
||||
EdgeBooleanClusterCollector (OutputContainer *output, EdgeBoolOp op)
|
||||
: db::cluster_collector<db::Edge, size_t, EdgeBooleanCluster<OutputContainer> > (EdgeBooleanCluster<OutputContainer> (output, op), op != EdgeAnd /*report single*/)
|
||||
: db::cluster_collector<db::Edge, size_t, EdgeBooleanCluster<OutputContainer> > (EdgeBooleanCluster<OutputContainer> (output, op == EdgeIntersections ? EdgeAnd : op), op != EdgeAnd && op != EdgeIntersections /*report single*/),
|
||||
mp_output (output), mp_intersections (op == EdgeIntersections ? output : 0)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
EdgeBooleanClusterCollector (OutputContainer *output, OutputContainer *intersections, EdgeBoolOp op)
|
||||
: db::cluster_collector<db::Edge, size_t, EdgeBooleanCluster<OutputContainer> > (EdgeBooleanCluster<OutputContainer> (output, op), op != EdgeAnd /*report single*/),
|
||||
mp_output (output), mp_intersections (intersections)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
|
@ -227,12 +241,180 @@ struct EdgeBooleanClusterCollector
|
|||
// 1.) not degenerate
|
||||
// 2.) parallel with some tolerance of roughly 1 dbu
|
||||
// 3.) connected
|
||||
// In intersection-detection mode, identify intersection points otherwise
|
||||
// and insert into the intersections container as degenerated edges.
|
||||
|
||||
if (! o1->is_degenerate () && ! o2->is_degenerate ()
|
||||
&& fabs ((double) db::vprod (*o1, *o2)) < db::coord_traits<db::Coord>::prec_distance () * std::min (o1->double_length (), o2->double_length ())
|
||||
&& (o1->p1 () == o2->p1 () || o1->p1 () == o2->p2 () || o1->p2 () == o2->p1 () || o1->p2 () == o2->p2 () || o1->coincident (*o2))) {
|
||||
|
||||
db::cluster_collector<db::Edge, size_t, EdgeBooleanCluster<OutputContainer> >::add (o1, p1, o2, p2);
|
||||
|
||||
} else if (mp_intersections && p1 != p2) {
|
||||
|
||||
std::pair<bool, db::Point> ip = o1->intersect_point (*o2);
|
||||
if (ip.first) {
|
||||
m_intersections.insert (ip.second);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A receiver for the reducer which removes points that are on the edges
|
||||
*/
|
||||
struct RemovePointsOnEdges
|
||||
: public db::box_scanner_receiver2<db::Edge, size_t, db::Point, size_t>
|
||||
{
|
||||
public:
|
||||
RemovePointsOnEdges (std::set<db::Point> &points_to_remove)
|
||||
: mp_points_to_remove (&points_to_remove)
|
||||
{ }
|
||||
|
||||
void add (const db::Edge *e, const size_t &, const db::Point *pt, const size_t &)
|
||||
{
|
||||
if (e->contains (*pt)) {
|
||||
mp_points_to_remove->insert (*pt);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::set<db::Point> *mp_points_to_remove;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An inserter to produce degenerated edges from points
|
||||
*/
|
||||
struct PointInserter
|
||||
: public std::iterator<std::output_iterator_tag, void, void, void, void>
|
||||
{
|
||||
typedef db::Point value_type;
|
||||
|
||||
PointInserter (OutputContainer *output)
|
||||
: mp_output (output)
|
||||
{ }
|
||||
|
||||
PointInserter &operator= (const db::Point &pt)
|
||||
{
|
||||
mp_output->insert (db::Edge (pt, pt));
|
||||
return *this;
|
||||
}
|
||||
|
||||
PointInserter &operator* () { return *this; }
|
||||
PointInserter &operator++ () { return *this; }
|
||||
PointInserter &operator++ (int) { return *this; }
|
||||
|
||||
private:
|
||||
OutputContainer *mp_output;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Finalizes the implementation for "EdgeIntersections"
|
||||
* This method pushes those points which don't interact with the edges to the output container
|
||||
* as degenerate edges. It needs to be called after the pass has been made.
|
||||
*/
|
||||
void finalize (bool)
|
||||
{
|
||||
if (m_intersections.empty ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
db::box_scanner2<db::Edge, size_t, db::Point, size_t> intersections_to_edge_scanner;
|
||||
for (typename OutputContainer::const_iterator e = mp_output->begin (); e != mp_output->end (); ++e) {
|
||||
intersections_to_edge_scanner.insert1 (e.operator-> (), 0);
|
||||
}
|
||||
for (std::set<db::Point>::const_iterator p = m_intersections.begin (); p != m_intersections.end (); ++p) {
|
||||
intersections_to_edge_scanner.insert2 (p.operator-> (), 0);
|
||||
}
|
||||
|
||||
std::set<db::Point> points_to_remove;
|
||||
RemovePointsOnEdges rpoe (points_to_remove);
|
||||
intersections_to_edge_scanner.process (rpoe, 1, db::box_convert<db::Edge> (), db::box_convert<db::Point> ());
|
||||
|
||||
std::set_difference (m_intersections.begin (), m_intersections.end (), points_to_remove.begin (), points_to_remove.end (), PointInserter (mp_intersections));
|
||||
}
|
||||
|
||||
private:
|
||||
OutputContainer *mp_output;
|
||||
OutputContainer *mp_intersections;
|
||||
std::set<db::Point> m_intersections;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A helper class to use db::Shapes as container for EdgeBooleanClusterCollector
|
||||
*/
|
||||
struct DB_PUBLIC ShapesToOutputContainerAdaptor
|
||||
{
|
||||
public:
|
||||
struct Iterator
|
||||
: public db::Shapes::shape_iterator
|
||||
{
|
||||
Iterator (const db::Shapes::shape_iterator &iter)
|
||||
: db::Shapes::shape_iterator (iter)
|
||||
{ }
|
||||
|
||||
Iterator ()
|
||||
: db::Shapes::shape_iterator ()
|
||||
{ }
|
||||
|
||||
const db::Edge *operator-> () const
|
||||
{
|
||||
return (db::Shapes::shape_iterator::operator* ()).basic_ptr (db::Edge::tag ());
|
||||
}
|
||||
|
||||
bool operator!= (const Iterator &other) const
|
||||
{
|
||||
// only for testing whether at end:
|
||||
return at_end () != other.at_end ();
|
||||
}
|
||||
|
||||
bool operator== (const Iterator &other) const
|
||||
{
|
||||
// only for testing whether at end:
|
||||
return at_end () == other.at_end ();
|
||||
}
|
||||
};
|
||||
|
||||
typedef Iterator const_iterator;
|
||||
|
||||
ShapesToOutputContainerAdaptor (db::Shapes &shapes)
|
||||
: mp_shapes (&shapes)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
const_iterator begin ()
|
||||
{
|
||||
return Iterator (mp_shapes->begin (db::ShapeIterator::Edges));
|
||||
}
|
||||
|
||||
const_iterator end ()
|
||||
{
|
||||
return Iterator ();
|
||||
}
|
||||
|
||||
void insert (const db::Edge &edge)
|
||||
{
|
||||
mp_shapes->insert (edge);
|
||||
}
|
||||
|
||||
private:
|
||||
db::Shapes *mp_shapes;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A specialization of the EdgeBooleanClusterCollector for a Shapes output container
|
||||
*/
|
||||
struct DB_PUBLIC EdgeBooleanClusterCollectorToShapes
|
||||
: EdgeBooleanClusterCollector<ShapesToOutputContainerAdaptor>
|
||||
{
|
||||
EdgeBooleanClusterCollectorToShapes (db::Shapes *output, EdgeBoolOp op)
|
||||
: EdgeBooleanClusterCollector<ShapesToOutputContainerAdaptor> (&m_adaptor, op), m_adaptor (*output)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
ShapesToOutputContainerAdaptor m_adaptor;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -950,6 +950,16 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Intersections with other edges
|
||||
* Intersections are similar to "AND", but will also report
|
||||
* non-parallel intersections between crossing edges.
|
||||
*/
|
||||
Edges intersections (const Edges &other) const
|
||||
{
|
||||
return Edges (mp_delegate->intersections (other));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief returns the extended edges
|
||||
*
|
||||
|
|
|
|||
|
|
@ -155,11 +155,6 @@ class DB_PUBLIC EdgeToEdgePairProcessorBase
|
|||
// .. nothing yet ..
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A common definition for the boolean operations available on edges
|
||||
*/
|
||||
enum EdgeBoolOp { EdgeOr, EdgeNot, EdgeXor, EdgeAnd };
|
||||
|
||||
class RecursiveShapeIterator;
|
||||
class EdgeFilterBase;
|
||||
class EdgePairsDelegate;
|
||||
|
|
@ -267,6 +262,7 @@ public:
|
|||
virtual EdgesDelegate *or_with (const Edges &other) const = 0;
|
||||
virtual EdgesDelegate *add_in_place (const Edges &other) = 0;
|
||||
virtual EdgesDelegate *add (const Edges &other) const = 0;
|
||||
virtual EdgesDelegate *intersections (const Edges &other) const = 0;
|
||||
|
||||
virtual RegionDelegate *extended (coord_type ext_b, coord_type ext_e, coord_type ext_o, coord_type ext_i, bool join) const = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ public:
|
|||
virtual EdgesDelegate *or_with (const Edges &other) const;
|
||||
virtual EdgesDelegate *add_in_place (const Edges &other);
|
||||
virtual EdgesDelegate *add (const Edges &other) const;
|
||||
virtual EdgesDelegate *intersections (const Edges &) const { return new EmptyEdges (); }
|
||||
|
||||
virtual RegionDelegate *extended (coord_type, coord_type, coord_type, coord_type, bool) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ FlatEdges::ensure_merged_edges_valid () const
|
|||
m_merged_edges.clear ();
|
||||
|
||||
db::Shapes tmp (false);
|
||||
EdgeBooleanClusterCollector<db::Shapes> cluster_collector (&tmp, EdgeOr);
|
||||
EdgeBooleanClusterCollectorToShapes cluster_collector (&tmp, EdgeOr);
|
||||
|
||||
db::box_scanner<db::Edge, size_t> scanner (report_progress (), progress_desc ());
|
||||
scanner.reserve (m_edges.size ());
|
||||
|
|
|
|||
|
|
@ -1228,11 +1228,6 @@ public:
|
|||
add_pair (*c1, *i2, p, t);
|
||||
}
|
||||
|
||||
bool stop () const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finally join the clusters in the join set
|
||||
*
|
||||
|
|
@ -1275,6 +1270,11 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
// needs explicit implementation because we have two base classes:
|
||||
bool stop () const { return false; }
|
||||
void initialize () { }
|
||||
void finalize (bool) { }
|
||||
|
||||
private:
|
||||
struct InteractionKeyForClustersType
|
||||
: public InstanceToInstanceInteraction
|
||||
|
|
|
|||
|
|
@ -177,8 +177,8 @@ std::string SelfOverlapMergeLocalOperation::description () const
|
|||
// ---------------------------------------------------------------------------------------------
|
||||
// EdgeBoolAndOrNotLocalOperation implementation
|
||||
|
||||
EdgeBoolAndOrNotLocalOperation::EdgeBoolAndOrNotLocalOperation (bool is_and)
|
||||
: m_is_and (is_and)
|
||||
EdgeBoolAndOrNotLocalOperation::EdgeBoolAndOrNotLocalOperation (EdgeBoolOp op)
|
||||
: m_op (op)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
|
@ -186,19 +186,27 @@ EdgeBoolAndOrNotLocalOperation::EdgeBoolAndOrNotLocalOperation (bool is_and)
|
|||
local_operation<db::Edge, db::Edge, db::Edge>::on_empty_intruder_mode
|
||||
EdgeBoolAndOrNotLocalOperation::on_empty_intruder_hint () const
|
||||
{
|
||||
return m_is_and ? Drop : Copy;
|
||||
return (m_op == EdgeAnd || m_op == EdgeIntersections) ? Drop : Copy;
|
||||
}
|
||||
|
||||
std::string
|
||||
EdgeBoolAndOrNotLocalOperation::description () const
|
||||
{
|
||||
return m_is_and ? tl::to_string (tr ("Edge AND operation")) : tl::to_string (tr ("Edge NOT operation"));
|
||||
if (m_op == EdgeIntersections) {
|
||||
return tl::to_string (tr ("Edge INTERSECTION operation"));
|
||||
} else if (m_op == EdgeAnd) {
|
||||
return tl::to_string (tr ("Edge AND operation"));
|
||||
} else if (m_op == EdgeNot) {
|
||||
return tl::to_string (tr ("Edge NOT operation"));
|
||||
} else {
|
||||
return std::string ();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
EdgeBoolAndOrNotLocalOperation::compute_local (db::Layout * /*layout*/, const shape_interactions<db::Edge, db::Edge> &interactions, std::unordered_set<db::Edge> &result, size_t /*max_vertex_count*/, double /*area_ratio*/) const
|
||||
{
|
||||
EdgeBooleanClusterCollector<std::unordered_set<db::Edge> > cluster_collector (&result, m_is_and ? EdgeAnd : EdgeNot);
|
||||
EdgeBooleanClusterCollector<std::unordered_set<db::Edge> > cluster_collector (&result, m_op);
|
||||
|
||||
db::box_scanner<db::Edge, size_t> scanner;
|
||||
|
||||
|
|
@ -210,17 +218,18 @@ EdgeBoolAndOrNotLocalOperation::compute_local (db::Layout * /*layout*/, const sh
|
|||
}
|
||||
|
||||
bool any_subject = false;
|
||||
bool is_and = (m_op == EdgeAnd || m_op == EdgeIntersections);
|
||||
|
||||
for (shape_interactions<db::Edge, db::Edge>::iterator i = interactions.begin (); i != interactions.end (); ++i) {
|
||||
|
||||
const db::Edge &subject = interactions.subject_shape (i->first);
|
||||
if (others.find (subject) != others.end ()) {
|
||||
if (m_is_and) {
|
||||
if (is_and) {
|
||||
result.insert (subject);
|
||||
}
|
||||
} else if (i->second.empty ()) {
|
||||
// shortcut (not: keep, and: drop)
|
||||
if (! m_is_and) {
|
||||
if (! is_and) {
|
||||
result.insert (subject);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include "dbCommon.h"
|
||||
|
||||
#include "dbLayout.h"
|
||||
#include "dbEdgeBoolean.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
|
@ -151,7 +152,7 @@ class DB_PUBLIC EdgeBoolAndOrNotLocalOperation
|
|||
: public local_operation<db::Edge, db::Edge, db::Edge>
|
||||
{
|
||||
public:
|
||||
EdgeBoolAndOrNotLocalOperation (bool is_and);
|
||||
EdgeBoolAndOrNotLocalOperation (EdgeBoolOp op);
|
||||
|
||||
virtual void compute_local (db::Layout *layout, const shape_interactions<db::Edge, db::Edge> &interactions, std::unordered_set<db::Edge> &result, size_t max_vertex_count, double area_ratio) const;
|
||||
virtual on_empty_intruder_mode on_empty_intruder_hint () const;
|
||||
|
|
@ -161,7 +162,7 @@ public:
|
|||
virtual db::Coord dist () const { return 1; }
|
||||
|
||||
private:
|
||||
bool m_is_and;
|
||||
EdgeBoolOp m_op;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ OriginalLayerEdges::ensure_merged_edges_valid () const
|
|||
m_merged_edges.clear ();
|
||||
|
||||
db::Shapes tmp (false);
|
||||
EdgeBooleanClusterCollector<db::Shapes> cluster_collector (&tmp, EdgeOr);
|
||||
EdgeBooleanClusterCollectorToShapes cluster_collector (&tmp, EdgeOr);
|
||||
|
||||
db::box_scanner<db::Edge, size_t> scanner (report_progress (), progress_desc ());
|
||||
scanner.reserve (size ());
|
||||
|
|
|
|||
|
|
@ -943,6 +943,13 @@ Class<db::Edges> dec_Edges ("db", "Edges",
|
|||
"\n"
|
||||
"This method has been introduced in version 0.26.1\n"
|
||||
) +
|
||||
method ("intersections", &db::Edges::intersections, gsi::arg ("other"),
|
||||
"@brief Computes the intersections between this edges and other edges\n"
|
||||
"This computation is like an AND operation, but also including crossing points between non-coincident edges as "
|
||||
"degenerated (point-like) edges.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.26.2\n"
|
||||
) +
|
||||
method ("inside_part", &db::Edges::inside_part, gsi::arg ("other"),
|
||||
"@brief Returns the parts of the edges of this edge collection which are inside the polygons of the region\n"
|
||||
"\n"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ struct BoxScannerTestRecorder
|
|||
}
|
||||
|
||||
bool stop () const { return false; }
|
||||
void initialize () { str += "[i]"; }
|
||||
void finalize (bool) { str += "[f]"; }
|
||||
|
||||
void add (const db::Box * /*b1*/, size_t p1, const db::Box * /*b2*/, size_t p2)
|
||||
{
|
||||
|
|
@ -54,6 +56,8 @@ struct BoxScannerTestRecorderStopping
|
|||
}
|
||||
|
||||
bool stop () const { return do_stop; }
|
||||
void initialize () { str += "[i]"; }
|
||||
void finalize (bool s) { str += s ? "[f+]" : "[f-]"; }
|
||||
|
||||
void add (const db::Box * /*b1*/, size_t p1, const db::Box * /*b2*/, size_t p2)
|
||||
{
|
||||
|
|
@ -70,6 +74,8 @@ struct BoxScannerTestRecorder2
|
|||
void finish (const db::Box *, size_t) { }
|
||||
|
||||
bool stop () const { return false; }
|
||||
void initialize () { }
|
||||
void finalize (bool) { }
|
||||
|
||||
void add (const db::Box * /*b1*/, size_t p1, const db::Box * /*b2*/, size_t p2)
|
||||
{
|
||||
|
|
@ -91,6 +97,8 @@ struct BoxScannerTestRecorderTwo
|
|||
}
|
||||
|
||||
bool stop () const { return false; }
|
||||
void initialize () { str += "[i]"; }
|
||||
void finalize (bool) { str += "[f]"; }
|
||||
|
||||
void add (const db::Box * /*b1*/, size_t p1, const db::SimplePolygon * /*b2*/, int p2)
|
||||
{
|
||||
|
|
@ -113,6 +121,8 @@ struct BoxScannerTestRecorderTwoStopping
|
|||
}
|
||||
|
||||
bool stop () const { return do_stop; }
|
||||
void initialize () { str += "[i]"; }
|
||||
void finalize (bool s) { str += s ? "[f+]" : "[f-]"; }
|
||||
|
||||
void add (const db::Box * /*b1*/, size_t p1, const db::SimplePolygon * /*b2*/, int p2)
|
||||
{
|
||||
|
|
@ -130,6 +140,8 @@ struct BoxScannerTestRecorder2Two
|
|||
void finish2 (const db::SimplePolygon *, int) { }
|
||||
|
||||
bool stop () const { return false; }
|
||||
void initialize () { }
|
||||
void finalize (bool) { }
|
||||
|
||||
void add (const db::Box * /*b1*/, size_t p1, const db::SimplePolygon * /*b2*/, int p2)
|
||||
{
|
||||
|
|
@ -159,11 +171,11 @@ TEST(1)
|
|||
db::box_convert<db::Box> bc;
|
||||
bs.set_scanner_threshold (0);
|
||||
EXPECT_EQ (bs.process (tr, 1, bc), true);
|
||||
EXPECT_EQ (tr.str, "(4-2)(5-2)(5-4)(3-2)(3-4)(5-3)<2><5><4><3>(1-0)<0><1>");
|
||||
EXPECT_EQ (tr.str, "[i](4-2)(5-2)(5-4)(3-2)(3-4)(5-3)<2><5><4><3>(1-0)<0><1>[f]");
|
||||
|
||||
BoxScannerTestRecorderStopping trstop;
|
||||
EXPECT_EQ (bs.process (trstop, 1, bc), false);
|
||||
EXPECT_EQ (trstop.str, "(4-2)");
|
||||
EXPECT_EQ (trstop.str, "[i](4-2)[f-]");
|
||||
}
|
||||
|
||||
TEST(1a)
|
||||
|
|
@ -182,7 +194,7 @@ TEST(1a)
|
|||
db::box_convert<db::Box> bc;
|
||||
bs.set_scanner_threshold (0);
|
||||
bs.process (tr, 1, bc);
|
||||
EXPECT_EQ (tr.str, "(1-0)<0><1>");
|
||||
EXPECT_EQ (tr.str, "[i](1-0)<0><1>[f]");
|
||||
}
|
||||
|
||||
TEST(1b)
|
||||
|
|
@ -204,7 +216,7 @@ TEST(1b)
|
|||
db::box_convert<db::Box> bc;
|
||||
bs.set_scanner_threshold (0);
|
||||
bs.process (tr, 1, bc);
|
||||
EXPECT_EQ (tr.str, "(3-0)(1-3)(4-1)(2-4)<0><3><1><4><2>");
|
||||
EXPECT_EQ (tr.str, "[i](3-0)(1-3)(4-1)(2-4)<0><3><1><4><2>[f]");
|
||||
}
|
||||
|
||||
TEST(1c)
|
||||
|
|
@ -226,7 +238,7 @@ TEST(1c)
|
|||
db::box_convert<db::Box> bc;
|
||||
bs.set_scanner_threshold (0);
|
||||
bs.process (tr, 1, bc);
|
||||
EXPECT_EQ (tr.str, "(3-0)(1-3)<0>(4-1)<3>(2-4)<1><4><2>");
|
||||
EXPECT_EQ (tr.str, "[i](3-0)(1-3)<0>(4-1)<3>(2-4)<1><4><2>[f]");
|
||||
}
|
||||
|
||||
TEST(1d)
|
||||
|
|
@ -248,7 +260,7 @@ TEST(1d)
|
|||
db::box_convert<db::Box> bc;
|
||||
bs.set_scanner_threshold (0);
|
||||
bs.process (tr, 0, bc);
|
||||
EXPECT_EQ (tr.str, "(3-0)<0><3><1><4><2>");
|
||||
EXPECT_EQ (tr.str, "[i](3-0)<0><3><1><4><2>[f]");
|
||||
}
|
||||
|
||||
TEST(1e)
|
||||
|
|
@ -269,7 +281,7 @@ TEST(1e)
|
|||
bs.set_fill_factor (0.0);
|
||||
db::box_convert<db::Box> bc;
|
||||
bs.process (tr, 0, bc);
|
||||
EXPECT_EQ (tr.str, "(0-3)<0><1><2><3><4>");
|
||||
EXPECT_EQ (tr.str, "[i](0-3)<0><1><2><3><4>[f]");
|
||||
}
|
||||
|
||||
TEST(1f)
|
||||
|
|
@ -280,7 +292,7 @@ TEST(1f)
|
|||
bs.set_fill_factor (0.0);
|
||||
db::box_convert<db::Box> bc;
|
||||
bs.process (tr, 0, bc);
|
||||
EXPECT_EQ (tr.str, "");
|
||||
EXPECT_EQ (tr.str, "[i][f]");
|
||||
}
|
||||
|
||||
TEST(1g)
|
||||
|
|
@ -302,7 +314,7 @@ TEST(1g)
|
|||
bs.set_fill_factor (0.0);
|
||||
db::box_convert<db::Box> bc;
|
||||
bs.process (tr, 0, bc);
|
||||
EXPECT_EQ (tr.str, "<2><4>(0-3)<0><1><3>");
|
||||
EXPECT_EQ (tr.str, "[i]<2><4>(0-3)<0><1><3>[f]");
|
||||
}
|
||||
|
||||
void run_test2 (tl::TestBase *_this, size_t n, double ff, db::Coord spread, bool touch = true)
|
||||
|
|
@ -927,7 +939,7 @@ TEST(two_1)
|
|||
db::box_convert<db::SimplePolygon> bc2;
|
||||
bs.set_scanner_threshold (0);
|
||||
bs.process (tr, 1, bc1, bc2);
|
||||
EXPECT_EQ (tr.str, "(2-12)(2-14)(4-12)(4-14)(2-15)(4-15)(5-12)(5-14)(5-15)(2-13)(4-13)(3-12)(3-14)(3-13)(3-15)(5-13)(0-10)<2><5><4><3><12><15><14><13>(0-11)(1-10)(1-11)<0><1><10><11>");
|
||||
EXPECT_EQ (tr.str, "[i](2-12)(2-14)(4-12)(4-14)(2-15)(4-15)(5-12)(5-14)(5-15)(2-13)(4-13)(3-12)(3-14)(3-13)(3-15)(5-13)(0-10)<2><5><4><3><12><15><14><13>(0-11)(1-10)(1-11)<0><1><10><11>[f]");
|
||||
}
|
||||
|
||||
TEST(two_1a)
|
||||
|
|
@ -963,7 +975,7 @@ TEST(two_1a)
|
|||
db::box_convert<db::SimplePolygon> bc2;
|
||||
bs.set_scanner_threshold (0);
|
||||
bs.process (tr, 1, bc1, bc2);
|
||||
EXPECT_EQ (tr.str, "(2-11)(2-12)(1-11)(1-12)<1><2><11><12>(0-10)<0><10>");
|
||||
EXPECT_EQ (tr.str, "[i](2-11)(2-12)(1-11)(1-12)<1><2><11><12>(0-10)<0><10>[f]");
|
||||
}
|
||||
|
||||
TEST(two_1b)
|
||||
|
|
@ -999,12 +1011,12 @@ TEST(two_1b)
|
|||
db::box_convert<db::SimplePolygon> bc2;
|
||||
bs.set_scanner_threshold (0);
|
||||
EXPECT_EQ (bs.process (tr, 1, bc1, bc2), true);
|
||||
EXPECT_EQ (tr.str, "(1-12)(2-12)(1-11)(2-11)<1><2><11><12>(0-10)<0><10>");
|
||||
EXPECT_EQ (tr.str, "[i](1-12)(2-12)(1-11)(2-11)<1><2><11><12>(0-10)<0><10>[f]");
|
||||
|
||||
|
||||
BoxScannerTestRecorderTwoStopping trstop;
|
||||
EXPECT_EQ (bs.process (trstop, 1, bc1, bc2), false);
|
||||
EXPECT_EQ (trstop.str, "(1-12)");
|
||||
EXPECT_EQ (trstop.str, "[i](1-12)[f-]");
|
||||
}
|
||||
|
||||
TEST(two_1c)
|
||||
|
|
@ -1035,7 +1047,7 @@ TEST(two_1c)
|
|||
db::box_convert<db::SimplePolygon> bc2;
|
||||
bs.set_scanner_threshold (0);
|
||||
EXPECT_EQ (bs.process (tr, 1, bc1, bc2), true);
|
||||
EXPECT_EQ (tr.str, "<0><10>(1-12)(2-12)(1-11)(2-11)<1><2><12><11>");
|
||||
EXPECT_EQ (tr.str, "[i]<0><10>(1-12)(2-12)(1-11)(2-11)<1><2><12><11>[f]");
|
||||
}
|
||||
|
||||
void run_test2_two (tl::TestBase *_this, size_t n, double ff, db::Coord spread, bool touch = true)
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ TEST(3_Edge2EdgeBooleans)
|
|||
db::Region r3 (db::RecursiveShapeIterator (ly, top_cell, l3), dss);
|
||||
db::Region r2and3 = r2 & r3;
|
||||
|
||||
db::Edges e2 = r2.edges ();
|
||||
db::Edges e3 = r3.edges ();
|
||||
db::Edges e2and3 = r2and3.edges ();
|
||||
|
||||
|
|
@ -144,6 +145,8 @@ TEST(3_Edge2EdgeBooleans)
|
|||
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (20, 0)), e3 & e2and3);
|
||||
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (21, 0)), e3 - e2and3);
|
||||
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (22, 0)), e3 ^ e2and3);
|
||||
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (23, 0)), e3.intersections(e2and3));
|
||||
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (24, 0)), e3.intersections(e2));
|
||||
|
||||
CHECKPOINT();
|
||||
db::compare_layouts (_this, target, tl::testsrc () + "/testdata/algo/deep_edges_au3.gds");
|
||||
|
|
|
|||
|
|
@ -852,6 +852,37 @@ TEST(22)
|
|||
ee.insert (db::Edge (4000,-2000,-2000,-2000));
|
||||
|
||||
EXPECT_EQ ((e & ee).to_string (), "(400,0;-2000,0);(500,-174;400,0);(1000,0;900,-173);(4000,0;1000,0)");
|
||||
EXPECT_EQ (e.intersections (ee).to_string (), "(400,0;-2000,0);(500,-174;400,0);(1000,0;900,-173);(4000,0;1000,0)");
|
||||
|
||||
// Edge/edge intersections
|
||||
ee.clear ();
|
||||
e.clear ();
|
||||
e.insert (db::Edge (0, -100, 0, 150));
|
||||
ee.insert (db::Edge (-50, 50, 50, 50));
|
||||
ee.insert (db::Edge (-50, 100, 50, 100));
|
||||
EXPECT_EQ ((e & ee).to_string (), ""); // AND does not report intersection points
|
||||
EXPECT_EQ (e.intersections (ee).to_string (), "(0,50;0,50);(0,100;0,100)");
|
||||
|
||||
// Edge is intersected by pair with connection point on this line
|
||||
ee.clear ();
|
||||
e.clear ();
|
||||
e.insert (db::Edge (0, -100, 0, 150));
|
||||
ee.insert (db::Edge (-50, 50, 0, 50));
|
||||
ee.insert (db::Edge (0, 60, 50, 60));
|
||||
ee.insert (db::Edge (-50, 100, 0, 100));
|
||||
ee.insert (db::Edge (0, 100, 50, 100));
|
||||
EXPECT_EQ ((e & ee).to_string (), ""); // AND does not report intersection points
|
||||
EXPECT_EQ (e.intersections (ee).to_string (), "(0,50;0,50);(0,60;0,60);(0,100;0,100)");
|
||||
|
||||
// Coincident edges are crossed by another one
|
||||
ee.clear ();
|
||||
e.clear ();
|
||||
e.insert (db::Edge (0, -100, 0, 250));
|
||||
ee.insert (db::Edge (0, 0, 0, 150));
|
||||
ee.insert (db::Edge (-50, 100, 50, 100));
|
||||
ee.insert (db::Edge (-50, 200, 50, 200));
|
||||
EXPECT_EQ ((e & ee).to_string (), "(0,0;0,150)");
|
||||
EXPECT_EQ (e.intersections (ee).to_string (), "(0,0;0,150);(0,200;0,200)");
|
||||
}
|
||||
|
||||
TEST(23)
|
||||
|
|
|
|||
|
|
@ -1601,6 +1601,16 @@ CODE
|
|||
# This method is available for polygon and edge layers. Edges can be selected
|
||||
# with respect to other edges or polygons.
|
||||
|
||||
# %DRC%
|
||||
# @name intersections
|
||||
# @brief Returns the intersection points of intersecting edge segments for two edge collections
|
||||
# @synopsis layer.intersections(edges)
|
||||
# This operation is similar to the "&" operator, but it does also report intersection points
|
||||
# between non-colinear, but intersection edges. Such points are reported as point-like,
|
||||
# degenerated edge objects.
|
||||
#
|
||||
# This method is available for edge layers. The argument must be an edge layer.
|
||||
|
||||
# %DRC%
|
||||
# @name inside_part
|
||||
# @brief Returns the parts of the edges inside the given region
|
||||
|
|
@ -1731,7 +1741,6 @@ CODE
|
|||
end
|
||||
|
||||
%w(inside_part outside_part).each do |f|
|
||||
# In tiled mode, there are no modifying versions. Emulate using the non-modifying one.
|
||||
eval <<"CODE"
|
||||
def #{f}(other)
|
||||
other.requires_region("#{f}")
|
||||
|
|
@ -1746,6 +1755,21 @@ CODE
|
|||
CODE
|
||||
end
|
||||
|
||||
%w(intersections).each do |f|
|
||||
eval <<"CODE"
|
||||
def #{f}(other)
|
||||
other.requires_edges("#{f}")
|
||||
requires_edges("#{f}")
|
||||
if @engine.is_tiled?
|
||||
@data = @engine._tcmd(@data, 0, @data.class, :#{f}, other.data)
|
||||
DRCLayer::new(@engine, @data)
|
||||
else
|
||||
DRCLayer::new(@engine, @engine._tcmd(@data, 0, @data.class, :#{f}, other.data))
|
||||
end
|
||||
end
|
||||
CODE
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name rectangles
|
||||
# @brief Selects all rectangle polygons from the input
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -589,6 +589,22 @@ def run_testsuite(dm, ic, tiled = false, hier = false)
|
|||
p = c.edges.pull_interacting(b.edges)
|
||||
p.output(lb + 5, dm)
|
||||
|
||||
lb += 10 #440
|
||||
message "--- edge booleans, intersections #{lb}"
|
||||
|
||||
p = c.edges - b.edges
|
||||
p.output(lb, dm)
|
||||
p = b.edges - c.edges
|
||||
p.output(lb + 1, dm)
|
||||
p = c.edges & b.edges
|
||||
p.output(lb + 2, dm)
|
||||
p = c.edges ^ b.edges
|
||||
p.output(lb + 3, dm)
|
||||
p = c.edges.intersections(b.edges)
|
||||
p.output(lb + 4, dm)
|
||||
p = b.edges.intersections(c.edges)
|
||||
p.output(lb + 5, dm)
|
||||
|
||||
end
|
||||
|
||||
if $drc_test_mode == 1
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue