diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index 3c16a85ed1..d31cc36ac5 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -2887,15 +2887,18 @@ Layout::get_context_info (cell_index_type cell_index, LayoutOrCellContextInfo &i if (pcell_variant) { const db::PCellDeclaration *pcell_decl = ly->pcell_declaration (pcell_variant->pcell_id ()); - - const std::vector &pcp = pcell_decl->parameter_declarations (); - std::vector::const_iterator pd = pcp.begin (); - for (std::vector::const_iterator p = pcell_variant->parameters ().begin (); p != pcell_variant->parameters ().end () && pd != pcp.end (); ++p, ++pd) { - info.pcell_parameters.insert (std::make_pair (pd->get_name (), *p)); + if (pcell_decl) { + const std::vector &pcp = pcell_decl->parameter_declarations (); + std::vector::const_iterator pd = pcp.begin (); + for (std::vector::const_iterator p = pcell_variant->parameters ().begin (); p != pcell_variant->parameters ().end () && pd != pcp.end (); ++p, ++pd) { + info.pcell_parameters.insert (std::make_pair (pd->get_name (), *p)); + } } const db::PCellHeader *header = ly->pcell_header (pcell_variant->pcell_id ()); - info.pcell_name = header->get_name (); + if (header) { + info.pcell_name = header->get_name (); + } } else if (ly != this) { info.cell_name = ly->cell_name (cptr->cell_index ()); diff --git a/src/db/db/dbLayoutUtils.cc b/src/db/db/dbLayoutUtils.cc index 2fe5174fbb..4829e9131f 100644 --- a/src/db/db/dbLayoutUtils.cc +++ b/src/db/db/dbLayoutUtils.cc @@ -683,5 +683,106 @@ scale_and_snap (db::Layout &layout, db::Cell &cell, db::Coord g, db::Coord m, db } } + +// ------------------------------------------------------------ +// break_polygons implementation + +static bool split_polygon (bool first, db::Polygon &poly, size_t max_vertex_count, double max_area_ratio, std::vector &parts) +{ + if ((max_vertex_count > 0 && poly.vertices () > max_vertex_count) || + (max_area_ratio > 0 && poly.area_ratio () > max_area_ratio)) { + + std::vector sp; + db::split_polygon (poly, sp); + for (auto p = sp.begin (); p != sp.end (); ++p) { + split_polygon (false, *p, max_vertex_count, max_area_ratio, parts); + } + + return true; + + } else { + + if (! first) { + parts.push_back (db::Polygon ()); + parts.back ().swap (poly); + } + + return false; + + } +} + +void +break_polygons (db::Shapes &shapes, size_t max_vertex_count, double max_area_ratio) +{ + if (shapes.is_editable ()) { + + std::vector new_polygons; + std::vector to_delete; + + for (auto s = shapes.begin (db::ShapeIterator::Polygons | db::ShapeIterator::Paths); ! s.at_end (); ++s) { + db::Polygon poly; + s->instantiate (poly); + if (split_polygon (true, poly, max_vertex_count, max_area_ratio, new_polygons)) { + to_delete.push_back (*s); + } + } + + shapes.erase_shapes (to_delete); + + for (auto p = new_polygons.begin (); p != new_polygons.end (); ++p) { + shapes.insert (*p); + } + + } else { + + // In non-editable mode we cannot do "erase", so we use a temporary, editable Shapes container + db::Shapes tmp (true); + tmp.insert (shapes); + + shapes.clear (); + break_polygons (tmp, max_vertex_count, max_area_ratio); + shapes.insert (tmp); + + tl_assert (!shapes.is_editable ()); + + } +} + +void +break_polygons (db::Layout &layout, db::cell_index_type cell_index, unsigned int layer, size_t max_vertex_count, double max_area_ratio) +{ + if (layout.is_valid_cell_index (cell_index) && layout.is_valid_layer (layer)) { + db::Cell &cell = layout.cell (cell_index); + break_polygons (cell.shapes (layer), max_vertex_count, max_area_ratio); + } +} + +void +break_polygons (db::Layout &layout, unsigned int layer, size_t max_vertex_count, double max_area_ratio) +{ + for (db::cell_index_type ci = 0; ci < layout.cells (); ++ci) { + if (layout.is_valid_cell_index (ci)) { + db::Cell &cell = layout.cell (ci); + break_polygons (cell.shapes (layer), max_vertex_count, max_area_ratio); + } + } +} + +void +break_polygons (db::Layout &layout, size_t max_vertex_count, double max_area_ratio) +{ + for (db::cell_index_type ci = 0; ci < layout.cells (); ++ci) { + if (layout.is_valid_cell_index (ci)) { + db::Cell &cell = layout.cell (ci); + for (unsigned int li = 0; li < layout.layers (); ++li) { + if (layout.is_valid_layer (li)) { + break_polygons (cell.shapes (li), max_vertex_count, max_area_ratio); + } + } + } + } +} + } diff --git a/src/db/db/dbLayoutUtils.h b/src/db/db/dbLayoutUtils.h index c4d06c2393..d3a15c2071 100644 --- a/src/db/db/dbLayoutUtils.h +++ b/src/db/db/dbLayoutUtils.h @@ -249,6 +249,33 @@ class DB_PUBLIC ContextCache */ DB_PUBLIC void scale_and_snap (db::Layout &layout, db::Cell &cell, db::Coord g, db::Coord m, db::Coord d); +/** + * @brief Breaks polygons according to max_vertex_count and max_area_ratio + * + * This method will investigate all polygons on the given layer and cell and split them in case they + * have more than the specified vertices and an bounding-box area to polygon area ratio larget + * than the specified max_area_ratio. This serves optimization for algorithms needing a good + * bounding box approximation. + * + * Setting max_vertex_count or max_area_ratio to 0 disables the respective check. + */ +DB_PUBLIC void break_polygons (db::Layout &layout, db::cell_index_type cell_index, unsigned int layer, size_t max_vertex_count, double max_area_ratio); + +/** + * @brief Like "break_polygons" before, but applies it to all cells. + */ +DB_PUBLIC void break_polygons (db::Layout &layout, unsigned int layer, size_t max_vertex_count, double max_area_ratio); + +/** + * @brief Like "break_polygons" before, but applies it to all cells and all layers. + */ +DB_PUBLIC void break_polygons (db::Layout &layout, size_t max_vertex_count, double max_area_ratio); + +/** + * @brief Like "break_polygons" before, but applies it to the given Shapes container. + */ +DB_PUBLIC void break_polygons (db::Shapes &shapes, size_t max_vertex_count, double max_area_ratio); + } // namespace db #endif diff --git a/src/db/db/dbNetlist.cc b/src/db/db/dbNetlist.cc index f0f8d177ce..e5e8018633 100644 --- a/src/db/db/dbNetlist.cc +++ b/src/db/db/dbNetlist.cc @@ -366,6 +366,47 @@ size_t Netlist::top_circuit_count () const return m_top_circuits; } +Circuit *Netlist::top_circuit () +{ + size_t ntop = top_circuit_count (); + if (ntop == 0) { + return 0; + } else if (ntop > 1) { + throw tl::Exception (tl::to_string (tr ("Netlist contains more than a single top circuit"))); + } else { + return begin_top_down ().operator-> (); + } +} + +const Circuit *Netlist::top_circuit () const +{ + return const_cast (this)->top_circuit (); +} + +std::vector Netlist::top_circuits () +{ + size_t ntop = top_circuit_count (); + std::vector result; + result.reserve (ntop); + for (auto c = begin_top_down (); ntop > 0 && c != end_top_down (); ++c) { + result.push_back (c.operator-> ()); + --ntop; + } + return result; +} + +std::vector Netlist::top_circuits () const +{ + size_t ntop = top_circuit_count (); + std::vector result; + result.reserve (ntop); + for (auto c = begin_top_down (); ntop > 0 && c != end_top_down (); ++c) { + result.push_back (c.operator-> ()); + --ntop; + } + return result; +} + Netlist::bottom_up_circuit_iterator Netlist::begin_bottom_up () { if (! m_valid_topology) { diff --git a/src/db/db/dbNetlist.h b/src/db/db/dbNetlist.h index 9e52217968..0bff649e15 100644 --- a/src/db/db/dbNetlist.h +++ b/src/db/db/dbNetlist.h @@ -271,6 +271,32 @@ class DB_PUBLIC Netlist return m_circuit_by_cell_index.object_by (cell_index); } + /** + * @brief Gets the top circuit if there is one + * This method will assert if there is more than a single top circuit. + * It will return 0 if there is no top circuit. + */ + Circuit *top_circuit (); + + /** + * @brief Gets the top circuit if there is one (const version) + * This method will assert if there is more than a single top circuit. + * It will return 0 if there is no top circuit. + */ + const Circuit *top_circuit () const; + + /** + * @brief Gets the top circuits + * This convenience method will return a list of top circuits. + */ + std::vector top_circuits (); + + /** + * @brief Gets the top circuits (const version) + * This convenience method will return a list of top circuits. + */ + std::vector top_circuits () const; + /** * @brief Gets the top-down circuits iterator (begin) * This iterator will deliver the circuits in a top-down way - i.e. child circuits diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index cc02f75a91..5c628def12 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -32,6 +32,7 @@ namespace db // Recursive shape iterator implementation RecursiveShapeIterator::RecursiveShapeIterator (const RecursiveShapeIterator &d) + : gsi::ObjectBase (d) { operator= (d); } diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index fc32fa381d..c3c0535502 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -1104,14 +1104,14 @@ static void set_cell_property (db::Cell *c, const tl::Variant &key, const tl::Va c->prop_id (layout->properties_repository ().properties_id (props)); } -static tl::Variant get_cell_property (db::Cell *c, const tl::Variant &key) +static tl::Variant get_cell_property (const db::Cell *c, const tl::Variant &key) { db::properties_id_type id = c->prop_id (); if (id == 0) { return tl::Variant (); } - db::Layout *layout = c->layout (); + const db::Layout *layout = c->layout (); if (! layout) { throw tl::Exception (tl::to_string (tr ("Cell does not reside inside a layout - cannot retrieve properties"))); } @@ -1130,6 +1130,26 @@ static tl::Variant get_cell_property (db::Cell *c, const tl::Variant &key) } } +static tl::Variant get_cell_properties (const db::Cell *c) +{ + db::properties_id_type id = c->prop_id (); + if (id == 0) { + return tl::Variant::empty_array (); + } + + const db::Layout *layout = c->layout (); + if (! layout) { + throw tl::Exception (tl::to_string (tr ("Cell does not reside inside a layout - cannot retrieve properties"))); + } + + tl::Variant res = tl::Variant::empty_array (); + const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (id); + for (auto i = props.begin (); i != props.end (); ++i) { + res.insert (layout->properties_repository ().prop_name (i->first), i->second); + } + return res; +} + static bool is_pcell_variant (const db::Cell *cell) { tl_assert (cell->layout () != 0); @@ -1841,10 +1861,16 @@ Class decl_Cell ("db", "Cell", "@brief Gets the user property with the given key\n" "This method is a convenience method that gets the property with the given key. " "If no property with that key exists, it will return nil. Using that method is more " - "convenient than using the layout object and the properties ID to retrieve the property value. " + "convenient than using the layout object and the properties ID to retrieve the property value.\n" "\n" "This method has been introduced in version 0.23." ) + + gsi::method_ext ("properties", &get_cell_properties, + "@brief Gets the user properties as a hash\n" + "This method is a convenience method that gets all user properties as a single hash.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::method_ext ("add_meta_info", &cell_add_meta_info, gsi::arg ("info"), "@brief Adds meta information to the cell\n" "See \\LayoutMetaInfo for details about cells and meta information.\n" @@ -3539,6 +3565,26 @@ static tl::Variant get_property (const db::Instance *i, const tl::Variant &key) } } +static tl::Variant get_properties (const db::Instance *i) +{ + db::properties_id_type id = i->prop_id (); + if (id == 0) { + return tl::Variant::empty_array (); + } + + const db::Layout *layout = layout_ptr_const (i); + if (! layout) { + throw tl::Exception (tl::to_string (tr ("Instance does not reside inside a layout - cannot retrieve properties"))); + } + + tl::Variant res = tl::Variant::empty_array (); + const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (id); + for (auto i = props.begin (); i != props.end (); ++i) { + res.insert (layout->properties_repository ().prop_name (i->first), i->second); + } + return res; +} + static bool inst_is_valid (const db::Instance *inst) { return inst->instances () && inst->instances ()->is_valid (*inst); @@ -4011,6 +4057,12 @@ Class decl_Instance ("db", "Instance", "\n" "This method has been introduced in version 0.22." ) + + gsi::method_ext ("properties", &get_properties, + "@brief Gets the user properties as a hash\n" + "This method is a convenience method that gets all user properties as a single hash.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + method_ext ("[]", &inst_index, gsi::arg ("key"), "@brief Gets the user property with the given key or, if available, the PCell parameter with the name given by the key\n" "Getting the PCell parameter has priority over the user property." diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index db8c78233d..751c4152e9 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -41,6 +41,7 @@ #include "dbLayerMapping.h" #include "dbCellMapping.h" #include "dbTechnology.h" +#include "dbLayoutUtils.h" #include "tlStream.h" #include "tlGlobPattern.h" @@ -343,7 +344,7 @@ static void set_layout_property (db::Layout *l, const tl::Variant &key, const tl l->prop_id (l->properties_repository ().properties_id (props)); } -static tl::Variant get_layout_property (db::Layout *l, const tl::Variant &key) +static tl::Variant get_layout_property (const db::Layout *l, const tl::Variant &key) { // TODO: check if is editable @@ -366,6 +367,21 @@ static tl::Variant get_layout_property (db::Layout *l, const tl::Variant &key) } } +static tl::Variant get_layout_properties (const db::Layout *layout) +{ + db::properties_id_type id = layout->prop_id (); + if (id == 0) { + return tl::Variant::empty_array (); + } + + tl::Variant res = tl::Variant::empty_array (); + const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (id); + for (auto i = props.begin (); i != props.end (); ++i) { + res.insert (layout->properties_repository ().prop_name (i->first), i->second); + } + return res; +} + static db::cell_index_type cell_by_name (db::Layout *l, const char *name) { std::pair c = l->cell_by_name (name); @@ -1050,6 +1066,16 @@ static void set_properties (db::Layout *layout, unsigned int index, const db::La } } +void break_polygons2 (db::Layout *layout, unsigned int layer, size_t max_vertex_count, double max_area_ratio) +{ + db::break_polygons (*layout, layer, max_vertex_count, max_area_ratio); +} + +void break_polygons1 (db::Layout *layout, size_t max_vertex_count, double max_area_ratio) +{ + db::break_polygons (*layout, max_vertex_count, max_area_ratio); +} + Class decl_Layout ("db", "Layout", gsi::constructor ("new", &layout_ctor_with_manager, gsi::arg ("manager"), "@brief Creates a layout object attached to a manager\n" @@ -1254,6 +1280,12 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.24." ) + + gsi::method_ext ("properties", &get_layout_properties, + "@brief Gets the user properties as a hash\n" + "This method is a convenience method that gets all user properties as a single hash.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::method_ext ("properties_id", &properties_id, gsi::arg ("properties"), "@brief Gets the properties ID for a given properties set\n" "\n" @@ -1956,6 +1988,33 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.26.1.\n" ) + + gsi::method_ext ("break_polygons", &break_polygons1, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio"), + "@brief Breaks the polygons of the layout into smaller ones\n" + "\n" + "There are two criteria for splitting a polygon: a polygon is split into parts with less then " + "'max_vertex_count' points and an bounding box-to-polygon area ratio less than 'max_area_ratio'. " + "The area ratio is supposed to render polygons whose bounding box is a better approximation. " + "This applies for example to 'L' shape polygons.\n" + "\n" + "Using a value of 0 for either limit means that the respective limit isn't checked. " + "Breaking happens by cutting the polygons into parts at 'good' locations. The " + "algorithm does not have a specific goal to minimize the number of parts for example. " + "The only goal is to achieve parts within the given limits.\n" + "\n" + "Breaking also applies to paths if their polygon representation satisfies the breaking criterion. " + "In that case, paths are converted to polygons and broken into smaller parts.\n" + "\n" + "This variant applies breaking to all cells and layers.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + + gsi::method_ext ("break_polygons", &break_polygons2, gsi::arg ("layer"), gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio"), + "@brief Breaks the polygons of the layer into smaller ones\n" + "\n" + "This variant applies breaking to all cells and the given layer.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::method ("transform", (void (db::Layout::*) (const db::Trans &t)) &db::Layout::transform, gsi::arg ("trans"), "@brief Transforms the layout with the given transformation\n" "\n" diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index f84294c9fe..1573631dcd 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -2044,6 +2044,32 @@ Class decl_dbNetlist ("db", "Netlist", "\n" "This method has been introduced in version 0.28.4.\n" ) + + gsi::method ("top_circuit", static_cast (&db::Netlist::top_circuit), + "@brief Gets the top circuit.\n" + "This method will return nil, if there is no top circuit. It will raise an error, if there is more than " + "a single top circuit.\n" + "\n" + "This convenience method has been added in version 0.29.5." + ) + + gsi::method ("top_circuit", static_cast (&db::Netlist::top_circuit), + "@brief Gets the top circuit (const version).\n" + "This method will return nil, if there is no top circuit. It will raise an error, if there is more than " + "a single top circuit.\n" + "\n" + "This convenience method has been added in version 0.29.5." + ) + + gsi::method ("top_circuits", static_cast (db::Netlist::*) ()> (&db::Netlist::top_circuits), + "@brief Gets the top circuits.\n" + "Returns a list of top circuits.\n" + "\n" + "This convenience method has been added in version 0.29.5." + ) + + gsi::method ("top_circuits", static_cast (db::Netlist::*) () const> (&db::Netlist::top_circuits), + "@brief Gets the top circuits.\n" + "Returns a list of top circuits.\n" + "\n" + "This convenience method has been added in version 0.29.5." + ) + gsi::method_ext ("nets_by_name", &nets_by_name_const_from_netlist, gsi::arg ("name_pattern"), "@brief Gets the net objects for a given name filter (const version).\n" "The name filter is a glob pattern. This method will return all \\Net objects matching the glob pattern.\n" diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 8e98a96566..04cae2c977 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -1132,6 +1132,24 @@ rasterize1 (const db::Region *region, const db::Point &origin, const db::Vector return rasterize2 (region, origin, pixel_size, pixel_size, nx, ny); } +static tl::Variant begin_shapes_rec (const db::Region *region) +{ + auto res = region->begin_iter (); + tl::Variant r = tl::Variant (std::vector ()); + r.push (tl::Variant (res.first)); + r.push (tl::Variant (res.second)); + return r; +} + +static tl::Variant begin_merged_shapes_rec (const db::Region *region) +{ + auto res = region->begin_merged_iter (); + tl::Variant r = tl::Variant (std::vector ()); + r.push (tl::Variant (res.first)); + r.push (tl::Variant (res.second)); + return r; +} + static db::Point default_origin; // provided by gsiDeclDbPolygon.cc: @@ -2895,7 +2913,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "This method returns all polygons in self which are not rectilinear." "Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n" ) + - method_ext ("break", &break_polygons, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), + method_ext ("break_polygons|#break", &break_polygons, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), "@brief Breaks the polygons of the region into smaller ones\n" "\n" "There are two criteria for splitting a polygon: a polygon is split into parts with less then " @@ -2908,7 +2926,8 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "algorithm does not have a specific goal to minimize the number of parts for example. " "The only goal is to achieve parts within the given limits.\n" "\n" - "This method has been introduced in version 0.26." + "This method has been introduced in version 0.26. The 'break_polygons' alias has been introduced " + "in version 0.29.5 to avoid issues with reserved keywords." ) + method_ext ("delaunay", &delaunay, "@brief Computes a constrained Delaunay triangulation from the given region\n" @@ -2932,6 +2951,8 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to " "a minimum angle of abouth 37 degree.\n" "\n" + "The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n" + "\n" "The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will " "make the implementation skip the refinement step. In that case, the results are identical to " "the standard constrained Delaunay triangulation.\n" @@ -3756,7 +3777,32 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "metal1_all_nets = metal1.nets\n" "@/code\n" "\n" - "This method was introduced in version 0.28.4" + "This method was introduced in version 0.28.4." + ) + + gsi::method_ext ("begin_shapes_rec", &begin_shapes_rec, + "@brief Returns a recursive shape iterator plus a transformation for the shapes constituting this region.\n" + "This method returns a pair consisting of a \\RecursiveShapeIterator plus a \\ICplxTrans transformation. " + "Both objects allow accessing the shapes (polygons) of the region in a detailed fashion. To iterate the " + "the polygons use a code like this:\n" + "\n" + "@code\n" + "iter, trans = region.begin_shapes_rec\n" + "iter.each do |i|\n" + " polygon = trans * iter.trans * i.shape.polygon\n" + " ...\n" + "end\n" + "@/code\n" + "\n" + "This method is the most powerful way of accessing the shapes inside the region. I allows for example to obtain the " + "properties attached to the polygons of the region. It is primarily intended for special applications like iterating net-annotated shapes.\n" + "\n" + "This speciality method was introduced in version 0.29.5." + ) + + gsi::method_ext ("begin_merged_shapes_rec", &begin_merged_shapes_rec, + "@brief Returns a recursive shape iterator plus a transformation for the shapes constituting the merged region.\n" + "It can be used like \\begin_shapes_rec, but delivers shapes from the merged polygons pool.\n" + "\n" + "This speciality method was introduced in version 0.29.5." ) + gsi::make_property_methods () , diff --git a/src/db/db/gsiDeclDbShape.cc b/src/db/db/gsiDeclDbShape.cc index 708a52f5ee..36e6ee4d49 100644 --- a/src/db/db/gsiDeclDbShape.cc +++ b/src/db/db/gsiDeclDbShape.cc @@ -1004,6 +1004,26 @@ static tl::Variant get_property (const db::Shape *s, const tl::Variant &key) } } +static tl::Variant get_properties (const db::Shape *s) +{ + db::properties_id_type id = s->prop_id (); + if (id == 0) { + return tl::Variant::empty_array (); + } + + const db::Layout *layout = layout_ptr_const (s); + if (! layout) { + throw tl::Exception (tl::to_string (tr ("Shape does not reside inside a layout - cannot retrieve properties"))); + } + + tl::Variant res = tl::Variant::empty_array (); + const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (id); + for (auto i = props.begin (); i != props.end (); ++i) { + res.insert (layout->properties_repository ().prop_name (i->first), i->second); + } + return res; +} + namespace { @@ -1354,6 +1374,12 @@ Class decl_Shape ("db", "Shape", "\n" "This method has been introduced in version 0.22." ) + + gsi::method_ext ("properties", &get_properties, + "@brief Gets the user properties\n" + "This method is a convenience method that gets the properties of the shape as a single hash.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::iterator ("each_point", &db::Shape::begin_point, &db::Shape::end_point, "@brief Iterates over all points of the object\n" "\n" diff --git a/src/db/db/gsiDeclDbShapes.cc b/src/db/db/gsiDeclDbShapes.cc index f2334f33b2..7393f3a89e 100644 --- a/src/db/db/gsiDeclDbShapes.cc +++ b/src/db/db/gsiDeclDbShapes.cc @@ -30,6 +30,7 @@ #include "dbRegion.h" #include "dbEdgePairs.h" #include "dbEdges.h" +#include "dbLayoutUtils.h" namespace gsi { @@ -441,6 +442,11 @@ static db::Layout *layout (db::Shapes *sh) } } +static void break_polygons (db::Shapes *sh, size_t max_vertex_count, double max_area_ratio) +{ + db::break_polygons (*sh, max_vertex_count, max_area_ratio); +} + static unsigned int s_all () { return db::ShapeIterator::All; } static unsigned int s_all_with_properties () { return db::ShapeIterator::AllWithProperties; } static unsigned int s_properties () { return db::ShapeIterator::Properties; } @@ -791,6 +797,24 @@ Class decl_Shapes ("db", "Shapes", "\n" "This method has been introduced in version 0.25.\n" ) + + gsi::method_ext ("break_polygons", &break_polygons, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), + "@brief Breaks the polygons of the shape container into smaller ones\n" + "\n" + "There are two criteria for splitting a polygon: a polygon is split into parts with less then " + "'max_vertex_count' points and an bounding box-to-polygon area ratio less than 'max_area_ratio'. " + "The area ratio is supposed to render polygons whose bounding box is a better approximation. " + "This applies for example to 'L' shape polygons.\n" + "\n" + "Using a value of 0 for either limit means that the respective limit isn't checked. " + "Breaking happens by cutting the polygons into parts at 'good' locations. The " + "algorithm does not have a specific goal to minimize the number of parts for example. " + "The only goal is to achieve parts within the given limits.\n" + "\n" + "Breaking also applies to paths if their polygon representation satisfies the breaking criterion. " + "In that case, paths are converted to polygons and broken into smaller parts.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::method_ext ("replace", &replace, gsi::arg ("shape"), gsi::arg ("box"), "@brief Replaces the given shape with a box\n" "@return A reference to the new shape (a \\Shape object)\n" diff --git a/src/db/unit_tests/dbLayoutUtilsTests.cc b/src/db/unit_tests/dbLayoutUtilsTests.cc index ce0a352c28..24d95c441c 100644 --- a/src/db/unit_tests/dbLayoutUtilsTests.cc +++ b/src/db/unit_tests/dbLayoutUtilsTests.cc @@ -779,3 +779,42 @@ TEST(20_scale_and_snap) db::compare_layouts (_this, l1, tl::testdata () + "/algo/layout_utils_au_sns4.oas", db::NormalizationMode (db::WriteOAS + db::WithArrays)); } + +TEST(21_break1) +{ + db::Layout l1; + { + std::string fn (tl::testdata ()); + fn += "/algo/break_polygons_test.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l1); + } + + db::break_polygons (l1, 10, 3.0); + + CHECKPOINT(); + db::compare_layouts (_this, l1, tl::testdata () + "/algo/layout_utils_au_bp1.gds"); +} + +TEST(22_break2) +{ + db::Layout l1; + { + std::string fn (tl::testdata ()); + fn += "/algo/break_polygons_test.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l1); + } + + unsigned int li1 = find_layer (l1, 1, 0); + unsigned int li2 = find_layer (l1, 2, 0); + + db::break_polygons (l1, li1, 10, 0.0); + db::break_polygons (l1, li2, 0, 3.0); + + CHECKPOINT(); + db::compare_layouts (_this, l1, tl::testdata () + "/algo/layout_utils_au_bp2.gds"); +} + diff --git a/src/db/unit_tests/dbNetlistTests.cc b/src/db/unit_tests/dbNetlistTests.cc index dbc8b8e9a7..6cb051c86d 100644 --- a/src/db/unit_tests/dbNetlistTests.cc +++ b/src/db/unit_tests/dbNetlistTests.cc @@ -208,6 +208,18 @@ static std::string parents2string (const db::Circuit *c) return res; } +static std::string td2string_nc (db::Netlist *nl) +{ + std::string res; + for (db::Netlist::top_down_circuit_iterator r = nl->begin_top_down (); r != nl->end_top_down (); ++r) { + if (!res.empty ()) { + res += ","; + } + res += r->name (); + } + return res; +} + static std::string td2string (const db::Netlist *nl) { std::string res; @@ -220,6 +232,64 @@ static std::string td2string (const db::Netlist *nl) return res; } +static std::string tcs2string_nc (db::Netlist *nl) +{ + std::string res; + std::vector tops = nl->top_circuits (); + for (auto i = tops.begin (); i != tops.end (); ++i) { + if (!res.empty ()) { + res += ","; + } + res += (*i)->name (); + } + return res; +} + +static std::string tcs2string (const db::Netlist *nl) +{ + std::string res; + std::vector tops = nl->top_circuits (); + for (auto i = tops.begin (); i != tops.end (); ++i) { + if (!res.empty ()) { + res += ","; + } + res += (*i)->name (); + } + return res; +} + +static std::string tc2string_nc (db::Netlist *nl) +{ + const db::Circuit *tc = nl->top_circuit (); + if (!tc) { + return "(nil)"; + } else { + return tc->name (); + } +} + +static std::string tc2string (const db::Netlist *nl) +{ + const db::Circuit *tc = nl->top_circuit (); + if (!tc) { + return "(nil)"; + } else { + return tc->name (); + } +} + +static std::string bu2string_nc (db::Netlist *nl) +{ + std::string res; + for (db::Netlist::bottom_up_circuit_iterator r = nl->begin_bottom_up (); r != nl->end_bottom_up (); ++r) { + if (!res.empty ()) { + res += ","; + } + res += r->name (); + } + return res; +} + static std::string bu2string (const db::Netlist *nl) { std::string res; @@ -1038,20 +1108,44 @@ TEST(12_NetlistTopology) { std::unique_ptr nl (new db::Netlist ()); EXPECT_EQ (nl->top_circuit_count (), size_t (0)); + EXPECT_EQ (tcs2string (nl.get ()), ""); + EXPECT_EQ (tcs2string_nc (nl.get ()), ""); + EXPECT_EQ (tc2string (nl.get ()), "(nil)"); + EXPECT_EQ (tc2string_nc (nl.get ()), "(nil)"); db::Circuit *c1 = new db::Circuit (); c1->set_name ("c1"); nl->add_circuit (c1); EXPECT_EQ (nl->top_circuit_count (), size_t (1)); EXPECT_EQ (td2string (nl.get ()), "c1"); + EXPECT_EQ (td2string_nc (nl.get ()), "c1"); + EXPECT_EQ (tcs2string (nl.get ()), "c1"); + EXPECT_EQ (tcs2string_nc (nl.get ()), "c1"); + EXPECT_EQ (tc2string (nl.get ()), "c1"); + EXPECT_EQ (tc2string_nc (nl.get ()), "c1"); EXPECT_EQ (bu2string (nl.get ()), "c1"); + EXPECT_EQ (bu2string_nc (nl.get ()), "c1"); db::Circuit *c2 = new db::Circuit (); c2->set_name ("c2"); nl->add_circuit (c2); EXPECT_EQ (nl->top_circuit_count (), size_t (2)); EXPECT_EQ (td2string (nl.get ()), "c2,c1"); + EXPECT_EQ (td2string_nc (nl.get ()), "c2,c1"); + EXPECT_EQ (tcs2string (nl.get ()), "c2,c1"); + EXPECT_EQ (tcs2string_nc (nl.get ()), "c2,c1"); + try { + tc2string (nl.get ()); + EXPECT_EQ (true, false); + } catch (...) { + } + try { + tc2string_nc (nl.get ()); + EXPECT_EQ (true, false); + } catch (...) { + } EXPECT_EQ (bu2string (nl.get ()), "c1,c2"); + EXPECT_EQ (bu2string_nc (nl.get ()), "c1,c2"); std::unique_ptr locker (new db::NetlistLocker (nl.get ())); diff --git a/src/edt/edt/edtInstPropertiesPage.cc b/src/edt/edt/edtInstPropertiesPage.cc index d84474411b..3baeb5a4b0 100644 --- a/src/edt/edt/edtInstPropertiesPage.cc +++ b/src/edt/edt/edtInstPropertiesPage.cc @@ -47,7 +47,7 @@ namespace edt // ------------------------------------------------------------------------- -static std::string cell_name_from_sel (const edt::Service::obj_iterator &pos, edt::Service *service) +static std::string cell_name_from_sel (EditableSelectionIterator::pointer pos, edt::Service *service) { if (! pos->is_cell_inst ()) { return std::string (); @@ -81,7 +81,7 @@ struct SelectionPtrSort // .. nothing yet .. } - bool operator() (const edt::Service::obj_iterator &a, const edt::Service::obj_iterator &b) + bool operator() (EditableSelectionIterator::pointer a, EditableSelectionIterator::pointer b) { return cell_name_from_sel (a, mp_service) < cell_name_from_sel (b, mp_service); } @@ -104,10 +104,9 @@ static bool is_orthogonal (const db::DVector &rv, const db::DVector &cv) InstPropertiesPage::InstPropertiesPage (edt::Service *service, db::Manager *manager, QWidget *parent) : lay::PropertiesPage (parent, manager, service), mp_service (service), m_enable_cb_callback (true), mp_pcell_parameters (0) { - const edt::Service::objects &selection = service->selection (); - m_selection_ptrs.reserve (selection.size ()); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { - m_selection_ptrs.push_back (s); + m_selection_ptrs.reserve (service->selection_size ()); + for (EditableSelectionIterator s = service->begin_selection (); ! s.at_end (); ++s) { + m_selection_ptrs.push_back (s.operator-> ()); } std::sort (m_selection_ptrs.begin (), m_selection_ptrs.end (), SelectionPtrSort (service)); @@ -221,7 +220,7 @@ BEGIN_PROTECTED lib = lib_cbx->current_library (); layout = &lib->layout (); } else { - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); layout = &cv->layout (); } @@ -300,7 +299,7 @@ InstPropertiesPage::select_entries (const std::vector &entries) std::string InstPropertiesPage::description (size_t entry) const { - edt::Service::obj_iterator pos = m_selection_ptrs [entry]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [entry]; std::string d = cell_name_from_sel (pos, mp_service); if (! pos->is_cell_inst ()) { @@ -343,12 +342,12 @@ InstPropertiesPage::update () return; } - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; tl_assert (pos->is_cell_inst ()); std::set highlights; for (auto i = m_indexes.begin (); i != m_indexes.end (); ++i) { - highlights.insert (m_selection_ptrs [*i].operator-> ()); + highlights.insert (m_selection_ptrs [*i]); } mp_service->highlight (highlights); @@ -472,7 +471,7 @@ InstPropertiesPage::show_cell () return; } - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; lay::CellView::unspecific_cell_path_type path (mp_service->view ()->cellview (pos->cv_index ()).combined_unspecific_path ()); for (lay::ObjectInstPath::iterator p = pos->begin (); p != pos->end (); ++p) { @@ -509,7 +508,7 @@ InstPropertiesPage::create_applicator (db::Cell & /*cell*/, const db::Instance & std::unique_ptr appl (new CombinedChangeApplicator ()); - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); bool du = dbu_cb->isChecked (); @@ -786,16 +785,15 @@ InstPropertiesPage::create_applicator (db::Cell & /*cell*/, const db::Instance & void InstPropertiesPage::recompute_selection_ptrs (const std::vector &new_sel) { - std::map ptrs; + std::map ptrs; - const edt::Service::objects &selection = mp_service->selection (); - for (edt::Service::obj_iterator pos = selection.begin (); pos != selection.end (); ++pos) { - ptrs.insert (std::make_pair (*pos, pos)); + for (EditableSelectionIterator pos = mp_service->begin_selection (); ! pos.at_end (); ++pos) { + ptrs.insert (std::make_pair (*pos, pos.operator -> ())); } m_selection_ptrs.clear (); for (std::vector::const_iterator s = new_sel.begin (); s != new_sel.end (); ++s) { - std::map::const_iterator pm = ptrs.find (*s); + std::map::const_iterator pm = ptrs.find (*s); tl_assert (pm != ptrs.end ()); m_selection_ptrs.push_back (pm->second); } @@ -814,7 +812,7 @@ InstPropertiesPage::do_apply (bool current_only, bool relative) std::unique_ptr applicator; { - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; tl_assert (pos->is_cell_inst ()); const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); @@ -846,7 +844,7 @@ InstPropertiesPage::do_apply (bool current_only, bool relative) std::vector new_sel; new_sel.reserve (m_selection_ptrs.size ()); - for (std::vector::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) { + for (std::vector::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) { new_sel.push_back (**p); } @@ -859,7 +857,7 @@ InstPropertiesPage::do_apply (bool current_only, bool relative) for (auto ii = m_indexes.begin (); ii != m_indexes.end (); ++ii) { size_t index = *ii; - edt::Service::obj_iterator pos = m_selection_ptrs [*ii]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [*ii]; // only update objects from the same layout - this is not practical limitation but saves a lot of effort for // managing different property id's etc. @@ -958,7 +956,7 @@ InstPropertiesPage::update_pcell_parameters () } else { - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); layout = &cv->layout (); @@ -982,7 +980,7 @@ InstPropertiesPage::update_pcell_parameters () std::vector parameters; - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); db::Cell &cell = cv->layout ().cell (pos->cell_index ()); std::pair pci = cell.is_pcell_instance (pos->back ().inst_ptr); diff --git a/src/edt/edt/edtInstPropertiesPage.h b/src/edt/edt/edtInstPropertiesPage.h index 713dff3286..e9b2f9c81c 100644 --- a/src/edt/edt/edtInstPropertiesPage.h +++ b/src/edt/edt/edtInstPropertiesPage.h @@ -58,7 +58,7 @@ Q_OBJECT void recompute_selection_ptrs (const std::vector &new_sel); protected: - std::vector m_selection_ptrs; + std::vector m_selection_ptrs; std::vector m_indexes; edt::Service *mp_service; bool m_enable_cb_callback; diff --git a/src/edt/edt/edtMainService.cc b/src/edt/edt/edtMainService.cc index 2477a77881..f08334d7c5 100644 --- a/src/edt/edt/edtMainService.cc +++ b/src/edt/edt/edtMainService.cc @@ -310,8 +310,7 @@ MainService::cm_descend () std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end () && common_inst.valid (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::objects::const_iterator sel = selection.begin (); sel != selection.end () && common_inst.valid (); ++sel) { + for (EditableSelectionIterator sel = (*es)->begin_selection (); ! sel.at_end () && common_inst.valid (); ++sel) { common_inst.add (*sel, 1); } } @@ -335,12 +334,10 @@ MainService::cm_descend () for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - new_selections.push_back (std::vector ()); - new_selections.back ().reserve (selection.size ()); + new_selections.back ().reserve ((*es)->selection_size ()); - for (edt::Service::objects::const_iterator sel = selection.begin (); sel != selection.end (); ++sel) { + for (EditableSelectionIterator sel = (*es)->begin_selection (); ! sel.at_end (); ++sel) { new_selections.back ().push_back (*sel); lay::ObjectInstPath &new_sel = new_selections.back ().back (); @@ -378,7 +375,14 @@ MainService::cm_ascend () new_selections.reserve (edt_services.size ()); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { new_selections.push_back (std::vector ()); - new_selections.back ().insert (new_selections.back ().end (), (*es)->selection ().begin (), (*es)->selection ().end ()); + size_t n = 0; + for (EditableSelectionIterator i = (*es)->begin_selection (); ! i.at_end (); ++i) { + ++n; + } + new_selections.back ().reserve (n); + for (EditableSelectionIterator i = (*es)->begin_selection (); ! i.at_end (); ++i) { + new_selections.back ().push_back (*i); + } } // this will clear the selection: @@ -439,8 +443,7 @@ MainService::cm_flatten_insts () std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &sel = (*es)->selection (); - for (edt::Service::objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); if (cv.is_valid ()) { @@ -494,12 +497,10 @@ MainService::cm_move_hier_up () std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - std::vector new_selection; - new_selection.reserve (selection.size ()); + new_selection.reserve ((*es)->selection_size ()); - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); if (cv.is_valid ()) { @@ -731,8 +732,7 @@ MainService::cm_make_cell_variants () // TODO: this limitation is not really necessary, but makes the code somewhat simpler int cv_index = -1; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { if (cv_index < 0) { cv_index = r->cv_index (); } else if (cv_index != int (r->cv_index ())) { @@ -755,8 +755,15 @@ MainService::cm_make_cell_variants () } std::vector new_selection; + size_t n = 0; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - new_selection.insert (new_selection.end (), (*es)->selection ().begin (), (*es)->selection ().end ()); + n += (*es)->selection_size (); + } + new_selection.reserve (n); + for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { + new_selection.push_back (*s); + } } size_t num_sel = new_selection.size (); @@ -916,7 +923,7 @@ MainService::cm_make_cell_variants () // Install the new selection size_t i0 = 0; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - size_t n = (*es)->selection ().size (); + size_t n = (*es)->selection_size (); if (n + i0 <= new_selection.size ()) { (*es)->set_selection (new_selection.begin () + i0, new_selection.begin () + i0 + n); } @@ -944,8 +951,7 @@ MainService::cm_resolve_arefs () int cv_index = -1; - const edt::Service::objects &selection = inst_service->selection (); - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = inst_service->begin_selection (); ! r.at_end (); ++r) { if (r->is_cell_inst () && r->back ().inst_ptr.size () > 1) { if (cv_index < 0) { cv_index = r->cv_index (); @@ -1026,8 +1032,7 @@ MainService::cm_make_cell () int cv_index = -1; std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection () ; - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { if (cv_index < 0) { cv_index = r->cv_index (); } else if (cv_index != int (r->cv_index ())) { @@ -1051,8 +1056,7 @@ MainService::cm_make_cell () db::Box selection_bbox; db::box_convert bc (cv->layout ()); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection () ; - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { if (r->is_cell_inst ()) { selection_bbox += db::ICplxTrans (r->trans ()) * r->back ().bbox (bc); } else { @@ -1086,8 +1090,7 @@ MainService::cm_make_cell () for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection () ; - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { if (r->is_cell_inst ()) { @@ -1148,8 +1151,7 @@ MainService::cm_convert_to_cell () // Do the conversion for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection () ; - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { const lay::CellView &cv = view ()->cellview (s->cv_index ()); db::cell_index_type ci = s->cell_index_tot (); @@ -1219,9 +1221,8 @@ MainService::cm_convert_to_pcell () // check whether the selection contains instances and reject it in that case size_t num_selected = 0; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - num_selected += selection.size (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + num_selected += (*es)->selection_size (); + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->is_cell_inst ()) { throw tl::Exception (tl::to_string (tr ("Selection contains instances - they cannot be converted to PCells."))); } @@ -1243,8 +1244,7 @@ MainService::cm_convert_to_pcell () const db::PCellDeclaration *pc_decl = lib->layout ().pcell_declaration (pc->second); size_t n = 1000; // 1000 tries max. for (std::vector::const_iterator es = edt_services.begin (); n > 0 && pc_decl && es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); n > 0 && pc_decl && s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); n > 0 && pc_decl && ! s.at_end (); ++s) { const lay::CellView &cv = view ()->cellview (s->cv_index ()); if (pc_decl->can_create_from_shape (cv->layout (), s->shape (), s->layer ())) { --n; @@ -1309,7 +1309,7 @@ MainService::cm_convert_to_pcell () manager ()->transaction (tl::to_string (tr ("Convert to PCell"))); } - std::vector to_delete; + std::vector to_delete; std::vector new_selection; bool any_non_converted = false; @@ -1321,8 +1321,7 @@ MainService::cm_convert_to_pcell () // convert the shapes which can be converted for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { const lay::CellView &cv = view ()->cellview (s->cv_index ()); @@ -1346,7 +1345,7 @@ MainService::cm_convert_to_pcell () new_selection.back ().add_path (db::InstElement (cell_inst)); // mark the shape for delete (later) - to_delete.push_back (s); + to_delete.push_back (s.operator-> ()); any_converted = true; @@ -1369,7 +1368,7 @@ MainService::cm_convert_to_pcell () } // Delete the shapes which have been converted - for (std::vector::const_iterator td = to_delete.begin (); td != to_delete.end (); ++td) { + for (std::vector::const_iterator td = to_delete.begin (); td != to_delete.end (); ++td) { db::Cell &cell = view ()->cellview ((*td)->cv_index ())->layout ().cell ((*td)->cell_index ()); if (cell.shapes ((*td)->layer ()).is_valid ((*td)->shape ())) { cell.shapes ((*td)->layer ()).erase_shape ((*td)->shape ()); @@ -1421,8 +1420,7 @@ void MainService::cm_area_perimeter () // get (common) cellview index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->is_cell_inst ()) { continue; @@ -1528,8 +1526,7 @@ MainService::cm_round_corners () // get (common) cellview index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { @@ -1603,8 +1600,7 @@ MainService::cm_round_corners () // Delete the current selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { db::Cell &cell = view ()->cellview (s->cv_index ())->layout ().cell (s->cell_index ()); if (cell.shapes (s->layer ()).is_valid (s->shape ())) { @@ -1664,8 +1660,7 @@ MainService::cm_size () // get (common) cellview index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { @@ -1735,8 +1730,7 @@ MainService::cm_size () // Delete the current selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { db::Cell &cell = view ()->cellview (s->cv_index ())->layout ().cell (s->cell_index ()); if (cell.shapes (s->layer ()).is_valid (s->shape ())) { @@ -1792,8 +1786,7 @@ MainService::boolean_op (int mode) // get (common) cellview index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->seq () == 0 && ! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { @@ -1827,8 +1820,7 @@ MainService::boolean_op (int mode) // get the secondary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->seq () > 0 && ! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { @@ -1865,8 +1857,7 @@ MainService::boolean_op (int mode) // Let's see whether this heuristics is more accepted. for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && int (s->layer ()) == layer_index && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { db::Cell &cell = view ()->cellview (s->cv_index ())->layout ().cell (s->cell_index ()); if (cell.shapes (s->layer ()).is_valid (s->shape ())) { @@ -2001,8 +1992,7 @@ MainService::cm_align () // get (common) bbox index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->seq () == 0) { @@ -2032,13 +2022,11 @@ MainService::cm_align () // do the alignment for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - // create a transformation vector that describes each shape's transformation std::vector tv; - tv.reserve (selection.size ()); + tv.reserve ((*es)->selection_size ()); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { db::DVector v; @@ -2105,8 +2093,7 @@ MainService::cm_distribute () // count the items size_t n = 0; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { ++n; } } @@ -2128,8 +2115,7 @@ MainService::cm_distribute () objects_for_service.push_back (std::make_pair (i, i)); - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { const db::Layout &layout = view ()->cellview (s->cv_index ())->layout (); db::CplxTrans tr = db::CplxTrans (layout.dbu ()) * s->trans (); @@ -2211,8 +2197,7 @@ MainService::cm_make_array () std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { ++n; } } @@ -2246,8 +2231,7 @@ MainService::cm_make_array () for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { const lay::CellView &cv = view ()->cellview (s->cv_index ()); if (! cv.is_valid ()) { @@ -2544,8 +2528,7 @@ MainService::check_no_guiding_shapes () { std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst ()) { if (s->layer () == view ()->cellview (s->cv_index ())->layout ().guiding_shape_layer ()) { throw tl::Exception (tl::to_string (tr ("This function cannot be applied to PCell guiding shapes"))); diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index b1861d9698..f6973af780 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -1182,6 +1182,19 @@ PartialService::timeout () mp_view->clear_transient_selection (); clear_mouse_cursors (); + double le = catch_distance () * 3.0; // see Editables::selection_catch_bbox() + db::DBox sel_catch_box = selection_bbox ().enlarged (db::DVector (le, le)); + if (has_selection () && sel_catch_box.contains (m_hover_point)) { + + // no transient selection if inside current selection - if we click there, we catch the + // currently selected objects + resize_markers (0, true); + resize_inst_markers (0, true); + + return; + + } + // compute search box double l = catch_distance (); db::DBox search_box = db::DBox (m_hover_point, m_hover_point).enlarged (db::DVector (l, l)); @@ -2035,45 +2048,13 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo // select is allowed to throw an exception try { - // compute search box - double l = catch_distance (); - db::DBox search_box = db::DBox (p, p).enlarged (db::DVector (l, l)); - - // check, if there is a selected shape under the mouse - in this case, we do not do a new selection - PartialShapeFinder finder (true /*point mode*/, m_top_level_sel, db::ShapeIterator::All); - finder.find (view (), search_box); - - // check, if there is a selected shape under the mouse - in this case, we do not do a new selection - lay::InstFinder inst_finder (true /*point mode*/, m_top_level_sel, true /*full arrays*/, true /*enclose*/, 0 /*no excludes*/, true /*visible layers*/); - inst_finder.find (view (), search_box); - - // collect the founds from the finder - // consider a new selection if new objects are selected or the current selection is shape-only - // (this may happen if points have been inserted) - bool new_selection = ((finder.begin () == finder.end () && inst_finder.begin () == inst_finder.end ()) - || mode != lay::Editable::Replace); - - for (PartialShapeFinder::iterator f = finder.begin (); f != finder.end () && ! new_selection; ++f) { - partial_objects::const_iterator sel = m_selection.find (f->first); - new_selection = true; - if (sel != m_selection.end ()) { - for (std::vector::const_iterator e = f->second.begin (); e != f->second.end () && new_selection; ++e) { - if (sel->second.find (*e) != sel->second.end ()) { - new_selection = false; - } - } - } - } - - if (finder.begin () == finder.end ()) { - - for (lay::InstFinder::iterator f = inst_finder.begin (); f != inst_finder.end () && ! new_selection; ++f) { - partial_objects::const_iterator sel = m_selection.find (*f); - if (sel == m_selection.end ()) { - new_selection = true; - } - } + bool new_selection = true; + double le = catch_distance () * 3.0; // see Editables::selection_catch_bbox() + db::DBox sel_catch_box = selection_bbox ().enlarged (db::DVector (le, le)); + if (mode == lay::Editable::Replace && has_selection () && sel_catch_box.contains (p)) { + // drag current selection + new_selection = false; } if (new_selection) { @@ -2082,6 +2063,18 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo m_selection.clear (); } + // compute search box + double l = catch_distance (); + db::DBox search_box = db::DBox (p, p).enlarged (db::DVector (l, l)); + + // identify the edges under the mouse + PartialShapeFinder finder (true /*point mode*/, m_top_level_sel, db::ShapeIterator::All); + finder.find (view (), search_box); + + // identify the instances under the mouse + lay::InstFinder inst_finder (true /*point mode*/, m_top_level_sel, true /*full arrays*/, true /*enclose*/, 0 /*no excludes*/, true /*visible layers*/); + inst_finder.find (view (), search_box); + // clear the selection if we now select a guiding shape or if it was consisting of a guiding shape before // (that way we ensure there is only a guiding shape selected) PartialShapeFinder::iterator f0 = finder.begin (); diff --git a/src/edt/edt/edtPropertiesPages.cc b/src/edt/edt/edtPropertiesPages.cc index ac31333c7a..1e4bf2114f 100644 --- a/src/edt/edt/edtPropertiesPages.cc +++ b/src/edt/edt/edtPropertiesPages.cc @@ -47,10 +47,9 @@ ShapePropertiesPage::ShapePropertiesPage (const std::string &description, edt::S : lay::PropertiesPage (parent, manager, service), m_description (description), mp_service (service), m_enable_cb_callback (true) { - const edt::Service::objects &selection = service->selection (); - m_selection_ptrs.reserve (selection.size ()); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { - m_selection_ptrs.push_back (s); + m_selection_ptrs.reserve (service->selection_size ()); + for (edt::EditableSelectionIterator s = service->begin_selection (); ! s.at_end (); ++s) { + m_selection_ptrs.push_back (s.operator-> ()); } m_prop_id = 0; mp_service->clear_highlights (); @@ -191,7 +190,7 @@ ShapePropertiesPage::update () { std::set highlights; for (auto i = m_indexes.begin (); i != m_indexes.end (); ++i) { - highlights.insert (m_selection_ptrs [*i].operator-> ()); + highlights.insert (m_selection_ptrs [*i]); } mp_service->highlight (highlights); @@ -201,16 +200,15 @@ ShapePropertiesPage::update () void ShapePropertiesPage::recompute_selection_ptrs (const std::vector &new_sel) { - std::map ptrs; + std::map ptrs; - const edt::Service::objects &selection = mp_service->selection (); - for (edt::Service::obj_iterator pos = selection.begin (); pos != selection.end (); ++pos) { - ptrs.insert (std::make_pair (*pos, pos)); + for (EditableSelectionIterator pos = mp_service->begin_selection (); ! pos.at_end (); ++pos) { + ptrs.insert (std::make_pair (*pos, pos.operator-> ())); } m_selection_ptrs.clear (); for (std::vector::const_iterator s = new_sel.begin (); s != new_sel.end (); ++s) { - std::map::const_iterator pm = ptrs.find (*s); + std::map::const_iterator pm = ptrs.find (*s); tl_assert (pm != ptrs.end ()); m_selection_ptrs.push_back (pm->second); } @@ -228,7 +226,7 @@ ShapePropertiesPage::do_apply (bool current_only, bool relative) unsigned int cv_index = m_selection_ptrs [m_indexes.front ()]->cv_index (); { - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; tl_assert (! pos->is_cell_inst ()); const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); @@ -261,7 +259,7 @@ ShapePropertiesPage::do_apply (bool current_only, bool relative) std::vector new_sel; new_sel.reserve (m_selection_ptrs.size ()); - for (std::vector::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) { + for (std::vector::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) { new_sel.push_back (**p); } @@ -274,7 +272,7 @@ ShapePropertiesPage::do_apply (bool current_only, bool relative) for (auto i = m_indexes.begin (); i != m_indexes.end (); ++i) { size_t index = *i; - edt::Service::obj_iterator pos = m_selection_ptrs [*i]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [*i]; // only update objects from the same layout - this is not practical limitation but saves a lot of effort for // managing different property id's etc. @@ -376,7 +374,7 @@ ShapePropertiesPage::update_shape () return; } - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); double dbu = cv->layout ().dbu (); diff --git a/src/edt/edt/edtPropertiesPages.h b/src/edt/edt/edtPropertiesPages.h index ae3ffbd2e5..21a22aaaf9 100644 --- a/src/edt/edt/edtPropertiesPages.h +++ b/src/edt/edt/edtPropertiesPages.h @@ -71,7 +71,7 @@ Q_OBJECT protected: std::string m_description; - std::vector m_selection_ptrs; + std::vector m_selection_ptrs; std::vector m_indexes; edt::Service *mp_service; bool m_enable_cb_callback; diff --git a/src/edt/edt/edtService.cc b/src/edt/edt/edtService.cc index d0f766888f..a39fdcd02a 100644 --- a/src/edt/edt/edtService.cc +++ b/src/edt/edt/edtService.cc @@ -459,11 +459,10 @@ Service::copy_selected () unsigned int inst_mode = 0; if (m_hier_copy_mode < 0) { - const objects &sel = selection (); - for (objects::const_iterator r = sel.begin (); r != sel.end () && ! need_to_ask_for_copy_mode; ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end () && ! need_to_ask_for_copy_mode; ++r) { if (r->is_cell_inst ()) { const db::Cell &cell = view ()->cellview (r->cv_index ())->layout ().cell (r->back ().inst_ptr.cell_index ()); - if (! cell.is_proxy ()) { + if (! cell.is_proxy () && ! cell.is_leaf ()) { need_to_ask_for_copy_mode = true; } } @@ -500,12 +499,10 @@ Service::copy_selected () void Service::copy_selected (unsigned int inst_mode) { - const objects &sel = selection (); - // create one ClipboardData object per cv_index because, this one assumes that there is // only one source layout object. std::set cv_indices; - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { cv_indices.insert (r->cv_index ()); } @@ -515,7 +512,7 @@ Service::copy_selected (unsigned int inst_mode) // add the selected objects to the clipboard data objects. const lay::CellView &cv = view ()->cellview (*cvi); - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { if (r->cv_index () == *cvi) { if (! r->is_cell_inst ()) { cd->get ().add (cv->layout (), r->layer (), r->shape (), cv.context_trans () * r->trans ()); @@ -618,8 +615,7 @@ Service::selection_bbox () db::DBox box; - const objects &sel = selection (); - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); const db::Layout &layout = cv->layout (); @@ -699,13 +695,10 @@ Service::transform (const db::DCplxTrans &trans, const std::vector obj_ptrs; - obj_ptrs.reserve (sel.size ()); - for (objects::iterator r = sel.begin (); r != sel.end (); ++r) { - obj_ptrs.push_back (r); + std::vector obj_ptrs; + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { + obj_ptrs.push_back (r.operator-> ()); } // build the transformation variants cache @@ -717,7 +710,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vector >, std::vector > shapes_by_cell; n = 0; - for (objects::iterator r = sel.begin (); r != sel.end (); ++r, ++n) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r, ++n) { if (! r->is_cell_inst ()) { shapes_by_cell.insert (std::make_pair (std::make_pair (r->cell_index (), std::make_pair (r->cv_index (), r->layer ())), std::vector ())).first->second.push_back (n); } @@ -740,7 +733,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vector::iterator si = sbc->second.begin (); si != sbc->second.end (); ++si) { - objects::iterator s = obj_ptrs [*si]; + EditableSelectionIterator::pointer s = obj_ptrs [*si]; // mt = transformation in DBU units db::ICplxTrans mt; @@ -764,14 +757,14 @@ Service::transform (const db::DCplxTrans &trans, const std::vector::iterator si = sbc->second.begin (); si != sbc->second.end (); ++si) { - objects::iterator &s = obj_ptrs [*si]; + EditableSelectionIterator::pointer &s = obj_ptrs [*si]; lay::ObjectInstPath new_path (*s); new_path.set_shape (new_shapes.find (s->shape ())->second); // modify the selection - m_selection.erase (s); - s = m_selection.insert (new_path).first; + m_selection.erase (*s); + s = m_selection.insert (new_path).first.operator-> (); } @@ -787,7 +780,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vector, std::vector > insts_by_cell; n = 0; - for (objects::iterator r = sel.begin (); r != sel.end (); ++r, ++n) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r, ++n) { if (r->is_cell_inst ()) { insts_by_cell.insert (std::make_pair (std::make_pair (r->cell_index (), r->cv_index ()), std::vector ())).first->second.push_back (n); } @@ -810,7 +803,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vector::iterator ii = ibc->second.begin (); ii != ibc->second.end (); ++ii) { - objects::iterator i = obj_ptrs [*ii]; + EditableSelectionIterator::pointer i = obj_ptrs [*ii]; // mt = transformation in DBU units db::ICplxTrans mt; @@ -836,14 +829,14 @@ Service::transform (const db::DCplxTrans &trans, const std::vector::iterator ii = ibc->second.begin (); ii != ibc->second.end (); ++ii) { - objects::iterator &i = obj_ptrs [*ii]; + EditableSelectionIterator::pointer &i = obj_ptrs [*ii]; lay::ObjectInstPath new_path (*i); new_path.back ().inst_ptr = new_insts.find (i->back ().inst_ptr)->second; // modify the selection - m_selection.erase (i); - i = m_selection.insert (new_path).first; + m_selection.erase (*i); + i = m_selection.insert (new_path).first.operator-> (); } @@ -1039,8 +1032,7 @@ Service::del_selected () std::set needs_cleanup; // delete all shapes and instances. - const objects &sel = selection (); - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); if (cv.is_valid ()) { db::Cell &cell = cv->layout ().cell (r->cell_index ()); @@ -1326,11 +1318,12 @@ static std::string path_to_string (const db::Layout &layout, const lay::ObjectIn void Service::display_status (bool transient) { - const objects *sel = transient ? &transient_selection () : &selection (); + EditableSelectionIterator r = transient ? begin_transient_selection () : begin_selection (); + EditableSelectionIterator rr = r; + ++rr; - if (sel->size () == 1) { + if (rr.at_end ()) { - objects::const_iterator r = sel->begin (); const db::Layout &layout = view ()->cellview (r->cv_index ())->layout (); if (m_cell_inst_service) { @@ -1606,6 +1599,18 @@ Service::transient_selection () const return m_transient_selection; } +EditableSelectionIterator +Service::begin_selection () const +{ + return EditableSelectionIterator (this, false); +} + +EditableSelectionIterator +Service::begin_transient_selection () const +{ + return EditableSelectionIterator (this, true); +} + bool Service::select (const lay::ObjectInstPath &obj, lay::Editable::SelectionMode mode) { @@ -1730,17 +1735,15 @@ Service::selection_to_view () void Service::do_selection_to_view () { - const objects &sel = selection (); - // Hint: this is a lower bound: - m_markers.reserve (sel.size ()); + m_markers.reserve (selection_size ()); // build the transformation variants cache TransformationVariants tv (view ()); // Build markers - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); @@ -1939,12 +1942,14 @@ Service::handle_guiding_shape_changes (const lay::ObjectInstPath &obj) const bool Service::handle_guiding_shape_changes () { + EditableSelectionIterator s = begin_selection (); + // just allow one guiding shape to be selected - if (selection ().empty ()) { + if (s.at_end ()) { return false; } - std::pair gs = handle_guiding_shape_changes (*selection ().begin ()); + std::pair gs = handle_guiding_shape_changes (*s); if (gs.first) { // remove superfluous proxies @@ -1966,7 +1971,20 @@ Service::handle_guiding_shape_changes () // Implementation of EditableSelectionIterator EditableSelectionIterator::EditableSelectionIterator (const std::vector &services, bool transient) - : m_services (services), m_service (0), m_transient_selection (transient) + : m_services (services.begin (), services.end ()), m_service (0), m_transient_selection (transient) +{ + init (); +} + +EditableSelectionIterator::EditableSelectionIterator (const edt::Service *service, bool transient) + : m_services (), m_service (0), m_transient_selection (transient) +{ + m_services.push_back (service); + init (); +} + +void +EditableSelectionIterator::init () { if (! m_services.empty ()) { if (m_transient_selection) { diff --git a/src/edt/edt/edtService.h b/src/edt/edt/edtService.h index dc0250f81b..b6f65ae827 100644 --- a/src/edt/edt/edtService.h +++ b/src/edt/edt/edtService.h @@ -71,6 +71,41 @@ std::map pcell_parameters_from_string (const std::stri // ------------------------------------------------------------- +/** + * @brief A utility class to implement a selection iterator across all editor services + */ +class EDT_PUBLIC EditableSelectionIterator +{ +public: + typedef std::set objects; + typedef objects::value_type value_type; + typedef objects::const_iterator iterator_type; + typedef const value_type *pointer; + typedef const value_type &reference; + typedef std::forward_iterator_tag iterator_category; + typedef void difference_type; + + EditableSelectionIterator (const std::vector &services, bool transient); + EditableSelectionIterator (const edt::Service *service, bool transient); + + bool at_end () const; + + EditableSelectionIterator &operator++ (); + reference operator* () const; + pointer operator-> () const; + +private: + std::vector m_services; + unsigned int m_service; + bool m_transient_selection; + iterator_type m_iter, m_end; + + void next (); + void init (); +}; + +// ------------------------------------------------------------- + class EDT_PUBLIC Service : public lay::EditorServiceBase, public db::Object @@ -226,16 +261,6 @@ class EDT_PUBLIC Service return m_color; } - /** - * @brief Get the selection container - */ - const objects &selection () const; - - /** - * @brief Get the transient selection container - */ - const objects &transient_selection () const; - /** * @brief Access to the view object */ @@ -257,11 +282,21 @@ class EDT_PUBLIC Service */ void clear_previous_selection (); + /** + * @brief Gets the selection iterator + */ + EditableSelectionIterator begin_selection () const; + /** * @brief Establish a transient selection */ bool transient_select (const db::DPoint &pos); + /** + * @brief Gets the transient selection iterator + */ + EditableSelectionIterator begin_transient_selection () const; + /** * @brief Turns the transient selection to the selection */ @@ -585,6 +620,8 @@ class EDT_PUBLIC Service lay::PointSnapToObjectResult snap2_details (const db::DPoint &p) const; private: + friend class EditableSelectionIterator; + // The layout view that the editor service is attached to lay::LayoutViewBase *mp_view; @@ -685,6 +722,16 @@ class EDT_PUBLIC Service */ void apply_highlights (); + /** + * @brief Get the selection container + */ + const objects &selection () const; + + /** + * @brief Get the transient selection container + */ + const objects &transient_selection () const; + private: void copy_selected (unsigned int inst_mode); void update_vector_snapped_point (const db::DPoint &pt, db::DVector &vr, bool &result_set) const; @@ -692,36 +739,6 @@ class EDT_PUBLIC Service void update_vector_snapped_marker (const lay::InstanceMarker *sm, const db::DTrans &trans, db::DVector &vr, bool &result_set, size_t &count) const; }; -/** - * @brief A utility class to implement a selection iterator across all editor services - */ -class EditableSelectionIterator -{ -public: - typedef edt::Service::objects::value_type value_type; - typedef edt::Service::objects::const_iterator iterator_type; - typedef const value_type *pointer; - typedef const value_type &reference; - typedef std::forward_iterator_tag iterator_category; - typedef void difference_type; - - EditableSelectionIterator (const std::vector &services, bool transient); - - bool at_end () const; - - EditableSelectionIterator &operator++ (); - reference operator* () const; - pointer operator-> () const; - -private: - std::vector m_services; - unsigned int m_service; - bool m_transient_selection; - iterator_type m_iter, m_end; - - void next (); -}; - /** * @brief Gets the combined selections over all editor services in the layout view */ diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index a087bab372..b381149152 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -94,16 +94,23 @@ namespace lay // -------------------------------------------------------------------------------- // Exception handlers -static void ui_exception_handler_tl (const tl::Exception &ex, QWidget *parent) +static void close_transaction () { - // Prevents severe side effects if there are pending deferred methods - tl::NoDeferredMethods silent; - // if any transaction is pending (this may happen when an operation threw an exception) // close transactions. - if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) { + // NOTE: don't do this in breakpoint mode as we do not want to interfere with things happening outside + if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting () && + !(lay::MacroEditorDialog::instance () && lay::MacroEditorDialog::instance ()->in_breakpoint ())) { lay::MainWindow::instance ()->manager ().commit (); } +} + +static void ui_exception_handler_tl (const tl::Exception &ex, QWidget *parent) +{ + // Prevents severe side effects if there are pending deferred methods + tl::NoDeferredMethods silent; + + close_transaction (); const tl::ExitException *gsi_exit = dynamic_cast (&ex); const tl::BreakException *gsi_break = dynamic_cast (&ex); @@ -155,13 +162,9 @@ static void ui_exception_handler_std (const std::exception &ex, QWidget *parent) // Prevents severe side effects if there are pending deferred methods tl::NoDeferredMethods silent; - // if any transaction is pending (this may happen when an operation threw an exception) - // close transactions. - if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) { - lay::MainWindow::instance ()->manager ().commit (); - } + close_transaction (); - tl::error << ex.what (); + tl::error << ex.what (); if (! parent) { parent = QApplication::activeWindow () ? QApplication::activeWindow () : lay::MainWindow::instance (); } @@ -173,11 +176,7 @@ static void ui_exception_handler_def (QWidget *parent) // Prevents severe side effects if there are pending deferred methods tl::NoDeferredMethods silent; - // if any transaction is pending (this may happen when an operation threw an exception) - // close transactions. - if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) { - lay::MainWindow::instance ()->manager ().commit (); - } + close_transaction (); if (! parent) { parent = QApplication::activeWindow () ? QApplication::activeWindow () : lay::MainWindow::instance (); diff --git a/src/lay/lay/layFillDialog.cc b/src/lay/lay/layFillDialog.cc index e37bf4cce7..6c6d267b85 100644 --- a/src/lay/lay/layFillDialog.cc +++ b/src/lay/lay/layFillDialog.cc @@ -408,7 +408,7 @@ FillDialog::get_fill_parameters () // selection std::vector edt_services = mp_view->get_plugins (); for (std::vector::const_iterator s = edt_services.begin (); s != edt_services.end (); ++s) { - for (edt::Service::objects::const_iterator sel = (*s)->selection ().begin (); sel != (*s)->selection ().end (); ++sel) { + for (edt::EditableSelectionIterator sel = (*s)->begin_selection (); ! sel.at_end (); ++sel) { if (! sel->is_cell_inst () && (sel->shape ().is_polygon () || sel->shape ().is_path () || sel->shape ().is_box ())) { db::Polygon poly; sel->shape ().polygon (poly); diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index 08df2a280a..bad1a9db20 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -1511,26 +1511,25 @@ MacroEditorDialog::eventFilter (QObject *obj, QEvent *event) if (lay::BusySection::is_busy ()) { -#if 0 - if (dynamic_cast (event) != 0 || dynamic_cast (event) != 0) { + if (m_in_breakpoint && (dynamic_cast (event) != 0 || dynamic_cast (event) != 0)) { - // In breakpoint or execution mode and while processing the events inside the debugger, + // In breakpoint mode and while processing the events inside the debugger, // ignore all input or paint events targeted to widgets which are not children of this or the assistant dialog. // Ignoring the paint event is required because otherwise a repaint action would be triggered on a layout which // is potentially unstable or inconsistent. // We nevertheless allow events send to a HelpDialog or ProgressWidget since those are vital for the application's // functionality and are known not to cause any interference. QObject *rec = obj; - while (rec && (rec != this && !dynamic_cast (rec) && !dynamic_cast (rec))) { + while (rec && rec != this) { rec = rec->parent (); } if (! rec) { // TODO: reschedule the paint events (?) + event->accept (); return true; } } -#endif } else { @@ -2267,19 +2266,35 @@ END_PROTECTED } void -MacroEditorDialog::help_requested(const QString &s) +MacroEditorDialog::help_requested (const QString &s) { +BEGIN_PROTECTED + // Do not allow modal popups in breakpoint mode - this would interfere with + // event filtering during breakpoint execution + if (m_in_breakpoint) { + throw tl::Exception (tl::to_string (tr ("The help function is not available in breakpoint mode."))); + } + lay::MainWindow::instance ()->show_assistant_topic (tl::to_string (s)); +END_PROTECTED } void -MacroEditorDialog::help_button_clicked() +MacroEditorDialog::help_button_clicked () { +BEGIN_PROTECTED + // Do not allow modal popups in breakpoint mode - this would interfere with + // event filtering during breakpoint execution + if (m_in_breakpoint) { + throw tl::Exception (tl::to_string (tr ("The help function is not available in breakpoint mode."))); + } + lay::MainWindow::instance ()->show_assistant_url ("int:/code/index.xml"); +END_PROTECTED } void -MacroEditorDialog::add_button_clicked() +MacroEditorDialog::add_button_clicked () { BEGIN_PROTECTED new_macro (); @@ -2287,7 +2302,7 @@ END_PROTECTED } lym::Macro * -MacroEditorDialog::new_macro() +MacroEditorDialog::new_macro () { ensure_writeable_collection_selected (); @@ -3451,6 +3466,12 @@ MacroEditorDialog::leave_breakpoint_mode () mp_current_interpreter = 0; do_update_ui_to_run_mode (); set_exec_point (0, -1, -1); + + // refresh UI that might have been spoiled because we filter events + auto tl_widgets = QApplication::topLevelWidgets (); + for (auto w = tl_widgets.begin (); w != tl_widgets.end (); ++w) { + (*w)->update (); + } } void diff --git a/src/lay/lay/layMacroEditorDialog.h b/src/lay/lay/layMacroEditorDialog.h index 0018299249..85befea085 100644 --- a/src/lay/lay/layMacroEditorDialog.h +++ b/src/lay/lay/layMacroEditorDialog.h @@ -166,6 +166,14 @@ class MacroEditorDialog return m_in_exec; } + /** + * @brief Returns true while the macro IDE is in breakpoint mode + */ + bool in_breakpoint () const + { + return m_in_breakpoint; + } + /** * @brief Selects the current category in the tree view */ diff --git a/src/laybasic/laybasic/layLayoutViewConfig.cc b/src/laybasic/laybasic/layLayoutViewConfig.cc index 6956ea9e43..d1372d9664 100644 --- a/src/laybasic/laybasic/layLayoutViewConfig.cc +++ b/src/laybasic/laybasic/layLayoutViewConfig.cc @@ -122,6 +122,7 @@ class LayoutViewBasicConfigDeclaration options.push_back (std::pair (cfg_line_style_palette, lay::LineStylePalette ().to_string ())); options.push_back (std::pair (cfg_no_stipple, "false")); options.push_back (std::pair (cfg_markers_visible, "true")); + options.push_back (std::pair (cfg_copy_cell_mode, "-1")); } }; diff --git a/src/laybasic/laybasic/laybasicConfig.h b/src/laybasic/laybasic/laybasicConfig.h index 6fc5476695..9fb24ee863 100644 --- a/src/laybasic/laybasic/laybasicConfig.h +++ b/src/laybasic/laybasic/laybasicConfig.h @@ -132,6 +132,7 @@ static const std::string cfg_default_font_size ("default-font-size"); static const std::string cfg_hide_empty_layers ("hide-empty-layers"); static const std::string cfg_test_shapes_in_view ("test-shapes-in-view"); +static const std::string cfg_copy_cell_mode ("copy-cell-mode"); static const std::string cfg_flat_cell_list ("flat-cell-list"); static const std::string cfg_split_cell_list ("split-cell-list"); static const std::string cfg_cell_list_sorting ("cell-list-sorting"); diff --git a/src/layui/layui/CopyCellModeDialog.ui b/src/layui/layui/CopyCellModeDialog.ui index 3f8497c35a..6dfe18c4c4 100644 --- a/src/layui/layui/CopyCellModeDialog.ui +++ b/src/layui/layui/CopyCellModeDialog.ui @@ -1,46 +1,41 @@ - + + CopyCellModeDialog - - + + 0 0 - 400 - 178 + 546 + 198 - + Copy Cell Options - - - 9 - - - 6 - + - - + + Copy Cell Mode - - - 9 - - + + 6 + + 9 + - - + + Shallow copy (don't copy subcells) - - + + Deep copy (include subcells) @@ -48,12 +43,19 @@ + + + + Don't ask again (you can always reset this in Setup: Application/Cells page) + + + - + Qt::Vertical - + 382 31 @@ -62,12 +64,12 @@ - - + + Qt::Horizontal - - QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok @@ -86,11 +88,11 @@ CopyCellModeDialog accept() - + 248 254 - + 157 274 @@ -102,11 +104,11 @@ CopyCellModeDialog reject() - + 316 260 - + 286 274 diff --git a/src/layui/layui/LayoutViewConfigPage8.ui b/src/layui/layui/LayoutViewConfigPage8.ui new file mode 100644 index 0000000000..9c15bae729 --- /dev/null +++ b/src/layui/layui/LayoutViewConfigPage8.ui @@ -0,0 +1,69 @@ + + + LayoutViewConfigPage8 + + + + 0 + 0 + 414 + 46 + + + + Form + + + + + + Cell copy mode + + + + + + + + 1 + 0 + + + + QComboBox::AdjustToContents + + + + Shallow mode (cell only) + + + + + Deep mode (cell and subcells) + + + + + Ask + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff --git a/src/layui/layui/layDialogs.cc b/src/layui/layui/layDialogs.cc index 748c205206..b1c60d1c41 100644 --- a/src/layui/layui/layDialogs.cc +++ b/src/layui/layui/layDialogs.cc @@ -579,7 +579,7 @@ CopyCellModeDialog::~CopyCellModeDialog () } bool -CopyCellModeDialog::exec_dialog (int ©_mode) +CopyCellModeDialog::exec_dialog (int ©_mode, bool &dont_ask) { QRadioButton *buttons [] = { mp_ui->shallow_rb, mp_ui->deep_rb }; @@ -592,6 +592,7 @@ CopyCellModeDialog::exec_dialog (int ©_mode) if (buttons [i]->isChecked ()) { copy_mode = i; } + dont_ask = mp_ui->dont_ask_cbx->isChecked (); } return true; } else { diff --git a/src/layui/layui/layDialogs.h b/src/layui/layui/layDialogs.h index 5aefa8a7c5..cea190b366 100644 --- a/src/layui/layui/layDialogs.h +++ b/src/layui/layui/layDialogs.h @@ -233,7 +233,7 @@ Q_OBJECT * * The mode is either 0 (for shallow), 1 (for deep) */ - bool exec_dialog (int ©_mode); + bool exec_dialog (int ©_mode, bool &dont_ask_again); private: Ui::CopyCellModeDialog *mp_ui; diff --git a/src/layui/layui/layHierarchyControlPanel.cc b/src/layui/layui/layHierarchyControlPanel.cc index 417148bee8..ba90fa9978 100644 --- a/src/layui/layui/layHierarchyControlPanel.cc +++ b/src/layui/layui/layHierarchyControlPanel.cc @@ -218,6 +218,7 @@ HierarchyControlPanel::HierarchyControlPanel (lay::LayoutViewBase *view, QWidget m_flat (false), m_split_mode (false), m_sorting (CellTreeModel::ByName), + m_cell_copy_mode (-1), m_do_update_content_dm (this, &HierarchyControlPanel::do_update_content), m_do_full_update_content_dm (this, &HierarchyControlPanel::do_full_update_content) { @@ -398,6 +399,12 @@ HierarchyControlPanel::clear_all () mp_cell_lists.clear (); } +void +HierarchyControlPanel::set_cell_copy_mode (int m) +{ + m_cell_copy_mode = m; +} + void HierarchyControlPanel::set_flat (bool f) { @@ -1002,6 +1009,47 @@ HierarchyControlPanel::has_focus () const return m_active_index >= 0 && m_active_index < int (mp_cell_lists.size ()) && mp_cell_lists [m_active_index]->hasFocus (); } +bool +HierarchyControlPanel::ask_for_cell_copy_mode (const db::Layout &layout, const std::vector &paths, int &cell_copy_mode) +{ + bool needs_to_ask = false; + cell_copy_mode = 0; + + if (m_cell_copy_mode < 0) { // ask + + // check if there is a cell that we have to ask for + for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { + if (! p->empty ()) { + const db::Cell &cell = layout.cell (p->back ()); + if (! cell.is_proxy () && ! cell.is_leaf ()) { + needs_to_ask = true; + } + } + } + + } else { + cell_copy_mode = m_cell_copy_mode; + } + + if (needs_to_ask) { + + bool dont_ask_again = false; + + lay::CopyCellModeDialog mode_dialog (this); + if (! mode_dialog.exec_dialog (cell_copy_mode, dont_ask_again)) { + return false; + } + + if (dont_ask_again) { + view ()->dispatcher ()->config_set (cfg_copy_cell_mode, tl::to_string (cell_copy_mode)); + view ()->dispatcher ()->config_end (); + } + + } + + return true; +} + void HierarchyControlPanel::cut () { @@ -1017,34 +1065,25 @@ HierarchyControlPanel::cut () } // first copy - bool needs_to_ask = false; db::Layout &layout = m_cellviews [m_active_index]->layout (); if (! layout.is_editable ()) { return; } - // collect the called cells of the cells to copy, so we don't copy a cell twice - db::Clipboard::instance ().clear (); - // don't copy the cells which would be copied anyway + int cut_mode = 1; // 0: shallow, 1: deep + if (! ask_for_cell_copy_mode (layout, paths, cut_mode)) { + return; + } + + // collect the called cells of the cells to copy, so we don't copy a cell twice std::set called_cells; for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { if (! p->empty ()) { const db::Cell &cell = layout.cell (p->back ()); cell.collect_called_cells (called_cells); - if (cell.cell_instances () > 0) { - needs_to_ask = true; - } - } - } - - int cut_mode = 1; // 0: shallow, 1: deep - if (needs_to_ask) { - lay::CopyCellModeDialog mode_dialog (this); - if (! mode_dialog.exec_dialog (cut_mode)) { - return; } } @@ -1115,34 +1154,25 @@ HierarchyControlPanel::copy () return; } - bool needs_to_ask = false; - db::Layout &layout = m_cellviews [m_active_index]->layout (); - // collect the called cells of the cells to copy, so we don't copy a cell twice - db::Clipboard::instance ().clear (); - // don't copy the cells which would be copied anyway + int copy_mode = 1; // 0: shallow, 1: deep + if (! ask_for_cell_copy_mode (layout, paths, copy_mode)) { + return; + } + + // collect the called cells of the cells to copy, so we don't copy a cell twice std::set called_cells; for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { if (! p->empty ()) { const db::Cell &cell = layout.cell (p->back ()); cell.collect_called_cells (called_cells); - if (cell.cell_instances () > 0) { - needs_to_ask = true; - } - } - } - - int copy_mode = 1; // 0: shallow, 1: deep - if (needs_to_ask) { - lay::CopyCellModeDialog mode_dialog (this); - if (! mode_dialog.exec_dialog (copy_mode)) { - return; } } + // actually copy for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { if (! p->empty () && called_cells.find (p->back ()) == called_cells.end ()) { db::ClipboardValue *cd = new db::ClipboardValue (); diff --git a/src/layui/layui/layHierarchyControlPanel.h b/src/layui/layui/layHierarchyControlPanel.h index 6801728d8f..2053628d12 100644 --- a/src/layui/layui/layHierarchyControlPanel.h +++ b/src/layui/layui/layHierarchyControlPanel.h @@ -219,6 +219,12 @@ Q_OBJECT */ void paste (); + /** + * @brief Selects cell copy mode + * 0: shallow, 1: deep, -1: ask + */ + void set_cell_copy_mode (int m); + /** * @brief Return true, if the panel has a selection */ @@ -308,6 +314,7 @@ public slots: QSplitter *mp_splitter; tl::Color m_background_color; tl::Color m_text_color; + int m_cell_copy_mode; tl::DeferredMethod m_do_update_content_dm; tl::DeferredMethod m_do_full_update_content_dm; std::unique_ptr mp_tree_style; @@ -336,6 +343,9 @@ public slots: // clears all widgets of the cell lists void clear_all (); + + // ask for cell copy mode + bool ask_for_cell_copy_mode (const db::Layout &layout, const std::vector &paths, int &cell_copy_mode); }; } // namespace lay diff --git a/src/layui/layui/layLayoutViewConfigPages.cc b/src/layui/layui/layLayoutViewConfigPages.cc index c7f57f806f..81d7cbf1e2 100644 --- a/src/layui/layui/layLayoutViewConfigPages.cc +++ b/src/layui/layui/layLayoutViewConfigPages.cc @@ -42,6 +42,7 @@ #include "ui_LayoutViewConfigPage6.h" #include "ui_LayoutViewConfigPage6a.h" #include "ui_LayoutViewConfigPage7.h" +#include "ui_LayoutViewConfigPage8.h" #include "laySelectStippleForm.h" #include "laySelectLineStyleForm.h" @@ -1529,6 +1530,37 @@ LayoutViewConfigPage7::commit (lay::Dispatcher *root) root->config_set (cfg_initial_hier_depth, mp_ui->def_depth->value ()); } +// ------------------------------------------------------------ +// LayoutConfigPage8 implementation + +LayoutViewConfigPage8::LayoutViewConfigPage8 (QWidget *parent) + : lay::ConfigPage (parent) +{ + mp_ui = new Ui::LayoutViewConfigPage8 (); + mp_ui->setupUi (this); +} + +LayoutViewConfigPage8::~LayoutViewConfigPage8 () +{ + delete mp_ui; + mp_ui = 0; +} + +void +LayoutViewConfigPage8::setup (lay::Dispatcher *root) +{ + int cpm = -1; + root->config_get (cfg_copy_cell_mode, cpm); + mp_ui->hier_copy_mode_cbx->setCurrentIndex ((cpm < 0 || cpm > 1) ? 2 : cpm); +} + +void +LayoutViewConfigPage8::commit (lay::Dispatcher *root) +{ + int cpm = mp_ui->hier_copy_mode_cbx->currentIndex (); + root->config_set (cfg_copy_cell_mode, (cpm < 0 || cpm > 1) ? -1 : cpm); +} + // ------------------------------------------------------------ // The dummy plugin declaration to register the configuration options @@ -1554,6 +1586,7 @@ class LayoutViewConfigDeclaration pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Tracking")), new LayoutViewConfigPage2d (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Layer Properties")), new LayoutViewConfigPage5 (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Units")), new LayoutViewConfigPage3c (parent))); + pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Cells")), new LayoutViewConfigPage8 (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Navigation|New Cell")), new LayoutViewConfigPage3a (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Navigation|Zoom And Pan")), new LayoutViewConfigPage3b (parent))); diff --git a/src/layui/layui/layLayoutViewConfigPages.h b/src/layui/layui/layLayoutViewConfigPages.h index ab6cbece6d..3d6c4718d0 100644 --- a/src/layui/layui/layLayoutViewConfigPages.h +++ b/src/layui/layui/layLayoutViewConfigPages.h @@ -50,6 +50,7 @@ namespace Ui { class LayoutViewConfigPage6; class LayoutViewConfigPage6a; class LayoutViewConfigPage7; + class LayoutViewConfigPage8; } namespace lay @@ -355,6 +356,22 @@ Q_OBJECT Ui::LayoutViewConfigPage7 *mp_ui; }; +class LayoutViewConfigPage8 + : public lay::ConfigPage +{ +Q_OBJECT + +public: + LayoutViewConfigPage8 (QWidget *parent); + ~LayoutViewConfigPage8 (); + + virtual void setup (lay::Dispatcher *root); + virtual void commit (lay::Dispatcher *root); + +private: + Ui::LayoutViewConfigPage8 *mp_ui; +}; + } #endif diff --git a/src/layui/layui/layui.pro b/src/layui/layui/layui.pro index 00099e9140..ed6ac69a0c 100644 --- a/src/layui/layui/layui.pro +++ b/src/layui/layui/layui.pro @@ -40,6 +40,7 @@ FORMS = \ LayoutViewConfigPage6.ui \ LayoutViewConfigPage7.ui \ LayoutViewConfigPage.ui \ + LayoutViewConfigPage8.ui \ LibraryCellSelectionForm.ui \ LoadLayoutOptionsDialog.ui \ MarkerBrowserConfigPage2.ui \ diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index e30283dba8..a63b74492f 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -938,6 +938,15 @@ LayoutView::configure (const std::string &name, const std::string &value) } return true; + } else if (name == cfg_copy_cell_mode) { + + if (mp_hierarchy_panel) { + int m = 0; + tl::from_string (value, m); + mp_hierarchy_panel->set_cell_copy_mode (m); + } + return true; + } else if (name == cfg_cell_list_sorting) { if (mp_hierarchy_panel) { diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc index 51f8ea1ab7..ee3885303a 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc @@ -140,7 +140,7 @@ NetTracerTechComponentEditor::NetTracerTechComponentEditor (QWidget *parent) connect (action, SIGNAL (triggered ()), this, SLOT (add_clicked ())); stack_tree->addAction (action); action = new QAction (QObject::tr ("Delete Selected Stacks"), this); - connect (action, SIGNAL (triggered ()), this, SLOT (delete_clicked ())); + connect (action, SIGNAL (triggered ()), this, SLOT (del_clicked ())); stack_tree->addAction (action); action = new QAction (QObject::tr ("Duplicate Stack"), this); connect (action, SIGNAL (triggered ()), this, SLOT (clone_clicked ())); diff --git a/src/pya/pya/pya.cc b/src/pya/pya/pya.cc index 89ae80c547..5f177fa56e 100644 --- a/src/pya/pya/pya.cc +++ b/src/pya/pya/pya.cc @@ -684,7 +684,11 @@ PythonInterpreter::trace_func (PyFrameObject *frame, int event, PyObject *arg) exc_value = PythonPtr (PyTuple_GetItem (arg, 1)); } - if (exc_type && exc_type.get () != PyExc_StopIteration) { +#if PY_VERSION_HEX >= 0x03050000 + if (exc_type && exc_type.get () != PyExc_StopIteration && exc_type.get () != PyExc_GeneratorExit && exc_type.get () != PyExc_StopAsyncIteration) { +#else + if (exc_type && exc_type.get () != PyExc_StopIteration && exc_type.get () != PyExc_GeneratorExit) { +#endif // If the next exception shall be ignored, do so if (m_ignore_next_exception) { diff --git a/src/pya/pya/pyaConvert.cc b/src/pya/pya/pyaConvert.cc index d80af95e94..017d030e21 100644 --- a/src/pya/pya/pyaConvert.cc +++ b/src/pya/pya/pyaConvert.cc @@ -533,8 +533,13 @@ PyObject *c2python_func::operator() (const tl::Variant &c) const gsi::ClassBase *cls = c.gsi_cls (); if (cls) { - void *obj = const_cast (c.to_user ()); - return object_to_python (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false); + if (! c.user_is_ref () && cls->is_managed ()) { + void *obj = c.user_unshare (); + return object_to_python (obj, 0, c.user_cls ()->gsi_cls (), true, c.user_is_const (), false, false); + } else { + void *obj = const_cast (c.to_user ()); + return object_to_python (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false); + } } else { // not a known type -> return nil Py_RETURN_NONE; diff --git a/src/rba/rba/rbaConvert.cc b/src/rba/rba/rbaConvert.cc index b803b64509..2a05a69cfc 100644 --- a/src/rba/rba/rbaConvert.cc +++ b/src/rba/rba/rbaConvert.cc @@ -287,8 +287,13 @@ VALUE c2ruby (const tl::Variant &c) } else if (c.is_user ()) { const gsi::ClassBase *cls = c.gsi_cls (); if (cls) { - void *obj = const_cast (c.to_user ()); - return object_to_ruby (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false); + if (! c.user_is_ref () && cls->is_managed ()) { + void *obj = c.user_unshare (); + return object_to_ruby (obj, 0, c.user_cls ()->gsi_cls (), true, c.user_is_const (), false, false); + } else { + void *obj = const_cast (c.to_user ()); + return object_to_ruby (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false); + } } else { // not a known type -> return nil return Qnil; diff --git a/src/tl/tl/tlObject.cc b/src/tl/tl/tlObject.cc index b28b0958a7..fd4f6aaf44 100644 --- a/src/tl/tl/tlObject.cc +++ b/src/tl/tl/tlObject.cc @@ -227,6 +227,11 @@ const Object *WeakOrSharedPtr::get () const return mp_t; } +void WeakOrSharedPtr::unshare () +{ + m_is_shared = false; +} + void WeakOrSharedPtr::reset_object () { tl::MutexLocker locker (&lock ()); diff --git a/src/tl/tl/tlObject.h b/src/tl/tl/tlObject.h index 9bea96ed4d..0157c6887a 100644 --- a/src/tl/tl/tlObject.h +++ b/src/tl/tl/tlObject.h @@ -197,6 +197,12 @@ class TL_PUBLIC WeakOrSharedPtr */ void detach_from_all_events (); + /** + * @brief Unshares the object + * This will turn a shared reference into a weak one. + */ + void unshare (); + /** * @brief Indicates that this object is an event * This property is intended for internal use only. @@ -401,6 +407,9 @@ class TL_PUBLIC_TEMPLATE weak_ptr { // .. nothing yet .. } + +private: + using weak_or_shared_ptr::unshare; }; /** @@ -429,6 +438,9 @@ class TL_PUBLIC_TEMPLATE shared_ptr { // .. nothing yet .. } + +private: + using weak_or_shared_ptr::unshare; }; } diff --git a/src/tl/tl/tlVariant.cc b/src/tl/tl/tlVariant.cc index ced774fbcc..5bdca8fec3 100644 --- a/src/tl/tl/tlVariant.cc +++ b/src/tl/tl/tlVariant.cc @@ -2723,6 +2723,21 @@ void *Variant::user_take () return obj; } +void *Variant::user_unshare () const +{ + tl_assert (is_user () && ! user_is_ref ()); + + if (m_type == t_user) { + Variant *nc_this = const_cast (this); + nc_this->m_var.mp_user.shared = false; + } else if (m_type == t_user_ref) { + tl::WeakOrSharedPtr *wptr = const_cast (reinterpret_cast (m_var.mp_user_ref.ptr)); + wptr->unshare (); + } + + return const_cast (to_user ()); +} + void Variant::user_assign (const tl::Variant &other) { tl_assert (is_user ()); diff --git a/src/tl/tl/tlVariant.h b/src/tl/tl/tlVariant.h index 15b66f06bf..9a81c329d9 100644 --- a/src/tl/tl/tlVariant.h +++ b/src/tl/tl/tlVariant.h @@ -994,6 +994,13 @@ class TL_PUBLIC Variant */ void *user_take (); + /** + * @brief Takes the user object and releases ownership by the variant + * This method is const as it does not change the value, but the ownership of + * the contained object. The object must not be "user_is_ref". + */ + void *user_unshare () const; + /** * @brief Assigns the object stored in other to self * diff --git a/testdata/algo/break_polygons_test.gds b/testdata/algo/break_polygons_test.gds new file mode 100644 index 0000000000..089d24e177 Binary files /dev/null and b/testdata/algo/break_polygons_test.gds differ diff --git a/testdata/algo/layout_utils_au_bp1.gds b/testdata/algo/layout_utils_au_bp1.gds new file mode 100644 index 0000000000..c66a1daf6c Binary files /dev/null and b/testdata/algo/layout_utils_au_bp1.gds differ diff --git a/testdata/algo/layout_utils_au_bp2.gds b/testdata/algo/layout_utils_au_bp2.gds new file mode 100644 index 0000000000..aacb20a862 Binary files /dev/null and b/testdata/algo/layout_utils_au_bp2.gds differ diff --git a/testdata/python/dbRegionTest.py b/testdata/python/dbRegionTest.py index 7a2e3af3c7..3384f933a4 100644 --- a/testdata/python/dbRegionTest.py +++ b/testdata/python/dbRegionTest.py @@ -81,6 +81,31 @@ def test_deep1(self): dss = None self.assertEqual(pya.DeepShapeStore.instance_count(), 0) + # begin_shapes_rec and begin_shapes_merged_rec + def test_extended_iter(self): + + r = pya.Region() + + # NOTE: this also tests the copy semantics of the RecursiveShape to Variant binding in RBA: + it, trans = r.begin_shapes_rec() + s = ",".join([ str(trans*i.trans()*i.shape().polygon) for i in it.each() ]) + self.assertEqual(s, "") + + it, trans = r.begin_merged_shapes_rec() + s = ",".join([ str(trans*i.trans()*i.shape().polygon) for i in it.each() ]) + self.assertEqual(s, "") + + r.insert(pya.Box(0, 0, 100, 100)) + r.insert(pya.Box(50, 50, 200, 200)) + + it, trans = r.begin_shapes_rec() + s = ",".join([ str(trans*i.trans()*i.shape().polygon) for i in it.each() ]) + self.assertEqual(s, "(0,0;0,100;100,100;100,0),(50,50;50,200;200,200;200,50)") + + it, trans = r.begin_merged_shapes_rec() + s = ",".join([ str(trans*i.trans()*i.shape().polygon) for i in it.each() ]) + self.assertEqual(s, "(0,0;0,100;50,100;50,200;200,200;200,50;100,50;100,0)") + # run unit tests if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(DBRegionTest) diff --git a/testdata/ruby/dbLayoutTests1.rb b/testdata/ruby/dbLayoutTests1.rb index 25c8bb7de1..6acbd24741 100644 --- a/testdata/ruby/dbLayoutTests1.rb +++ b/testdata/ruby/dbLayoutTests1.rb @@ -1334,6 +1334,7 @@ def test_12 i0 = nil c0c.each_inst { |i| i.cell_index == l.cell("c1$1").cell_index && i0 = i } assert_equal(i0.property("p"), 18) + assert_equal(i0.properties, {"p" => 18}) assert_equal(l.cell("c1$1").begin_shapes_rec(0).shape.property("p"), 17) assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](0,100;1000,1200)/[c2$1](100,0;1100,1100)/[c3$1](1200,0;2200,1100)/[c3$1](-1200,0;-100,1000)/[c1$1](0,100;1000,1200)") @@ -1379,6 +1380,7 @@ def test_13 tt = RBA::Trans.new i0 = c0.insert(RBA::CellInstArray.new(c1.cell_index, tt)) + assert_equal(i0.properties, {}) i0.set_property("p", 18) c0.insert(RBA::CellInstArray.new(c2.cell_index, RBA::Trans.new(RBA::Point.new(100, -100)))) c0.insert(RBA::CellInstArray.new(c3.cell_index, RBA::Trans.new(1))) @@ -2100,6 +2102,48 @@ def test_24 end + # break_polygons + def test_25 + + def shapes2str(shapes) + str = [] + shapes.each do |s| + str << s.to_s + end + str.join(";") + end + + ly = RBA::Layout::new + top = ly.create_cell("TOP") + l1 = ly.layer(1, 0) + l2 = ly.layer(2, 0) + + top.shapes(l1).insert(RBA::Polygon::new([ [0, 0], [0, 10000], [10000, 10000], [10000, 9000], [1000, 9000], [1000, 0] ])) + top.shapes(l2).insert(RBA::Polygon::new([ [0, 0], [0, 10000], [10000, 10000], [10000, 9000], [1000, 9000], [1000, 0] ])) + + assert_equal(shapes2str(top.shapes(l1)), "polygon (0,0;0,10000;10000,10000;10000,9000;1000,9000;1000,0)") + assert_equal(shapes2str(top.shapes(l2)), "polygon (0,0;0,10000;10000,10000;10000,9000;1000,9000;1000,0)") + + s1 = top.shapes(l1).dup + assert_equal(shapes2str(s1), "polygon (0,0;0,10000;10000,10000;10000,9000;1000,9000;1000,0)") + s1.break_polygons(10, 3.0) + assert_equal(shapes2str(s1), "polygon (0,0;0,9000;1000,9000;1000,0);polygon (0,9000;0,10000;10000,10000;10000,9000)") + + ly2 = ly.dup + top2 = ly2.top_cell + + ly.break_polygons(10, 3.0) + + assert_equal(shapes2str(top.shapes(l1)), "polygon (0,0;0,9000;1000,9000;1000,0);polygon (0,9000;0,10000;10000,10000;10000,9000)") + assert_equal(shapes2str(top.shapes(l2)), "polygon (0,0;0,9000;1000,9000;1000,0);polygon (0,9000;0,10000;10000,10000;10000,9000)") + + ly2.break_polygons(ly2.layer(1, 0), 10, 3.0) + + assert_equal(shapes2str(top2.shapes(ly2.layer(1, 0))), "polygon (0,0;0,9000;1000,9000;1000,0);polygon (0,9000;0,10000;10000,10000;10000,9000)") + assert_equal(shapes2str(top2.shapes(ly2.layer(2, 0))), "polygon (0,0;0,10000;10000,10000;10000,9000;1000,9000;1000,0)") + + end + # Iterating while flatten def test_issue200 diff --git a/testdata/ruby/dbLayoutTests2.rb b/testdata/ruby/dbLayoutTests2.rb index bf53496459..fc1be5ac66 100644 --- a/testdata/ruby/dbLayoutTests2.rb +++ b/testdata/ruby/dbLayoutTests2.rb @@ -674,6 +674,7 @@ def test_6_Layout_props2 lindex = ly.insert_layer( linfo ) c1 = ly.cell( ci1 ) + assert_equal( c1.properties, {} ) c2 = ly.cell( ci2 ) tr = RBA::Trans::new inst = c2.insert( RBA::CellInstArray::new( c1.cell_index, tr ) ) @@ -701,6 +702,7 @@ def test_6_Layout_props2 c1.prop_id = pid assert_equal( c1.prop_id, pid ) assert_equal( c1.property( 17 ).inspect, "\"a\"" ) + assert_equal( c1.properties, { 17 => "a", "b" => [1, 5, 7] } ) c1.set_property( 5, 23 ) c1.delete_property( 17 ) assert_equal( c1.property( 17 ).inspect, "nil" ) @@ -1027,6 +1029,7 @@ def test_10 def test_11 ly = RBA::Layout::new + assert_equal(ly.properties, {}) assert_equal(ly.prop_id, 0) ly.prop_id = 1 @@ -1037,6 +1040,7 @@ def test_11 ly.set_property("x", 1) assert_equal(ly.prop_id, 1) assert_equal(ly.property("x"), 1) + assert_equal(ly.properties, {"x" => 1}) ly.set_property("x", 17) assert_equal(ly.prop_id, 2) assert_equal(ly.property("x"), 17) diff --git a/testdata/ruby/dbNetlist.rb b/testdata/ruby/dbNetlist.rb index 1ceb1331c5..07b8bfa5c0 100644 --- a/testdata/ruby/dbNetlist.rb +++ b/testdata/ruby/dbNetlist.rb @@ -832,18 +832,25 @@ def test_10_NetlistTopology nl = RBA::Netlist::new assert_equal(nl.top_circuit_count, 0) + assert_equal(nl.top_circuit == nil, true) c1 = RBA::Circuit::new c1.name = "C1" c1.cell_index = 17 nl.add(c1) assert_equal(nl.top_circuit_count, 1) + assert_equal(nl.top_circuit.name, "C1") c2 = RBA::Circuit::new c2.name = "C2" c1.cell_index = 42 nl.add(c2) assert_equal(nl.top_circuit_count, 2) + begin + nl.top_circuit + assert_equal(true, false) + rescue + end c3 = RBA::Circuit::new c3.name = "C3" @@ -854,6 +861,10 @@ def test_10_NetlistTopology nl.each_circuit_top_down { |c| names << c.name } assert_equal(names.join(","), "C3,C2,C1") + names = [] + nl.top_circuits.each { |c| names << c.name } + assert_equal(names.join(","), "C3,C2,C1") + names = [] nl.each_circuit_bottom_up { |c| names << c.name } assert_equal(names.join(","), "C1,C2,C3") diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index cd4546c7c0..149c231711 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -1471,6 +1471,33 @@ def test_drc_hull end + # begin_shapes_rec and begin_shapes_merged_rec + def test_extended_iter + + r = RBA::Region::new() + + # NOTE: this also tests the copy semantics of the RecursiveShape to Variant binding in RBA: + iter, trans = r.begin_shapes_rec + str = iter.each.collect { |i| (trans*i.trans*i.shape.polygon).to_s }.join(",") + assert_equal(str, "") + + iter, trans = r.begin_merged_shapes_rec + str = iter.each.collect { |i| (trans*i.trans*i.shape.polygon).to_s }.join(",") + assert_equal(str, "") + + r.insert(RBA::Box::new(0, 0, 100, 100)) + r.insert(RBA::Box::new(50, 50, 200, 200)) + + iter, trans = r.begin_shapes_rec + str = iter.each.collect { |i| (trans*i.trans*i.shape.polygon).to_s }.join(",") + assert_equal(str, "(0,0;0,100;100,100;100,0),(50,50;50,200;200,200;200,50)") + + iter, trans = r.begin_merged_shapes_rec + str = iter.each.collect { |i| (trans*i.trans*i.shape.polygon).to_s }.join(",") + assert_equal(str, "(0,0;0,100;50,100;50,200;200,200;200,50;100,50;100,0)") + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/dbShapesTest.rb b/testdata/ruby/dbShapesTest.rb index 61b8c92435..b77f7f183e 100644 --- a/testdata/ruby/dbShapesTest.rb +++ b/testdata/ruby/dbShapesTest.rb @@ -1656,6 +1656,28 @@ def test_12 end + # Shape objects and properties + def test_13 + + ly = RBA::Layout::new + l1 = ly.layer(1, 0) + tc = ly.create_cell("TOP") + sh = tc.shapes(l1).insert(RBA::Box::new(0, 0, 100, 200)) + + assert_equal(sh.property("k").inspect, "nil") + assert_equal(sh.properties.inspect, "{}") + + sh.set_property("k", 17) + + assert_equal(sh.property("k").inspect, "17") + assert_equal(sh.property("u").inspect, "nil") + assert_equal(sh.properties.inspect, "{\"k\"=>17}") + + sh.set_property("u", "42") + assert_equal(sh.properties.inspect, "{\"k\"=>17, \"u\"=>\"42\"}") + + end + end load("test_epilogue.rb")