mirror of https://github.com/KLayout/klayout.git
3191 lines
90 KiB
C++
3191 lines
90 KiB
C++
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2023 Matthias Koefferlein
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
#include "dbDXFReader.h"
|
|
#include "dbStream.h"
|
|
#include "dbObjectWithProperties.h"
|
|
#include "dbArray.h"
|
|
#include "dbStatic.h"
|
|
#include "dbRecursiveShapeIterator.h"
|
|
#include "dbEdgeProcessor.h"
|
|
#include "dbEdgesToContours.h"
|
|
#include "dbVariableWidthPath.h"
|
|
|
|
#include "tlException.h"
|
|
#include "tlString.h"
|
|
#include "tlUtils.h"
|
|
#include "tlClassRegistry.h"
|
|
|
|
#include <cctype>
|
|
#include <set>
|
|
|
|
#if defined(HAVE_QT)
|
|
# include <QString>
|
|
# include <QStringList>
|
|
# include <QFont>
|
|
# include <QFontMetrics>
|
|
# include <QPolygon>
|
|
# include <QPainterPath>
|
|
#endif
|
|
|
|
namespace db
|
|
{
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
#if defined(HAVE_QT)
|
|
|
|
static int fm_width (const QFontMetrics &fm, const QString &s)
|
|
{
|
|
#if QT_VERSION >= 0x60000
|
|
return fm.horizontalAdvance (s);
|
|
#else
|
|
return fm.width (s);
|
|
#endif
|
|
}
|
|
|
|
static int fm_width (const QFontMetrics &fm, const QChar &s)
|
|
{
|
|
#if QT_VERSION >= 0x60000
|
|
return fm.horizontalAdvance (s);
|
|
#else
|
|
return fm.width (s);
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
|
|
// ---------------------------------------------------------------
|
|
// DXFReader
|
|
|
|
static std::string zero_layer_name ("0");
|
|
|
|
DXFReader::DXFReader (tl::InputStream &s)
|
|
: m_stream (s),
|
|
m_progress (tl::to_string (tr ("Reading DXF file")), 1000),
|
|
m_dbu (0.001), m_unit (1.0), m_text_scaling (1.0), m_polyline_mode (0), m_circle_points (100), m_circle_accuracy (0.0), m_contour_accuracy (0.0),
|
|
m_ascii (false), m_initial (true), m_render_texts_as_polygons (false), m_keep_other_cells (false), m_line_number (0),
|
|
m_zero_layer (0)
|
|
{
|
|
m_progress.set_format (tl::to_string (tr ("%.0fk lines")));
|
|
m_progress.set_format_unit (1000.0);
|
|
m_progress.set_unit (100000.0);
|
|
}
|
|
|
|
DXFReader::~DXFReader ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
void
|
|
DXFReader::check_coord (double x)
|
|
{
|
|
// Note: we stay on the safe side by dropping one bit (*0.5)
|
|
if (x < std::numeric_limits <db::Coord>::min () * 0.5 ||
|
|
x > std::numeric_limits <db::Coord>::max () * 0.5) {
|
|
error (tl::to_string (tr ("Coordinate value overflow")));
|
|
}
|
|
}
|
|
|
|
void
|
|
DXFReader::check_point (const db::DPoint &p)
|
|
{
|
|
check_coord (p.x ());
|
|
check_coord (p.y ());
|
|
}
|
|
|
|
void
|
|
DXFReader::check_vector (const db::DVector &p)
|
|
{
|
|
check_coord (p.x ());
|
|
check_coord (p.y ());
|
|
}
|
|
|
|
db::Polygon
|
|
DXFReader::safe_from_double (const db::DPolygon &p)
|
|
{
|
|
for (db::DPolygon::polygon_contour_iterator q = p.begin_hull (); q != p.end_hull (); ++q) {
|
|
check_point (*q);
|
|
}
|
|
for (unsigned int h = 0; h < p.holes (); ++h) {
|
|
for (db::DPolygon::polygon_contour_iterator q = p.begin_hole (h); q != p.end_hole (h); ++q) {
|
|
check_point (*q);
|
|
}
|
|
}
|
|
return db::Polygon (p);
|
|
}
|
|
|
|
db::SimplePolygon
|
|
DXFReader::safe_from_double (const db::DSimplePolygon &p)
|
|
{
|
|
for (db::DPolygon::polygon_contour_iterator q = p.begin_hull (); q != p.end_hull (); ++q) {
|
|
check_point (*q);
|
|
}
|
|
return db::SimplePolygon (p);
|
|
}
|
|
|
|
db::Text
|
|
DXFReader::safe_from_double (const db::DText &p)
|
|
{
|
|
check_vector (p.trans ().disp ());
|
|
check_coord (p.size ());
|
|
return db::Text (p);
|
|
}
|
|
|
|
db::Path
|
|
DXFReader::safe_from_double (const db::DPath &p)
|
|
{
|
|
for (db::DPath::iterator q = p.begin (); q != p.end (); ++q) {
|
|
check_point (*q);
|
|
}
|
|
check_coord (p.width ());
|
|
check_coord (p.bgn_ext ());
|
|
check_coord (p.end_ext ());
|
|
return db::Path (p);
|
|
}
|
|
|
|
db::Point
|
|
DXFReader::safe_from_double (const db::DPoint &p)
|
|
{
|
|
check_point (p);
|
|
return db::Point (p);
|
|
}
|
|
|
|
db::Vector
|
|
DXFReader::safe_from_double (const db::DVector &p)
|
|
{
|
|
check_vector (p);
|
|
return db::Vector (p);
|
|
}
|
|
|
|
db::Edge
|
|
DXFReader::safe_from_double (const db::DEdge &p)
|
|
{
|
|
check_point (p.p1 ());
|
|
check_point (p.p2 ());
|
|
return db::Edge (p);
|
|
}
|
|
|
|
db::Box
|
|
DXFReader::safe_from_double (const db::DBox &p)
|
|
{
|
|
check_point (p.p1 ());
|
|
check_point (p.p2 ());
|
|
return db::Box (p);
|
|
}
|
|
|
|
void
|
|
DXFReader::parse_entity (const std::string &entity_code, size_t &nsolids, size_t &closed_polylines)
|
|
{
|
|
int g;
|
|
|
|
if (entity_code == "HATCH" || entity_code == "SOLID") {
|
|
++nsolids;
|
|
while ((g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
} else if (entity_code == "POLYLINE" || entity_code == "LWPOLYLINE") {
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 70) {
|
|
int flags = read_int16 ();
|
|
if ((flags & 1) != 0) {
|
|
++closed_polylines;
|
|
}
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
} else {
|
|
while ((g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
DXFReader::determine_polyline_mode ()
|
|
{
|
|
m_initial = true;
|
|
m_line_number = 0;
|
|
|
|
size_t nsolids = 0;
|
|
size_t closed_polylines = 0;
|
|
|
|
// Read sections
|
|
int g;
|
|
|
|
while (true) {
|
|
|
|
while ((g = read_group_code()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
|
|
const std::string &name = read_string (true);
|
|
if (name == "EOF") {
|
|
|
|
break;
|
|
|
|
} else if (name == "SECTION") {
|
|
|
|
while ((g = read_group_code()) != 2) {
|
|
skip_value (g);
|
|
}
|
|
|
|
const std::string §ion_name = read_string (true);
|
|
if (section_name == "BLOCKS") {
|
|
|
|
while (true) {
|
|
|
|
while ((g = read_group_code()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
|
|
const std::string &entity = read_string (true);
|
|
if (entity == "BLOCK") {
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
|
|
while (true) {
|
|
const std::string &entity_code = read_string (true);
|
|
if (entity_code == "ENDBLK") {
|
|
break;
|
|
} else {
|
|
parse_entity (entity_code, nsolids, closed_polylines);
|
|
}
|
|
}
|
|
|
|
} else if (entity == "ENDSEC") {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
} else if (section_name == "ENTITIES") {
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
|
|
while (true) {
|
|
const std::string &entity_code = read_string (true);
|
|
if (entity_code == "ENDSEC") {
|
|
break;
|
|
} else {
|
|
parse_entity (entity_code, nsolids, closed_polylines);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if at least one "solid style" entity is found, create lines from polylines. Otherwise create polygons from closed polylines.
|
|
if (nsolids > 0) {
|
|
return 1; // solid mode
|
|
} else if (closed_polylines > 0) {
|
|
return 2; // polyline to polygon mode
|
|
} else {
|
|
return 3; // merge lines mode
|
|
}
|
|
}
|
|
|
|
const LayerMap &
|
|
DXFReader::read (db::Layout &layout, const db::LoadLayoutOptions &options)
|
|
{
|
|
init (options);
|
|
|
|
const db::DXFReaderOptions &specific_options = options.get_options<db::DXFReaderOptions> ();
|
|
|
|
m_dbu = specific_options.dbu;
|
|
m_unit = specific_options.unit;
|
|
m_text_scaling = specific_options.text_scaling;
|
|
m_polyline_mode = specific_options.polyline_mode;
|
|
m_circle_points = specific_options.circle_points;
|
|
m_circle_accuracy = specific_options.circle_accuracy;
|
|
m_contour_accuracy = specific_options.contour_accuracy;
|
|
m_render_texts_as_polygons = specific_options.render_texts_as_polygons;
|
|
m_keep_other_cells = specific_options.keep_other_cells;
|
|
|
|
if (m_polyline_mode == 0 /*auto mode*/) {
|
|
m_polyline_mode = determine_polyline_mode ();
|
|
if (m_polyline_mode == 3) {
|
|
tl::log << tl::to_string (tr ("Automatic polyline mode: merge lines with width = 0 into polygons"));
|
|
} else if (m_polyline_mode == 2) {
|
|
tl::log << tl::to_string (tr ("Automatic polyline mode: create polygons from closed polylines with width = 0"));
|
|
} else if (m_polyline_mode == 1) {
|
|
tl::log << tl::to_string (tr ("Automatic polyline mode: keep lines, make polygons from solid and hatch entities"));
|
|
}
|
|
}
|
|
|
|
m_stream.reset ();
|
|
m_initial = true;
|
|
m_line_number = 0;
|
|
set_layer_map (specific_options.layer_map);
|
|
set_create_layers (specific_options.create_other_layers);
|
|
set_keep_layer_names (specific_options.keep_layer_names);
|
|
|
|
db::cell_index_type top = layout.add_cell("TOP"); // TODO: make variable ..
|
|
|
|
layout.dbu (m_dbu);
|
|
do_read (layout, top);
|
|
cleanup (layout, top);
|
|
|
|
return layer_map_out ();
|
|
}
|
|
|
|
const LayerMap &
|
|
DXFReader::read (db::Layout &layout)
|
|
{
|
|
return read (layout, db::LoadLayoutOptions ());
|
|
}
|
|
|
|
void
|
|
DXFReader::error (const std::string &msg)
|
|
{
|
|
if (m_ascii) {
|
|
throw DXFReaderException (msg, m_line_number, m_cellname);
|
|
} else {
|
|
throw DXFReaderException (msg, m_stream.pos (), m_cellname);
|
|
}
|
|
}
|
|
|
|
void
|
|
DXFReader::warn (const std::string &msg, int wl)
|
|
{
|
|
if (warn_level () < wl) {
|
|
return;
|
|
}
|
|
|
|
// TODO: compress
|
|
if (m_ascii) {
|
|
tl::warn << msg
|
|
<< tl::to_string (tr (" (line=")) << m_line_number
|
|
<< tl::to_string (tr (", cell=")) << m_cellname
|
|
<< ")";
|
|
} else {
|
|
tl::warn << msg
|
|
<< tl::to_string (tr (" (position=")) << m_stream.pos ()
|
|
<< tl::to_string (tr (", cell=")) << m_cellname
|
|
<< ")";
|
|
}
|
|
}
|
|
|
|
void
|
|
DXFReader::do_read (db::Layout &layout, db::cell_index_type top)
|
|
{
|
|
prepare_layers (layout);
|
|
|
|
// create the zero layer - this is not mapped to GDS but can be specified in the layer mapping as
|
|
// a layer named "0".
|
|
|
|
std::pair<bool, unsigned int> li = NamedLayerReader::open_layer (layout, zero_layer_name, true /*keep layer name*/, false /*don't create a new layer*/);
|
|
if (li.first) {
|
|
|
|
// we got one from the layer mapping
|
|
m_zero_layer = li.second;
|
|
|
|
} else {
|
|
|
|
// or we explicitly create the layer
|
|
db::LayerProperties lp_zero (0, 0, zero_layer_name);
|
|
m_zero_layer = layout.insert_layer (lp_zero);
|
|
map_layer (zero_layer_name, m_zero_layer);
|
|
|
|
}
|
|
|
|
// Read sections
|
|
|
|
int g;
|
|
|
|
while (true) {
|
|
|
|
while ((g = read_group_code()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
|
|
const std::string &name = read_string (true);
|
|
if (name == "EOF") {
|
|
|
|
break;
|
|
|
|
} else if (name == "SECTION") {
|
|
|
|
while ((g = read_group_code()) != 2) {
|
|
skip_value (g);
|
|
}
|
|
|
|
const std::string §ion_name = read_string (true);
|
|
if (section_name == "BLOCKS") {
|
|
|
|
while (true) {
|
|
|
|
while ((g = read_group_code()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
|
|
const std::string &entity = read_string (true);
|
|
if (entity == "BLOCK") {
|
|
read_cell (layout);
|
|
} else if (entity == "ENDSEC") {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
} else if (section_name == "TABLES") {
|
|
|
|
while (true) {
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
|
|
const std::string &entity = read_string (true);
|
|
if (entity == "TABLE") {
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 2) {
|
|
break;
|
|
}
|
|
skip_value (g);
|
|
}
|
|
|
|
if (g == 2) {
|
|
|
|
std::string table_name = read_string (true);
|
|
if (table_name == "LAYER") {
|
|
|
|
while (true) {
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 2) {
|
|
std::string layer_name = read_string (true);
|
|
open_layer (layout, layer_name);
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
if (read_string (true) == "ENDTAB") {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (entity == "ENDSEC") {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
} else if (section_name == "ENTITIES") {
|
|
|
|
// skip groups to first entity (consume the group code for this one)
|
|
int g;
|
|
while ((g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
|
|
read_entities (layout, layout.cell (top), db::DVector ());
|
|
|
|
} else {
|
|
|
|
do {
|
|
while ((g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
} while (read_string (true) != "ENDSEC");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
finish_layers (layout);
|
|
}
|
|
|
|
void
|
|
DXFReader::cleanup (db::Layout &layout, db::cell_index_type top_cell)
|
|
{
|
|
std::vector <db::cell_index_type> cells_to_delete;
|
|
|
|
do
|
|
{
|
|
cells_to_delete.clear ();
|
|
|
|
// remove all cells which are not used except for the top cell
|
|
for (db::Layout::const_iterator c = layout.begin (); c != layout.end (); ++c) {
|
|
if ((! m_keep_other_cells || m_used_template_cells.find (c->cell_index ()) != m_used_template_cells.end ()) && c->is_top () && c->cell_index () != top_cell) {
|
|
cells_to_delete.push_back (c->cell_index ());
|
|
}
|
|
}
|
|
|
|
// it's more efficient to remove the cells afterwards because is_top requires an updated hierarchy
|
|
for (std::vector <db::cell_index_type>::const_iterator c = cells_to_delete.begin (); c != cells_to_delete.end (); ++c) {
|
|
layout.delete_cell (*c);
|
|
}
|
|
|
|
// deleting cells can make other cells "top", thus we iterate
|
|
|
|
} while (!cells_to_delete.empty ());
|
|
|
|
// rename the remaining cells
|
|
for (std::map <std::string, db::cell_index_type>::const_iterator b = m_block_per_name.begin (); b != m_block_per_name.end (); ++b) {
|
|
if (layout.is_valid_cell_index (b->second)) {
|
|
layout.rename_cell (b->second, layout.uniquify_cell_name (b->first.c_str ()).c_str ());
|
|
}
|
|
}
|
|
|
|
m_template_cells.clear ();
|
|
m_used_template_cells.clear ();
|
|
m_block_per_name.clear ();
|
|
}
|
|
|
|
void
|
|
DXFReader::read_cell (db::Layout &layout)
|
|
{
|
|
std::string cell_name;
|
|
double xoff = 0.0, yoff = 0.0;
|
|
|
|
int g;
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 2) {
|
|
cell_name = read_string (true);
|
|
} else if (g == 10) {
|
|
xoff = read_double ();
|
|
} else if (g == 20) {
|
|
yoff = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
std::map <std::string, db::cell_index_type>::const_iterator b = m_block_per_name.find (cell_name);
|
|
if (b == m_block_per_name.end ()) {
|
|
|
|
// create a first representative. Later, layer variants are built
|
|
db::cell_index_type cell = layout.add_cell ();
|
|
m_block_per_name.insert (std::make_pair (cell_name, cell));
|
|
m_template_cells.insert (std::make_pair (cell, cell_name));
|
|
read_entities (layout, layout.cell (cell), db::DVector (-xoff, -yoff));
|
|
|
|
} else {
|
|
|
|
// read the entities and create all layer variants required so far.
|
|
read_entities (layout, layout.cell (b->second), db::DVector (-xoff, -yoff));
|
|
|
|
for (std::map <VariantKey, db::cell_index_type>::const_iterator b2l = m_block_to_variant.begin (); b2l != m_block_to_variant.end (); ++b2l) {
|
|
if (b2l->first.cell_index == b->second) {
|
|
fill_layer_variant_cell (layout, cell_name, b->second, b2l->second, b2l->first.layer, b2l->first.sx, b2l->first.sy);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
DXFReader::fill_layer_variant_cell (db::Layout &layout, const std::string & /*cellname*/, db::cell_index_type template_cell, db::cell_index_type var_cell, unsigned int layer, double sx, double sy)
|
|
{
|
|
m_used_template_cells.insert (template_cell);
|
|
|
|
const db::Cell &src = layout.cell (template_cell);
|
|
db::Cell &target = layout.cell (var_cell);
|
|
|
|
// copy all instances
|
|
for (db::Cell::const_iterator i = src.begin (); ! i.at_end (); ++i) {
|
|
|
|
db::CellInstArray cell_inst = i->cell_inst ();
|
|
|
|
// replace instances to template cells (those are not layer variants yet). This
|
|
// achieves a recursive variant building.
|
|
std::map<db::cell_index_type, std::string>::const_iterator tc = m_template_cells.find (cell_inst.object ().cell_index ());
|
|
if (tc != m_template_cells.end () || fabs (sx - 1.0) > 1e-6 || fabs (sy - 1.0) > 1e-6) {
|
|
|
|
db::Trans t = cell_inst.front ();
|
|
t = db::Trans (t.rot (), db::Vector (t.disp ().x () * sx, t.disp ().y () * sy));
|
|
|
|
bool swap_sxy = ((t.angle () % 2) != 0);
|
|
db::CellInst obj (make_layer_variant (layout, tc->second, cell_inst.object ().cell_index (), layer, swap_sxy ? sy : sx, swap_sxy ? sx : sy));
|
|
|
|
db::Vector a, b;
|
|
unsigned long na = 0, nb = 0;
|
|
if (cell_inst.is_regular_array (a, b, na, nb)) {
|
|
a = db::Vector (a.x () * sx, a.y () * sy);
|
|
b = db::Vector (b.x () * sx, b.y () * sy);
|
|
cell_inst = db::CellInstArray (obj, t, a, b, na, nb);
|
|
} else {
|
|
cell_inst = db::CellInstArray (obj, t);
|
|
}
|
|
|
|
}
|
|
|
|
target.insert (cell_inst);
|
|
|
|
}
|
|
|
|
if (fabs (sx - 1.0) < 1e-6 && fabs (sy - 1.0) < 1e-6) {
|
|
|
|
// copy the shapes except for the zero layer ...
|
|
for (db::Layout::layer_iterator l = layout.begin_layers (); l != layout.end_layers (); ++l) {
|
|
if ((*l).first != m_zero_layer || layer == m_zero_layer) {
|
|
target.shapes ((*l).first) = src.shapes ((*l).first);
|
|
}
|
|
}
|
|
|
|
// translate the zero layer shapes to the destination layer
|
|
if (layer != m_zero_layer) {
|
|
db::Shapes &ts = target.shapes (layer);
|
|
for (db::Shapes::shape_iterator s = src.shapes (m_zero_layer).begin (db::Shapes::shape_iterator::All); ! s.at_end (); ++s) {
|
|
ts.insert (*s);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
db::Matrix3d m (sx, 0.0, 0.0, sy);
|
|
|
|
// copy the shapes except for the zero layer ...
|
|
for (db::Layout::layer_iterator l = layout.begin_layers (); l != layout.end_layers (); ++l) {
|
|
if ((*l).first != m_zero_layer || layer == m_zero_layer) {
|
|
for (db::Shapes::shape_iterator s = src.shapes ((*l).first).begin (db::Shapes::shape_iterator::All); ! s.at_end (); ++s) {
|
|
insert_scaled (target.shapes ((*l).first), *s, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
// translate the zero layer shapes to the destination layer
|
|
if (layer != m_zero_layer) {
|
|
db::Shapes &ts = target.shapes (layer);
|
|
for (db::Shapes::shape_iterator s = src.shapes (m_zero_layer).begin (db::Shapes::shape_iterator::All); ! s.at_end (); ++s) {
|
|
insert_scaled (ts, *s, m);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
DXFReader::insert_scaled (db::Shapes &target, const db::Shape &src, const db::Matrix3d &m)
|
|
{
|
|
if (src.is_edge ()) {
|
|
db::Edge e;
|
|
src.edge (e);
|
|
target.insert (safe_from_double (e.transformed (m)));
|
|
} else if (src.is_box ()) {
|
|
db::Box b;
|
|
src.box (b);
|
|
target.insert (safe_from_double (b.transformed (m)));
|
|
} else if (src.is_path () || src.is_polygon ()) {
|
|
db::Polygon p;
|
|
src.polygon (p);
|
|
target.insert (safe_from_double (p.transformed (m)));
|
|
} else if (src.is_text ()) {
|
|
db::Text t;
|
|
src.text (t);
|
|
db::Trans tt = t.trans ();
|
|
t.trans (db::Trans (tt.rot (), safe_from_double (tt.disp ().transformed (m))));
|
|
t.size (db::coord_traits<db::Coord>::rounded (t.size () * m.mag_y ()));
|
|
target.insert (t);
|
|
}
|
|
}
|
|
|
|
db::cell_index_type
|
|
DXFReader::make_layer_variant (db::Layout &layout, const std::string &cellname, db::cell_index_type template_cell, unsigned int layer, double sx, double sy)
|
|
{
|
|
db::cell_index_type ci;
|
|
|
|
// for the zero layer the variant is equal to the template cell
|
|
if (layer == m_zero_layer && fabs (sx - 1.0) < 1e-6 && fabs (sy - 1.0) < 1e-6) {
|
|
return template_cell;
|
|
}
|
|
|
|
std::map <VariantKey, db::cell_index_type>::const_iterator b2l = m_block_to_variant.find (VariantKey (template_cell, layer, sx, sy));
|
|
if (b2l == m_block_to_variant.end ()) {
|
|
|
|
// create a new base layer variant
|
|
ci = layout.add_cell (cellname.c_str ());
|
|
m_block_to_variant.insert (std::make_pair (VariantKey (template_cell, layer, sx, sy), ci));
|
|
|
|
fill_layer_variant_cell (layout, cellname, template_cell, ci, layer, sx, sy);
|
|
|
|
} else {
|
|
ci = b2l->second;
|
|
}
|
|
|
|
return ci;
|
|
}
|
|
|
|
db::DCplxTrans
|
|
DXFReader::global_trans (const db::DVector &offset, double ex, double ey, double ez)
|
|
{
|
|
if (fabs (ex) > 1e-6 || fabs (ey) > 1e-6 || fabs (fabs (ez) - 1.0) > 1e-6) {
|
|
warn ("Only (0,0,1) and (0,0,-1) extrusion directions are supported");
|
|
}
|
|
|
|
double f = m_unit / m_dbu;
|
|
if (ez < 0.0) {
|
|
return db::DCplxTrans (f, 180.0, true, offset * f);
|
|
} else {
|
|
return db::DCplxTrans (f, 0.0, false, offset * f);
|
|
}
|
|
}
|
|
|
|
int
|
|
DXFReader::ncircle_for_radius (double rad) const
|
|
{
|
|
double accu = std::max (m_circle_accuracy, m_dbu / m_unit);
|
|
|
|
// This is roughly the limit where a circle will be 4 points always
|
|
if (rad < accu * 3) {
|
|
return 4;
|
|
}
|
|
|
|
// num of points = 1 / delta
|
|
double delta = acos (1.0 - accu / rad) / M_PI;
|
|
|
|
return int (0.5 + std::max (4.0, 1.0 / std::max (1.0 / double (std::max (4, m_circle_points)), delta)));
|
|
}
|
|
|
|
void
|
|
DXFReader::add_bulge_segment (std::vector<db::DPoint> &points, const db::DPoint &p, double b)
|
|
{
|
|
if (!points.empty () && fabs (b) > 1e-10) {
|
|
|
|
double a = 2.0 * atan (b);
|
|
|
|
db::DPoint p0 = points.back ();
|
|
db::DVector d = p - p0;
|
|
db::DVector t = db::DVector (-d.y (), d.x ());
|
|
db::DPoint m = (p0 + d * 0.5) + t * (0.5 / tan (a));
|
|
|
|
db::DVector r = p0 - m;
|
|
db::DVector s (-r.y (), r.x ());
|
|
|
|
int n = int (ceil (ncircle_for_radius (r.length ()) * fabs (a) / M_PI));
|
|
double da = 2 * a / std::max (1, n);
|
|
double dr = 1.0 / cos (0.5 * da);
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
points.push_back (m + r * (dr * cos (da * (0.5 + i))) + s * (dr * sin (da * (0.5 + i))));
|
|
}
|
|
|
|
}
|
|
|
|
points.push_back (p);
|
|
}
|
|
|
|
/*
|
|
|
|
Rational B-Splines (NURBS) vs. non-rational B-Splines:
|
|
https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline
|
|
|
|
De Boor algorithm for NURBS
|
|
https://github.com/caadxyz/DeBoorAlgorithmNurbs
|
|
|
|
*/
|
|
|
|
static db::DPoint
|
|
b_spline_point (double x, const std::vector<std::pair<db::DPoint, double> > &control_points, int p, const std::vector<double> &t, int &k)
|
|
{
|
|
k = (int) (std::lower_bound (t.begin (), t.end (), x - 1e-6) - t.begin ());
|
|
--k;
|
|
if (k < p) {
|
|
k = p;
|
|
} else if (k >= (int) control_points.size ()) {
|
|
k = (int) control_points.size () - 1;
|
|
}
|
|
|
|
std::vector<db::DPoint> d;
|
|
std::vector<double> dw;
|
|
d.reserve(p + 1);
|
|
for (int j = 0; j <= p; ++j) {
|
|
double w = control_points[j + k - p].second;
|
|
d.push_back (control_points[j + k - p].first * w);
|
|
dw.push_back (w);
|
|
}
|
|
|
|
for (int r = 1; r <= p; ++r) {
|
|
for (int j = p; j >= r; --j) {
|
|
double alpha = (x - t[j + k - p]) / (t[j + 1 + k - r] - t[j + k - p]);
|
|
d[j] = d[j] * alpha + (d[j - 1] - d[j - 1] * alpha);
|
|
dw[j] = dw[j] * alpha + dw[j - 1] * (1.0 - alpha);
|
|
}
|
|
}
|
|
|
|
return d[p] * (1.0 / dw[p]);
|
|
}
|
|
|
|
/**
|
|
* @brief Inserts new points into a sequence of points to refine the curve
|
|
*
|
|
* The idea is bisection of the segments until the desired degree of accuracy has been reached.
|
|
*
|
|
* @param control_points The control points
|
|
* @param curve_points The list of curve points which is going to be extended
|
|
* @param current_curve_point The iterator pointing to the current curve point
|
|
* @param t_start The t (curve parameter) value of the current curve point
|
|
* @param dt The current t interval
|
|
* @param degree The degree of the spline
|
|
* @param knots The knots
|
|
* @param sin_da The relative accuracy value implied by the circle resolution
|
|
* @param accu The desired absolute accuracy value
|
|
*
|
|
* New points are going to be inserted after current_curve_point and current_curve_point + 1 to achieve the
|
|
* required curvature.
|
|
*/
|
|
static void
|
|
spline_interpolate (std::list<db::DPoint> &curve_points,
|
|
std::list<db::DPoint>::iterator current_curve_point,
|
|
double t_start,
|
|
double dt,
|
|
const std::vector<std::pair<db::DPoint, double> > &control_points,
|
|
int degree,
|
|
const std::vector<double> &knots,
|
|
double sin_da,
|
|
double accu)
|
|
{
|
|
std::list<db::DPoint>::iterator pm = current_curve_point;
|
|
++pm;
|
|
std::list<db::DPoint>::iterator pe = pm;
|
|
++pe;
|
|
|
|
int k1 = 0, k2 = 0;
|
|
|
|
db::DPoint s1 = b_spline_point (t_start + 0.5 * dt, control_points, degree, knots, k1);
|
|
db::DPoint s2 = b_spline_point (t_start + 1.5 * dt, control_points, degree, knots, k2);
|
|
|
|
db::DVector p1 (s1, *current_curve_point);
|
|
db::DVector p2 (*pm, s1);
|
|
double pl1 = p1.length(), pl2 = p2.length();
|
|
|
|
if (k1 != k2) {
|
|
|
|
curve_points.insert (pm, s1);
|
|
spline_interpolate (curve_points, current_curve_point, t_start, dt * 0.5, control_points, degree, knots, sin_da, accu);
|
|
|
|
curve_points.insert (pe, s2);
|
|
spline_interpolate (curve_points, pm, t_start + dt, dt * 0.5, control_points, degree, knots, sin_da, accu);
|
|
|
|
} else {
|
|
|
|
db::DVector q1 (s2, *pm);
|
|
db::DVector q2 (*pe, s2);
|
|
double ql1 = q1.length(), ql2 = q2.length();
|
|
|
|
db::DVector p (*pm, *current_curve_point);
|
|
db::DVector q (*pe, *pm);
|
|
double pl = p.length (), ql = q.length ();
|
|
|
|
if (fabs (db::vprod (p, q)) > pl * ql * sin_da ||
|
|
fabs (db::vprod (p1, p2)) > pl1 * pl2 * sin_da ||
|
|
fabs (db::vprod (q1, q2)) > ql1 * ql2 * sin_da) {
|
|
|
|
// angle between the segments is bigger than 2PI/n -> circle resolution is
|
|
// too small. Or: the angle between the new segments that we would introduce
|
|
// is also bigger (hence, the original segments may have a small angle but the
|
|
// curve "wiggles" when we increase the resolution)
|
|
|
|
if (fabs (db::vprod (p1, p)) > pl * accu) {
|
|
|
|
// In addition, the estimated accuracy is not good enough on the first segment: bisect this
|
|
// segment.
|
|
|
|
curve_points.insert (pm, s1);
|
|
spline_interpolate (curve_points, current_curve_point, t_start, dt * 0.5, control_points, degree, knots, sin_da, accu);
|
|
|
|
}
|
|
|
|
if (fabs (db::vprod (q1, q)) > ql * accu) {
|
|
|
|
// In addition, the estimated accuracy is not good enough on the first segment: bisect this
|
|
// segment.
|
|
|
|
curve_points.insert (pe, s2);
|
|
spline_interpolate (curve_points, pm, t_start + dt, dt * 0.5, control_points, degree, knots, sin_da, accu);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
std::list<db::DPoint>
|
|
DXFReader::spline_interpolation (std::vector<std::pair<db::DPoint, double> > &control_points, int degree, const std::vector<double> &knots)
|
|
{
|
|
// TODO: this is quite inefficient
|
|
if (int (knots.size()) != int (control_points.size() + degree + 1)) {
|
|
warn ("Spline interpolation failed: mismatch between number of knots and points");
|
|
return std::list<db::DPoint> ();
|
|
}
|
|
|
|
if (int(knots.size ()) <= degree || control_points.empty () || degree <= 1) {
|
|
return std::list<db::DPoint> ();
|
|
}
|
|
|
|
double t0 = knots [degree];
|
|
double tn = knots [knots.size () - degree - 1];
|
|
|
|
// we shall have at least min_points points per spline curve
|
|
double sin_da = sin (2.0 * M_PI / m_circle_points);
|
|
double accu = std::max (m_circle_accuracy, m_dbu / m_unit);
|
|
|
|
std::list<db::DPoint> new_points;
|
|
|
|
double dt = 0.5 * (tn - t0);
|
|
|
|
for (double t = t0; t < tn + 1e-6; t += dt) {
|
|
int k = 0;
|
|
db::DPoint s = b_spline_point (t, control_points, degree, knots, k);
|
|
new_points.push_back (s);
|
|
}
|
|
|
|
spline_interpolate (new_points, new_points.begin (), t0, dt, control_points, degree, knots, sin_da, accu);
|
|
|
|
return new_points;
|
|
}
|
|
|
|
void
|
|
DXFReader::elliptic_interpolation (std::vector<db::DPoint> &points, const std::vector<double> &rmin, const std::vector<db::DPoint> &vmaj, const std::vector<double> &start, const std::vector<double> &end, const std::vector<int> &ccw)
|
|
{
|
|
if (rmin.size () != points.size () || vmaj.size () != points.size () || start.size () != points.size () || end.size () != points.size () || (!ccw.empty () && ccw.size () != points.size ())) {
|
|
warn ("Elliptic arc interpolation failed: mismatch between number of parameters and points");
|
|
return;
|
|
}
|
|
|
|
std::vector<db::DPoint> new_points;
|
|
|
|
for (size_t i = 0; i < points.size (); ++i) {
|
|
|
|
double sa = start [i], ea = end [i];
|
|
while (ea < sa - 1e-6) {
|
|
ea += 360;
|
|
}
|
|
|
|
db::DVector vx = db::DVector (vmaj [i]);
|
|
db::DVector vy = db::DVector (vx.y (), -vx.x ()) * rmin [i];
|
|
|
|
sa *= M_PI / 180.0;
|
|
ea *= M_PI / 180.0;
|
|
|
|
int n = int (std::max (1.0, floor (0.5 + (ea - sa) * ncircle_for_radius (std::min (vx.length (), vy.length ())) / (2.0 * M_PI))));
|
|
|
|
double da = (ea - sa) / n;
|
|
double dr = 1.0 / cos (0.5 * da);
|
|
|
|
bool ccw_flag = (ccw.empty () || ccw [i]);
|
|
if (ccw_flag) {
|
|
vy = -vy;
|
|
}
|
|
|
|
new_points.push_back (points[i] + vx * cos (sa) + vy * sin (sa));
|
|
|
|
for (int j = 0; j < n; ++j) {
|
|
new_points.push_back (points[i] + vx * (dr * cos (sa + (j + 0.5) * da)) + vy * (dr * sin (sa + (j + 0.5) * da)));
|
|
}
|
|
|
|
new_points.push_back (points[i] + vx * cos (ea) + vy * sin (ea));
|
|
|
|
}
|
|
|
|
points.swap (new_points);
|
|
}
|
|
|
|
void
|
|
DXFReader::arc_interpolation (std::vector<db::DPoint> &points, const std::vector<double> &rad, const std::vector<double> &start, const std::vector<double> &end, const std::vector<int> &ccw)
|
|
{
|
|
if (rad.size () != points.size () || start.size () != points.size () || end.size () != points.size () || (!ccw.empty () && ccw.size () != points.size ())) {
|
|
warn ("Circular arc interpolation failed: mismatch between number of parameters and points");
|
|
return;
|
|
}
|
|
|
|
std::vector<db::DPoint> new_points;
|
|
|
|
for (size_t i = 0; i < points.size (); ++i) {
|
|
|
|
double sa = start [i], ea = end [i];
|
|
while (ea < sa - 1e-6) {
|
|
ea += 360;
|
|
}
|
|
|
|
sa *= M_PI / 180.0;
|
|
ea *= M_PI / 180.0;
|
|
|
|
int n = int (std::max (1.0, floor (0.5 + (ea - sa) * ncircle_for_radius (rad [i]) / (2.0 * M_PI))));
|
|
|
|
double da = (ea - sa) / n;
|
|
double dr = 1.0 / cos (0.5 * da);
|
|
|
|
db::DVector vx = db::DVector (rad [i], 0.0);
|
|
db::DVector vy = db::DVector (vx.y (), -vx.x ());
|
|
|
|
bool ccw_flag = (ccw.empty () || ccw [i]);
|
|
if (ccw_flag) {
|
|
vy = -vy;
|
|
}
|
|
|
|
new_points.push_back (points[i] + vx * cos (sa) + vy * sin (sa));
|
|
|
|
for (int j = 0; j < n; ++j) {
|
|
new_points.push_back (points[i] + vx * (dr * cos (sa + (0.5 + j) * da)) + vy * (dr * sin (sa + (0.5 + j) * da)));
|
|
}
|
|
|
|
new_points.push_back (points[i] + vx * cos (ea) + vy * sin (ea));
|
|
|
|
}
|
|
|
|
points.swap (new_points);
|
|
}
|
|
|
|
void
|
|
DXFReader::deliver_points_to_edges (std::vector<db::DPoint> &points, const std::vector<db::DPoint> &points2, const db::DCplxTrans &tt, int edge_type, int value94, const std::vector<double> &value40, const std::vector<double> &value50, const std::vector<double> &value51, const std::vector<int> &value73, std::vector<db::Edge> &iedges)
|
|
{
|
|
if (points.empty ()) {
|
|
return;
|
|
}
|
|
|
|
if (edge_type == 4) {
|
|
|
|
std::vector<std::pair<db::DPoint, double> > control_points;
|
|
control_points.reserve (points.size ());
|
|
for (std::vector<db::DPoint>::const_iterator p = points.begin (); p != points.end (); ++p) {
|
|
control_points.push_back (std::make_pair (*p, 1.0));
|
|
}
|
|
|
|
std::list<db::DPoint> new_points = spline_interpolation (control_points, value94, value40);
|
|
if (! new_points.empty ()) {
|
|
points.clear ();
|
|
points.insert (points.end (), ++new_points.begin (), new_points.end ());
|
|
}
|
|
|
|
} else if (edge_type == 1) {
|
|
|
|
if (points.size () != points2.size ()) {
|
|
warn ("Line interpolation failed: mismatch between number of points");
|
|
return;
|
|
}
|
|
|
|
std::vector<db::DPoint> points1;
|
|
points1.swap (points);
|
|
|
|
points.reserve (points1.size () + points2.size ());
|
|
for (size_t i = 0; i < points1.size (); ++i) {
|
|
points.push_back (points1 [i]);
|
|
points.push_back (points2 [i]);
|
|
}
|
|
|
|
} else if (edge_type == 2) {
|
|
|
|
arc_interpolation (points, value40, value50, value51, value73);
|
|
|
|
} else if (edge_type == 3) {
|
|
|
|
elliptic_interpolation (points, value40, points2, value50, value51, value73);
|
|
|
|
}
|
|
|
|
// produce the edges. If cont is true, continue with the previous edge
|
|
if (! points.empty ()) {
|
|
db::Point pl = safe_from_double (tt * points.front ());
|
|
for (std::vector<db::DPoint>::const_iterator p = points.begin () + 1; p != points.end (); ++p) {
|
|
db::Edge ie (pl, safe_from_double (tt * *p));
|
|
if (! ie.is_degenerate ()) {
|
|
iedges.push_back (ie);
|
|
}
|
|
pl = ie.p2 ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Adds closing edges to the loop
|
|
* For this we look for edges not having a connecting edge and insert edges to the nearest points.
|
|
*/
|
|
static void
|
|
finish_loop (size_t from, size_t to, std::vector<db::Edge> &edges)
|
|
{
|
|
std::multiset<db::Point> p1;
|
|
for (size_t i = from; i < to; ++i) {
|
|
p1.insert (edges [i].p1 ());
|
|
}
|
|
|
|
for (size_t i = from; i < to; ++i) {
|
|
|
|
db::Point pi2 = edges [i].p2 ();
|
|
|
|
std::multiset<db::Point>::iterator ip1 = p1.find (pi2);
|
|
if (ip1 != p1.end ()) {
|
|
|
|
p1.erase (ip1);
|
|
|
|
} else {
|
|
|
|
// search for the nearest point to connect to
|
|
|
|
db::Point p1min = edges [i].p1 ();
|
|
std::multiset<db::Point>::iterator ip1min = p1.end ();
|
|
|
|
double d = -1.0;
|
|
for (size_t j = from; j < to; ++j) {
|
|
db::Point pj1 = edges [j].p1 ();
|
|
double dd = pj1.sq_double_distance (pi2);
|
|
if (j != i && (d < 0.0 || dd < d) && (ip1 = p1.find (pj1)) != p1.end ()) {
|
|
ip1min = ip1;
|
|
p1min = pj1;
|
|
d = dd;
|
|
}
|
|
}
|
|
|
|
if (ip1min != p1.end ()) {
|
|
p1.erase (ip1min);
|
|
}
|
|
|
|
edges.push_back (db::Edge (pi2, p1min));
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static std::string
|
|
normalize_string (const std::string &in, bool for_mtext)
|
|
{
|
|
/*
|
|
Note: MTEXT's have some embedded formatting options.
|
|
These are some options taken from http://www.cadforum.cz/forum_en/forum_posts.asp?TID=5178:
|
|
\O...\o Turns overline on and off
|
|
\L...\l Turns underline on and off
|
|
\~ Inserts a nonbreaking space
|
|
\\ Inserts a backslash
|
|
\{...\} Inserts an opening and closing brace
|
|
\File name; Changes to the specified font file
|
|
\Hvalue; Changes to the text height specified in drawing units
|
|
\Hvaluex; Changes the text height to a multiple of the current text height
|
|
\S...^...; Stacks the subsequent text at the \, #, or ^ symbol
|
|
\Tvalue; Adjusts the space between characters, from.75 to 4 times
|
|
\Qangle; Changes obliquing angle
|
|
\Wvalue; Changes width factor to produce wide text
|
|
\Avalue; Sets the alignment value; valid values: 0, 1, 2 (bottom, center, top)
|
|
|
|
Some other codes that appear to be used:
|
|
%%d %
|
|
%%p +/-
|
|
*/
|
|
std::string s;
|
|
|
|
for (const char *c = in.c_str (); *c; ) {
|
|
|
|
if (*c == '%' && c[1] == '%' && c[2] && tolower (c[2]) == 'p') {
|
|
// replace %%p by +/-
|
|
s += "+/-";
|
|
c += 3;
|
|
} else if (*c == '%' && c[1] == '%' && tolower (c[2]) == 'd') {
|
|
// replace %%d by %
|
|
s += "%";
|
|
c += 3;
|
|
} else if (for_mtext && *c == '^' && c[1] == 'J') {
|
|
// replace
|
|
s += "\n";
|
|
c += 2;
|
|
} else if (for_mtext && (*c == '{' || *c == '}')) {
|
|
// ignore { .. } brackets
|
|
++c;
|
|
} else if (*c == '\\' && c[1] && tolower (c[1]) == 'u') {
|
|
|
|
c += 2;
|
|
if (*c == '+') {
|
|
++c;
|
|
}
|
|
|
|
int code = 0;
|
|
for (int i = 0; i < 4; ++i) {
|
|
if (!*c) {
|
|
break;
|
|
} else if (isdigit(*c)) {
|
|
code = (code * 16) + int(*c - '0');
|
|
++c;
|
|
} else if (tolower(*c) <= 'f' && tolower(*c) >= 'a') {
|
|
code = (code * 16) + int(tolower(*c) - 'a' + 10);
|
|
++c;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::wstring ws;
|
|
ws += wchar_t (code);
|
|
s += tl::to_string (ws);
|
|
|
|
} else if (for_mtext && *c == '\\' && c[1] && tolower(c[1]) == 'p') {
|
|
s += "\n";
|
|
c += 2;
|
|
} else if (for_mtext && *c == '\\' && c[1] && (tolower(c[1]) == 'o' || tolower(c[1]) == 'l')) {
|
|
// ignore underline, overline,
|
|
c += 2;
|
|
} else if (for_mtext && *c == '\\' && c[1] && tolower(c[1]) == '~') {
|
|
// ignore non-breaking space
|
|
c += 2;
|
|
} else if (for_mtext && *c == '\\' && c[1] && isalpha (c[1])) {
|
|
// ignore other formatting commands
|
|
c += 2;
|
|
while (*c && *c != ';') {
|
|
++c;
|
|
}
|
|
if (*c) {
|
|
++c;
|
|
}
|
|
} else if (*c == '\\' && c[1]) {
|
|
// backslash escape
|
|
s += c[1];
|
|
c += 2;
|
|
} else {
|
|
s += *c;
|
|
++c;
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
void
|
|
DXFReader::deliver_text (db::Shapes &shapes, const std::string &s, const db::DCplxTrans &text_trans, double h, double ls, int halign, int valign, double w)
|
|
{
|
|
db::HAlign ha = db::NoHAlign;
|
|
if (halign == 0) {
|
|
ha = HAlignLeft;
|
|
// TODO: what is the interpretation of halign (TEXT code 72) value 3 and 5?
|
|
} else if (halign == 1 || halign == 3 || halign == 4 || halign == 5) {
|
|
ha = HAlignCenter;
|
|
} else if (halign == 2) {
|
|
ha = HAlignRight;
|
|
}
|
|
db::VAlign va = db::NoVAlign;
|
|
if (valign == 0 || valign == 1) {
|
|
va = VAlignBottom;
|
|
} else if (valign == 2) {
|
|
va = VAlignCenter;
|
|
} else if (valign == 3) {
|
|
va = VAlignTop;
|
|
}
|
|
|
|
if (m_render_texts_as_polygons) {
|
|
|
|
#if defined(HAVE_QT)
|
|
|
|
db::EdgeProcessor ep;
|
|
|
|
// we use a pixel size of 200 for reference, so we are less dependent on the accuracy of the
|
|
// font rendering engine
|
|
QFont f (QString::fromUtf8 ("Courier"));
|
|
f.setPixelSize (200);
|
|
QFontMetrics fm (f);
|
|
|
|
// The m_text_scaling divider is the letter width in percent of the height.
|
|
// 92 is the default letter pitch in percent of the text height.
|
|
int pixel_size_ref = int (floor (0.5 + 100.0 * fm_width (fm, QChar::fromLatin1 ('X')) / (0.92 * m_text_scaling)));
|
|
|
|
// split text into lines
|
|
QStringList lines = QString::fromUtf8 (s.c_str ()).split (QString::fromUtf8 ("\n"));
|
|
double y0 = 0.0;
|
|
if (va == VAlignBottom || va == NoVAlign) {
|
|
y0 += h * (lines.size () - 1);
|
|
} else if (va == VAlignCenter) {
|
|
y0 += h * (0.5 * lines.size () - 1);
|
|
} else {
|
|
y0 = -h;
|
|
}
|
|
|
|
std::vector <db::Point> points;
|
|
std::vector <db::Edge> iedges;
|
|
|
|
if (w > 0.0) {
|
|
|
|
// wrap lines if required
|
|
QStringList ll = lines;
|
|
lines.clear ();
|
|
for (QStringList::const_iterator l = ll.begin (); l != ll.end (); ++l) {
|
|
|
|
if (fm_width (fm, *l) * h / pixel_size_ref > w) {
|
|
|
|
// wrapping required
|
|
QString line;
|
|
double wl = 0.0;
|
|
for (int i = 0; i < int (l->size ()); ) {
|
|
|
|
QString ls;
|
|
bool any_word = false;
|
|
while (i < int (l->size ()) && ((*l)[i].isLetter () || (*l)[i].isDigit ())) {
|
|
ls += (*l)[i];
|
|
++i;
|
|
any_word = true;
|
|
}
|
|
if (! any_word) {
|
|
ls += (*l)[i];
|
|
++i;
|
|
}
|
|
|
|
double wc = fm_width (fm, ls) * h / pixel_size_ref;
|
|
if (wl + wc > w) {
|
|
lines.push_back (line);
|
|
line.clear ();
|
|
wl = 0;
|
|
}
|
|
|
|
line += ls;
|
|
wl += wc;
|
|
|
|
}
|
|
|
|
if (! line.isEmpty ()) {
|
|
lines.push_back (line);
|
|
}
|
|
|
|
} else {
|
|
// no wrapping required
|
|
lines.push_back (*l);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (QStringList::const_iterator l = lines.begin (); l != lines.end (); ++l) {
|
|
|
|
double x0 = 0.0;
|
|
if (ha == HAlignLeft || ha == NoHAlign) {
|
|
} else if (ha == HAlignCenter) {
|
|
x0 -= fm_width (fm, *l) * 0.5 * h / pixel_size_ref;
|
|
} else {
|
|
x0 -= fm_width (fm, *l) * h / pixel_size_ref;
|
|
}
|
|
|
|
QPainterPath pp;
|
|
pp.addText (QPointF (0.0, 0.0), f, *l);
|
|
QList<QPolygonF> polygons = pp.toFillPolygons ();
|
|
for (QList<QPolygonF>::const_iterator poly = polygons.begin (); poly != polygons.end (); ++poly) {
|
|
|
|
points.clear ();
|
|
for (QPolygonF::const_iterator pt = poly->begin (); pt != poly->end (); ++pt) {
|
|
points.push_back (safe_from_double (text_trans * db::DPoint (pt->x () * h / pixel_size_ref + x0, -pt->y () * h / pixel_size_ref + y0)));
|
|
}
|
|
|
|
for (size_t i = 0; i < points.size (); ++i) {
|
|
if (i == 0) {
|
|
iedges.push_back (db::Edge (points.back (), points [i]));
|
|
} else {
|
|
iedges.push_back (db::Edge (points [i - 1], points [i]));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
std::vector <db::Polygon> pout;
|
|
ep.simple_merge (iedges, pout, true /*resolve holes*/, true /*min coherence*/, 0);
|
|
|
|
for (std::vector <db::Polygon>::const_iterator po = pout.begin (); po != pout.end (); ++po) {
|
|
shapes.insert (*po);
|
|
}
|
|
|
|
y0 -= ls;
|
|
|
|
}
|
|
|
|
#else
|
|
error (tl::to_string (tr ("Render texts as polygons is not available (Qt not compiled in)")));
|
|
#endif
|
|
|
|
} else {
|
|
db::DText text (s, db::DTrans (text_trans), text_trans.ctrans (h * m_text_scaling / 100.0), db::NoFont, ha, va);
|
|
shapes.insert (safe_from_double (text));
|
|
}
|
|
}
|
|
|
|
void
|
|
DXFReader::read_entities (db::Layout &layout, db::Cell &cell, const db::DVector &offset)
|
|
{
|
|
std::map <unsigned int, std::vector <db::Edge> > collected_edges;
|
|
db::EdgeProcessor ep (true /* with progress*/);
|
|
|
|
int g;
|
|
|
|
while (true) {
|
|
|
|
const std::string &entity_code = read_string (true);
|
|
|
|
if (entity_code == "ENDSEC") {
|
|
break;
|
|
} else if (entity_code == "ENDBLK") {
|
|
break;
|
|
} else if (entity_code == "LWPOLYLINE" || entity_code == "POLYLINE") {
|
|
|
|
std::vector<db::DPoint> points;
|
|
std::vector<std::pair<size_t, double> > widths;
|
|
|
|
std::string layer;
|
|
int flags = 0;
|
|
double width1 = 0.0, width2 = 0.0;
|
|
double common_width1 = 0.0, common_width2 = 0.0;
|
|
unsigned int common_width_set = 0;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
size_t tot_points = 0;
|
|
double b = 0.0;
|
|
|
|
if (entity_code == "LWPOLYLINE") {
|
|
|
|
unsigned int xy_flags = 0;
|
|
double x = 0.0, y = 0.0;
|
|
unsigned int got_width = 0;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 70) {
|
|
flags = read_int16 ();
|
|
} else if (g == 10 || g == 20) {
|
|
|
|
if (g == 10) {
|
|
x = read_double ();
|
|
xy_flags |= 1;
|
|
} else {
|
|
y = read_double ();
|
|
xy_flags |= 2;
|
|
}
|
|
|
|
if (xy_flags == 3) {
|
|
|
|
size_t seg_start = std::max (size_t (1), points.size ()) - 1;
|
|
add_bulge_segment (points, db::DPoint (x, y), b);
|
|
++tot_points;
|
|
b = 0.0;
|
|
xy_flags = 0;
|
|
|
|
if (got_width == 3) {
|
|
widths.push_back (std::make_pair (seg_start, width1));
|
|
widths.push_back (std::make_pair (points.size () - 1, width2));
|
|
}
|
|
|
|
got_width = 0;
|
|
|
|
}
|
|
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else if (g == 43) {
|
|
|
|
common_width1 = common_width2 = read_double ();
|
|
common_width_set = 3;
|
|
|
|
} else if (g == 42) {
|
|
b = read_double (); // bulge
|
|
} else if (g == 41 || g == 40) {
|
|
|
|
if (g == 41) {
|
|
got_width |= 2;
|
|
width2 = read_double ();
|
|
} else {
|
|
got_width |= 1;
|
|
width1 = read_double ();
|
|
}
|
|
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 70) {
|
|
flags = read_int16 ();
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else if (g == 40 || g == 41) {
|
|
|
|
if (g == 40) {
|
|
common_width1 = read_double ();
|
|
common_width_set |= 1;
|
|
} else {
|
|
common_width2 = read_double ();
|
|
common_width_set |= 2;
|
|
}
|
|
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
|
|
}
|
|
|
|
while (true) {
|
|
|
|
const std::string &e = read_string (true);
|
|
if (e == "VERTEX") {
|
|
|
|
unsigned int got_width = 0;
|
|
|
|
double x = 0.0, y = 0.0;
|
|
double bnew = 0.0;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 10) {
|
|
x = read_double ();
|
|
} else if (g == 20) {
|
|
y = read_double ();
|
|
} else if (g == 42) {
|
|
bnew = read_double (); // bulge
|
|
} else if (g == 40 || g == 41) {
|
|
|
|
if (g == 41) {
|
|
got_width |= 2;
|
|
width2 = read_double ();
|
|
} else {
|
|
got_width |= 1;
|
|
width1 = read_double ();
|
|
}
|
|
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
size_t seg_start = std::max (size_t (1), points.size ()) - 1;
|
|
add_bulge_segment (points, db::DPoint (x, y), b);
|
|
++tot_points;
|
|
b = bnew;
|
|
|
|
if (got_width == 3) {
|
|
widths.push_back (std::make_pair (seg_start, width1));
|
|
widths.push_back (std::make_pair (points.size () - 1, width2));
|
|
}
|
|
|
|
} else if (e == "SEQEND") {
|
|
while ((g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
break;
|
|
} else {
|
|
while ((g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Adds the common width if given
|
|
if (common_width_set > 0 && ! points.empty ()) {
|
|
if (widths.empty ()) {
|
|
widths.insert (widths.begin (), std::make_pair (size_t (0), common_width1));
|
|
widths.push_back (std::make_pair (points.size () - 1, common_width2));
|
|
} else {
|
|
if (widths.front ().first != 0) {
|
|
widths.insert (widths.begin (), std::make_pair (size_t (0), common_width1));
|
|
}
|
|
if (widths.back ().first != points.size () - 1) {
|
|
widths.push_back (std::make_pair (points.size () - 1, common_width2));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a closing arc if a bulge was specified on the last point and the polygon is
|
|
// marked as a closed one
|
|
if (fabs (b) > 1e-10 && (flags & 1) != 0) {
|
|
// Hint: needs a copy, since points may be altered by add_bulge_segment
|
|
db::DPoint p0 (points [0]);
|
|
add_bulge_segment (points, p0, b);
|
|
if (! widths.empty ()) {
|
|
widths.push_back (std::make_pair (points.size () - 1, widths.front ().second));
|
|
}
|
|
}
|
|
|
|
// check whether there is a common width and create a path if there is one
|
|
bool width_set = false;
|
|
double width = 0.0;
|
|
for (std::vector<std::pair<size_t, double> >::const_iterator w = widths.begin (); w != widths.end (); ++w) {
|
|
if (! width_set) {
|
|
width = w->second;
|
|
width_set = true;
|
|
} else if (width > -1e-6 && fabs (width - w->second) > 1e-6) {
|
|
width = -1.0;
|
|
}
|
|
}
|
|
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
// create the polygon or path for width = 0: in mode 2, the polygon is always created, in modes 3 and 4
|
|
// the polygon is split into edges and joined with other edges later (this will resolve holes formed by
|
|
// other polygons)
|
|
if (width < -1e-6) {
|
|
|
|
db::DVariableWidthPath vp (points.begin (), points.end (), widths.begin (), widths.end (), tt);
|
|
cell.shapes(ll.second).insert (safe_from_double (vp.to_poly ()));
|
|
|
|
} else if (width < 1e-6 && (flags & 1) != 0 && m_polyline_mode == 2) {
|
|
|
|
db::DPolygon p;
|
|
p.assign_hull (points.begin (), points.end (), tt);
|
|
cell.shapes(ll.second).insert (safe_from_double (p));
|
|
|
|
} else if (! points.empty ()) {
|
|
|
|
// in the merge line modes create a set of edges from an open polyline and merge later
|
|
if (width < 1e-6 && /*(flags & 1) == 0 &&*/ m_polyline_mode >= 3) {
|
|
|
|
std::vector <db::Edge> &edges = collected_edges.insert (std::make_pair (ll.second, std::vector <db::Edge> ())).first->second;
|
|
for (std::vector<db::DPoint>::const_iterator p = points.begin () + 1; p != points.end (); ++p) {
|
|
edges.push_back (safe_from_double (db::DEdge (tt.trans (p[-1]), tt.trans (*p))));
|
|
}
|
|
|
|
if ((flags & 1) != 0) {
|
|
edges.push_back (safe_from_double (db::DEdge (tt.trans (points.back ()), tt.trans (points.front ()))));
|
|
}
|
|
|
|
} else {
|
|
|
|
if ((flags & 1) != 0 && fabs (width) > 1e-6 && points.size () > 2) {
|
|
|
|
// closed polylines are created by forming the rim of a polygon
|
|
// with the specified width
|
|
|
|
db::DPolygon p;
|
|
p.assign_hull (points.begin (), points.end (), tt, false /*no compression*/);
|
|
std::vector <db::Polygon> pin;
|
|
pin.push_back (safe_from_double (p));
|
|
|
|
std::vector <db::Polygon> pouter, pinner;
|
|
db::Coord w = db::coord_traits<db::Coord>::rounded (tt.ctrans (std::max (0.0, width)));
|
|
ep.size (pin, w * 0.5, w * 0.5, pouter, 2, false);
|
|
ep.size (pouter, -w, -w, pinner, 2, false);
|
|
|
|
pin.clear ();
|
|
ep.boolean (pouter, pinner, pin, db::BooleanOp::ANotB, true /*resolve holes*/);
|
|
|
|
for (std::vector <db::Polygon>::const_iterator po = pin.begin (); po != pin.end (); ++po) {
|
|
cell.shapes(ll.second).insert (*po);
|
|
}
|
|
|
|
} else {
|
|
|
|
if ((flags & 1) != 0) {
|
|
points.push_back (points.front ());
|
|
}
|
|
|
|
db::DPath p;
|
|
p.assign (points.begin (), points.end (), tt);
|
|
p.bgn_ext (0.0);
|
|
p.end_ext (0.0);
|
|
p.width (tt.ctrans (std::max (0.0, width)));
|
|
cell.shapes (ll.second).insert (safe_from_double (p));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (entity_code == "SPLINE") {
|
|
|
|
std::vector<double> knots;
|
|
std::vector<std::pair<db::DPoint, double> > control_points;
|
|
std::vector<double> weights;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
|
|
std::string layer;
|
|
unsigned int xy_flag = 0;
|
|
int degree = 1;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10 || g == 20) {
|
|
|
|
if (xy_flag == 0) {
|
|
control_points.push_back (std::make_pair (db::DPoint (), 1.0));
|
|
}
|
|
|
|
if (g == 10) {
|
|
control_points.back ().first.set_x (read_double ());
|
|
xy_flag |= 1;
|
|
} else {
|
|
control_points.back ().first.set_y (read_double ());
|
|
xy_flag |= 2;
|
|
}
|
|
if (xy_flag == 3) {
|
|
xy_flag = 0;
|
|
}
|
|
|
|
} else if (g == 70) {
|
|
|
|
int flags = read_int32 ();
|
|
if (flags != 8 && flags != 12) {
|
|
warn ("Invalid SPLINE flag (code 70): " + tl::to_string (flags) + ". Only types 8 (non-rational) and 12 (rational) are supported currently.");
|
|
}
|
|
|
|
} else if (g == 71) {
|
|
degree = read_int32 ();
|
|
} else if (g == 40) {
|
|
knots.push_back (read_double ());
|
|
} else if (g == 41) {
|
|
weights.push_back (read_double ());
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < weights.size () && i < control_points.size (); ++i) {
|
|
control_points [i].second = weights [i];
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first && ! control_points.empty ()) {
|
|
|
|
std::list<db::DPoint> new_points = spline_interpolation (control_points, degree, knots);
|
|
|
|
if (m_polyline_mode == 3 || m_polyline_mode == 4) {
|
|
|
|
// in "join" mode, add an edge for each segment
|
|
std::vector <db::Edge> &edges = collected_edges.insert (std::make_pair (ll.second, std::vector <db::Edge> ())).first->second;
|
|
std::list<db::DPoint>::const_iterator i = new_points.begin ();
|
|
if (i != new_points.end ()) {
|
|
std::list<db::DPoint>::const_iterator ii = i;
|
|
++ii;
|
|
while (ii != new_points.end ()) {
|
|
db::Edge edge = safe_from_double (db::DEdge (tt.trans (*i), tt.trans (*ii)));
|
|
if (! edge.is_degenerate ()) {
|
|
edges.push_back (edge);
|
|
}
|
|
++i;
|
|
++ii;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
// create a path with width 0 for the spline
|
|
db::DPath p;
|
|
p.assign (new_points.begin (), new_points.end (), tt);
|
|
p.bgn_ext (0.0);
|
|
p.end_ext (0.0);
|
|
p.width (0);
|
|
cell.shapes (ll.second).insert (safe_from_double (p));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (entity_code == "LINE") {
|
|
|
|
db::DPoint p1, p2;
|
|
double w = 0.0;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
|
|
std::string layer;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10) {
|
|
p1.set_x (read_double ());
|
|
} else if (g == 20) {
|
|
p1.set_y (read_double ());
|
|
} else if (g == 11) {
|
|
p2.set_x (read_double ());
|
|
} else if (g == 21) {
|
|
p2.set_y (read_double ());
|
|
} else if (g == 39) {
|
|
w = read_double ();
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
|
|
if (w < 1e-6 && (m_polyline_mode == 3 || m_polyline_mode == 4)) {
|
|
|
|
std::vector <db::Edge> &edges = collected_edges.insert (std::make_pair (ll.second, std::vector <db::Edge> ())).first->second;
|
|
edges.push_back (safe_from_double (db::DEdge (tt.trans (p1), tt.trans (p2))));
|
|
|
|
} else {
|
|
|
|
// create the path
|
|
db::DPoint points [2] = { p1, p2 };
|
|
db::DPath p;
|
|
p.assign (&points[0], points + 2, tt);
|
|
p.bgn_ext (0.0);
|
|
p.end_ext (0.0);
|
|
p.width (tt.ctrans (std::max (0.0, w)));
|
|
cell.shapes (ll.second).insert (safe_from_double (p));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (entity_code == "TRACE") {
|
|
|
|
db::DPoint p1, p2, p3, p4;
|
|
/* not used currently:
|
|
double w = 0.0;
|
|
*/
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
|
|
std::string layer;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10) {
|
|
p1.set_x (read_double ());
|
|
} else if (g == 20) {
|
|
p1.set_y (read_double ());
|
|
} else if (g == 11) {
|
|
p2.set_x (read_double ());
|
|
} else if (g == 21) {
|
|
p2.set_y (read_double ());
|
|
} else if (g == 12) {
|
|
p3.set_x (read_double ());
|
|
} else if (g == 22) {
|
|
p3.set_y (read_double ());
|
|
} else if (g == 13) {
|
|
p4.set_x (read_double ());
|
|
} else if (g == 23) {
|
|
p4.set_y (read_double ());
|
|
} else if (g == 39) {
|
|
// not used currently: w = read_double ();
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
|
|
// TODO: what to do with the width?
|
|
|
|
// create the path
|
|
db::DPoint points [4] = { p1, p2, p4, p3 };
|
|
db::DPolygon p;
|
|
p.assign_hull (&points[0], points + 4, tt);
|
|
cell.shapes(ll.second).insert (safe_from_double (p));
|
|
|
|
}
|
|
|
|
} else if (entity_code == "ARC") {
|
|
|
|
db::DPoint pc;
|
|
double as = 0.0, ae = 0.0;
|
|
double r = 0.0;
|
|
double w = 0.0;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
|
|
std::string layer;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10) {
|
|
pc.set_x (read_double ());
|
|
} else if (g == 20) {
|
|
pc.set_y (read_double ());
|
|
} else if (g == 50) {
|
|
as = read_double ();
|
|
} else if (g == 51) {
|
|
ae = read_double ();
|
|
} else if (g == 40) {
|
|
r = read_double ();
|
|
} else if (g == 39) {
|
|
w = read_double ();
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
if (ae < as - 1e-6) {
|
|
ae += -floor ((ae - as - 1e-6) / 360.0) * 360.0;
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
|
|
ae *= M_PI / 180.0;
|
|
as *= M_PI / 180.0;
|
|
|
|
int n = int (std::max (1.0, ceil ((ae - as) / (2.0 * M_PI) * ncircle_for_radius (r))));
|
|
double da = (ae - as) / n;
|
|
double dr = 1.0 / cos (0.5 * da);
|
|
|
|
std::vector<db::DPoint> points;
|
|
points.reserve (n + 1);
|
|
|
|
points.push_back (db::DPoint (pc.x () + r * cos (as), pc.y () + r * sin (as)));
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
points.push_back (db::DPoint (pc.x () + r * (dr * cos (as + da * (i + 0.5))), pc.y () + r * (dr * sin (as + da * (i + 0.5)))));
|
|
}
|
|
|
|
points.push_back (db::DPoint (pc.x () + r * cos (ae), pc.y () + r * sin (ae)));
|
|
|
|
if (w < 1e-6 && (m_polyline_mode == 3 || m_polyline_mode == 4)) {
|
|
|
|
std::vector <db::Edge> &edges = collected_edges.insert (std::make_pair (ll.second, std::vector <db::Edge> ())).first->second;
|
|
for (size_t i = 1; i < points.size (); ++i) {
|
|
edges.push_back (safe_from_double (db::DEdge (tt.trans (points [i - 1]), tt.trans (points [i]))));
|
|
}
|
|
|
|
} else {
|
|
|
|
// create the path
|
|
db::DPath p;
|
|
p.assign (&points[0], &points[0] + points.size (), tt);
|
|
p.bgn_ext (0.0);
|
|
p.end_ext (0.0);
|
|
p.width (tt.ctrans (std::max (0.0, w)));
|
|
cell.shapes(ll.second).insert (safe_from_double (p));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (entity_code == "MTEXT") {
|
|
|
|
db::DVector p;
|
|
db::DVector xv;
|
|
double h = 0.0, ls = 1.0;
|
|
double w = -1.0;
|
|
std::string s;
|
|
std::string layer;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
int m = 0;
|
|
bool in_columns = false;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10) {
|
|
p.set_x (read_double ());
|
|
} else if (g == 20) {
|
|
p.set_y (read_double ());
|
|
} else if (g == 11) {
|
|
xv.set_x (read_double ());
|
|
} else if (g == 21) {
|
|
xv.set_y (read_double ());
|
|
} else if (g == 40) {
|
|
h = read_double ();
|
|
} else if (g == 41) {
|
|
w = read_double ();
|
|
} else if (g == 46) {
|
|
// undocumented feature?
|
|
double w46 = read_double ();
|
|
if (w < 0.0) {
|
|
w = w46;
|
|
}
|
|
} else if (g == 44) {
|
|
ls = read_double ();
|
|
} else if (g == 50) {
|
|
double v = read_double ();
|
|
if (! in_columns) {
|
|
xv = db::DVector (cos (v / 180.0 * M_PI), sin (v / 180.0 * M_PI));
|
|
}
|
|
} else if (g == 71) {
|
|
m = read_int32 ();
|
|
} else if (g == 75) {
|
|
read_int32 ();
|
|
in_columns = true;
|
|
} else if (g == 1 || g == 3) {
|
|
s += read_string (false);
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
// create the text
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
|
|
double a = 0.0;
|
|
if (xv.x () != 0.0 || xv.y () != 0.0) {
|
|
a = atan2 (xv.y (), xv.x ()) / M_PI * 180.0;
|
|
}
|
|
|
|
db::DCplxTrans text_trans (1.0, a, false, db::DVector ());
|
|
|
|
int halign = 0;
|
|
int valign = 0;
|
|
if (m > 0) {
|
|
int va = (m - 1) / 3; // 0: top, 1: middle, 2: bottom
|
|
if (va == 0) {
|
|
valign = 3; // top for TEXT objects
|
|
} else if (va == 1) {
|
|
valign = 2; // center for TEXT objects
|
|
} else if (va == 2) {
|
|
valign = 0; // bottom for TEXT objects
|
|
}
|
|
halign = (m - 1) % 3; // 0: left, 1: middle, 2: right
|
|
}
|
|
|
|
deliver_text (cell.shapes(ll.second), normalize_string (s, true), tt * db::DCplxTrans (p) * text_trans, h, h * ls, halign, valign, w);
|
|
|
|
}
|
|
|
|
} else if (entity_code == "TEXT" || entity_code == "ATTRIB" || entity_code == "ATTDEF") {
|
|
|
|
bool is_text = (entity_code == "TEXT");
|
|
|
|
db::DPoint p, p2;
|
|
bool has_p2 = false;
|
|
double h = 0.0;
|
|
std::string s;
|
|
std::string layer;
|
|
double a = 0.0;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
int m = 0, halign = 0, valign = 0;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10) {
|
|
p.set_x (read_double ());
|
|
} else if (g == 20) {
|
|
p.set_y (read_double ());
|
|
} else if (g == 11) {
|
|
has_p2 = true;
|
|
p2.set_x (read_double ());
|
|
} else if (g == 21) {
|
|
has_p2 = true;
|
|
p2.set_y (read_double ());
|
|
} else if (g == 40) {
|
|
h = read_double ();
|
|
} else if (g == 50) {
|
|
a = read_double ();
|
|
} else if (g == 71) {
|
|
m = read_int32 ();
|
|
} else if (g == 72) {
|
|
halign = read_int32 ();
|
|
} else if ((is_text && g == 73 /*for TEXT*/) || (!is_text && g == 74 /*for ATTRIB and ATTDEF*/)) {
|
|
valign = read_int32 ();
|
|
} else if (g == 1) {
|
|
s = read_string (false);
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
// create the text
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
|
|
// TODO: check implementation, in particular alignment and mirror flags
|
|
db::DCplxTrans text_trans (1.0, a, false, db::DVector ());
|
|
if ((m & 2) != 0) {
|
|
text_trans = db::DCplxTrans (db::DFTrans (db::DFTrans::m90));
|
|
}
|
|
if ((m & 4) != 0) {
|
|
text_trans = db::DCplxTrans (db::DFTrans (db::DFTrans::m0));
|
|
}
|
|
|
|
s = normalize_string (s, false);
|
|
|
|
if (has_p2) {
|
|
if (valign == 0 && halign >= 5) {
|
|
if (halign == 5) {
|
|
// fit the text for the "fit" type
|
|
double lt = s.size () * h;
|
|
if (lt > 0.0 && lt > p.distance (p2)) {
|
|
h *= p.distance (p2) / lt;
|
|
}
|
|
}
|
|
p = p + (p2 - p) * 0.5;
|
|
} else if (halign != 0 || valign != 0) {
|
|
std::swap (p, p2);
|
|
}
|
|
}
|
|
|
|
deliver_text (cell.shapes (ll.second), s, tt * db::DCplxTrans (p - db::DPoint ()) * text_trans, h, h, halign, valign);
|
|
|
|
}
|
|
|
|
} else if (entity_code == "HATCH") {
|
|
|
|
std::string layer;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else if (g == 91) {
|
|
read_int32 ();
|
|
break;
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
std::vector <db::Edge> iedges;
|
|
|
|
db::DPoint pc, pc2;
|
|
std::vector<db::DPoint> points;
|
|
std::vector<double> value40;
|
|
std::vector<double> value50;
|
|
std::vector<double> value51;
|
|
std::vector<int> value73;
|
|
std::vector<db::DPoint> points2;
|
|
unsigned int xy_flag = 0;
|
|
unsigned int xy_flag2 = 0;
|
|
double b = 0.0;
|
|
int edge_type = 0;
|
|
int value94 = 0;
|
|
size_t loop_start = iedges.size ();
|
|
|
|
bool is_polyline = false;
|
|
|
|
while (g != 0 && (g = read_group_code ()) != 0) {
|
|
|
|
if (g == 98) {
|
|
|
|
// stop at the seed point definition (98) since that would be
|
|
// interpreted as a point
|
|
read_int32 ();
|
|
break;
|
|
|
|
} else if (g == 72 || g == 92 || g == 93) {
|
|
|
|
// generate the next segment, create a closing arc if a bulge was specified on the last point
|
|
if (! points.empty ()) {
|
|
if (fabs (b) > 1e-10) {
|
|
// Hint: needs a copy, since points may be altered by add_bulge_segment
|
|
db::DPoint p0 (points [0]);
|
|
add_bulge_segment (points, p0, b);
|
|
}
|
|
deliver_points_to_edges (points, points2, tt, edge_type, value94, value40, value50, value51, value73, iedges);
|
|
}
|
|
|
|
// close previous loop if necessary
|
|
if (g != 72) {
|
|
finish_loop (loop_start, iedges.size (), iedges);
|
|
loop_start = iedges.size ();
|
|
}
|
|
|
|
value40.clear ();
|
|
value50.clear ();
|
|
value51.clear ();
|
|
value73.clear ();
|
|
points.clear ();
|
|
points2.clear ();
|
|
b = 0.0;
|
|
xy_flag = 0;
|
|
xy_flag2 = 0;
|
|
edge_type = 0;
|
|
value94 = 0;
|
|
|
|
int v = read_int32 ();
|
|
if (g == 92) {
|
|
is_polyline = ((v & 2) != 0);
|
|
} else if (g == 72) {
|
|
if (! is_polyline) {
|
|
edge_type = v;
|
|
}
|
|
}
|
|
|
|
} else if (g == 73) {
|
|
|
|
value73.push_back (read_int32 ());
|
|
|
|
} else if (g == 94) {
|
|
|
|
value94 = read_int32 ();
|
|
|
|
} else if (g == 40) {
|
|
|
|
value40.push_back (read_double ());
|
|
|
|
} else if (g == 50) {
|
|
|
|
value50.push_back (read_double ());
|
|
|
|
} else if (g == 51) {
|
|
|
|
value51.push_back (read_double ());
|
|
|
|
} else if (g == 42) {
|
|
|
|
double v = read_double ();
|
|
if (is_polyline) {
|
|
b = v; // bulge for polyline
|
|
}
|
|
|
|
} else if (g == 11 || g == 21) {
|
|
|
|
if (g == 11) {
|
|
pc2.set_x (read_double ());
|
|
xy_flag2 |= 1;
|
|
} else {
|
|
pc2.set_y (read_double ());
|
|
xy_flag2 |= 2;
|
|
}
|
|
|
|
if (xy_flag2 == 3) {
|
|
points2.push_back (pc2);
|
|
xy_flag2 = 0;
|
|
}
|
|
|
|
} else if (g == 10 || g == 20) {
|
|
|
|
if (g == 10) {
|
|
pc.set_x (read_double ());
|
|
xy_flag |= 1;
|
|
} else {
|
|
pc.set_y (read_double ());
|
|
xy_flag |= 2;
|
|
}
|
|
|
|
if (xy_flag == 3) {
|
|
add_bulge_segment (points, pc, b);
|
|
b = 0.0;
|
|
xy_flag = 0;
|
|
}
|
|
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
|
|
}
|
|
|
|
while (g != 0 && (g = read_group_code ()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
|
|
// generate the final segment, create a closing arc if a bulge was specified on the last point
|
|
if (! points.empty ()) {
|
|
if (fabs (b) > 1e-10) {
|
|
// Hint: needs a copy, since points may be altered by add_bulge_segment
|
|
db::DPoint p0 (points [0]);
|
|
add_bulge_segment (points, p0, b);
|
|
}
|
|
deliver_points_to_edges (points, points2, tt, edge_type, value94, value40, value50, value51, value73, iedges);
|
|
}
|
|
|
|
// close previous loop if necessary
|
|
finish_loop (loop_start, iedges.size (), iedges);
|
|
|
|
// create the polygons
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
|
|
std::vector <db::Polygon> pout;
|
|
ep.simple_merge (iedges, pout, true /*resolve holes*/, true /*min coherence*/, 0);
|
|
|
|
for (std::vector <db::Polygon>::const_iterator po = pout.begin (); po != pout.end (); ++po) {
|
|
cell.shapes(ll.second).insert (*po);
|
|
}
|
|
|
|
}
|
|
|
|
} else if (entity_code == "SOLID") {
|
|
|
|
std::vector <db::DPoint> p;
|
|
p.push_back (db::DPoint ());
|
|
std::string layer;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
|
|
if (g == 8) {
|
|
|
|
layer = read_string (true);
|
|
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else if (g >= 10 && g <= 13) {
|
|
|
|
while (p.size () < size_t (g - 10 + 1)) {
|
|
p.push_back (db::DPoint ());
|
|
}
|
|
|
|
p[g - 10].set_x (read_double ());
|
|
|
|
} else if (g >= 20 && g <= 23) {
|
|
|
|
while (p.size () < size_t (g - 20 + 1)) {
|
|
p.push_back (db::DPoint ());
|
|
}
|
|
|
|
p[g - 20].set_y (read_double ());
|
|
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
// create the polygon
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
db::DSimplePolygon poly;
|
|
if (p.size () == 4) {
|
|
std::swap (p [2], p [3]);
|
|
}
|
|
poly.assign_hull (p.begin (), p.end (), tt);
|
|
cell.shapes(ll.second).insert (safe_from_double (poly));
|
|
}
|
|
|
|
} else if (entity_code == "ELLIPSE") {
|
|
|
|
db::DPoint pc, pm;
|
|
double r = 1.0;
|
|
std::string layer;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
double sa = 0.0, ea = M_PI * 2.0;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10) {
|
|
pc.set_x (read_double ());
|
|
} else if (g == 20) {
|
|
pc.set_y (read_double ());
|
|
} else if (g == 11) {
|
|
pm.set_x (read_double ());
|
|
} else if (g == 21) {
|
|
pm.set_y (read_double ());
|
|
} else if (g == 40) {
|
|
r = read_double ();
|
|
} else if (g == 41) {
|
|
sa = read_double ();
|
|
} else if (g == 42) {
|
|
ea = read_double ();
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
// create the polygon
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
|
|
if (m_polyline_mode == 3 || m_polyline_mode == 4) {
|
|
|
|
std::vector <db::Edge> &edges = collected_edges.insert (std::make_pair (ll.second, std::vector <db::Edge> ())).first->second;
|
|
|
|
db::DVector vmaj = db::DVector (pm); // documentation says that pm is the "endpoint",
|
|
db::DVector vmin (-vmaj.y () * r, vmaj.x () * r);
|
|
|
|
int n = int (std::max (1.0, floor (0.5 + (ea - sa) * ncircle_for_radius (std::min (vmaj.length (), vmin.length ())) / (M_PI * 2.0))));
|
|
double da = (ea - sa) / n;
|
|
double dr = 1.0 / cos (0.5 * da);
|
|
|
|
db::DPoint pl = tt * (pc + vmaj * cos (sa) + vmin * sin (sa));
|
|
db::DPoint pp;
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
double a = sa + (i + 0.5) * da;
|
|
pp = tt * (pc + vmaj * (dr * cos (a)) + vmin * (dr * sin (a)));
|
|
|
|
edges.push_back (db::Edge (safe_from_double (pl), safe_from_double (pp)));
|
|
|
|
pl = pp;
|
|
|
|
}
|
|
|
|
pp = tt * (pc + vmaj * (dr * cos (ea)) + vmin * (dr * sin (ea)));
|
|
edges.push_back (db::Edge (safe_from_double (pl), safe_from_double (pp)));
|
|
|
|
} else {
|
|
|
|
db::DVector vmaj = db::DVector (pm); // documentation says that pm is the "endpoint",
|
|
db::DVector vmin (-vmaj.y () * r, vmaj.x () * r);
|
|
|
|
int n = int (std::max (1.0, floor (0.5 + (ea - sa) * ncircle_for_radius (std::min (vmaj.length (), vmin.length ())) / (M_PI * 2.0))));
|
|
double da = (ea - sa) / n;
|
|
double dr = 1.0 / cos (0.5 * da);
|
|
|
|
db::DPoint pp;
|
|
db::DPoint pl = tt * (pc + vmaj * cos (sa) + vmin * sin (sa));
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
double a = sa + (i + 0.5) * da;
|
|
pp = tt * (pc + vmaj * (dr * cos (a)) + vmin * (dr * sin (a)));
|
|
|
|
// create the line segment
|
|
db::DPoint points [2] = { pl, pp };
|
|
db::DPath p;
|
|
p.assign (&points[0], points + 2);
|
|
p.bgn_ext (0.0);
|
|
p.end_ext (0.0);
|
|
p.width (0.0);
|
|
cell.shapes(ll.second).insert (safe_from_double (p));
|
|
|
|
pl = pp;
|
|
|
|
}
|
|
|
|
pp = tt * (pc + vmaj * cos (ea) + vmin * sin (ea));
|
|
|
|
{
|
|
// create the line segment
|
|
db::DPoint points [2] = { pl, pp };
|
|
db::DPath p;
|
|
p.assign (&points[0], points + 2);
|
|
p.bgn_ext (0.0);
|
|
p.end_ext (0.0);
|
|
p.width (0.0);
|
|
cell.shapes(ll.second).insert (safe_from_double (p));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (entity_code == "CIRCLE") {
|
|
|
|
db::DPoint p;
|
|
double r = 0.0;
|
|
std::string layer;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10) {
|
|
p.set_x (read_double ());
|
|
} else if (g == 20) {
|
|
p.set_y (read_double ());
|
|
} else if (g == 40) {
|
|
r = read_double ();
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
db::DCplxTrans tt = global_trans (offset, ex, ey, ez);
|
|
|
|
// create the polygon
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
if (ll.first) {
|
|
|
|
if (m_polyline_mode == 3 || m_polyline_mode == 4) {
|
|
|
|
std::vector <db::Edge> &edges = collected_edges.insert (std::make_pair (ll.second, std::vector <db::Edge> ())).first->second;
|
|
|
|
int n = ncircle_for_radius (r);
|
|
double da = (M_PI * 2.0) / n;
|
|
double dr = 1.0 / cos (0.5 * da);
|
|
|
|
db::DPoint pl = tt * (p + db::DVector (0, r));
|
|
db::DPoint pp;
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
double a = (i + 0.5) * da;
|
|
db::DPoint pp = tt * (p + db::DVector (0, r) * (dr * cos (a)) + db::DVector (r, 0) * (dr * sin (a)));
|
|
|
|
edges.push_back (db::Edge (safe_from_double (pl), safe_from_double (pp)));
|
|
|
|
pl = pp;
|
|
|
|
}
|
|
|
|
pp = tt * (p + db::DVector (0, r));
|
|
edges.push_back (db::Edge (safe_from_double (pl), safe_from_double (pp)));
|
|
|
|
} else {
|
|
|
|
db::DPoint pv[1] = { tt * p };
|
|
db::DPath path (pv, pv + 1, tt.ctrans (r * 2), tt.ctrans (r), tt.ctrans (r), true);
|
|
cell.shapes(ll.second).insert (safe_from_double (path));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (entity_code == "DIMENSION") {
|
|
|
|
db::DPoint p1, p2;
|
|
std::string cellname, layer (zero_layer_name);
|
|
// not used currently: double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 2) {
|
|
cellname = read_string (true);
|
|
} else if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10) {
|
|
p1.set_x (read_double ());
|
|
} else if (g == 20) {
|
|
p1.set_y (read_double ());
|
|
} else if (g == 11) {
|
|
p2.set_x (read_double ());
|
|
} else if (g == 21) {
|
|
p2.set_y (read_double ());
|
|
} else if (g == 210) {
|
|
// not used currently: ex = read_double ();
|
|
} else if (g == 220) {
|
|
// not used currently: ey = read_double ();
|
|
} else if (g == 230) {
|
|
// not used currently: ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
// TODO: beside the placement of the representative BLOCK (group 2 record), this
|
|
// implementation does nothing.
|
|
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
// fallback: if the target layer does not exist (i.e. is not mapped, use the zero layer
|
|
if (! ll.first) {
|
|
ll = open_layer (layout, zero_layer_name);
|
|
}
|
|
if (ll.first) {
|
|
|
|
// Place the BLOCK for that DIMENSION object - no text is generated yet
|
|
if (! cellname.empty ()) {
|
|
|
|
std::map <std::string, db::cell_index_type>::const_iterator b = m_block_per_name.find (cellname);
|
|
if (b == m_block_per_name.end ()) {
|
|
|
|
// create a first representative. Build variants and fill later when the cell is defined in a BLOCK statement.
|
|
db::cell_index_type cell = layout.add_cell ();
|
|
b = m_block_per_name.insert (std::make_pair (cellname, cell)).first;
|
|
m_template_cells.insert (std::make_pair (cell, cellname));
|
|
|
|
}
|
|
|
|
db::cell_index_type ci = make_layer_variant (layout, cellname, b->second, ll.second, 1.0, 1.0);
|
|
|
|
db::DCplxTrans gt = global_trans (offset, 0.0, 0.0, 1.0);
|
|
double f = gt.mag ();
|
|
db::DCplxTrans t = gt * db::DCplxTrans(1.0 / f);
|
|
|
|
if (t.is_ortho () && ! t.is_mag ()) {
|
|
cell.insert (db::CellInstArray (db::CellInst (ci), db::Trans (db::ICplxTrans (t))));
|
|
} else {
|
|
cell.insert (db::CellInstArray (db::CellInst (ci), db::ICplxTrans (t)));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (entity_code == "INSERT") {
|
|
|
|
db::DVector p;
|
|
double sx = 1.0, sy = 1.0;
|
|
int nx = 1, ny = 1;
|
|
double dx = 0.0, dy = 0.0;
|
|
std::string s;
|
|
std::string cellname, layer (zero_layer_name);
|
|
double a = 0.0;
|
|
double ex = 0.0, ey = 0.0, ez = 1.0;
|
|
|
|
while ((g = read_group_code ()) != 0) {
|
|
if (g == 2) {
|
|
cellname = read_string (true);
|
|
} else if (g == 8) {
|
|
layer = read_string (true);
|
|
} else if (g == 10) {
|
|
p.set_x (read_double ());
|
|
} else if (g == 20) {
|
|
p.set_y (read_double ());
|
|
} else if (g == 41) {
|
|
sx = read_double ();
|
|
} else if (g == 42) {
|
|
sy = read_double ();
|
|
} else if (g == 50) {
|
|
a = read_double ();
|
|
} else if (g == 70) {
|
|
nx = read_int16 ();
|
|
} else if (g == 71) {
|
|
ny = read_int16 ();
|
|
} else if (g == 44) {
|
|
dx = read_double ();
|
|
} else if (g == 45) {
|
|
dy = read_double ();
|
|
} else if (g == 210) {
|
|
ex = read_double ();
|
|
} else if (g == 220) {
|
|
ey = read_double ();
|
|
} else if (g == 230) {
|
|
ez = read_double ();
|
|
} else {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
std::pair <bool, unsigned int> ll = open_layer (layout, layer);
|
|
// fallback: if the target layer does not exist (i.e. is not mapped, use the zero layer
|
|
if (! ll.first) {
|
|
ll = open_layer (layout, zero_layer_name);
|
|
}
|
|
if (ll.first) {
|
|
|
|
std::map <std::string, db::cell_index_type>::const_iterator b = m_block_per_name.find (cellname);
|
|
if (b == m_block_per_name.end ()) {
|
|
|
|
// create a first representative. Build variants and fill later when the cell is defined in a BLOCK statement.
|
|
db::cell_index_type cell = layout.add_cell ();
|
|
b = m_block_per_name.insert (std::make_pair (cellname, cell)).first;
|
|
m_template_cells.insert (std::make_pair (cell, cellname));
|
|
|
|
}
|
|
|
|
if (fabs (sx) < 1e-6 || fabs (sy) < 1e-6) {
|
|
warn ("Invalid scaling value " + tl::to_string (sx) + "," + tl::to_string (sy) + " ignored");
|
|
sx = sy = 1.0;
|
|
}
|
|
|
|
double s = std::min (fabs (sx), fabs (sy));
|
|
db::cell_index_type ci = make_layer_variant (layout, cellname, b->second, ll.second, fabs (sx) / s, fabs (sy) / s);
|
|
sx *= s / fabs (sx);
|
|
sy *= s / fabs (sy);
|
|
|
|
db::DCplxTrans tb;
|
|
|
|
db::DCplxTrans t (fabs (fabs (sx) - 1.0) > 1e-6 ? fabs (sx) : 1.0, 0.0, false, db::DVector ());
|
|
if (sx < 0) {
|
|
t = db::DCplxTrans (DFTrans::m90) * t;
|
|
}
|
|
if (sy < 0) {
|
|
t = db::DCplxTrans (DFTrans::m0) * t;
|
|
}
|
|
if (fabs (a) > 1e-6) {
|
|
t = db::DCplxTrans (1.0, a, false, db::DVector ()) * t;
|
|
tb = db::DCplxTrans (1.0, a, false, db::DVector ());
|
|
}
|
|
|
|
t = db::DCplxTrans (p) * t;
|
|
|
|
db::DCplxTrans gt = db::DCplxTrans (global_trans (offset, ex, ey, ez));
|
|
double f = gt.mag ();
|
|
|
|
t = gt * t * db::DCplxTrans(1.0 / f);
|
|
|
|
if (nx == 1 && ny == 1) {
|
|
if (t.is_ortho () && !t.is_mag ()) {
|
|
cell.insert (db::CellInstArray (db::CellInst (ci), db::Trans (db::ICplxTrans (t))));
|
|
} else {
|
|
cell.insert (db::CellInstArray (db::CellInst (ci), db::ICplxTrans (t)));
|
|
}
|
|
} else {
|
|
db::Vector vx = safe_from_double (tb * db::DVector (f * dx, 0));
|
|
db::Vector vy = safe_from_double (tb * db::DVector (0, f * dy));
|
|
if (t.is_ortho () && fabs (t.mag () - 1.0) < 1e-6) {
|
|
cell.insert (db::CellInstArray (db::CellInst (ci), db::Trans (db::ICplxTrans (t)), vx, vy, nx, ny));
|
|
} else {
|
|
cell.insert (db::CellInstArray (db::CellInst (ci), db::ICplxTrans (t), vx, vy, nx, ny));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
warn ("Entity " + entity_code + " not supported - ignored.", 2);
|
|
while ((g = read_group_code()) != 0) {
|
|
skip_value (g);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// merge the edges
|
|
|
|
if (! collected_edges.empty ()) {
|
|
|
|
tl::RelativeProgress progress (tl::to_string (tr ("Merging edges")), 1000000, 10000);
|
|
|
|
db::EdgesToContours e2c;
|
|
|
|
db::Coord accuracy = db::coord_traits<db::Coord>::rounded (m_contour_accuracy * m_unit / m_dbu);
|
|
|
|
for (std::map <unsigned int, std::vector <db::Edge> >::iterator ce = collected_edges.begin (); ce != collected_edges.end (); ++ce) {
|
|
|
|
std::vector <db::Edge> &edges = ce->second;
|
|
if (! edges.empty ()) {
|
|
|
|
std::vector<db::Edge> cc_edges;
|
|
|
|
e2c.fill (edges.begin (), edges.end (), true /*unordered*/, accuracy, &progress);
|
|
|
|
for (size_t c = 0; c < e2c.contours (); ++c) {
|
|
|
|
if (e2c.contour_closed (c) || m_polyline_mode == 4 /*auto-close*/) {
|
|
|
|
// closed contour: store for later merging
|
|
for (std::vector<db::Point>::const_iterator cc = e2c.contour (c).begin (); cc + 1 != e2c.contour (c).end (); ++cc) {
|
|
cc_edges.push_back (db::Edge (cc[0], cc[1]));
|
|
}
|
|
|
|
cc_edges.push_back (db::Edge (e2c.contour (c).back (), e2c.contour (c).front ()));
|
|
|
|
} else {
|
|
|
|
// open contour: create a path with width = 0
|
|
db::Path p;
|
|
p.assign (e2c.contour (c).begin (), e2c.contour (c).end ());
|
|
p.width (0);
|
|
cell.shapes (ce->first).insert (p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// merge the closed contours to resolve holes
|
|
if (! cc_edges.empty ()) {
|
|
|
|
std::vector <db::Polygon> pout;
|
|
ep.simple_merge (cc_edges, pout, true /*resolve holes*/, true /*min coherence*/, 0);
|
|
|
|
for (std::vector <db::Polygon>::const_iterator po = pout.begin (); po != pout.end (); ++po) {
|
|
cell.shapes (ce->first).insert (*po);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
bool
|
|
DXFReader::prepare_read (bool ignore_empty_lines)
|
|
{
|
|
if (m_initial) {
|
|
|
|
// Detect binary format
|
|
const char *h = m_stream.get (22);
|
|
if (h && h[21] == 0 && std::string (h) == "AutoCAD Binary DXF\015\012\032") {
|
|
m_ascii = false;
|
|
} else {
|
|
m_stream.unget (22);
|
|
m_ascii = true;
|
|
}
|
|
|
|
m_initial = false;
|
|
|
|
}
|
|
|
|
if (m_ascii) {
|
|
|
|
const char *c;
|
|
|
|
do {
|
|
|
|
++m_line_number;
|
|
m_progress.set (m_line_number);
|
|
|
|
// does not release the buffer ..
|
|
m_line.clear ();
|
|
|
|
// read one line
|
|
while ((c = m_stream.get (1)) != 0) {
|
|
if (*c == '\015' /*CR*/ || *c == '\012') {
|
|
break;
|
|
}
|
|
m_line += *c;
|
|
}
|
|
|
|
// consume CR + LF for windows compatibility
|
|
if (c && *c == '\015' /*CR*/) {
|
|
c = m_stream.get (1);
|
|
if (c && *c != '\012' /*LF*/) {
|
|
m_stream.unget (1);
|
|
}
|
|
}
|
|
|
|
tl::Extractor ex (m_line.c_str ());
|
|
if (ignore_empty_lines && ex.at_end ()) {
|
|
warn ("Empty line ignored");
|
|
} else {
|
|
return true;
|
|
}
|
|
|
|
} while (c != 0);
|
|
|
|
return false;
|
|
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void
|
|
DXFReader::skip_value (int g)
|
|
{
|
|
// TODO: this table is very likely to be incomplete ..
|
|
if (g < 10) {
|
|
read_string (false);
|
|
} else if (g < 60) {
|
|
read_double ();
|
|
} else if (g < 90) {
|
|
read_int16 ();
|
|
} else if (g < 100) {
|
|
read_int32 ();
|
|
} else if (g < 110) {
|
|
read_string (false);
|
|
} else if (g < 160) {
|
|
read_double ();
|
|
} else if (g < 210) {
|
|
read_int16 ();
|
|
} else if (g < 270) {
|
|
read_double ();
|
|
} else if (g < 290) {
|
|
read_int16 ();
|
|
} else if (g < 300) {
|
|
// Documentation says "bool": read_bool (), but how?
|
|
read_int16 ();
|
|
} else if (g < 370) {
|
|
read_string (false);
|
|
} else if (g < 390) {
|
|
read_int16 ();
|
|
} else if (g < 400) {
|
|
read_string (false);
|
|
} else if (g < 410) {
|
|
read_int16 ();
|
|
} else if (g < 420) {
|
|
read_string (false);
|
|
} else if (g < 430) {
|
|
read_int32 ();
|
|
} else if (g < 440) {
|
|
read_string (false);
|
|
} else if (g < 460) {
|
|
read_int32 ();
|
|
} else if (g < 470) {
|
|
read_double ();
|
|
} else if (g < 1010) {
|
|
read_string (false);
|
|
} else if (g < 1060) {
|
|
read_double ();
|
|
} else if (g < 1071) {
|
|
read_int16 ();
|
|
} else if (g < 1072) {
|
|
read_int32 ();
|
|
} else {
|
|
if (m_ascii) {
|
|
warn ("Unexpected group code: " + tl::to_string (g), 2);
|
|
} else {
|
|
error ("Unexpected group code: " + tl::to_string (g));
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
DXFReader::read_group_code ()
|
|
{
|
|
prepare_read (true);
|
|
|
|
if (m_ascii) {
|
|
|
|
do {
|
|
|
|
// ignore uninterpretable lines to work around buggy DXF files with empty lines ..
|
|
tl::Extractor ex (m_line.c_str ());
|
|
int x = 0;
|
|
if (! ex.try_read (x) || ! ex.at_end ()) {
|
|
warn ("Expected an ASCII integer value - line ignored", 2);
|
|
} else {
|
|
return x;
|
|
}
|
|
|
|
} while (prepare_read (true));
|
|
|
|
error ("Unexpected end of file - group code expected");
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
const unsigned char *x = reinterpret_cast<const unsigned char *> (m_stream.get (1));
|
|
if (! x) {
|
|
error ("Unexpected end of file");
|
|
return 0;
|
|
}
|
|
|
|
if (*x == 255) {
|
|
x = reinterpret_cast<const unsigned char *> (m_stream.get (2));
|
|
if (! x) {
|
|
error ("Unexpected end of file");
|
|
return 0;
|
|
}
|
|
return int(x[0]) + (int (x[1]) << 8);
|
|
} else {
|
|
return x[0];
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
int
|
|
DXFReader::read_int16 ()
|
|
{
|
|
if (m_ascii) {
|
|
return read_int32 ();
|
|
} else {
|
|
|
|
prepare_read (true);
|
|
|
|
const unsigned char *x = reinterpret_cast<const unsigned char *> (m_stream.get (2));
|
|
if (! x) {
|
|
error ("Unexpected end of file");
|
|
return 0;
|
|
}
|
|
|
|
return int(x[0]) + (int (x[1]) << 8);
|
|
|
|
}
|
|
}
|
|
|
|
long long
|
|
DXFReader::read_int64 ()
|
|
{
|
|
prepare_read (true);
|
|
|
|
if (m_ascii) {
|
|
tl::Extractor ex (m_line.c_str ());
|
|
double x = 0;
|
|
if (! ex.try_read (x) || ! ex.at_end ()) {
|
|
error ("Expected an ASCII numerical value");
|
|
}
|
|
if (x < double (std::numeric_limits<long long>::min()) || x > double (std::numeric_limits<long long>::max())) {
|
|
error ("Value is out of limits for a 64 bit signed integer");
|
|
}
|
|
return (long long) x;
|
|
} else {
|
|
|
|
const unsigned char *x = reinterpret_cast<const unsigned char *> (m_stream.get (8));
|
|
if (! x) {
|
|
error ("Unexpected end of file");
|
|
return 0;
|
|
}
|
|
|
|
// TODO: can be done faster probably ..
|
|
long long ll = (long long)x[0] + ((long long)x[1] << 8) +
|
|
(((long long)x[2] + ((long long)x[3] << 8)) << 16) +
|
|
(((long long)x[4] + ((long long)x[5] << 8) +
|
|
(((long long)x[6] + ((long long)x[7] << 8)) << 16)) << 32);
|
|
return ll;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
double
|
|
DXFReader::read_double ()
|
|
{
|
|
prepare_read (true);
|
|
|
|
if (m_ascii) {
|
|
tl::Extractor ex (m_line.c_str ());
|
|
double x = 0;
|
|
if (! ex.try_read (x) || ! ex.at_end ()) {
|
|
error ("Expected an ASCII floating-point value");
|
|
}
|
|
return x;
|
|
} else {
|
|
|
|
const unsigned char *x = reinterpret_cast<const unsigned char *> (m_stream.get (8));
|
|
if (! x) {
|
|
error ("Unexpected end of file");
|
|
return 0.0;
|
|
}
|
|
|
|
// TODO: can be done faster probably ..
|
|
long long ll = (long long)x[0] + ((long long)x[1] << 8) +
|
|
(((long long)x[2] + ((long long)x[3] << 8)) << 16) +
|
|
(((long long)x[4] + ((long long)x[5] << 8) +
|
|
(((long long)x[6] + ((long long)x[7] << 8)) << 16)) << 32);
|
|
|
|
union {
|
|
long long ll;
|
|
double d;
|
|
} converter;
|
|
|
|
converter.ll = ll;
|
|
return converter.d;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int
|
|
DXFReader::read_int32 ()
|
|
{
|
|
prepare_read (true);
|
|
|
|
if (m_ascii) {
|
|
tl::Extractor ex (m_line.c_str ());
|
|
double x = 0;
|
|
if (! ex.try_read (x) || ! ex.at_end ()) {
|
|
error ("Expected an ASCII numerical value");
|
|
}
|
|
if (x < std::numeric_limits<int>::min() || x > std::numeric_limits<int>::max()) {
|
|
error ("Value is out of limits for a 32 bit signed integer");
|
|
}
|
|
return int (x);
|
|
} else {
|
|
|
|
const unsigned char *x = reinterpret_cast<const unsigned char *> (m_stream.get (4));
|
|
if (! x) {
|
|
error ("Unexpected end of file");
|
|
return 0;
|
|
}
|
|
|
|
return (int)x[0] + ((int)x[1] << 8) + (((int)x[2] + ((int)x[3] << 8)) << 16);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const std::string &
|
|
DXFReader::read_string (bool ignore_empty_lines)
|
|
{
|
|
prepare_read (ignore_empty_lines);
|
|
|
|
if (! m_ascii) {
|
|
|
|
// reuse "m_line" for collecting strings ..
|
|
m_line.clear ();
|
|
|
|
// read one string
|
|
const char *c;
|
|
while ((c = m_stream.get (1)) != 0 && *c) {
|
|
m_line += *c;
|
|
}
|
|
|
|
if (! c) {
|
|
error ("Unexpected end of file");
|
|
}
|
|
|
|
}
|
|
|
|
return m_line;
|
|
}
|
|
|
|
}
|
|
|