IDF file Linking.
Can read PCB outlines and cutouts, as well as Pin and Mounting holes. A simple PPCB model sans components is added to the assembly.pull/726/head
parent
0a061b6f9e
commit
8cf9d68ecf
|
@ -175,6 +175,7 @@ set(solvespace_core_SOURCES
|
|||
group.cpp
|
||||
groupmesh.cpp
|
||||
importdxf.cpp
|
||||
importidf.cpp
|
||||
mesh.cpp
|
||||
modify.cpp
|
||||
mouse.cpp
|
||||
|
|
10
src/file.cpp
10
src/file.cpp
|
@ -702,6 +702,16 @@ void SolveSpaceUI::UpgradeLegacyData() {
|
|||
|
||||
bool SolveSpaceUI::LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
|
||||
SMesh *m, SShell *sh)
|
||||
{
|
||||
if(strcmp(filename.Extension().c_str(), "emn")==0) {
|
||||
return LinkIDF(filename, le, m, sh);
|
||||
} else {
|
||||
return LoadEntitiesFromSlvs(filename, le, m, sh);
|
||||
}
|
||||
}
|
||||
|
||||
bool SolveSpaceUI::LoadEntitiesFromSlvs(const Platform::Path &filename, EntityList *le,
|
||||
SMesh *m, SShell *sh)
|
||||
{
|
||||
SSurface srf = {};
|
||||
SCurve crv = {};
|
||||
|
|
|
@ -287,7 +287,7 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
|
|||
g.meshCombine = CombineAs::ASSEMBLE;
|
||||
if(g.linkFile.IsEmpty()) {
|
||||
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
|
||||
dialog->AddFilters(Platform::SolveSpaceLinkFileFilters);
|
||||
dialog->ThawChoices(settings, "LinkSketch");
|
||||
if(!dialog->RunModal()) return;
|
||||
dialog->FreezeChoices(settings, "LinkSketch");
|
||||
|
|
|
@ -0,0 +1,500 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Intermediate Data Format (IDF) file reader. Reads an IDF file for PCB outlines and creates
|
||||
// an equivalent SovleSpace sketch/extrusion. Supports only Linking, not import.
|
||||
// Part placement is not currently supported.
|
||||
//
|
||||
// Copyright 2020 Paul Kahler.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "solvespace.h"
|
||||
#include "sketch.h"
|
||||
|
||||
// Split a string into substrings separated by spaces.
|
||||
// Allow quotes to enclose spaces within a string
|
||||
static std::vector <std::string> splitString(const std::string line) {
|
||||
std::vector <std::string> v = {};
|
||||
|
||||
if(line.length() == 0) return v;
|
||||
|
||||
std::string s = "";
|
||||
bool inString = false;
|
||||
bool inQuotes = false;
|
||||
|
||||
for (size_t i=0; i<line.length(); i++) {
|
||||
char c = line.at(i);
|
||||
if (inQuotes) {
|
||||
if (c != '"') {
|
||||
s.push_back(c);
|
||||
} else {
|
||||
v.push_back(s);
|
||||
inQuotes = false;
|
||||
inString = false;
|
||||
s = "";
|
||||
}
|
||||
} else if (inString) {
|
||||
if (c != ' ') {
|
||||
s.push_back(c);
|
||||
} else {
|
||||
v.push_back(s);
|
||||
inString = false;
|
||||
s = "";
|
||||
}
|
||||
} else if(c == '"') {
|
||||
inString = true;
|
||||
inQuotes = true;
|
||||
} else if(c != ' ') {
|
||||
s = "";
|
||||
s.push_back(c);
|
||||
inString = true;
|
||||
}
|
||||
}
|
||||
if(s.length() > 0)
|
||||
v.push_back(s);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Functions for linking an IDF file - we need to create entites that
|
||||
// get remapped into a linked group similar to linking .slvs files
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Make a new point - type doesn't matter since we will make a copy later
|
||||
static hEntity newPoint(EntityList *el, int *id, Vector p, bool visible = true) {
|
||||
Entity en = {};
|
||||
en.type = Entity::Type::POINT_N_COPY;
|
||||
en.extraPoints = 0;
|
||||
en.timesApplied = 0;
|
||||
en.group.v = 462;
|
||||
en.actPoint = p;
|
||||
en.construction = false;
|
||||
en.style.v = Style::DATUM;
|
||||
en.actVisible = visible;
|
||||
en.forceHidden = false;
|
||||
|
||||
*id = *id+1;
|
||||
en.h.v = *id + en.group.v*65536;
|
||||
el->Add(&en);
|
||||
return en.h;
|
||||
}
|
||||
|
||||
static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1) {
|
||||
Entity en = {};
|
||||
en.type = Entity::Type::LINE_SEGMENT;
|
||||
en.point[0] = p0;
|
||||
en.point[1] = p1;
|
||||
en.extraPoints = 0;
|
||||
en.timesApplied = 0;
|
||||
en.group.v = 493;
|
||||
en.construction = false;
|
||||
en.style.v = Style::ACTIVE_GRP;
|
||||
en.actVisible = true;
|
||||
en.forceHidden = false;
|
||||
|
||||
*id = *id+1;
|
||||
en.h.v = *id + en.group.v*65536;
|
||||
el->Add(&en);
|
||||
return en.h;
|
||||
}
|
||||
|
||||
static hEntity newNormal(EntityList *el, int *id, Quaternion normal) {
|
||||
// normals have parameters, but we don't need them to make a NORMAL_N_COPY from this
|
||||
Entity en = {};
|
||||
en.type = Entity::Type::NORMAL_N_COPY;
|
||||
en.extraPoints = 0;
|
||||
en.timesApplied = 0;
|
||||
en.group.v = 472;
|
||||
en.actNormal = normal;
|
||||
en.construction = false;
|
||||
en.style.v = Style::ACTIVE_GRP;
|
||||
// to be visible we need to add a point.
|
||||
en.point[0] = newPoint(el, id, Vector::From(0,0,3), /*visible=*/ true);
|
||||
en.actVisible = true;
|
||||
en.forceHidden = false;
|
||||
|
||||
*id = *id+1;
|
||||
en.h.v = *id + en.group.v*65536;
|
||||
el->Add(&en);
|
||||
return en.h;
|
||||
}
|
||||
|
||||
static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity pc, hEntity hnorm) {
|
||||
Entity en = {};
|
||||
en.type = Entity::Type::ARC_OF_CIRCLE;
|
||||
en.point[0] = pc;
|
||||
en.point[1] = p0;
|
||||
en.point[2] = p1;
|
||||
en.normal = hnorm;
|
||||
en.extraPoints = 0;
|
||||
en.timesApplied = 0;
|
||||
en.group.v = 403;
|
||||
en.construction = false;
|
||||
en.style.v = Style::ACTIVE_GRP;
|
||||
en.actVisible = true;
|
||||
en.forceHidden = false; *id = *id+1;
|
||||
|
||||
*id = *id + 1;
|
||||
en.h.v = *id + en.group.v*65536;
|
||||
el->Add(&en);
|
||||
return en.h;
|
||||
}
|
||||
|
||||
static hEntity newDistance(EntityList *el, int *id, double distance) {
|
||||
// normals have parameters, but we don't need them to make a NORMAL_N_COPY from this
|
||||
Entity en = {};
|
||||
en.type = Entity::Type::DISTANCE;
|
||||
en.extraPoints = 0;
|
||||
en.timesApplied = 0;
|
||||
en.group.v = 472;
|
||||
en.actDistance = distance;
|
||||
en.construction = false;
|
||||
en.style.v = Style::ACTIVE_GRP;
|
||||
// to be visible we'll need to add a point?
|
||||
en.actVisible = false;
|
||||
en.forceHidden = false;
|
||||
|
||||
*id = *id+1;
|
||||
en.h.v = *id + en.group.v*65536;
|
||||
el->Add(&en);
|
||||
return en.h;
|
||||
}
|
||||
|
||||
static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEntity hnorm) {
|
||||
Entity en = {};
|
||||
en.type = Entity::Type::CIRCLE;
|
||||
en.point[0] = p0;
|
||||
en.normal = hnorm;
|
||||
en.distance = hdist;
|
||||
en.extraPoints = 0;
|
||||
en.timesApplied = 0;
|
||||
en.group.v = 399;
|
||||
en.construction = false;
|
||||
en.style.v = Style::ACTIVE_GRP;
|
||||
en.actVisible = true;
|
||||
en.forceHidden = false;
|
||||
|
||||
*id = *id+1;
|
||||
en.h.v = *id + en.group.v*65536;
|
||||
el->Add(&en);
|
||||
return en.h;
|
||||
}
|
||||
|
||||
static Vector ArcCenter(Vector p0, Vector p1, double angle) {
|
||||
// locate the center of an arc
|
||||
Vector m = p0.Plus(p1).ScaledBy(0.5);
|
||||
Vector perp = Vector::From(p1.y-p0.y, p0.x-p1.x, 0.0).WithMagnitude(1.0);
|
||||
double dist = 0;
|
||||
if (angle != 180) {
|
||||
dist = (p1.Minus(m).Magnitude())/tan(0.5*angle*3.141592653589793/180.0);
|
||||
} else {
|
||||
dist = 0.0;
|
||||
}
|
||||
Vector c = m.Minus(perp.ScaledBy(dist));
|
||||
return c;
|
||||
}
|
||||
|
||||
// Add an IDF line or arc to the entity list. According to spec, zero angle indicates a line.
|
||||
// Positive angles are counter clockwise, negative are clockwise. An angle of 360
|
||||
// indicates a circle centered at x1,y1 passing through x2,y2 and is a complete loop.
|
||||
static void CreateEntity(EntityList *el, int *id, hEntity h0, hEntity h1, hEntity hnorm,
|
||||
Vector p0, Vector p1, double angle) {
|
||||
if (angle == 0.0) {
|
||||
//line
|
||||
if(p0.Equals(p1)) return;
|
||||
|
||||
newLine(el, id, h0, h1);
|
||||
|
||||
} else if(angle == 360.0) {
|
||||
// circle
|
||||
double d = p1.Minus(p0).Magnitude();
|
||||
hEntity hd = newDistance(el, id, d);
|
||||
newCircle(el, id, h1, hd, hnorm);
|
||||
|
||||
} else {
|
||||
// arc
|
||||
if(angle < 0.0) {
|
||||
swap(p0,p1);
|
||||
swap(h0,h1);
|
||||
}
|
||||
// locate the center of the arc
|
||||
Vector m = p0.Plus(p1).ScaledBy(0.5);
|
||||
Vector perp = Vector::From(p1.y-p0.y, p0.x-p1.x, 0.0).WithMagnitude(1.0);
|
||||
double dist = 0;
|
||||
if (angle != 180) {
|
||||
dist = (p1.Minus(m).Magnitude())/tan(0.5*angle*3.141592653589793/180.0);
|
||||
} else {
|
||||
dist = 0.0;
|
||||
}
|
||||
Vector c = m.Minus(perp.ScaledBy(dist));
|
||||
hEntity hc = newPoint(el, id, c, /*visible=*/false);
|
||||
newArc(el, id, h0, h1, hc, hnorm);
|
||||
}
|
||||
}
|
||||
|
||||
// borrowed from Entity::GenerateBezierCurves because we don't have parameters.
|
||||
static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vector pb,
|
||||
Quaternion q, double angle) {
|
||||
|
||||
Vector u = q.RotationU(), v = q.RotationV();
|
||||
double r = pa.Minus(center).Magnitude();
|
||||
double thetaa, thetab, dtheta;
|
||||
|
||||
if(angle == 360.0) {
|
||||
thetaa = 0;
|
||||
thetab = 2*PI;
|
||||
dtheta = 2*PI;
|
||||
} else {
|
||||
Point2d c2 = center.Project2d(u, v);
|
||||
Point2d pa2 = (pa.Project2d(u, v)).Minus(c2);
|
||||
Point2d pb2 = (pb.Project2d(u, v)).Minus(c2);
|
||||
|
||||
thetaa = atan2(pa2.y, pa2.x);
|
||||
thetab = atan2(pb2.y, pb2.x);
|
||||
dtheta = thetab - thetaa;
|
||||
}
|
||||
int i, n;
|
||||
if(dtheta > (3*PI/2 + 0.01)) {
|
||||
n = 4;
|
||||
} else if(dtheta > (PI + 0.01)) {
|
||||
n = 3;
|
||||
} else if(dtheta > (PI/2 + 0.01)) {
|
||||
n = 2;
|
||||
} else {
|
||||
n = 1;
|
||||
}
|
||||
dtheta /= n;
|
||||
|
||||
for(i = 0; i < n; i++) {
|
||||
double s, c;
|
||||
|
||||
c = cos(thetaa);
|
||||
s = sin(thetaa);
|
||||
// The start point of the curve, and the tangent vector at
|
||||
// that start point.
|
||||
Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
|
||||
t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
|
||||
|
||||
thetaa += dtheta;
|
||||
|
||||
c = cos(thetaa);
|
||||
s = sin(thetaa);
|
||||
Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
|
||||
t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
|
||||
|
||||
// The control point must lie on both tangents.
|
||||
Vector p1 = Vector::AtIntersectionOfLines(p0, p0.Plus(t0),
|
||||
p2, p2.Plus(t2),
|
||||
NULL);
|
||||
|
||||
SBezier sb = SBezier::From(p0, p1, p2);
|
||||
sb.weight[1] = cos(dtheta/2);
|
||||
sbl->l.Add(&sb);
|
||||
}
|
||||
}
|
||||
|
||||
namespace SolveSpace {
|
||||
|
||||
// Here we read the important section of an IDF file. SolveSpace Entities are directly created by
|
||||
// the funcions above, which is only OK because of the way linking works. For example points do
|
||||
// not have handles for solver parameters (coordinates), they only have their actPoint values
|
||||
// set (or actNormal or actDistance). These are incompete entites and would be a problem if
|
||||
// they were part of the sketch, but they are not. After making a list of them here, a new group
|
||||
// gets created from copies of these. Those copies are complete and part of the sketch group.
|
||||
bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) {
|
||||
dbp("\nLink IDF board outline.");
|
||||
el->Clear();
|
||||
std::string data;
|
||||
if(!ReadFile(filename, &data)) {
|
||||
Error("Couldn't read from '%s'", filename.raw.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
enum IDF_SECTION {
|
||||
none,
|
||||
header,
|
||||
board_outline,
|
||||
other_outline,
|
||||
routing_outline,
|
||||
placement_outline,
|
||||
routing_keepout,
|
||||
via_keepout,
|
||||
placement_group,
|
||||
drilled_holes,
|
||||
notes,
|
||||
component_placement
|
||||
} section;
|
||||
|
||||
section = IDF_SECTION::none;
|
||||
int record_number = 0;
|
||||
int curve = -1;
|
||||
int entityCount = 0;
|
||||
|
||||
hEntity hprev;
|
||||
hEntity hprevTop;
|
||||
Vector pprev = Vector::From(0,0,0);
|
||||
Vector pprevTop = Vector::From(0,0,0);
|
||||
|
||||
double board_thickness = 10.0;
|
||||
double scale = 1.0; //mm
|
||||
|
||||
Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0));
|
||||
hEntity hnorm = newNormal(el, &entityCount, normal);
|
||||
|
||||
// to create the extursion we will need to collect a set of bezier curves defined
|
||||
// by the perimeter, cutouts, and holes.
|
||||
SBezierList sbl = {};
|
||||
|
||||
std::stringstream stream(data);
|
||||
for(std::string line; getline( stream, line ); ) {
|
||||
if (line.find(".END_") == 0) {
|
||||
section = none;
|
||||
}
|
||||
switch (section) {
|
||||
case none:
|
||||
if(line.find(".HEADER") == 0) {
|
||||
section = header;
|
||||
record_number = 1;
|
||||
} else if (line.find(".BOARD_OUTLINE") == 0) {
|
||||
section = board_outline;
|
||||
record_number = 1;
|
||||
} else if(line.find(".DRILLED_HOLES") == 0) {
|
||||
section = drilled_holes;
|
||||
record_number = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case header:
|
||||
if(record_number == 3) {
|
||||
if(line.find("MM") != std::string::npos) {
|
||||
dbp("IDF units are MM");
|
||||
scale = 1.0;
|
||||
} else if(line.find("THOU") != std::string::npos) {
|
||||
dbp("IDF units are thousandths of an inch");
|
||||
scale = 0.0254;
|
||||
} else {
|
||||
dbp("IDF import, no units found in file.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case board_outline:
|
||||
if (record_number == 2) {
|
||||
board_thickness = std::stod(line) * scale;
|
||||
dbp("IDF board thickness: %lf", board_thickness);
|
||||
} else { // records 3+ are lines, arcs, and circles
|
||||
std::vector <std::string> values = splitString(line);
|
||||
if(values.size() != 4) continue;
|
||||
int c = stoi(values[0]);
|
||||
double x = stof(values[1]);
|
||||
double y = stof(values[2]);
|
||||
double ang = stof(values[3]);
|
||||
Vector point = Vector::From(x,y,0.0);
|
||||
Vector pTop = Vector::From(x,y,board_thickness);
|
||||
if(c != curve) { // start a new curve
|
||||
curve = c;
|
||||
hprev = newPoint(el, &entityCount, point, /*visible=*/false);
|
||||
hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false);
|
||||
pprev = point;
|
||||
pprevTop = pTop;
|
||||
} else {
|
||||
// create a bezier for the extrusion
|
||||
if (ang == 0) {
|
||||
// straight lines
|
||||
SBezier sb = SBezier::From(pprev, point);
|
||||
sbl.l.Add(&sb);
|
||||
} else if (ang != 360.0) {
|
||||
// Arcs
|
||||
Vector c = ArcCenter(pprev, point, ang);
|
||||
MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang);
|
||||
} else {
|
||||
// circles
|
||||
MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang);
|
||||
}
|
||||
// next create the entities
|
||||
// only curves and points at circle centers will be visible
|
||||
bool vis = (ang == 360.0);
|
||||
hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis);
|
||||
CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang);
|
||||
pprev = point;
|
||||
hprev = hp;
|
||||
hp = newPoint(el, &entityCount, pTop, /*visible=*/vis);
|
||||
CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang);
|
||||
pprevTop = pTop;
|
||||
hprevTop = hp;
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case other_outline:
|
||||
case routing_outline:
|
||||
case placement_outline:
|
||||
case routing_keepout:
|
||||
case via_keepout:
|
||||
case placement_group:
|
||||
break;
|
||||
|
||||
case drilled_holes: {
|
||||
std::vector <std::string> values = splitString(line);
|
||||
if(values.size() < 6) continue;
|
||||
double d = stof(values[0]);
|
||||
double x = stof(values[1]);
|
||||
double y = stof(values[2]);
|
||||
// Only show holes likely to be useful in MCAD to reduce complexity.
|
||||
if((d > 1.7) || (values[5].compare(0,3,"PIN") == 0)
|
||||
|| (values[5].compare(0,3,"MTG") == 0)) {
|
||||
// create the entity
|
||||
Vector cent = Vector::From(x,y,0.0);
|
||||
hEntity hcent = newPoint(el, &entityCount, cent);
|
||||
hEntity hdist = newDistance(el, &entityCount, d/2);
|
||||
newCircle(el, &entityCount, hcent, hdist, hnorm);
|
||||
// and again for the top
|
||||
Vector cTop = Vector::From(x,y,board_thickness);
|
||||
hcent = newPoint(el, &entityCount, cTop);
|
||||
hdist = newDistance(el, &entityCount, d/2);
|
||||
newCircle(el, &entityCount, hcent, hdist, hnorm);
|
||||
// create the curves for the extrusion
|
||||
Vector pt = Vector::From(x+d/2, y, 0.0);
|
||||
MakeBeziersForArcs(&sbl, cent, pt, pt, normal, 360.0);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case notes:
|
||||
case component_placement:
|
||||
break;
|
||||
|
||||
default:
|
||||
section = none;
|
||||
break;
|
||||
}
|
||||
record_number++;
|
||||
}
|
||||
// now we can create an extrusion from all the Bezier curves. We can skip things
|
||||
// like checking for a coplanar sketch because everything is at z=0.
|
||||
SPolygon polyLoops = {};
|
||||
bool allClosed;
|
||||
bool allCoplanar;
|
||||
Vector errorPointAt = Vector::From(0,0,0);
|
||||
SEdge errorAt = {};
|
||||
|
||||
SBezierLoopSetSet sblss = {};
|
||||
sblss.FindOuterFacesFrom(&sbl, &polyLoops, NULL,
|
||||
100.0, &allClosed, &errorAt,
|
||||
&allCoplanar, &errorPointAt, NULL);
|
||||
|
||||
//hack for when there is no sketch yet and the first group is a linked IDF
|
||||
double ctc = SS.chordTolCalculated;
|
||||
if(ctc == 0.0) SS.chordTolCalculated = 0.1; //mm
|
||||
// there should only by one sbls in the sblss unless a board has disjointed parts...
|
||||
sh->MakeFromExtrusionOf(sblss.l.First(), Vector::From(0.0, 0.0, 0.0),
|
||||
Vector::From(0.0, 0.0, board_thickness),
|
||||
RgbaColor::From(0, 180, 0) );
|
||||
SS.chordTolCalculated = ctc;
|
||||
sblss.Clear();
|
||||
sbl.Clear();
|
||||
sh->booleanFailed = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -85,6 +85,11 @@ std::vector<FileFilter> SolveSpaceModelFileFilters = {
|
|||
{ CN_("file-type", "SolveSpace models"), { "slvs" } },
|
||||
};
|
||||
|
||||
std::vector<FileFilter> SolveSpaceLinkFileFilters = {
|
||||
{ CN_("file-type", "SolveSpace models"), { "slvs" } },
|
||||
{ CN_("file-type", "IDF circuit board"), { "emn" } },
|
||||
};
|
||||
|
||||
std::vector<FileFilter> RasterFileFilters = {
|
||||
{ CN_("file-type", "PNG image"), { "png" } },
|
||||
};
|
||||
|
|
|
@ -329,6 +329,8 @@ struct FileFilter {
|
|||
|
||||
// SolveSpace's native file format
|
||||
extern std::vector<FileFilter> SolveSpaceModelFileFilters;
|
||||
// SolveSpace's linkable file formats
|
||||
extern std::vector<FileFilter> SolveSpaceLinkFileFilters;
|
||||
// Raster image
|
||||
extern std::vector<FileFilter> RasterFileFilters;
|
||||
// Triangle mesh
|
||||
|
|
|
@ -679,6 +679,8 @@ public:
|
|||
void UpgradeLegacyData();
|
||||
bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
|
||||
SMesh *m, SShell *sh);
|
||||
bool LoadEntitiesFromSlvs(const Platform::Path &filename, EntityList *le,
|
||||
SMesh *m, SShell *sh);
|
||||
bool ReloadAllLinked(const Platform::Path &filename, bool canCancel = false);
|
||||
// And the various export options
|
||||
void ExportAsPngTo(const Platform::Path &filename);
|
||||
|
@ -810,6 +812,7 @@ public:
|
|||
|
||||
void ImportDxf(const Platform::Path &file);
|
||||
void ImportDwg(const Platform::Path &file);
|
||||
bool LinkIDF(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh);
|
||||
|
||||
extern SolveSpaceUI SS;
|
||||
extern Sketch SK;
|
||||
|
|
Loading…
Reference in New Issue