WIP: DRC properties constraints now need explicit generation of output properties.

This commit is contained in:
Matthias Koefferlein 2023-01-21 14:04:06 +01:00
parent 9b64224cb4
commit 38808fccf7
12 changed files with 161 additions and 49 deletions

View File

@ -1017,7 +1017,7 @@ EdgePairsDelegate *
AsIfFlatRegion::cop_to_edge_pairs (db::CompoundRegionOperationNode &node, db::PropertyConstraint prop_constraint)
{
std::unique_ptr<FlatEdgePairs> output (new FlatEdgePairs ());
if (prop_constraint == db::IgnoreProperties) {
if (pc_skip (prop_constraint)) {
region_cop_impl<db::EdgePair> (this, &output->raw_edge_pairs (), node);
} else {
region_cop_with_properties_impl<db::EdgePair> (this, &output->raw_edge_pairs (), output->properties_repository (), node, prop_constraint);
@ -1029,7 +1029,7 @@ RegionDelegate *
AsIfFlatRegion::cop_to_region (db::CompoundRegionOperationNode &node, db::PropertyConstraint prop_constraint)
{
std::unique_ptr<FlatRegion> output (new FlatRegion ());
if (prop_constraint == db::IgnoreProperties) {
if (pc_skip (prop_constraint)) {
region_cop_impl<db::Polygon> (this, &output->raw_polygons (), node);
} else {
region_cop_with_properties_impl<db::Polygon> (this, &output->raw_polygons (), output->properties_repository (), node, prop_constraint);
@ -1041,7 +1041,7 @@ EdgesDelegate *
AsIfFlatRegion::cop_to_edges (db::CompoundRegionOperationNode &node, PropertyConstraint prop_constraint)
{
std::unique_ptr<FlatEdges> output (new FlatEdges ());
if (prop_constraint == db::IgnoreProperties) {
if (pc_skip (prop_constraint)) {
region_cop_impl<db::Edge> (this, &output->raw_edges (), node);
} else {
region_cop_with_properties_impl<db::Edge> (this, &output->raw_edges (), output->properties_repository (), node, prop_constraint);
@ -1113,7 +1113,7 @@ EdgePairsDelegate *
AsIfFlatRegion::run_check (db::edge_relation_type rel, bool different_polygons, const Region *other, db::Coord d, const RegionCheckOptions &options) const
{
// force different polygons in the different properties case to skip intra-polygon checks
if (options.prop_constraint == DifferentPropertiesConstraint) {
if (pc_always_different (options.prop_constraint)) {
different_polygons = true;
}
@ -1159,7 +1159,7 @@ AsIfFlatRegion::run_check (db::edge_relation_type rel, bool different_polygons,
std::vector<db::Shapes *> results;
results.push_back (&output->raw_edge_pairs ());
if (options.prop_constraint == db::IgnoreProperties) {
if (pc_skip (options.prop_constraint)) {
db::check_local_operation<db::Polygon, db::Polygon> op (check, different_polygons, primary_is_merged, has_other, other_is_merged, options);
@ -1209,7 +1209,7 @@ AsIfFlatRegion::run_single_polygon_check (db::edge_relation_type rel, db::Coord
for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
edge2edge_check_negative_or_positive<db::Shapes> edge_check (check, result->raw_edge_pairs (), options.negative, false /*=same polygons*/, false /*=same layers*/, options.shielded, true /*symmetric edge pairs*/, options.prop_constraint == db::IgnoreProperties ? 0 : pm (p.prop_id ()));
edge2edge_check_negative_or_positive<db::Shapes> edge_check (check, result->raw_edge_pairs (), options.negative, false /*=same polygons*/, false /*=same layers*/, options.shielded, true /*symmetric edge pairs*/, pc_remove (options.prop_constraint) ? 0 : pm (p.prop_id ()));
poly2poly_check<db::Polygon> poly_check (edge_check);
do {
@ -1364,7 +1364,7 @@ AsIfFlatRegion::and_with (const Region &other, PropertyConstraint property_const
// Nothing to do
return new EmptyRegion ();
} else if (property_constraint == db::IgnoreProperties && is_box () && other.is_box ()) {
} else if (pc_skip (property_constraint) && is_box () && other.is_box ()) {
// @@@ TODO: implement this with property constraints as that is important for the clip implementation!
@ -1373,7 +1373,7 @@ AsIfFlatRegion::and_with (const Region &other, PropertyConstraint property_const
b &= other.bbox ();
return region_from_box (b);
} else if (property_constraint == db::IgnoreProperties && is_box () && ! other.strict_handling ()) {
} else if (pc_skip (property_constraint) && is_box () && ! other.strict_handling ()) {
// @@@ TODO: implement this with property constraints as that is important for the clip implementation!
@ -1390,7 +1390,7 @@ AsIfFlatRegion::and_with (const Region &other, PropertyConstraint property_const
return new_region.release ();
} else if (property_constraint == db::IgnoreProperties && other.is_box () && ! strict_handling ()) {
} else if (pc_skip (property_constraint) && other.is_box () && ! strict_handling ()) {
// @@@ TODO: implement this with property constraints as that is important for the clip implementation!
@ -1443,7 +1443,7 @@ AsIfFlatRegion::not_with (const Region &other, PropertyConstraint property_const
RegionDelegate *
AsIfFlatRegion::and_or_not_with (bool is_and, const Region &other, PropertyConstraint property_constraint) const
{
if (property_constraint == db::IgnoreProperties) {
if (pc_skip (property_constraint)) {
// Generic case
db::EdgeProcessor ep (report_progress (), progress_desc ());
@ -1520,7 +1520,7 @@ AsIfFlatRegion::andnot_with (const Region &other, PropertyConstraint property_co
// Nothing to do
return std::make_pair (new EmptyRegion (), clone ());
} else if (property_constraint == db::IgnoreProperties) {
} else if (pc_skip (property_constraint)) {
// Generic case
db::EdgeProcessor ep (report_progress (), progress_desc ());

View File

@ -1549,7 +1549,7 @@ CompoundRegionCheckOperationNode::CompoundRegionCheckOperationNode (CompoundRegi
set_description ("check");
// force different polygons in the different properties case to skip intra-polygon checks
if (m_options.prop_constraint == DifferentPropertiesConstraint) {
if (pc_always_different (m_options.prop_constraint)) {
m_different_polygons = true;
}

View File

@ -1658,7 +1658,7 @@ protected:
for (size_t n = 0; n < results.size (); ++n) {
for (auto i = results_wo_props [n].begin (); i != results_wo_props [n].end (); ++i) {
results [n].insert (db::object_with_properties<TR> (*i, s2p->first));
results [n].insert (db::object_with_properties<TR> (*i, pc_norm (m_prop_constraint, s2p->first)));
}
}

View File

@ -858,7 +858,7 @@ DeepRegion::and_or_not_with (const DeepRegion *other, bool and_op, db::PropertyC
{
DeepLayer dl_out (deep_layer ().derived ());
if (property_constraint == db::IgnoreProperties) {
if (pc_skip (property_constraint)) {
db::BoolAndOrNotLocalOperation op (and_op);
@ -893,7 +893,7 @@ DeepRegion::and_and_not_with (const DeepRegion *other, PropertyConstraint proper
DeepLayer dl_out1 (deep_layer ().derived ());
DeepLayer dl_out2 (deep_layer ().derived ());
if (property_constraint == db::IgnoreProperties) {
if (pc_skip (property_constraint)) {
db::TwoBoolAndNotLocalOperation op;
@ -1840,7 +1840,7 @@ EdgePairsDelegate *
DeepRegion::cop_to_edge_pairs (db::CompoundRegionOperationNode &node, db::PropertyConstraint prop_constraint)
{
DeepEdgePairs *output = 0;
if (prop_constraint == db::IgnoreProperties) {
if (pc_skip (prop_constraint)) {
output = region_cop_impl<db::EdgePair, DeepEdgePairs> (this, node);
} else {
output = region_cop_with_properties_impl<db::EdgePair, DeepEdgePairs> (this, node, prop_constraint);
@ -1856,7 +1856,7 @@ RegionDelegate *
DeepRegion::cop_to_region (db::CompoundRegionOperationNode &node, db::PropertyConstraint prop_constraint)
{
DeepRegion *output = 0;
if (prop_constraint == db::IgnoreProperties) {
if (pc_skip (prop_constraint)) {
output = region_cop_impl<db::PolygonRef, db::DeepRegion> (this, node);
} else {
output = region_cop_with_properties_impl<db::PolygonRef, DeepRegion> (this, node, prop_constraint);
@ -1872,7 +1872,7 @@ EdgesDelegate *
DeepRegion::cop_to_edges (db::CompoundRegionOperationNode &node, db::PropertyConstraint prop_constraint)
{
DeepEdges *output = 0;
if (prop_constraint == db::IgnoreProperties) {
if (pc_skip (prop_constraint)) {
output = region_cop_impl<db::Edge, DeepEdges> (this, node);
} else {
output = region_cop_with_properties_impl<db::Edge, DeepEdges> (this, node, prop_constraint);
@ -1894,7 +1894,7 @@ DeepRegion::run_check (db::edge_relation_type rel, bool different_polygons, cons
}
// force different polygons in the different properties case to skip intra-polygon checks
if (options.prop_constraint == DifferentPropertiesConstraint) {
if (pc_always_different (options.prop_constraint)) {
different_polygons = true;
}
@ -2004,7 +2004,7 @@ DeepRegion::run_single_polygon_check (db::edge_relation_type rel, db::Coord d, c
for (db::Shapes::shape_iterator s = shapes.begin (db::ShapeIterator::Polygons); ! s.at_end (); ++s) {
edge2edge_check_negative_or_positive<db::Shapes> edge_check (check, result, options.negative, false /*does not require different polygons*/, false /*does not require different layers*/, options.shielded, true /*symmetric edge pairs*/, options.prop_constraint == db::IgnoreProperties ? 0 : s->prop_id ());
edge2edge_check_negative_or_positive<db::Shapes> edge_check (check, result, options.negative, false /*does not require different polygons*/, false /*does not require different layers*/, options.shielded, true /*symmetric edge pairs*/, pc_remove (options.prop_constraint) ? 0 : s->prop_id ());
poly2poly_check<db::Polygon> poly_check (edge_check);
db::Polygon poly;

View File

@ -341,9 +341,8 @@ separate_interactions_by_properties (const shape_interactions<db::object_with_pr
for (auto ii = i->second.begin (); ii != i->second.end (); ++ii) {
const std::pair<unsigned int, db::object_with_properties<TI> > &intruder = interactions.intruder_shape (*ii);
db::properties_id_type intruder_prop_id = (property_constraint == db::NoPropertyConstraint ? prop_id : pmi (intruder.second.properties_id ()));
if ((property_constraint == db::DifferentPropertiesConstraint) == (prop_id != intruder_prop_id)) {
if (pc_match (property_constraint, prop_id, pmi (intruder.second.properties_id ()))) {
s2p.second.insert (&intruder.second);
}
@ -382,9 +381,8 @@ separate_interactions_to_interactions_by_properties (const shape_interactions<db
const std::pair<unsigned int, db::object_with_properties<TI> > &intruder = interactions.intruder_shape (*ii);
tl_assert (intruder.first < (unsigned int) pmis.size ());
db::properties_id_type intruder_prop_id = (property_constraint == db::NoPropertyConstraint ? prop_id : pmis[intruder.first] (intruder.second.properties_id ()));
if ((property_constraint == db::DifferentPropertiesConstraint) == (prop_id != intruder_prop_id)) {
if (pc_match (property_constraint, prop_id, pmis[intruder.first] (intruder.second.properties_id ()))) {
s2p.add_interaction (i->first, *ii);
intruder_ids.insert (*ii);
}

View File

@ -26,6 +26,7 @@
#define HDR_dbPropertyConstraint
#include "dbCommon.h"
#include "dbTypes.h"
namespace db
{
@ -56,14 +57,74 @@ enum PropertyConstraint
*/
SamePropertiesConstraint = 2,
/**
* @brief Shapes are processed if their properties are the same
*
* No properties are attached to the output.
*/
SamePropertiesConstraintDrop = 3,
/**
* @brief Shapes are processed if their properties are different
*
* Properties are attached to the outputs where applicable.
*/
DifferentPropertiesConstraint = 3
DifferentPropertiesConstraint = 4,
/**
* @brief Shapes are processed if their properties are different
*
* No properties are attached to the output.
*/
DifferentPropertiesConstraintDrop = 5
};
/**
* @brief Returns a predicate indicating whether properties need to be considered
*/
bool inline pc_skip (PropertyConstraint pc)
{
return pc == IgnoreProperties;
}
/**
* @brief Returns a predicate indicating whether properties are always different
*/
bool inline pc_always_different (PropertyConstraint pc)
{
return pc == DifferentPropertiesConstraint || pc == DifferentPropertiesConstraintDrop;
}
/**
* @brief Returns a value indicating whether two properties satisfy the condition
*/
bool inline pc_match (PropertyConstraint pc, db::properties_id_type a, db::properties_id_type b)
{
if (pc == SamePropertiesConstraint || pc == SamePropertiesConstraintDrop) {
return a == b;
} else if (pc == DifferentPropertiesConstraint || pc == DifferentPropertiesConstraintDrop) {
return a != b;
} else {
return true;
}
}
/**
* @brief Returns a value indicating whether the property can be removed on output
*/
bool inline pc_remove (PropertyConstraint pc)
{
return pc == IgnoreProperties || pc == SamePropertiesConstraintDrop || pc == DifferentPropertiesConstraintDrop;
}
/**
* @brief Returns a normalized property for output
*/
db::properties_id_type inline pc_norm (PropertyConstraint pc, db::properties_id_type prop_id)
{
return pc_remove (pc) ? 0 : prop_id;
}
}
#endif

View File

@ -782,7 +782,7 @@ check_local_operation_with_properties<TS, TI>::do_compute_local (db::Layout *lay
}
for (auto r = result.begin (); r != result.end (); ++r) {
results.front ().insert (db::EdgePairWithProperties (*r, s2p->first));
results.front ().insert (db::EdgePairWithProperties (*r, pc_norm (check_local_operation_base<TS, TI>::m_options.prop_constraint, s2p->first)));
}
}
@ -1668,8 +1668,7 @@ bool_and_or_not_local_operation_with_properties<TS, TI, TR>::do_compute_local (d
for (auto j = i->second.begin (); j != i->second.end (); ++j) {
const db::object_with_properties<TI> &intruder = interactions.intruder_shape (*j).second;
db::properties_id_type prop_id_i = (m_property_constraint != db::NoPropertyConstraint ? m_pmi (intruder.properties_id ()) : prop_id_s);
if ((prop_id_i != prop_id_s) == (m_property_constraint == db::DifferentPropertiesConstraint)) {
if (pc_match (m_property_constraint, prop_id_s, m_pmi (intruder.properties_id ()))) {
shapes_by_prop.second.insert (intruder);
}
}
@ -1684,7 +1683,7 @@ bool_and_or_not_local_operation_with_properties<TS, TI, TR>::do_compute_local (d
size_t p1 = 0, p2 = 1;
const std::set<TI> &others = p2s->second.second;
db::properties_id_type prop_id = p2s->first;
db::properties_id_type prop_id = pc_norm (m_property_constraint, p2s->first);
for (auto s = p2s->second.first.begin (); s != p2s->second.first.end (); ++s) {
@ -1857,8 +1856,7 @@ two_bool_and_not_local_operation_with_properties<TS, TI, TR>::do_compute_local (
for (auto j = i->second.begin (); j != i->second.end (); ++j) {
const db::object_with_properties<TI> &intruder = interactions.intruder_shape (*j).second;
db::properties_id_type prop_id_i = (m_property_constraint != db::NoPropertyConstraint ? m_pmi (intruder.properties_id ()) : prop_id_s);
if ((prop_id_i != prop_id_s) == (m_property_constraint == db::DifferentPropertiesConstraint)) {
if (pc_match (m_property_constraint, prop_id_s, m_pmi (intruder.properties_id ()))) {
shapes_by_prop.second.insert (intruder);
}
}
@ -1873,7 +1871,7 @@ two_bool_and_not_local_operation_with_properties<TS, TI, TR>::do_compute_local (
size_t p1 = 0, p2 = 1;
const std::set<TR> &others = p2s->second.second;
db::properties_id_type prop_id = p2s->first;
db::properties_id_type prop_id = pc_norm (m_property_constraint, p2s->first);
for (auto s = p2s->second.first.begin (); s != p2s->second.first.end (); ++s) {

View File

@ -3172,10 +3172,20 @@ gsi::EnumIn<db::Region, db::PropertyConstraint> decl_Region_PropertyConstraint (
"When using this constraint - for example on a boolean operation - shapes are considered "
"only if their user properties are the same. Properties are generated on the output shapes where applicable."
) +
gsi::enum_const ("SamePropertiesConstraintDrop", db::SamePropertiesConstraintDrop,
"@brief Specifies to consider shapes only if their user properties are the same\n"
"When using this constraint - for example on a boolean operation - shapes are considered "
"only if their user properties are the same. No properties are generated on the output shapes."
) +
gsi::enum_const ("DifferentPropertiesConstraint", db::DifferentPropertiesConstraint,
"@brief Specifies to consider shapes only if their user properties are different\n"
"When using this constraint - for example on a boolean operation - shapes are considered "
"only if their user properties are different. Properties are generated on the output shapes where applicable."
) +
gsi::enum_const ("DifferentPropertiesConstraintDrop", db::DifferentPropertiesConstraintDrop,
"@brief Specifies to consider shapes only if their user properties are different\n"
"When using this constraint - for example on a boolean operation - shapes are considered "
"only if their user properties are the same. No properties are generated on the output shapes."
),
"@brief This class represents the property constraint for boolean and check functions.\n"
"\n"

View File

@ -267,6 +267,13 @@ module DRC
#
# m1m2_overlap_connected = metal1.nets.and(metal2, props_eq)
# @/code
#
# "props_eq" can be combined with \props_copy. In this case, properties
# are transferred to the output shapes and can be used in further processing:
#
# @code
# m1m2_overlap_connected = metal1.nets.and(metal2, props_eq + props_copy)
# @/code
#
# See also \props_ne.
@ -291,6 +298,13 @@ module DRC
# m1m2_overlap_not_connected = metal1.nets.and(metal2, props_ne)
# @/code
#
# "props_ne" can be combined with \props_copy. In this case, properties
# are transferred to the output shapes and can be used in further processing:
#
# @code
# m1m2_overlap_connected = metal1.nets.and(metal2, props_ne + props_copy)
# @/code
#
# See also \props_eq.
# %DRC%

View File

@ -3781,10 +3781,17 @@ CODE
# @code
#
# \props_copy is a special properties constraint that does not alter the behaviour of
# the checks, but copies the primary shape's properties to the output markers
# (a behaviour that is implied by \props_eq and \props_ne, but not there by default).
# the checks, but copies the primary shape's properties to the output markers.
# This constraint is applicable to \width and \notch checks too. The effect is that
# the original polygon's properties are copied to the error markers.
# \props_copy can be combined with \props_eq and \props_ne to copy the original
# shape's properties to the output too:
#
# @code
# space_not_connected = metal1_nets.space(0.4.um, props_ne + props_copy)
# space_connected = metal1_nets.space(0.4.um, props_eq + props_copy)
# @code
#
# %DRC%
# @name space

View File

@ -151,6 +151,30 @@ module DRC
def initialize(v)
self.value = v
end
def is_eq?
self.value == RBA::Region::SamePropertiesConstraint || self.value == RBA::Region::SamePropertiesConstraintDrop
end
def is_ne?
self.value == RBA::Region::DifferentPropertiesConstraint || self.value == RBA::Region::DifferentPropertiesConstraintDrop
end
def is_copy?
self.value == RBA::Region::NoPropertyConstraint || self.value == RBA::Region::SamePropertiesConstraint || self.value == RBA::Region::DifferentPropertiesConstraint
end
def +(other)
other.is_a?(DRCPropertiesConstraint) || raise("'+' needs to be applied to two properties constraints (got #{other.inspect} for the second one)")
is_eq = self.is_eq? || other.is_eq?
is_ne = self.is_ne? || other.is_ne?
is_copy = self.is_copy? || other.is_copy?
if is_eq == is_ne
DRCPropertiesConstraint::new(is_copy ? RBA::Region::NoPropertyConstraint : RBA::Region::IgnoreProperties)
elsif is_eq
DRCPropertiesConstraint::new(is_copy ? RBA::Region::SamePropertiesConstraint : RBA::Region::SamePropertiesConstraintDrop)
elsif is_ne
DRCPropertiesConstraint::new(is_copy ? RBA::Region::DifferentPropertiesConstraint : RBA::Region::DifferentPropertiesConstraintDrop)
else
nil
end
end
end
# Negative output on checks

View File

@ -40,15 +40,15 @@ l4_wp.output(16, 0)
# booleans with properties constraints
l3_wp.and(l4_wp, props_eq).output(20, 0)
l3_wp1.and(l4_wp, props_eq).output(21, 0)
l3_wp2as1.and(l4_wp, props_eq).output(22, 0)
l3_nowp.and(l4_wp, props_eq).output(23, 0)
l3_wp.and(l4_wp, props_eq + props_copy).output(20, 0)
l3_wp1.and(l4_wp, props_eq + props_copy).output(21, 0)
l3_wp2as1.and(l4_wp, props_eq + props_copy).output(22, 0)
l3_nowp.and(l4_wp, props_eq + props_copy).output(23, 0)
l3_wp.and(l4_wp, props_ne).output(30, 0)
l3_wp1.and(l4_wp, props_ne).output(31, 0)
l3_wp2as1.and(l4_wp, props_ne).output(32, 0)
l3_nowp.and(l4_wp, props_ne).output(33, 0)
l3_wp.and(l4_wp, props_ne + props_copy).output(30, 0)
l3_wp1.and(l4_wp, props_ne + props_copy).output(31, 0)
l3_wp2as1.and(l4_wp, props_ne + props_copy).output(32, 0)
l3_nowp.and(l4_wp, props_ne + props_copy).output(33, 0)
l3_wp.and(l4_wp, props_copy).output(40, 0)
l3_wp1.and(l4_wp, props_copy).output(41, 0)
@ -79,15 +79,15 @@ l1.nets("NOTEXIST", "NOTEXIST").output(113, 0)
l1_nets = l1.nets
l1_nets.space(1.0.um, projection).polygons.output(200, 0)
l1_nets.space(1.0.um, projection, props_eq).polygons.output(201, 0)
l1_nets.space(1.0.um, projection, props_ne).polygons.output(202, 0)
l1_nets.space(1.0.um, projection, props_eq + props_copy).polygons.output(201, 0)
l1_nets.space(1.0.um, projection, props_ne + props_copy).polygons.output(202, 0)
l1_nets.space(1.0.um, projection, props_copy).polygons.output(203, 0)
l1_nets.width(1.0.um, projection).polygons.output(204, 0)
l1_nets.width(1.0.um, projection, props_copy).polygons.output(205, 0)
l1_nets.drc(space(projection) < 1.0.um).polygons.output(210, 0)
l1_nets.drc(space(projection) < 1.0.um, props_eq).polygons.output(211, 0)
l1_nets.drc(space(projection) < 1.0.um, props_ne).polygons.output(212, 0)
l1_nets.drc(space(projection) < 1.0.um, props_eq + props_copy).polygons.output(211, 0)
l1_nets.drc(space(projection) < 1.0.um, props_ne + props_copy).polygons.output(212, 0)
l1_nets.drc(space(projection) < 1.0.um, props_copy).polygons.output(213, 0)
l1_nets.drc(width(projection) < 1.0.um).polygons.output(214, 0)
l1_nets.drc(width(projection) < 1.0.um, props_copy).polygons.output(215, 0)
@ -103,10 +103,10 @@ l1_nets.space(1.0.um, projection, props_copy).edges.extended_in(10.nm).output(22
# sizing with properties
l1_nets_sized = l1_nets.sized(0.2.um)
l1_nets_sized.and(l1_nets_sized, props_ne).output(300, 0) # overlap of different nets, same layer
l1_nets_sized.and(l1_nets_sized, props_ne + props_copy).output(300, 0) # overlap of different nets, same layer
l1_nets.drc(primary.sized(0.2.um) & foreign.sized(0.2.um)).output(310, 0)
l1_nets.drc(primary.sized(0.2.um) & foreign.sized(0.2.um), props_ne).output(311, 0)
l1_nets.drc(primary.sized(0.2.um) & foreign.sized(0.2.um), props_eq).output(312, 0)
l1_nets.drc(primary.sized(0.2.um) & foreign.sized(0.2.um), props_ne + props_copy).output(311, 0)
l1_nets.drc(primary.sized(0.2.um) & foreign.sized(0.2.um), props_eq + props_copy).output(312, 0)
l1_nets.drc(primary.sized(0.2.um) & foreign.sized(0.2.um), props_copy).output(313, 0)