commit
a02a157920
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -47,6 +47,7 @@ New constraint features:
|
|||
with a value.
|
||||
|
||||
New export/import features:
|
||||
* Link IDF circuit boards in an assembly (.emn files)
|
||||
* Three.js: allow configuring projection for exported model, and initially
|
||||
use the current viewport projection.
|
||||
* Wavefront OBJ: a material file is exported alongside the model, containing
|
||||
|
@ -81,6 +82,9 @@ New measurement/analysis features:
|
|||
workplane is displayed.
|
||||
|
||||
Other new features:
|
||||
* Added ExportBackgroundColor in configuration for EPS, PDF, and SVG files.
|
||||
* Improvements to the text window for selected entities and constraints.
|
||||
* Ambient light source added in text window to allow flat shaded renderings.
|
||||
* New command-line interface, for batch exporting and more.
|
||||
* The graphical interface now supports HiDPI screens on every OS.
|
||||
* New option to lock Z axis to be always vertical, like in SketchUp.
|
||||
|
@ -104,8 +108,15 @@ Other new features:
|
|||
* On Linux, native file chooser dialog can be used.
|
||||
* New edit menu items "Line Styles", "View Projection" and "Configuration"
|
||||
that are shortcuts to the respective configuration screens.
|
||||
* New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes
|
||||
to enable support for multi-threading and link-time optimization.
|
||||
|
||||
Bugs fixed:
|
||||
* Fixed broken --view options for command line thumbnail image creation.
|
||||
* Some errors in Triangulation of surfaces.
|
||||
* Some NURNS boolean operations that failed particularly on surfaces
|
||||
created with Lathe, Revolve, or Helix.
|
||||
* Segfault in Remove Spline Point context menu.
|
||||
* A point in 3d constrained to any line whose length is free no longer
|
||||
causes the line length to collapse.
|
||||
* Curve-line constraints (in 3d), parallel constraints (in 3d), and
|
||||
|
|
|
@ -98,6 +98,10 @@ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "i686" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "X8
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLOAT_FLAGS}")
|
||||
endif()
|
||||
|
||||
if(ENABLE_LTO)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
endif()
|
||||
|
||||
if(ENABLE_OPENMP)
|
||||
include(FindOpenMP)
|
||||
if(OpenMP_FOUND)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 03fa5f30f1a1db7231a25653c9dd38044fe06640
|
||||
Subproject commit 0b7b7b709d9299565db603f878214656ef5e9ddf
|
|
@ -1 +1 @@
|
|||
Subproject commit 07c6e60a5a3bd7de09e4a170cd97bafba59cfafd
|
||||
Subproject commit a9686d6ecf00e4467e772f7c0b4ef76a15f325f6
|
1202
res/locales/de_DE.po
1202
res/locales/de_DE.po
File diff suppressed because it is too large
Load Diff
1203
res/locales/en_US.po
1203
res/locales/en_US.po
File diff suppressed because it is too large
Load Diff
1183
res/locales/fr_FR.po
1183
res/locales/fr_FR.po
File diff suppressed because it is too large
Load Diff
1259
res/locales/ru_RU.po
1259
res/locales/ru_RU.po
File diff suppressed because it is too large
Load Diff
1116
res/locales/uk_UA.po
1116
res/locales/uk_UA.po
File diff suppressed because it is too large
Load Diff
1111
res/messages.pot
1111
res/messages.pot
File diff suppressed because it is too large
Load Diff
|
@ -175,6 +175,7 @@ set(solvespace_core_SOURCES
|
|||
group.cpp
|
||||
groupmesh.cpp
|
||||
importdxf.cpp
|
||||
importidf.cpp
|
||||
mesh.cpp
|
||||
modify.cpp
|
||||
mouse.cpp
|
||||
|
|
|
@ -94,6 +94,10 @@ void TextWindow::ScreenChangeFixExportColors(int link, uint32_t v) {
|
|||
SS.fixExportColors = !SS.fixExportColors;
|
||||
}
|
||||
|
||||
void TextWindow::ScreenChangeExportBackgroundColor(int link, uint32_t v) {
|
||||
SS.exportBackgroundColor = !SS.exportBackgroundColor;
|
||||
}
|
||||
|
||||
void TextWindow::ScreenChangeBackFaces(int link, uint32_t v) {
|
||||
SS.drawBackFaces = !SS.drawBackFaces;
|
||||
SS.GW.Invalidate(/*clearPersistent=*/true);
|
||||
|
@ -200,6 +204,11 @@ void TextWindow::ScreenChangeAutosaveInterval(int link, uint32_t v) {
|
|||
SS.TW.edit.meaning = Edit::AUTOSAVE_INTERVAL;
|
||||
}
|
||||
|
||||
void TextWindow::ScreenChangeFindConstraintTimeout(int link, uint32_t v) {
|
||||
SS.TW.ShowEditControl(3, std::to_string(SS.timeoutRedundantConstr));
|
||||
SS.TW.edit.meaning = Edit::FIND_CONSTRAINT_TIMEOUT;
|
||||
}
|
||||
|
||||
void TextWindow::ShowConfiguration() {
|
||||
int i;
|
||||
Printf(true, "%Ft user color (r, g, b)");
|
||||
|
@ -299,6 +308,9 @@ void TextWindow::ShowConfiguration() {
|
|||
Printf(false, " %Fd%f%Ll%s fix white exported lines%E",
|
||||
&ScreenChangeFixExportColors,
|
||||
SS.fixExportColors ? CHECK_TRUE : CHECK_FALSE);
|
||||
Printf(false, " %Fd%f%Ll%s export background color%E",
|
||||
&ScreenChangeExportBackgroundColor,
|
||||
SS.exportBackgroundColor ? CHECK_TRUE : CHECK_FALSE);
|
||||
|
||||
Printf(false, "");
|
||||
Printf(false, "%Ft export canvas size: "
|
||||
|
@ -364,6 +376,10 @@ void TextWindow::ShowConfiguration() {
|
|||
Printf(false, "%Ft autosave interval (in minutes)%E");
|
||||
Printf(false, "%Ba %d %Fl%Ll%f[change]%E",
|
||||
SS.autosaveInterval, &ScreenChangeAutosaveInterval);
|
||||
Printf(false, "");
|
||||
Printf(false, "%Ft redundant constraint timeout (in ms)%E");
|
||||
Printf(false, "%Ba %d %Fl%Ll%f[change]%E",
|
||||
SS.timeoutRedundantConstr, &ScreenChangeFindConstraintTimeout);
|
||||
|
||||
if(canvas) {
|
||||
const char *gl_vendor, *gl_renderer, *gl_version;
|
||||
|
@ -535,6 +551,17 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case Edit::FIND_CONSTRAINT_TIMEOUT: {
|
||||
int timeout = atoi(s.c_str());
|
||||
if(timeout) {
|
||||
if(timeout >= 1) {
|
||||
SS.timeoutRedundantConstr = timeout;
|
||||
} else {
|
||||
SS.timeoutRedundantConstr = 1000;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: return false;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,50 @@ bool ConstraintBase::HasLabel() const {
|
|||
}
|
||||
}
|
||||
|
||||
bool ConstraintBase::IsProjectible() const {
|
||||
switch(type) {
|
||||
case Type::POINTS_COINCIDENT:
|
||||
case Type::PT_PT_DISTANCE:
|
||||
case Type::PT_LINE_DISTANCE:
|
||||
case Type::PT_ON_LINE:
|
||||
case Type::EQUAL_LENGTH_LINES:
|
||||
case Type::EQ_LEN_PT_LINE_D:
|
||||
case Type::EQ_PT_LN_DISTANCES:
|
||||
case Type::EQUAL_ANGLE:
|
||||
case Type::LENGTH_RATIO:
|
||||
case Type::LENGTH_DIFFERENCE:
|
||||
case Type::SYMMETRIC:
|
||||
case Type::SYMMETRIC_HORIZ:
|
||||
case Type::SYMMETRIC_VERT:
|
||||
case Type::SYMMETRIC_LINE:
|
||||
case Type::AT_MIDPOINT:
|
||||
case Type::HORIZONTAL:
|
||||
case Type::VERTICAL:
|
||||
case Type::ANGLE:
|
||||
case Type::PARALLEL:
|
||||
case Type::PERPENDICULAR:
|
||||
case Type::WHERE_DRAGGED:
|
||||
case Type::COMMENT:
|
||||
return true;
|
||||
|
||||
case Type::PT_PLANE_DISTANCE:
|
||||
case Type::PT_FACE_DISTANCE:
|
||||
case Type::PROJ_PT_DISTANCE:
|
||||
case Type::PT_IN_PLANE:
|
||||
case Type::PT_ON_FACE:
|
||||
case Type::EQUAL_LINE_ARC_LEN:
|
||||
case Type::DIAMETER:
|
||||
case Type::PT_ON_CIRCLE:
|
||||
case Type::SAME_ORIENTATION:
|
||||
case Type::CUBIC_LINE_TANGENT:
|
||||
case Type::CURVE_CURVE_TANGENT:
|
||||
case Type::ARC_LINE_TANGENT:
|
||||
case Type::EQUAL_RADIUS:
|
||||
return false;
|
||||
}
|
||||
ssassert(false, "Impossible");
|
||||
}
|
||||
|
||||
ExprVector ConstraintBase::VectorsParallel3d(ExprVector a, ExprVector b, hParam p) {
|
||||
return a.Minus(b.ScaledBy(Expr::From(p)));
|
||||
}
|
||||
|
|
|
@ -40,6 +40,16 @@ void TextWindow::ScreenSetTtfFont(int link, uint32_t v) {
|
|||
SS.ScheduleShowTW();
|
||||
}
|
||||
|
||||
void TextWindow::ScreenConstraintToggleReference(int link, uint32_t v) {
|
||||
hConstraint hc = { v };
|
||||
Constraint *c = SK.GetConstraint(hc);
|
||||
|
||||
SS.UndoRemember();
|
||||
c->reference = !c->reference;
|
||||
|
||||
SS.ScheduleShowTW();
|
||||
}
|
||||
|
||||
void TextWindow::ScreenConstraintShowAsRadius(int link, uint32_t v) {
|
||||
hConstraint hc = { v };
|
||||
Constraint *c = SK.GetConstraint(hc);
|
||||
|
@ -211,52 +221,96 @@ void TextWindow::DescribeSelection() {
|
|||
break;
|
||||
}
|
||||
|
||||
Group *g = SK.GetGroup(e->group);
|
||||
Printf(false, "");
|
||||
Printf(false, "%FtIN GROUP%E %s", g->DescriptionString().c_str());
|
||||
if(e->h.isFromRequest()) {
|
||||
Request *r = SK.GetRequest(e->h.request());
|
||||
if(e->h == r->h.entity(0)) {
|
||||
Printf(false, "%FtFROM REQUEST%E %s",
|
||||
r->DescriptionString().c_str());
|
||||
} else {
|
||||
Printf(false, "%FtFROM REQUEST%E %Fl%Ll%D%f%h%s%E",
|
||||
r->h.v, (&TextWindow::ScreenSelectRequest), &(TextWindow::ScreenHoverRequest),
|
||||
r->DescriptionString().c_str());
|
||||
}
|
||||
}
|
||||
Group *g = SK.GetGroup(e->group);
|
||||
Printf(false, "%FtIN GROUP%E %Fl%Ll%D%f%s%E",
|
||||
g->h.v, (&TextWindow::ScreenSelectGroup),
|
||||
g->DescriptionString().c_str());
|
||||
if(e->workplane == Entity::FREE_IN_3D) {
|
||||
Printf(false, "%FtNOT LOCKED IN WORKPLANE%E");
|
||||
} else {
|
||||
Entity *w = SK.GetEntity(e->workplane);
|
||||
Printf(false, "%FtIN WORKPLANE%E %s", w->DescriptionString().c_str());
|
||||
if(w->h.isFromRequest()) {
|
||||
Printf(false, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E",
|
||||
w->h.request().v,
|
||||
(&TextWindow::ScreenSelectRequest), &(TextWindow::ScreenHoverRequest),
|
||||
w->DescriptionString().c_str());
|
||||
} else {
|
||||
Printf(false, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E",
|
||||
w->h.group().v,
|
||||
(&TextWindow::ScreenSelectGroup), (&TextWindow::ScreenHoverGroupWorkplane),
|
||||
w->DescriptionString().c_str());
|
||||
}
|
||||
}
|
||||
if(e->style.v) {
|
||||
Style *s = Style::Get(e->style);
|
||||
Printf(false, "%FtIN STYLE%E %s", s->DescriptionString().c_str());
|
||||
} else {
|
||||
Printf(false, "%FtIN STYLE%E none");
|
||||
if(e->IsStylable()) {
|
||||
if(e->style.v) {
|
||||
Style *s = Style::Get(e->style);
|
||||
Printf(false, "%FtIN STYLE%E %Fl%Ll%D%f%s%E",
|
||||
s->h.v, (&TextWindow::ScreenShowStyleInfo),
|
||||
s->DescriptionString().c_str());
|
||||
} else {
|
||||
Printf(false, "%FtIN STYLE%E none");
|
||||
}
|
||||
}
|
||||
if(e->construction) {
|
||||
Printf(false, "%FtCONSTRUCTION");
|
||||
}
|
||||
|
||||
std::vector<hConstraint> lhc = {};
|
||||
for(const Constraint &c : SK.constraint) {
|
||||
if(!(c.ptA == e->h ||
|
||||
c.ptB == e->h ||
|
||||
c.entityA == e->h ||
|
||||
c.entityB == e->h ||
|
||||
c.entityC == e->h ||
|
||||
c.entityD == e->h))
|
||||
continue;
|
||||
lhc.push_back(c.h);
|
||||
auto FindConstraints = [&](hEntity he) {
|
||||
for(const Constraint &c : SK.constraint) {
|
||||
if(!(c.ptA == he || c.ptB == he ||
|
||||
c.entityA == he || c.entityB == he || c.entityC == he || c.entityD == he))
|
||||
continue;
|
||||
lhc.push_back(c.h);
|
||||
}
|
||||
};
|
||||
FindConstraints(e->h);
|
||||
if(!e->IsPoint()) {
|
||||
for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) {
|
||||
if(e->point[i].v == 0) break;
|
||||
FindConstraints(e->point[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if(!lhc.empty()) {
|
||||
Printf(true, "%FtCONSTRAINED BY:%E");
|
||||
std::sort(lhc.begin(), lhc.end());
|
||||
lhc.erase(std::unique(lhc.begin(), lhc.end()), lhc.end());
|
||||
|
||||
auto ListConstraints = [&](bool reference) {
|
||||
bool first = true;
|
||||
int a = 0;
|
||||
for(hConstraint hc : lhc) {
|
||||
Constraint *c = SK.GetConstraint(hc);
|
||||
std::string s = c->DescriptionString();
|
||||
Printf(false, "%Bp %Fl%Ll%D%f%h%s%E %s",
|
||||
if(c->reference != reference) continue;
|
||||
if(first) {
|
||||
first = false;
|
||||
if(reference) {
|
||||
Printf(true, "%FtMEASURED BY:%E");
|
||||
} else {
|
||||
Printf(true, "%FtCONSTRAINED BY:%E");
|
||||
}
|
||||
}
|
||||
Printf(false, "%Bp %Fl%Ll%D%f%h%s%E",
|
||||
(a & 1) ? 'd' : 'a',
|
||||
c->h.v, (&TextWindow::ScreenSelectConstraint),
|
||||
(&TextWindow::ScreenHoverConstraint), s.c_str(),
|
||||
c->reference ? "(ref)" : "");
|
||||
(&TextWindow::ScreenHoverConstraint),
|
||||
c->DescriptionString().c_str());
|
||||
a++;
|
||||
}
|
||||
}
|
||||
};
|
||||
ListConstraints(/*reference=*/false);
|
||||
ListConstraints(/*reference=*/true);
|
||||
} else if(gs.n == 2 && gs.points == 2) {
|
||||
Printf(false, "%FtTWO POINTS");
|
||||
Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum();
|
||||
|
@ -365,16 +419,45 @@ void TextWindow::DescribeSelection() {
|
|||
Printf(false, "%FtSELECTED:%E comment text");
|
||||
} else if(gs.n == 0 && gs.constraints == 1) {
|
||||
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
||||
const std::string &desc = c->DescriptionString().c_str();
|
||||
|
||||
if(c->type == Constraint::Type::DIAMETER) {
|
||||
Printf(false, "%FtDIAMETER CONSTRAINT");
|
||||
|
||||
Printf(true, " %Fd%f%D%Ll%s show as radius",
|
||||
&ScreenConstraintShowAsRadius, gs.constraint[0].v,
|
||||
c->other ? CHECK_TRUE : CHECK_FALSE);
|
||||
if(c->type == Constraint::Type::COMMENT) {
|
||||
Printf(false, "%FtCOMMENT%E %s", desc.c_str());
|
||||
} else if(c->HasLabel()) {
|
||||
if(c->reference) {
|
||||
Printf(false, "%FtREFERENCE%E %s", desc.c_str());
|
||||
} else {
|
||||
Printf(false, "%FtDIMENSION%E %s", desc.c_str());
|
||||
}
|
||||
Printf(true, " %Fd%f%D%Ll%s reference",
|
||||
&ScreenConstraintToggleReference, gs.constraint[0].v,
|
||||
c->reference ? CHECK_TRUE : CHECK_FALSE);
|
||||
if(c->type == Constraint::Type::DIAMETER) {
|
||||
Printf(false, " %Fd%f%D%Ll%s use radius",
|
||||
&ScreenConstraintShowAsRadius, gs.constraint[0].v,
|
||||
c->other ? CHECK_TRUE : CHECK_FALSE);
|
||||
}
|
||||
} else {
|
||||
Printf(false, "%FtSELECTED:%E %s",
|
||||
c->DescriptionString().c_str());
|
||||
Printf(false, "%FtCONSTRAINT%E %s", desc.c_str());
|
||||
}
|
||||
|
||||
if(c->IsProjectible()) {
|
||||
if(c->workplane == Entity::FREE_IN_3D) {
|
||||
Printf(true, "%FtNOT PROJECTED TO WORKPLANE%E");
|
||||
} else {
|
||||
Entity *w = SK.GetEntity(c->workplane);
|
||||
if(w->h.isFromRequest()) {
|
||||
Printf(true, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E",
|
||||
w->h.request().v,
|
||||
(&TextWindow::ScreenSelectRequest), &(TextWindow::ScreenHoverRequest),
|
||||
w->DescriptionString().c_str());
|
||||
} else {
|
||||
Printf(true, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E",
|
||||
w->h.group().v,
|
||||
(&TextWindow::ScreenSelectGroup), (&TextWindow::ScreenHoverGroupWorkplane),
|
||||
w->DescriptionString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<hEntity> lhe = {};
|
||||
|
@ -391,16 +474,20 @@ void TextWindow::DescribeSelection() {
|
|||
lhe.erase(it, lhe.end());
|
||||
|
||||
if(!lhe.empty()) {
|
||||
Printf(true, "%FtCONSTRAINS:%E");
|
||||
if(c->reference) {
|
||||
Printf(true, "%FtMEASURES:%E");
|
||||
} else {
|
||||
Printf(true, "%FtCONSTRAINS:%E");
|
||||
}
|
||||
|
||||
int a = 0;
|
||||
for(hEntity he : lhe) {
|
||||
Request *r = SK.GetRequest(he.request());
|
||||
std::string s = r->DescriptionString();
|
||||
Entity *e = SK.GetEntity(he);
|
||||
Printf(false, "%Bp %Fl%Ll%D%f%h%s%E",
|
||||
(a & 1) ? 'd' : 'a',
|
||||
r->h.v, (&TextWindow::ScreenSelectRequest),
|
||||
&(TextWindow::ScreenHoverRequest), s.c_str());
|
||||
e->h.v, (&TextWindow::ScreenSelectEntity),
|
||||
&(TextWindow::ScreenHoverEntity),
|
||||
e->DescriptionString().c_str());
|
||||
a++;
|
||||
}
|
||||
}
|
||||
|
|
32
src/draw.cpp
32
src/draw.cpp
|
@ -353,18 +353,34 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {
|
|||
return sel;
|
||||
}
|
||||
|
||||
// This uses the same logic as hovering and static entity selection
|
||||
// but ignores points known not to be draggable
|
||||
GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() {
|
||||
Selection sel = {};
|
||||
for(const Hover &hov : hoverList) {
|
||||
if(hov.selection.entity.v == 0) continue;
|
||||
if(!hov.selection.entity.isFromRequest()) continue;
|
||||
sel = hov.selection;
|
||||
break;
|
||||
}
|
||||
if(!sel.IsEmpty()) {
|
||||
if(hoverList.IsEmpty())
|
||||
return sel;
|
||||
|
||||
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
|
||||
int bestOrder = -1;
|
||||
int bestZIndex = 0;
|
||||
for(const Hover &hov : hoverList) {
|
||||
hGroup hg = {};
|
||||
if(hov.selection.entity.v != 0) {
|
||||
Entity *e = SK.GetEntity(hov.selection.entity);
|
||||
if (!e->CanBeDragged()) continue;
|
||||
hg = e->group;
|
||||
} else if(hov.selection.constraint.v != 0) {
|
||||
hg = SK.GetConstraint(hov.selection.constraint)->group;
|
||||
}
|
||||
|
||||
Group *g = SK.GetGroup(hg);
|
||||
if(g->order > activeGroup->order) continue;
|
||||
if(bestOrder != -1 && (bestOrder >= g->order || bestZIndex > hov.zIndex)) continue;
|
||||
bestOrder = g->order;
|
||||
bestZIndex = hov.zIndex;
|
||||
sel = hov.selection;
|
||||
}
|
||||
return ChooseFromHoverToSelect();
|
||||
return sel;
|
||||
}
|
||||
|
||||
void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
|
||||
|
|
|
@ -121,6 +121,7 @@ void Entity::GetReferencePoints(std::vector<Vector> *refs) {
|
|||
case Type::FACE_N_TRANS:
|
||||
case Type::FACE_N_ROT_AA:
|
||||
case Type::FACE_ROT_NORMAL_PT:
|
||||
case Type::FACE_N_ROT_AXIS_TRANS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -179,6 +180,24 @@ bool Entity::IsVisible() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
// entities that were created via some copy types will not be
|
||||
// draggable with the mouse. We identify the undraggables here
|
||||
bool Entity::CanBeDragged() const {
|
||||
// a numeric copy can not move
|
||||
if(type == Entity::Type::POINT_N_COPY) return false;
|
||||
// these transforms applied zero times can not be moved
|
||||
if(((type == Entity::Type::POINT_N_TRANS) ||
|
||||
(type == Entity::Type::POINT_N_ROT_AA) ||
|
||||
(type == Entity::Type::POINT_N_ROT_AXIS_TRANS))
|
||||
&& (timesApplied == 0)) return false;
|
||||
// for these types of entities the first point will indicate draggability
|
||||
if(HasEndpoints() || type == Entity::Type::CIRCLE) {
|
||||
return SK.GetEntity(point[0])->CanBeDragged();
|
||||
}
|
||||
// if we're not certain it can't be dragged then default to true
|
||||
return true;
|
||||
}
|
||||
|
||||
void Entity::CalculateNumerical(bool forExport) {
|
||||
if(IsPoint()) actPoint = PointGetNum();
|
||||
if(IsNormal()) actNormal = NormalGetNum();
|
||||
|
@ -757,6 +776,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
|||
case Type::FACE_N_TRANS:
|
||||
case Type::FACE_N_ROT_AA:
|
||||
case Type::FACE_ROT_NORMAL_PT:
|
||||
case Type::FACE_N_ROT_AXIS_TRANS:
|
||||
// Do nothing; these are drawn with the triangle mesh
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -368,7 +368,11 @@ public:
|
|||
|
||||
H AddAndAssignId(T *t) {
|
||||
t->h.v = (MaximumId() + 1);
|
||||
Add(t);
|
||||
AllocForOneMore();
|
||||
|
||||
// Copy-construct at the end of the list.
|
||||
new(&elem[n]) T(*t);
|
||||
++n;
|
||||
|
||||
return t->h;
|
||||
}
|
||||
|
|
|
@ -702,6 +702,7 @@ bool EntityBase::IsFace() const {
|
|||
case Type::FACE_N_TRANS:
|
||||
case Type::FACE_N_ROT_AA:
|
||||
case Type::FACE_ROT_NORMAL_PT:
|
||||
case Type::FACE_N_ROT_AXIS_TRANS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -774,6 +775,15 @@ ExprVector EntityBase::FaceGetPointExprs() const {
|
|||
r = ExprVector::From(numPoint);
|
||||
r = q.Rotate(r);
|
||||
r = r.Plus(trans);
|
||||
} else if(type == Type::FACE_N_ROT_AXIS_TRANS) {
|
||||
ExprVector orig = ExprVector::From(numPoint);
|
||||
ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
|
||||
ExprVector displace = ExprVector::From(param[4], param[5], param[6])
|
||||
.WithMagnitude(Expr::From(param[7])).ScaledBy(Expr::From(timesApplied));
|
||||
ExprQuaternion q = GetAxisAngleQuaternionExprs(3);
|
||||
orig = orig.Minus(trans);
|
||||
orig = q.Rotate(orig);
|
||||
r = orig.Plus(trans).Plus(displace);
|
||||
} else if(type == Type::FACE_N_TRANS) {
|
||||
ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
|
||||
r = ExprVector::From(numPoint);
|
||||
|
@ -801,6 +811,14 @@ Vector EntityBase::FaceGetPointNum() const {
|
|||
Quaternion q = Quaternion::From(param[3], param[4], param[5], param[6]);
|
||||
r = q.Rotate(numPoint);
|
||||
r = r.Plus(trans);
|
||||
} else if(type == Type::FACE_N_ROT_AXIS_TRANS) {
|
||||
Vector offset = Vector::From(param[0], param[1], param[2]);
|
||||
Vector displace = Vector::From(param[4], param[5], param[6])
|
||||
.WithMagnitude(SK.GetParam(param[7])->val).ScaledBy(timesApplied);
|
||||
Quaternion q = PointGetQuaternion();
|
||||
r = numPoint.Minus(offset);
|
||||
r = q.Rotate(r);
|
||||
r = r.Plus(offset).Plus(displace);
|
||||
} else if(type == Type::FACE_N_TRANS) {
|
||||
Vector trans = Vector::From(param[0], param[1], param[2]);
|
||||
r = numPoint.Plus(trans.ScaledBy(timesApplied));
|
||||
|
|
|
@ -717,13 +717,16 @@ void VectorFileWriter::OutputLinesAndMesh(SBezierLoopSetSet *sblss, SMesh *sm) {
|
|||
ptMin.y -= s*SS.exportMargin.bottom;
|
||||
ptMax.y += s*SS.exportMargin.top;
|
||||
} else {
|
||||
ptMin.x = -(s*SS.exportCanvas.dx);
|
||||
ptMin.y = -(s*SS.exportCanvas.dy);
|
||||
ptMin.x = (s*SS.exportCanvas.dx);
|
||||
ptMin.y = (s*SS.exportCanvas.dy);
|
||||
ptMax.x = ptMin.x + (s*SS.exportCanvas.width);
|
||||
ptMax.y = ptMin.y + (s*SS.exportCanvas.height);
|
||||
}
|
||||
|
||||
StartFile();
|
||||
if(SS.exportBackgroundColor) {
|
||||
Background(SS.backgroundColor);
|
||||
}
|
||||
if(sm && SS.exportShadedTriangles) {
|
||||
for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
|
||||
Triangle(tr);
|
||||
|
|
|
@ -551,6 +551,9 @@ void DxfFileWriter::StartFile() {
|
|||
paths.clear();
|
||||
}
|
||||
|
||||
void DxfFileWriter::Background(RgbaColor color) {
|
||||
}
|
||||
|
||||
void DxfFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth,
|
||||
bool filled, RgbaColor fillRgb, hStyle hs)
|
||||
{
|
||||
|
@ -696,6 +699,35 @@ void EpsFileWriter::StartFile() {
|
|||
MmToPts(ptMax.y - ptMin.y));
|
||||
}
|
||||
|
||||
void EpsFileWriter::Background(RgbaColor color) {
|
||||
double width = ptMax.x - ptMin.x;
|
||||
double height = ptMax.y - ptMin.y;
|
||||
|
||||
fprintf(f,
|
||||
"%.3f %.3f %.3f setrgbcolor\r\n"
|
||||
"newpath\r\n"
|
||||
" %.3f %.3f moveto\r\n"
|
||||
" %.3f %.3f lineto\r\n"
|
||||
" %.3f %.3f lineto\r\n"
|
||||
" %.3f %.3f lineto\r\n"
|
||||
" closepath\r\n"
|
||||
"gsave fill grestore\r\n",
|
||||
color.redF(), color.greenF(), color.blueF(),
|
||||
MmToPts(0), MmToPts(0),
|
||||
MmToPts(width), MmToPts(0),
|
||||
MmToPts(width), MmToPts(height),
|
||||
MmToPts(0), MmToPts(height));
|
||||
|
||||
// same issue with cracks, stroke it to avoid them
|
||||
double sw = max(width, height) / 1000;
|
||||
fprintf(f,
|
||||
"1 setlinejoin\r\n"
|
||||
"1 setlinecap\r\n"
|
||||
"%.3f setlinewidth\r\n"
|
||||
"gsave stroke grestore\r\n",
|
||||
MmToPts(sw));
|
||||
}
|
||||
|
||||
void EpsFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth,
|
||||
bool filled, RgbaColor fillRgb, hStyle hs)
|
||||
{
|
||||
|
@ -926,6 +958,30 @@ void PdfFileWriter::FinishAndCloseFile() {
|
|||
|
||||
}
|
||||
|
||||
void PdfFileWriter::Background(RgbaColor color) {
|
||||
double width = ptMax.x - ptMin.x;
|
||||
double height = ptMax.y - ptMin.y;
|
||||
double sw = max(width, height) / 1000;
|
||||
|
||||
fprintf(f,
|
||||
"1 J 1 j\r\n"
|
||||
"%.3f %.3f %.3f RG\r\n"
|
||||
"%.3f %.3f %.3f rg\r\n"
|
||||
"%.3f w\r\n"
|
||||
"%.3f %.3f m\r\n"
|
||||
"%.3f %.3f l\r\n"
|
||||
"%.3f %.3f l\r\n"
|
||||
"%.3f %.3f l\r\n"
|
||||
"b\r\n",
|
||||
color.redF(), color.greenF(), color.blueF(),
|
||||
color.redF(), color.greenF(), color.blueF(),
|
||||
MmToPts(sw),
|
||||
MmToPts(0), MmToPts(0),
|
||||
MmToPts(width), MmToPts(0),
|
||||
MmToPts(width), MmToPts(height),
|
||||
MmToPts(0), MmToPts(height));
|
||||
}
|
||||
|
||||
void PdfFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth,
|
||||
bool filled, RgbaColor fillRgb, hStyle hs)
|
||||
{
|
||||
|
@ -1051,6 +1107,16 @@ void SvgFileWriter::StartFile() {
|
|||
fprintf(f, "]]></style>\r\n");
|
||||
}
|
||||
|
||||
void SvgFileWriter::Background(RgbaColor color) {
|
||||
fprintf(f,
|
||||
"<style><![CDATA[\r\n"
|
||||
"svg {\r\n"
|
||||
"background-color:#%02x%02x%02x;\r\n"
|
||||
"}\r\n"
|
||||
"]]></style>\r\n",
|
||||
color.red, color.green, color.blue);
|
||||
}
|
||||
|
||||
void SvgFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth,
|
||||
bool filled, RgbaColor fillRgb, hStyle hs)
|
||||
{
|
||||
|
@ -1146,6 +1212,9 @@ void HpglFileWriter::StartFile() {
|
|||
fprintf(f, "SP1;\r\n");
|
||||
}
|
||||
|
||||
void HpglFileWriter::Background(RgbaColor color) {
|
||||
}
|
||||
|
||||
void HpglFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth,
|
||||
bool filled, RgbaColor fillRgb, hStyle hs)
|
||||
{
|
||||
|
@ -1187,6 +1256,8 @@ void GCodeFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth,
|
|||
bool filled, RgbaColor fillRgb, hStyle hs)
|
||||
{
|
||||
}
|
||||
void GCodeFileWriter::Background(RgbaColor color) {
|
||||
}
|
||||
void GCodeFileWriter::FinishPath(RgbaColor strokeRgb, double lineWidth,
|
||||
bool filled, RgbaColor fillRgb, hStyle hs)
|
||||
{
|
||||
|
@ -1248,6 +1319,9 @@ void Step2dFileWriter::StartFile() {
|
|||
sfw.WriteHeader();
|
||||
}
|
||||
|
||||
void Step2dFileWriter::Background(RgbaColor color) {
|
||||
}
|
||||
|
||||
void Step2dFileWriter::Triangle(STriangle *tr) {
|
||||
}
|
||||
|
||||
|
|
11
src/file.cpp
11
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 = {};
|
||||
|
@ -892,6 +902,7 @@ try_again:
|
|||
}
|
||||
}
|
||||
} else if(linkMap.count(g.linkFile) == 0) {
|
||||
dbp("Missing file for group: %s", g.name.c_str());
|
||||
// The file was moved; prompt the user for its new location.
|
||||
switch(LocateImportedFile(g.linkFile.RelativeTo(saveFile), canCancel)) {
|
||||
case Platform::MessageDialog::Response::YES: {
|
||||
|
|
|
@ -533,6 +533,7 @@ void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) {
|
|||
WriteEqSystemForGroup(hg);
|
||||
Group *g = SK.GetGroup(hg);
|
||||
g->solved.remove.Clear();
|
||||
g->solved.findToFixTimeout = SS.timeoutRedundantConstr;
|
||||
SolveResult how = sys.Solve(g, NULL,
|
||||
&(g->solved.dof),
|
||||
&(g->solved.remove),
|
||||
|
|
|
@ -1243,6 +1243,8 @@ void GraphicsWindow::MenuRequest(Command id) {
|
|||
"not have a default workplane. Try selecting a "
|
||||
"workplane, or activating a sketch-in-new-workplane "
|
||||
"group."));
|
||||
//update checkboxes in the menus
|
||||
SS.GW.EnsureValidActives();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
@ -1113,6 +1113,7 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
|
|||
case Entity::Type::FACE_N_TRANS:
|
||||
case Entity::Type::FACE_N_ROT_AA:
|
||||
case Entity::Type::FACE_ROT_NORMAL_PT:
|
||||
case Entity::Type::FACE_N_ROT_AXIS_TRANS:
|
||||
if(as == CopyAs::N_TRANS) {
|
||||
en.type = Entity::Type::FACE_N_TRANS;
|
||||
en.param[0] = dx;
|
||||
|
@ -1120,8 +1121,18 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
|
|||
en.param[2] = dz;
|
||||
} else if (as == CopyAs::NUMERIC) {
|
||||
en.type = Entity::Type::FACE_NORMAL_PT;
|
||||
} else if (as == CopyAs::N_ROT_AXIS_TRANS) {
|
||||
en.type = Entity::Type::FACE_N_ROT_AXIS_TRANS;
|
||||
en.param[0] = dx;
|
||||
en.param[1] = dy;
|
||||
en.param[2] = dz;
|
||||
en.param[3] = qw;
|
||||
en.param[4] = qvx;
|
||||
en.param[5] = qvy;
|
||||
en.param[6] = qvz;
|
||||
en.param[7] = dist;
|
||||
} else {
|
||||
if(as == CopyAs::N_ROT_AA || as == CopyAs::N_ROT_AXIS_TRANS) {
|
||||
if(as == CopyAs::N_ROT_AA) {
|
||||
en.type = Entity::Type::FACE_N_ROT_AA;
|
||||
} else {
|
||||
en.type = Entity::Type::FACE_N_ROT_TRANS;
|
||||
|
|
|
@ -106,25 +106,28 @@ void SMesh::RemapFaces(Group *g, int remap) {
|
|||
|
||||
template<class T>
|
||||
void Group::GenerateForStepAndRepeat(T *steps, T *outs, Group::CombineAs forWhat) {
|
||||
T workA, workB;
|
||||
workA = {};
|
||||
workB = {};
|
||||
T *soFar = &workA, *scratch = &workB;
|
||||
|
||||
int n = (int)valA, a0 = 0;
|
||||
if(subtype == Subtype::ONE_SIDED && skipFirst) {
|
||||
a0++; n++;
|
||||
}
|
||||
int a;
|
||||
for(a = a0; a < n; a++) {
|
||||
int ap = a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1));
|
||||
int remap = (a == (n - 1)) ? REMAP_LAST : a;
|
||||
|
||||
T transd = {};
|
||||
int a;
|
||||
// create all the transformed copies
|
||||
std::vector <T> transd(n);
|
||||
std::vector <T> workA(n);
|
||||
workA[0] = {};
|
||||
// first generate a shell/mesh with each transformed copy
|
||||
#pragma omp parallel for
|
||||
for(a = a0; a < n; a++) {
|
||||
transd[a] = {};
|
||||
workA[a] = {};
|
||||
int ap = a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1));
|
||||
|
||||
if(type == Type::TRANSLATE) {
|
||||
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
|
||||
trans = trans.ScaledBy(ap);
|
||||
transd.MakeFromTransformationOf(steps,
|
||||
transd[a].MakeFromTransformationOf(steps,
|
||||
trans, Quaternion::IDENTITY, 1.0);
|
||||
} else {
|
||||
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
|
||||
|
@ -133,29 +136,45 @@ void Group::GenerateForStepAndRepeat(T *steps, T *outs, Group::CombineAs forWhat
|
|||
Vector axis = Vector::From(h.param(4), h.param(5), h.param(6));
|
||||
Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z);
|
||||
// Rotation is centered at t; so A(x - t) + t = Ax + (t - At)
|
||||
transd.MakeFromTransformationOf(steps,
|
||||
transd[a].MakeFromTransformationOf(steps,
|
||||
trans.Minus(q.Rotate(trans)), q, 1.0);
|
||||
}
|
||||
|
||||
}
|
||||
for(a = a0; a < n; a++) {
|
||||
// We need to rewrite any plane face entities to the transformed ones.
|
||||
transd.RemapFaces(this, remap);
|
||||
|
||||
// And tack this transformed copy on to the return.
|
||||
if(soFar->IsEmpty()) {
|
||||
scratch->MakeFromCopyOf(&transd);
|
||||
} else if(forWhat == CombineAs::ASSEMBLE) {
|
||||
scratch->MakeFromAssemblyOf(soFar, &transd);
|
||||
} else {
|
||||
scratch->MakeFromUnionOf(soFar, &transd);
|
||||
}
|
||||
|
||||
swap(scratch, soFar);
|
||||
scratch->Clear();
|
||||
transd.Clear();
|
||||
int remap = (a == (n - 1)) ? REMAP_LAST : a;
|
||||
transd[a].RemapFaces(this, remap);
|
||||
}
|
||||
|
||||
std::vector<T> *soFar = &transd;
|
||||
std::vector<T> *scratch = &workA;
|
||||
// do the boolean operations on pairs of equal size
|
||||
while(n > 1) {
|
||||
for(a = 0; a < n; a+=2) {
|
||||
scratch->at(a/2).Clear();
|
||||
// combine a pair of shells
|
||||
if((a==0) && (a0==1)) { // if the first was skipped just copy the 2nd
|
||||
scratch->at(a/2).MakeFromCopyOf(&(soFar->at(a+1)));
|
||||
(soFar->at(a+1)).Clear();
|
||||
a0 = 0;
|
||||
} else if (a == n-1) { // for an odd number just copy the last one
|
||||
scratch->at(a/2).MakeFromCopyOf(&(soFar->at(a)));
|
||||
(soFar->at(a)).Clear();
|
||||
} else if(forWhat == CombineAs::ASSEMBLE) {
|
||||
scratch->at(a/2).MakeFromAssemblyOf(&(soFar->at(a)), &(soFar->at(a+1)));
|
||||
(soFar->at(a)).Clear();
|
||||
(soFar->at(a+1)).Clear();
|
||||
} else {
|
||||
scratch->at(a/2).MakeFromUnionOf(&(soFar->at(a)), &(soFar->at(a+1)));
|
||||
(soFar->at(a)).Clear();
|
||||
(soFar->at(a+1)).Clear();
|
||||
}
|
||||
}
|
||||
swap(scratch, soFar);
|
||||
n = (n+1)/2;
|
||||
}
|
||||
outs->Clear();
|
||||
*outs = *soFar;
|
||||
*outs = soFar->at(0);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
|
|
|
@ -9,6 +9,21 @@ static std::string ToUpper(std::string str) {
|
|||
return str;
|
||||
}
|
||||
|
||||
static Quaternion NormalFromExtPoint(Vector extPoint) {
|
||||
// DXF arbitrary axis algorithm for transforming a Z-vector into a rotated
|
||||
// coordinate system
|
||||
Vector ax, ay;
|
||||
Vector az = extPoint.WithMagnitude(1.0);
|
||||
|
||||
if ((fabs(az.x) < 1/64.) && (fabs(az.y) < 1/64.)) {
|
||||
ax = Vector::From(0, 1, 0).Cross(az).WithMagnitude(1.0);
|
||||
} else {
|
||||
ax = Vector::From(0, 0, 1).Cross(az).WithMagnitude(1.0);
|
||||
}
|
||||
ay = az.Cross(ax).WithMagnitude(1.0);
|
||||
return Quaternion::From(ax, ay);
|
||||
}
|
||||
|
||||
class DxfImport : public DRW_Interface {
|
||||
public:
|
||||
Vector blockX;
|
||||
|
@ -516,12 +531,41 @@ public:
|
|||
return hr.entity(0);
|
||||
}
|
||||
|
||||
hEntity createCircle(const Vector &c, double r, hStyle style) {
|
||||
hEntity createWorkplane(const Vector &p, const Quaternion &q) {
|
||||
hRequest hr = SS.GW.AddRequest(Request::Type::WORKPLANE, /*rememberForUndo=*/false);
|
||||
SK.GetEntity(hr.entity(1))->PointForceTo(p);
|
||||
processPoint(hr.entity(1));
|
||||
SK.GetEntity(hr.entity(32))->NormalForceTo(q);
|
||||
return hr.entity(0);
|
||||
}
|
||||
|
||||
hEntity findOrCreateWorkplane(const Vector &p, const Quaternion &q) {
|
||||
Vector z = q.RotationN();
|
||||
for(auto &r : SK.request) {
|
||||
if((r.type == Request::Type::WORKPLANE) && (r.group == SS.GW.activeGroup)) {
|
||||
Vector wp = SK.GetEntity(r.h.entity(1))->PointGetNum();
|
||||
Vector wz = SK.GetEntity(r.h.entity(32))->NormalN();
|
||||
|
||||
if ((p.DistanceToPlane(wz, wp) < LENGTH_EPS) && z.Equals(wz)) {
|
||||
return r.h.entity(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return createWorkplane(p, q);
|
||||
}
|
||||
|
||||
static void activateWorkplane(hEntity he) {
|
||||
Group *g = SK.GetGroup(SS.GW.activeGroup);
|
||||
g->activeWorkplane = he;
|
||||
}
|
||||
|
||||
hEntity createCircle(const Vector &c, const Quaternion &q, double r, hStyle style) {
|
||||
hRequest hr = SS.GW.AddRequest(Request::Type::CIRCLE, /*rememberForUndo=*/false);
|
||||
SK.GetEntity(hr.entity(1))->PointForceTo(c);
|
||||
processPoint(hr.entity(1));
|
||||
SK.GetEntity(hr.entity(32))->NormalForceTo(q);
|
||||
SK.GetEntity(hr.entity(64))->DistanceForceTo(r);
|
||||
|
||||
configureRequest(hr, style);
|
||||
return hr.entity(0);
|
||||
}
|
||||
|
@ -560,13 +604,25 @@ public:
|
|||
if(data.space != DRW::ModelSpace) return;
|
||||
if(addPendingBlockEntity<DRW_Arc>(data)) return;
|
||||
|
||||
hRequest hr = SS.GW.AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false);
|
||||
double r = data.radious;
|
||||
double sa = data.staangle;
|
||||
double ea = data.endangle;
|
||||
Vector c = Vector::From(data.basePoint.x, data.basePoint.y, data.basePoint.z);
|
||||
Vector rvs = Vector::From(r * cos(sa), r * sin(sa), data.basePoint.z).Plus(c);
|
||||
Vector rve = Vector::From(r * cos(ea), r * sin(ea), data.basePoint.z).Plus(c);
|
||||
Vector c = toVector(data.basePoint);
|
||||
Vector nz = toVector(data.extPoint);
|
||||
Quaternion q = NormalFromExtPoint(nz);
|
||||
|
||||
bool planar = q.RotationN().Equals(Vector::From(0, 0, 1));
|
||||
bool onPlane = c.z < LENGTH_EPS;
|
||||
|
||||
hEntity oldWorkplane = SS.GW.ActiveWorkplane();
|
||||
if (!planar || !onPlane) {
|
||||
activateWorkplane(findOrCreateWorkplane(c, q));
|
||||
}
|
||||
|
||||
hRequest hr = SS.GW.AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false);
|
||||
Vector u = q.RotationU(), v = q.RotationV();
|
||||
Vector rvs = c.Plus(u.ScaledBy(r * cos(sa))).Plus(v.ScaledBy(r * sin(sa)));
|
||||
Vector rve = c.Plus(u.ScaledBy(r * cos(ea))).Plus(v.ScaledBy(r * sin(ea)));
|
||||
|
||||
if(data.extPoint.z == -1.0) {
|
||||
c.x = -c.x;
|
||||
|
@ -584,13 +640,16 @@ public:
|
|||
processPoint(hr.entity(2));
|
||||
processPoint(hr.entity(3));
|
||||
configureRequest(hr, styleFor(&data));
|
||||
activateWorkplane(oldWorkplane);
|
||||
}
|
||||
|
||||
void addCircle(const DRW_Circle &data) override {
|
||||
if(data.space != DRW::ModelSpace) return;
|
||||
if(addPendingBlockEntity<DRW_Circle>(data)) return;
|
||||
|
||||
createCircle(toVector(data.basePoint), data.radious, styleFor(&data));
|
||||
Vector nz = toVector(data.extPoint);
|
||||
Quaternion normal = NormalFromExtPoint(nz);
|
||||
createCircle(toVector(data.basePoint), normal, data.radious, styleFor(&data));
|
||||
}
|
||||
|
||||
void addLWPolyline(const DRW_LWPolyline &data) override {
|
||||
|
@ -835,9 +894,9 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
hConstraint createDiametric(Vector cp, double r, Vector tp, double actual,
|
||||
bool asRadius = false) {
|
||||
hEntity he = createCircle(cp, r, invisibleStyle());
|
||||
hConstraint createDiametric(Vector cp, Quaternion q, double r, Vector tp,
|
||||
double actual, bool asRadius = false) {
|
||||
hEntity he = createCircle(cp, q, r, invisibleStyle());
|
||||
|
||||
hConstraint hc = Constraint::Constrain(
|
||||
Constraint::Type::DIAMETER,
|
||||
|
@ -869,7 +928,9 @@ public:
|
|||
actual = data->getActualMeasurement();
|
||||
}
|
||||
|
||||
createDiametric(cp, cp.Minus(dp).Magnitude(), tp, actual, /*asRadius=*/true);
|
||||
Vector nz = toVector(data->getExtrusion());
|
||||
Quaternion q = NormalFromExtPoint(nz);
|
||||
createDiametric(cp, q, cp.Minus(dp).Magnitude(), tp, actual, /*asRadius=*/true);
|
||||
}
|
||||
|
||||
void addDimDiametric(const DRW_DimDiametric *data) override {
|
||||
|
@ -886,7 +947,9 @@ public:
|
|||
actual = data->getActualMeasurement();
|
||||
}
|
||||
|
||||
createDiametric(cp, cp.Minus(dp1).Magnitude(), tp, actual, /*asRadius=*/false);
|
||||
Vector nz = toVector(data->getExtrusion());
|
||||
Quaternion q = NormalFromExtPoint(nz);
|
||||
createDiametric(cp, q, cp.Minus(dp1).Magnitude(), tp, actual, /*asRadius=*/false);
|
||||
}
|
||||
|
||||
void addDimAngular3P(const DRW_DimAngular3p *data) override {
|
||||
|
@ -972,11 +1035,13 @@ public:
|
|||
void addArc(const DRW_Arc &data) override {
|
||||
if(data.space != DRW::ModelSpace) return;
|
||||
checkCoord(data.basePoint);
|
||||
checkExt(data.extPoint);
|
||||
}
|
||||
|
||||
void addCircle(const DRW_Circle &data) override {
|
||||
if(data.space != DRW::ModelSpace) return;
|
||||
checkCoord(data.basePoint);
|
||||
checkExt(data.extPoint);
|
||||
}
|
||||
|
||||
void addPolyline(const DRW_Polyline &data) override {
|
||||
|
@ -1041,6 +1106,7 @@ public:
|
|||
checkCoord(data->getCenterPoint());
|
||||
checkCoord(data->getDiameterPoint());
|
||||
checkCoord(data->getTextPoint());
|
||||
checkExt(data->getExtrusion());
|
||||
}
|
||||
|
||||
void addDimDiametric(const DRW_DimDiametric *data) override {
|
||||
|
@ -1048,6 +1114,7 @@ public:
|
|||
checkCoord(data->getDiameter1Point());
|
||||
checkCoord(data->getDiameter2Point());
|
||||
checkCoord(data->getTextPoint());
|
||||
checkExt(data->getExtrusion());
|
||||
}
|
||||
|
||||
void addDimAngular3P(const DRW_DimAngular3p *data) override {
|
||||
|
@ -1066,6 +1133,12 @@ public:
|
|||
is3d = true;
|
||||
}
|
||||
}
|
||||
|
||||
void checkExt(const DRW_Coord &coord) {
|
||||
if ((fabs(coord.x) > 1/64.) || (fabs(coord.y) > 1/64.)) {
|
||||
is3d = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void
|
||||
|
@ -1112,14 +1185,14 @@ ImportDwgDxf(const Platform::Path &filename,
|
|||
void ImportDxf(const Platform::Path &filename) {
|
||||
ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) {
|
||||
std::stringstream stream(data);
|
||||
return dxfRW().read(stream, intf, /*ext=*/false);
|
||||
return dxfRW().read(stream, intf, /*ext=*/true);
|
||||
});
|
||||
}
|
||||
|
||||
void ImportDwg(const Platform::Path &filename) {
|
||||
ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) {
|
||||
std::stringstream stream(data);
|
||||
return dwgR().read(stream, intf, /*ext=*/false);
|
||||
return dwgR().read(stream, intf, /*ext=*/true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
10
src/mesh.cpp
10
src/mesh.cpp
|
@ -971,9 +971,15 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, EdgeKind how, bool coplanarIs
|
|||
|
||||
switch(how) {
|
||||
case EdgeKind::NAKED_OR_SELF_INTER:
|
||||
// there should be one anti-parllel edge
|
||||
if(info.count != 1) {
|
||||
sel->AddEdge(a, b, auxA);
|
||||
if(leaky) *leaky = true;
|
||||
// but there may be multiple parallel coincident edges
|
||||
SKdNode::EdgeOnInfo parallelInfo = {};
|
||||
FindEdgeOn(b, a, -cnt, coplanarIsInter, ¶llelInfo);
|
||||
if (info.count != parallelInfo.count) {
|
||||
sel->AddEdge(a, b, auxA);
|
||||
if(leaky) *leaky = true;
|
||||
}
|
||||
}
|
||||
if(info.intersectsMesh) {
|
||||
sel->AddEdge(a, b, auxA);
|
||||
|
|
|
@ -25,6 +25,8 @@ Common options:
|
|||
piecewise linear, and exact surfaces into triangle meshes.
|
||||
For export commands, the unit is mm, and the default is 1.0 mm.
|
||||
For non-export commands, the unit is %%, and the default is 1.0 %%.
|
||||
-b, --bg-color <on|off>
|
||||
Whether to export the background colour in vector formats. Defaults to off.
|
||||
|
||||
Commands:
|
||||
thumbnail --output <pattern> --size <size> --view <direction>
|
||||
|
@ -33,6 +35,7 @@ Commands:
|
|||
<size> is <width>x<height>, in pixels. Graphics acceleration is
|
||||
not used, and the output may look slightly different from the GUI.
|
||||
export-view --output <pattern> --view <direction> [--chord-tol <tolerance>]
|
||||
[--bg-color <on|off>]
|
||||
Exports a view of the sketch, in a 2d vector format.
|
||||
export-wireframe --output <pattern> [--chord-tol <tolerance>]
|
||||
Exports a wireframe of the sketch, in a 3d vector format.
|
||||
|
@ -118,22 +121,22 @@ static bool RunCommand(const std::vector<std::string> args) {
|
|||
argn++;
|
||||
if(args[argn] == "top") {
|
||||
projRight = Vector::From(1, 0, 0);
|
||||
projUp = Vector::From(0, 1, 0);
|
||||
projUp = Vector::From(0, 0, -1);
|
||||
} else if(args[argn] == "bottom") {
|
||||
projRight = Vector::From(-1, 0, 0);
|
||||
projUp = Vector::From(0, 1, 0);
|
||||
} else if(args[argn] == "left") {
|
||||
projRight = Vector::From(0, 1, 0);
|
||||
projUp = Vector::From(0, 0, 1);
|
||||
} else if(args[argn] == "right") {
|
||||
projRight = Vector::From(0, -1, 0);
|
||||
projUp = Vector::From(0, 0, 1);
|
||||
} else if(args[argn] == "front") {
|
||||
projRight = Vector::From(-1, 0, 0);
|
||||
projUp = Vector::From(0, 0, 1);
|
||||
} else if(args[argn] == "back") {
|
||||
projRight = Vector::From(1, 0, 0);
|
||||
projUp = Vector::From(0, 0, 1);
|
||||
} else if(args[argn] == "left") {
|
||||
projRight = Vector::From(0, 0, 1);
|
||||
projUp = Vector::From(0, 1, 0);
|
||||
} else if(args[argn] == "right") {
|
||||
projRight = Vector::From(0, 0, -1);
|
||||
projUp = Vector::From(0, 1, 0);
|
||||
} else if(args[argn] == "front") {
|
||||
projRight = Vector::From(1, 0, 0);
|
||||
projUp = Vector::From(0, 1, 0);
|
||||
} else if(args[argn] == "back") {
|
||||
projRight = Vector::From(-1, 0, 0);
|
||||
projUp = Vector::From(0, 1, 0);
|
||||
} else if(args[argn] == "isometric") {
|
||||
projRight = Vector::From(0.707, 0.000, -0.707);
|
||||
projUp = Vector::From(-0.408, 0.816, -0.408);
|
||||
|
@ -155,6 +158,21 @@ static bool RunCommand(const std::vector<std::string> args) {
|
|||
} else return false;
|
||||
};
|
||||
|
||||
bool bg_color = false;
|
||||
auto ParseBgColor = [&](size_t &argn) {
|
||||
if(argn + 1 < args.size() && (args[argn] == "--bg-color" ||
|
||||
args[argn] == "-b")) {
|
||||
argn++;
|
||||
if(args[argn] == "on") {
|
||||
bg_color = true;
|
||||
return true;
|
||||
} else if(args[argn] == "off") {
|
||||
bg_color = false;
|
||||
return true;
|
||||
} else return false;
|
||||
} else return false;
|
||||
};
|
||||
|
||||
unsigned width = 0, height = 0;
|
||||
if(args[1] == "thumbnail") {
|
||||
auto ParseSize = [&](size_t &argn) {
|
||||
|
@ -193,13 +211,14 @@ static bool RunCommand(const std::vector<std::string> args) {
|
|||
camera.gridFit = true;
|
||||
camera.width = width;
|
||||
camera.height = height;
|
||||
camera.projUp = SS.GW.projUp;
|
||||
camera.projRight = SS.GW.projRight;
|
||||
camera.projUp = projUp;
|
||||
camera.projRight = projRight;
|
||||
|
||||
SS.GW.projUp = projUp;
|
||||
SS.GW.projRight = projRight;
|
||||
SS.GW.scale = SS.GW.ZoomToFit(camera);
|
||||
camera.scale = SS.GW.scale;
|
||||
camera.offset = SS.GW.offset;
|
||||
SS.GenerateAll();
|
||||
|
||||
CairoPixmapRenderer pixmapCanvas;
|
||||
|
@ -221,7 +240,8 @@ static bool RunCommand(const std::vector<std::string> args) {
|
|||
if(!(ParseInputFile(argn) ||
|
||||
ParseOutputPattern(argn) ||
|
||||
ParseViewDirection(argn) ||
|
||||
ParseChordTolerance(argn))) {
|
||||
ParseChordTolerance(argn) ||
|
||||
ParseBgColor(argn))) {
|
||||
fprintf(stderr, "Unrecognized option '%s'.\n", args[argn].c_str());
|
||||
return false;
|
||||
}
|
||||
|
@ -233,9 +253,10 @@ static bool RunCommand(const std::vector<std::string> args) {
|
|||
}
|
||||
|
||||
runner = [&](const Platform::Path &output) {
|
||||
SS.GW.projRight = projRight;
|
||||
SS.GW.projUp = projUp;
|
||||
SS.exportChordTol = chordTol;
|
||||
SS.GW.projRight = projRight;
|
||||
SS.GW.projUp = projUp;
|
||||
SS.exportChordTol = chordTol;
|
||||
SS.exportBackgroundColor = bg_color;
|
||||
|
||||
SS.ExportViewOrWireframeTo(output, /*exportWireframe=*/false);
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -458,13 +458,25 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
}
|
||||
}
|
||||
|
||||
- (void)mouseMotionEvent:(NSEvent *)nsEvent withButton:(Platform::MouseEvent::Button)button {
|
||||
using Platform::MouseEvent;
|
||||
|
||||
MouseEvent event = [self convertMouseEvent:nsEvent];
|
||||
event.type = MouseEvent::Type::MOTION;
|
||||
event.button = button;
|
||||
|
||||
if(receiver->onMouseEvent) {
|
||||
receiver->onMouseEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)nsEvent {
|
||||
[self mouseMotionEvent:nsEvent];
|
||||
[super mouseMoved:nsEvent];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(NSEvent *)nsEvent {
|
||||
[self mouseMotionEvent:nsEvent];
|
||||
[self mouseMotionEvent:nsEvent withButton:Platform::MouseEvent::Button::LEFT];
|
||||
}
|
||||
|
||||
- (void)otherMouseDragged:(NSEvent *)nsEvent {
|
||||
|
|
|
@ -1058,7 +1058,7 @@ public:
|
|||
sscheck(window = (WindowImplWin32 *)GetWindowLongPtr(hWindow, 0));
|
||||
|
||||
switch(msg) {
|
||||
case WM_KEYDOWN:
|
||||
case WM_CHAR:
|
||||
if(wParam == VK_RETURN) {
|
||||
if(window->onEditingDone) {
|
||||
int length;
|
||||
|
|
|
@ -131,6 +131,7 @@ public:
|
|||
void FindPointWithMinX();
|
||||
Vector AnyEdgeMidpoint() const;
|
||||
|
||||
bool IsEmptyTriangle(int ap, int bp, int cp, double scaledEPS) const;
|
||||
bool IsEar(int bp, double scaledEps) const;
|
||||
bool BridgeToContour(SContour *sc, SEdgeList *el, List<Vector> *vl);
|
||||
void ClipEarInto(SMesh *m, int bp, double scaledEps);
|
||||
|
|
|
@ -189,6 +189,8 @@ public:
|
|||
struct {
|
||||
SolveResult how;
|
||||
int dof;
|
||||
int findToFixTimeout;
|
||||
bool timeout;
|
||||
List<hConstraint> remove;
|
||||
} solved;
|
||||
|
||||
|
@ -409,6 +411,7 @@ public:
|
|||
FACE_N_TRANS = 5003,
|
||||
FACE_N_ROT_AA = 5004,
|
||||
FACE_ROT_NORMAL_PT = 5005,
|
||||
FACE_N_ROT_AXIS_TRANS = 5006,
|
||||
|
||||
WORKPLANE = 10000,
|
||||
LINE_SEGMENT = 11000,
|
||||
|
@ -565,6 +568,7 @@ public:
|
|||
|
||||
bool IsStylable() const;
|
||||
bool IsVisible() const;
|
||||
bool CanBeDragged() const;
|
||||
|
||||
enum class DrawAs { DEFAULT, OVERLAY, HIDDEN, HOVERED, SELECTED };
|
||||
void Draw(DrawAs how, Canvas *canvas);
|
||||
|
@ -703,6 +707,7 @@ public:
|
|||
}
|
||||
|
||||
bool HasLabel() const;
|
||||
bool IsProjectible() const;
|
||||
|
||||
void Generate(IdList<Param, hParam> *param);
|
||||
|
||||
|
|
|
@ -51,6 +51,8 @@ void SolveSpaceUI::Init() {
|
|||
exportChordTol = settings->ThawFloat("ExportChordTolerance", 0.1);
|
||||
// Max pwl segments to generate
|
||||
exportMaxSegments = settings->ThawInt("ExportMaxSegments", 64);
|
||||
// Timeout value for finding redundant constrains (ms)
|
||||
timeoutRedundantConstr = settings->ThawInt("TimeoutRedundantConstraints", 1000);
|
||||
// View units
|
||||
viewUnits = (Unit)settings->ThawInt("ViewUnits", (uint32_t)Unit::MM);
|
||||
// Number of digits after the decimal point
|
||||
|
@ -68,6 +70,8 @@ void SolveSpaceUI::Init() {
|
|||
exportOffset = settings->ThawFloat("ExportOffset", 0.0);
|
||||
// Rewrite exported colors close to white into black (assuming white bg)
|
||||
fixExportColors = settings->ThawBool("FixExportColors", true);
|
||||
// Export background color
|
||||
exportBackgroundColor = settings->ThawBool("ExportBackgroundColor", false);
|
||||
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
|
||||
drawBackFaces = settings->ThawBool("DrawBackFaces", true);
|
||||
// Use turntable mouse navigation
|
||||
|
@ -229,6 +233,8 @@ void SolveSpaceUI::Exit() {
|
|||
settings->FreezeFloat("ExportChordTolerance", (float)exportChordTol);
|
||||
// Export Max pwl segments to generate
|
||||
settings->FreezeInt("ExportMaxSegments", (uint32_t)exportMaxSegments);
|
||||
// Timeout for finding which constraints to fix Jacobian
|
||||
settings->FreezeInt("TimeoutRedundantConstraints", (uint32_t)timeoutRedundantConstr);
|
||||
// View units
|
||||
settings->FreezeInt("ViewUnits", (uint32_t)viewUnits);
|
||||
// Number of digits after the decimal point
|
||||
|
@ -246,6 +252,8 @@ void SolveSpaceUI::Exit() {
|
|||
settings->FreezeFloat("ExportOffset", exportOffset);
|
||||
// Rewrite exported colors close to white into black (assuming white bg)
|
||||
settings->FreezeBool("FixExportColors", fixExportColors);
|
||||
// Export background color
|
||||
settings->FreezeBool("ExportBackgroundColor", exportBackgroundColor);
|
||||
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
|
||||
settings->FreezeBool("DrawBackFaces", drawBackFaces);
|
||||
// Draw closed polygons areas
|
||||
|
|
|
@ -122,10 +122,6 @@ static constexpr double LENGTH_EPS = 1e-6;
|
|||
static constexpr double VERY_POSITIVE = 1e10;
|
||||
static constexpr double VERY_NEGATIVE = -1e10;
|
||||
|
||||
inline double Random(double vmax) {
|
||||
return (vmax*rand()) / RAND_MAX;
|
||||
}
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/gui.h"
|
||||
#include "resource.h"
|
||||
|
@ -272,7 +268,8 @@ public:
|
|||
void EvalJacobian();
|
||||
|
||||
void WriteEquationsExceptFor(hConstraint hc, Group *g);
|
||||
void FindWhichToRemoveToFixJacobian(Group *g, List<hConstraint> *bad, bool forceDofCheck);
|
||||
void FindWhichToRemoveToFixJacobian(Group *g, List<hConstraint> *bad,
|
||||
bool forceDofCheck);
|
||||
void SolveBySubstitution();
|
||||
|
||||
bool IsDragged(hParam p);
|
||||
|
@ -343,6 +340,7 @@ public:
|
|||
virtual void Bezier(SBezier *sb) = 0;
|
||||
virtual void Triangle(STriangle *tr) = 0;
|
||||
virtual bool OutputConstraints(IdList<Constraint,hConstraint> *) { return false; }
|
||||
virtual void Background(RgbaColor color) = 0;
|
||||
virtual void StartFile() = 0;
|
||||
virtual void FinishAndCloseFile() = 0;
|
||||
virtual bool HasCanvasSize() const = 0;
|
||||
|
@ -367,6 +365,7 @@ public:
|
|||
bool filled, RgbaColor fillRgb, hStyle hs) override;
|
||||
void Triangle(STriangle *tr) override;
|
||||
void Bezier(SBezier *sb) override;
|
||||
void Background(RgbaColor color) override;
|
||||
void StartFile() override;
|
||||
void FinishAndCloseFile() override;
|
||||
bool HasCanvasSize() const override { return false; }
|
||||
|
@ -384,6 +383,7 @@ public:
|
|||
bool filled, RgbaColor fillRgb, hStyle hs) override;
|
||||
void Triangle(STriangle *tr) override;
|
||||
void Bezier(SBezier *sb) override;
|
||||
void Background(RgbaColor color) override;
|
||||
void StartFile() override;
|
||||
void FinishAndCloseFile() override;
|
||||
bool HasCanvasSize() const override { return true; }
|
||||
|
@ -402,6 +402,7 @@ public:
|
|||
bool filled, RgbaColor fillRgb, hStyle hs) override;
|
||||
void Triangle(STriangle *tr) override;
|
||||
void Bezier(SBezier *sb) override;
|
||||
void Background(RgbaColor color) override;
|
||||
void StartFile() override;
|
||||
void FinishAndCloseFile() override;
|
||||
bool HasCanvasSize() const override { return true; }
|
||||
|
@ -418,6 +419,7 @@ public:
|
|||
bool filled, RgbaColor fillRgb, hStyle hs) override;
|
||||
void Triangle(STriangle *tr) override;
|
||||
void Bezier(SBezier *sb) override;
|
||||
void Background(RgbaColor color) override;
|
||||
void StartFile() override;
|
||||
void FinishAndCloseFile() override;
|
||||
bool HasCanvasSize() const override { return true; }
|
||||
|
@ -432,6 +434,7 @@ public:
|
|||
bool filled, RgbaColor fillRgb, hStyle hs) override;
|
||||
void Triangle(STriangle *tr) override;
|
||||
void Bezier(SBezier *sb) override;
|
||||
void Background(RgbaColor color) override;
|
||||
void StartFile() override;
|
||||
void FinishAndCloseFile() override;
|
||||
bool HasCanvasSize() const override { return false; }
|
||||
|
@ -445,6 +448,7 @@ class Step2dFileWriter : public VectorFileWriter {
|
|||
bool filled, RgbaColor fillRgb, hStyle hs) override;
|
||||
void Triangle(STriangle *tr) override;
|
||||
void Bezier(SBezier *sb) override;
|
||||
void Background(RgbaColor color) override;
|
||||
void StartFile() override;
|
||||
void FinishAndCloseFile() override;
|
||||
bool HasCanvasSize() const override { return false; }
|
||||
|
@ -459,6 +463,7 @@ public:
|
|||
bool filled, RgbaColor fillRgb, hStyle hs) override;
|
||||
void Triangle(STriangle *tr) override;
|
||||
void Bezier(SBezier *sb) override;
|
||||
void Background(RgbaColor color) override;
|
||||
void StartFile() override;
|
||||
void FinishAndCloseFile() override;
|
||||
bool HasCanvasSize() const override { return false; }
|
||||
|
@ -558,11 +563,13 @@ public:
|
|||
int maxSegments;
|
||||
double exportChordTol;
|
||||
int exportMaxSegments;
|
||||
int timeoutRedundantConstr; //milliseconds
|
||||
double cameraTangent;
|
||||
double gridSpacing;
|
||||
double exportScale;
|
||||
double exportOffset;
|
||||
bool fixExportColors;
|
||||
bool exportBackgroundColor;
|
||||
bool drawBackFaces;
|
||||
bool showContourAreas;
|
||||
bool checkClosedContour;
|
||||
|
@ -672,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);
|
||||
|
@ -803,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;
|
||||
|
|
|
@ -88,8 +88,15 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
|
|||
}
|
||||
}
|
||||
|
||||
// We're keeping the intersection, so actually refine it.
|
||||
(pi->srf)->PointOnSurfaces(srfA, srfB, &(puv.x), &(puv.y));
|
||||
// We're keeping the intersection, so actually refine it. Finding the intersection
|
||||
// to within EPS is important to match the ends of different chopped trim curves.
|
||||
// The general 3-surface intersection fails to refine for trims where surfaces
|
||||
// are tangent at the curve, but those trims are usually exact, so…
|
||||
if(isExact) {
|
||||
(pi->srf)->PointOnCurve(&exact, &(puv.x), &(puv.y));
|
||||
} else {
|
||||
(pi->srf)->PointOnSurfaces(srfA, srfB, &(puv.x), &(puv.y));
|
||||
}
|
||||
pi->p = (pi->srf)->PointAt(puv);
|
||||
}
|
||||
il.RemoveTagged();
|
||||
|
@ -447,49 +454,48 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent,
|
|||
SEdgeList inter = {};
|
||||
|
||||
SSurface *ss;
|
||||
for(ss = agnst->surface.First(); ss; ss = agnst->surface.NextAfter(ss)) {
|
||||
SCurve *sc;
|
||||
for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) {
|
||||
if(sc->source != SCurve::Source::INTERSECTION) continue;
|
||||
if(opA) {
|
||||
if(sc->surfA != h || sc->surfB != ss->h) continue;
|
||||
} else {
|
||||
if(sc->surfB != h || sc->surfA != ss->h) continue;
|
||||
}
|
||||
SCurve *sc;
|
||||
for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) {
|
||||
if(sc->source != SCurve::Source::INTERSECTION) continue;
|
||||
if(opA) {
|
||||
if(sc->surfA != h) continue;
|
||||
ss = shb->surface.FindById(sc->surfB);
|
||||
} else {
|
||||
if(sc->surfB != h) continue;
|
||||
ss = sha->surface.FindById(sc->surfA);
|
||||
}
|
||||
int i;
|
||||
for(i = 1; i < sc->pts.n; i++) {
|
||||
Vector a = sc->pts[i-1].p,
|
||||
b = sc->pts[i].p;
|
||||
|
||||
int i;
|
||||
for(i = 1; i < sc->pts.n; i++) {
|
||||
Vector a = sc->pts[i-1].p,
|
||||
b = sc->pts[i].p;
|
||||
Point2d auv, buv;
|
||||
ss->ClosestPointTo(a, &(auv.x), &(auv.y));
|
||||
ss->ClosestPointTo(b, &(buv.x), &(buv.y));
|
||||
|
||||
Point2d auv, buv;
|
||||
ss->ClosestPointTo(a, &(auv.x), &(auv.y));
|
||||
ss->ClosestPointTo(b, &(buv.x), &(buv.y));
|
||||
SBspUv::Class c = (ss->bsp) ? ss->bsp->ClassifyEdge(auv, buv, ss) : SBspUv::Class::OUTSIDE;
|
||||
if(c != SBspUv::Class::OUTSIDE) {
|
||||
Vector ta = Vector::From(0, 0, 0);
|
||||
Vector tb = Vector::From(0, 0, 0);
|
||||
ret.ClosestPointTo(a, &(ta.x), &(ta.y));
|
||||
ret.ClosestPointTo(b, &(tb.x), &(tb.y));
|
||||
|
||||
SBspUv::Class c = (ss->bsp) ? ss->bsp->ClassifyEdge(auv, buv, ss) : SBspUv::Class::OUTSIDE;
|
||||
if(c != SBspUv::Class::OUTSIDE) {
|
||||
Vector ta = Vector::From(0, 0, 0);
|
||||
Vector tb = Vector::From(0, 0, 0);
|
||||
ret.ClosestPointTo(a, &(ta.x), &(ta.y));
|
||||
ret.ClosestPointTo(b, &(tb.x), &(tb.y));
|
||||
Vector tn = ret.NormalAt(ta.x, ta.y);
|
||||
Vector sn = ss->NormalAt(auv.x, auv.y);
|
||||
|
||||
Vector tn = ret.NormalAt(ta.x, ta.y);
|
||||
Vector sn = ss->NormalAt(auv.x, auv.y);
|
||||
|
||||
// We are subtracting the portion of our surface that
|
||||
// lies in the shell, so the in-plane edge normal should
|
||||
// point opposite to the surface normal.
|
||||
bool bkwds = true;
|
||||
if((tn.Cross(b.Minus(a))).Dot(sn) < 0) bkwds = !bkwds;
|
||||
if((type == SSurface::CombineAs::DIFFERENCE && !opA) ||
|
||||
(type == SSurface::CombineAs::INTERSECTION)) { // Invert all newly created edges for intersection
|
||||
bkwds = !bkwds;
|
||||
}
|
||||
if(bkwds) {
|
||||
inter.AddEdge(tb, ta, sc->h.v, 1);
|
||||
} else {
|
||||
inter.AddEdge(ta, tb, sc->h.v, 0);
|
||||
}
|
||||
// We are subtracting the portion of our surface that
|
||||
// lies in the shell, so the in-plane edge normal should
|
||||
// point opposite to the surface normal.
|
||||
bool bkwds = true;
|
||||
if((tn.Cross(b.Minus(a))).Dot(sn) < 0) bkwds = !bkwds;
|
||||
if((type == SSurface::CombineAs::DIFFERENCE && !opA) ||
|
||||
(type == SSurface::CombineAs::INTERSECTION)) { // Invert all newly created edges for intersection
|
||||
bkwds = !bkwds;
|
||||
}
|
||||
if(bkwds) {
|
||||
inter.AddEdge(tb, ta, sc->h.v, 1);
|
||||
} else {
|
||||
inter.AddEdge(ta, tb, sc->h.v, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -618,23 +624,26 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent,
|
|||
}
|
||||
|
||||
void SShell::CopySurfacesTrimAgainst(SShell *sha, SShell *shb, SShell *into, SSurface::CombineAs type) {
|
||||
std::vector <SSurface> ssn(surface.n);
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < surface.n; i++)
|
||||
{
|
||||
SSurface *ss = &surface[i];
|
||||
SSurface ssn;
|
||||
ssn = ss->MakeCopyTrimAgainst(this, sha, shb, into, type, i);
|
||||
#pragma omp critical
|
||||
{
|
||||
ss->newH = into->surface.AddAndAssignId(&ssn);
|
||||
}
|
||||
ssn[i] = ss->MakeCopyTrimAgainst(this, sha, shb, into, type, i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < surface.n; i++)
|
||||
{
|
||||
surface[i].newH = into->surface.AddAndAssignId(&ssn[i]);
|
||||
}
|
||||
I += surface.n;
|
||||
}
|
||||
|
||||
void SShell::MakeIntersectionCurvesAgainst(SShell *agnst, SShell *into) {
|
||||
SSurface *sa;
|
||||
for(sa = surface.First(); sa; sa = surface.NextAfter(sa)) {
|
||||
#pragma omp parallel for
|
||||
for(int i = 0; i< surface.n; i++) {
|
||||
SSurface *sa = &surface[i];
|
||||
|
||||
SSurface *sb;
|
||||
for(sb = agnst->surface.First(); sb; sb = agnst->surface.NextAfter(sb)){
|
||||
// Intersect every surface from our shell against every surface
|
||||
|
|
|
@ -447,11 +447,13 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool mustConverge)
|
|||
|
||||
// If we failed to converge, then at least don't return NaN.
|
||||
if(mustConverge) {
|
||||
Vector p0 = PointAt(*u, *v);
|
||||
dbp("didn't converge");
|
||||
dbp("have %.3f %.3f %.3f", CO(p0));
|
||||
dbp("want %.3f %.3f %.3f", CO(p));
|
||||
dbp("distance = %g", (p.Minus(p0)).Magnitude());
|
||||
// This is expected not to converge when the target point is not on the surface but nearby.
|
||||
// let's not pollute the output window for normal use.
|
||||
// Vector p0 = PointAt(*u, *v);
|
||||
// dbp("didn't converge");
|
||||
// dbp("have %.3f %.3f %.3f", CO(p0));
|
||||
// dbp("want %.3f %.3f %.3f", CO(p));
|
||||
// dbp("distance = %g", (p.Minus(p0)).Magnitude());
|
||||
}
|
||||
if(IsReasonable(*u) || IsReasonable(*v)) {
|
||||
*u = *v = 0;
|
||||
|
@ -500,7 +502,7 @@ bool SSurface::ClosestPointNewton(Vector p, double *u, double *v, bool mustConve
|
|||
bool SSurface::PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) const
|
||||
{
|
||||
int i;
|
||||
for(i = 0; i < 15; i++) {
|
||||
for(i = 0; i < 20; i++) {
|
||||
Vector pi, p, tu, tv;
|
||||
p = PointAt(*u, *v);
|
||||
TangentsAt(*u, *v, &tu, &tv);
|
||||
|
@ -510,7 +512,10 @@ bool SSurface::PointIntersectingLine(Vector p0, Vector p1, double *u, double *v)
|
|||
|
||||
bool parallel;
|
||||
pi = Vector::AtIntersectionOfPlaneAndLine(n, d, p0, p1, ¶llel);
|
||||
if(parallel) break;
|
||||
if(parallel) {
|
||||
dbp("parallel (surface intersecting line)");
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for convergence
|
||||
if(pi.Equals(p, RATPOLY_EPS)) return true;
|
||||
|
@ -617,7 +622,10 @@ void SSurface::PointOnSurfaces(SSurface *s1, SSurface *s2, double *up, double *v
|
|||
Vector pi = Vector::AtIntersectionOfPlanes(n[0], d[0],
|
||||
n[1], d[1],
|
||||
n[2], d[2], ¶llel);
|
||||
if(parallel) break;
|
||||
|
||||
if(parallel) { // lets try something else for parallel planes
|
||||
pi = p[0].Plus(p[1]).Plus(p[2]).ScaledBy(1.0/3.0);
|
||||
}
|
||||
|
||||
for(j = 0; j < 3; j++) {
|
||||
Vector n = tu[j].Cross(tv[j]);
|
||||
|
@ -634,3 +642,58 @@ void SSurface::PointOnSurfaces(SSurface *s1, SSurface *s2, double *up, double *v
|
|||
dbp("didn't converge (three surfaces intersecting)");
|
||||
}
|
||||
|
||||
void SSurface::PointOnCurve(const SBezier *curve, double *up, double *vp)
|
||||
{
|
||||
Vector tu,tv,n;
|
||||
double u = *up, v = *vp;
|
||||
Vector ps = PointAt(u, v);
|
||||
// Get initial guesses for t on the curve
|
||||
double tCurve = 0.5;
|
||||
curve->ClosestPointTo(ps, &tCurve, /*mustConverge=*/false);
|
||||
if(tCurve < 0.0) tCurve = 0.0;
|
||||
if(tCurve > 1.0) tCurve = 1.0;
|
||||
|
||||
for(int i = 0; i < 30; i++) {
|
||||
// Approximate the surface by a plane
|
||||
Vector ps = PointAt(u, v);
|
||||
TangentsAt(u, v, &tu, &tv);
|
||||
n = tu.Cross(tv).WithMagnitude(1);
|
||||
|
||||
// point on curve and tangent line direction
|
||||
Vector pc = curve->PointAt(tCurve);
|
||||
Vector tc = curve->TangentAt(tCurve);
|
||||
|
||||
if(ps.Equals(pc, RATPOLY_EPS)) {
|
||||
*up = u;
|
||||
*vp = v;
|
||||
return;
|
||||
}
|
||||
|
||||
//pi is where the curve tangent line intersects the surface tangent plane
|
||||
Vector pi;
|
||||
double d = tc.Dot(n);
|
||||
if (fabs(d) < 1e-10) { // parallel line and plane, guess the average rather than fail
|
||||
pi = pc.Plus(ps).ScaledBy(0.5);
|
||||
} else {
|
||||
pi = pc.Minus(tc.ScaledBy(pc.Minus(ps).Dot(n)/d));
|
||||
}
|
||||
|
||||
// project the point onto the tangent plane and line
|
||||
{
|
||||
Vector n = tu.Cross(tv);
|
||||
Vector ty = n.Cross(tu).ScaledBy(1.0/tu.MagSquared());
|
||||
Vector tx = tv.Cross(n).ScaledBy(1.0/tv.MagSquared());
|
||||
|
||||
Vector dp = pi.Minus(ps);
|
||||
double du = dp.Dot(tx), dv = dp.Dot(ty);
|
||||
|
||||
u += du / tx.MagSquared();
|
||||
v += dv / ty.MagSquared();
|
||||
}
|
||||
tCurve += pi.Minus(pc).Dot(tc) / tc.MagSquared();
|
||||
if(tCurve < 0.0) tCurve = 0.0;
|
||||
if(tCurve > 1.0) tCurve = 1.0;
|
||||
}
|
||||
dbp("didn't converge (surface and curve intersecting)");
|
||||
}
|
||||
|
||||
|
|
|
@ -130,39 +130,38 @@ void SSurface::SplitInHalf(bool byU, SSurface *sa, SSurface *sb) {
|
|||
sa->degn = sb->degn = degn;
|
||||
|
||||
// by de Casteljau's algorithm in a projective space; so we must work
|
||||
// on points (w*x, w*y, w*z, w)
|
||||
WeightControlPoints();
|
||||
// on points (w*x, w*y, w*z, w) so create a temporary copy
|
||||
SSurface st;
|
||||
st = *this;
|
||||
st.WeightControlPoints();
|
||||
|
||||
switch(byU ? degm : degn) {
|
||||
case 1:
|
||||
sa->CopyRowOrCol (byU, 0, this, 0);
|
||||
sb->CopyRowOrCol (byU, 1, this, 1);
|
||||
sa->CopyRowOrCol (byU, 0, &st, 0);
|
||||
sb->CopyRowOrCol (byU, 1, &st, 1);
|
||||
|
||||
sa->BlendRowOrCol(byU, 1, this, 0, this, 1);
|
||||
sb->BlendRowOrCol(byU, 0, this, 0, this, 1);
|
||||
sa->BlendRowOrCol(byU, 1, &st, 0, &st, 1);
|
||||
sb->BlendRowOrCol(byU, 0, &st, 0, &st, 1);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
sa->CopyRowOrCol (byU, 0, this, 0);
|
||||
sb->CopyRowOrCol (byU, 2, this, 2);
|
||||
sa->CopyRowOrCol (byU, 0, &st, 0);
|
||||
sb->CopyRowOrCol (byU, 2, &st, 2);
|
||||
|
||||
sa->BlendRowOrCol(byU, 1, this, 0, this, 1);
|
||||
sb->BlendRowOrCol(byU, 1, this, 1, this, 2);
|
||||
sa->BlendRowOrCol(byU, 1, &st, 0, &st, 1);
|
||||
sb->BlendRowOrCol(byU, 1, &st, 1, &st, 2);
|
||||
|
||||
sa->BlendRowOrCol(byU, 2, sa, 1, sb, 1);
|
||||
sb->BlendRowOrCol(byU, 0, sa, 1, sb, 1);
|
||||
break;
|
||||
|
||||
case 3: {
|
||||
SSurface st;
|
||||
st.degm = degm; st.degn = degn;
|
||||
sa->CopyRowOrCol (byU, 0, &st, 0);
|
||||
sb->CopyRowOrCol (byU, 3, &st, 3);
|
||||
|
||||
sa->CopyRowOrCol (byU, 0, this, 0);
|
||||
sb->CopyRowOrCol (byU, 3, this, 3);
|
||||
|
||||
sa->BlendRowOrCol(byU, 1, this, 0, this, 1);
|
||||
sb->BlendRowOrCol(byU, 2, this, 2, this, 3);
|
||||
st. BlendRowOrCol(byU, 0, this, 1, this, 2); // scratch var
|
||||
sa->BlendRowOrCol(byU, 1, &st, 0, &st, 1);
|
||||
sb->BlendRowOrCol(byU, 2, &st, 2, &st, 3);
|
||||
st. BlendRowOrCol(byU, 0, &st, 1, &st, 2); // use row/col 0 as scratch
|
||||
|
||||
sa->BlendRowOrCol(byU, 2, sa, 1, &st, 0);
|
||||
sb->BlendRowOrCol(byU, 1, sb, 2, &st, 0);
|
||||
|
@ -177,7 +176,6 @@ void SSurface::SplitInHalf(bool byU, SSurface *sa, SSurface *sb) {
|
|||
|
||||
sa->UnWeightControlPoints();
|
||||
sb->UnWeightControlPoints();
|
||||
UnWeightControlPoints();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -421,6 +419,11 @@ SShell::Class SShell::ClassifyRegion(Vector edge_n, Vector inter_surf_n,
|
|||
// using the closest intersection point. If the ray hits a surface on edge,
|
||||
// then just reattempt in a different random direction.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// table of vectors in 6 arbitrary directions covering 4 of the 8 octants.
|
||||
// use overlapping sets of 3 to reduce memory usage.
|
||||
static const double Random[8] = {1.278, 5.0103, 9.427, -2.331, 7.13, 2.954, 5.034, -4.777};
|
||||
|
||||
bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
||||
Vector ea, Vector eb,
|
||||
Vector p,
|
||||
|
@ -428,8 +431,6 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
|||
{
|
||||
List<SInter> l = {};
|
||||
|
||||
srand(0);
|
||||
|
||||
// First, check for edge-on-edge
|
||||
int edge_inters = 0;
|
||||
Vector inter_surf_n[2], inter_edge_n[2];
|
||||
|
@ -551,7 +552,7 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
|||
// Cast a ray in a random direction (two-sided so that we test if
|
||||
// the point lies on a surface, but use only one side for in/out
|
||||
// testing)
|
||||
Vector ray = Vector::From(Random(1), Random(1), Random(1));
|
||||
Vector ray = Vector::From(Random[cnt], Random[cnt+1], Random[cnt+2]);
|
||||
|
||||
AllPointsIntersecting(
|
||||
p.Minus(ray), p.Plus(ray), &l,
|
||||
|
@ -600,7 +601,8 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
|||
// then our ray always lies on edge, and that's okay. Otherwise
|
||||
// try again in a different random direction.
|
||||
if(!onEdge) break;
|
||||
if(cnt++ > 5) {
|
||||
cnt++;
|
||||
if(cnt > 5) {
|
||||
dbp("can't find a ray that doesn't hit on edge!");
|
||||
dbp("on edge = %d, edge_inters = %d", onEdge, edge_inters);
|
||||
SS.nakedEdges.AddEdge(ea, eb);
|
||||
|
|
|
@ -648,12 +648,14 @@ void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector
|
|||
double dist = distf - dists;
|
||||
int sections = (int)(fabs(anglef - angles) / (PI / 2) + 1);
|
||||
double wedge = (anglef - angles) / sections;
|
||||
int startMapping = Group::REMAP_LATHE_START, endMapping = Group::REMAP_LATHE_END;
|
||||
|
||||
if(CheckNormalAxisRelationship(sbls, pt, axis, anglef-angles, distf-dists)) {
|
||||
swap(angles, anglef);
|
||||
swap(dists, distf);
|
||||
dist = -dist;
|
||||
wedge = -wedge;
|
||||
swap(startMapping, endMapping);
|
||||
}
|
||||
|
||||
// Define a coordinate system to contain the original sketch, and get
|
||||
|
@ -678,7 +680,7 @@ void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector
|
|||
u.RotatedAbout(axis, angles), v.RotatedAbout(axis, angles));
|
||||
s0.color = color;
|
||||
|
||||
hEntity face0 = group->Remap(Entity::NO_ENTITY, Group::REMAP_LATHE_START);
|
||||
hEntity face0 = group->Remap(Entity::NO_ENTITY, startMapping);
|
||||
s0.face = face0.v;
|
||||
|
||||
s1 = SSurface::FromPlane(
|
||||
|
@ -686,7 +688,7 @@ void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector
|
|||
u.ScaledBy(-1).RotatedAbout(axis, anglef), v.RotatedAbout(axis, anglef));
|
||||
s1.color = color;
|
||||
|
||||
hEntity face1 = group->Remap(Entity::NO_ENTITY, Group::REMAP_LATHE_END);
|
||||
hEntity face1 = group->Remap(Entity::NO_ENTITY, endMapping);
|
||||
s1.face = face1.v;
|
||||
|
||||
hSSurface hs0 = surface.AddAndAssignId(&s0);
|
||||
|
|
|
@ -332,6 +332,7 @@ public:
|
|||
bool PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) const;
|
||||
Vector ClosestPointOnThisAndSurface(SSurface *srf2, Vector p);
|
||||
void PointOnSurfaces(SSurface *s1, SSurface *s2, double *u, double *v);
|
||||
void PointOnCurve(const SBezier *curve, double *up, double *vp);
|
||||
Vector PointAt(double u, double v) const;
|
||||
Vector PointAt(Point2d puv) const;
|
||||
void TangentsAt(double u, double v, Vector *tu, Vector *tv) const;
|
||||
|
|
|
@ -27,19 +27,22 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB,
|
|||
SBezier sbrev = *sb;
|
||||
sbrev.Reverse();
|
||||
bool backwards = false;
|
||||
for(se = into->curve.First(); se; se = into->curve.NextAfter(se)) {
|
||||
if(se->isExact) {
|
||||
if(sb->Equals(&(se->exact))) {
|
||||
existing = se;
|
||||
break;
|
||||
}
|
||||
if(sbrev.Equals(&(se->exact))) {
|
||||
existing = se;
|
||||
backwards = true;
|
||||
break;
|
||||
#pragma omp critical(into)
|
||||
{
|
||||
for(se = into->curve.First(); se; se = into->curve.NextAfter(se)) {
|
||||
if(se->isExact) {
|
||||
if(sb->Equals(&(se->exact))) {
|
||||
existing = se;
|
||||
break;
|
||||
}
|
||||
if(sbrev.Equals(&(se->exact))) {
|
||||
existing = se;
|
||||
backwards = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}// end omp critical
|
||||
if(existing) {
|
||||
SCurvePt *v;
|
||||
for(v = existing->pts.First(); v; v = existing->pts.NextAfter(v)) {
|
||||
|
@ -101,7 +104,10 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB,
|
|||
"Unexpected zero-length edge");
|
||||
|
||||
split.source = SCurve::Source::INTERSECTION;
|
||||
into->curve.AddAndAssignId(&split);
|
||||
#pragma omp critical(into)
|
||||
{
|
||||
into->curve.AddAndAssignId(&split);
|
||||
}
|
||||
}
|
||||
|
||||
void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
|
||||
|
@ -341,7 +347,11 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
|
|||
Vector p = si->p;
|
||||
double u, v;
|
||||
srfB->ClosestPointTo(p, &u, &v);
|
||||
srfB->PointOnSurfaces(srfA, other, &u, &v);
|
||||
if(sc->isExact) {
|
||||
srfB->PointOnCurve(&(sc->exact), &u, &v);
|
||||
} else {
|
||||
srfB->PointOnSurfaces(srfA, other, &u, &v);
|
||||
}
|
||||
p = srfB->PointAt(u, v);
|
||||
if(!spl.ContainsPoint(p)) {
|
||||
SPoint sp;
|
||||
|
@ -452,7 +462,10 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
|
|||
// And now we split and insert the curve
|
||||
SCurve split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, b);
|
||||
sc.Clear();
|
||||
into->curve.AddAndAssignId(&split);
|
||||
#pragma omp critical(into)
|
||||
{
|
||||
into->curve.AddAndAssignId(&split);
|
||||
}
|
||||
}
|
||||
spl.Clear();
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ bool SContour::BridgeToContour(SContour *sc,
|
|||
SEdgeList *avoidEdges, List<Vector> *avoidPts)
|
||||
{
|
||||
int i, j;
|
||||
bool withbridge = true;
|
||||
|
||||
// Start looking for a bridge on our new hole near its leftmost (min x)
|
||||
// point.
|
||||
|
@ -123,7 +124,7 @@ bool SContour::BridgeToContour(SContour *sc,
|
|||
// to the leftmost point of the new segment.
|
||||
int thiso = 0;
|
||||
double dmin = 1e10;
|
||||
for(i = 0; i < l.n; i++) {
|
||||
for(i = 0; i < l.n-1; i++) {
|
||||
Vector p = l[i].p;
|
||||
double d = (p.Minus(sc->xminPt)).MagSquared();
|
||||
if(d < dmin) {
|
||||
|
@ -139,7 +140,7 @@ bool SContour::BridgeToContour(SContour *sc,
|
|||
// First check if the contours share a point; in that case we should
|
||||
// merge them there, without a bridge.
|
||||
for(i = 0; i < l.n; i++) {
|
||||
thisp = WRAP(i+thiso, l.n);
|
||||
thisp = WRAP(i+thiso, l.n-1);
|
||||
a = l[thisp].p;
|
||||
|
||||
for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) {
|
||||
|
@ -152,6 +153,7 @@ bool SContour::BridgeToContour(SContour *sc,
|
|||
b = sc->l[scp].p;
|
||||
|
||||
if(a.Equals(b)) {
|
||||
withbridge = false;
|
||||
goto haveEdge;
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +192,9 @@ bool SContour::BridgeToContour(SContour *sc,
|
|||
haveEdge:
|
||||
SContour merged = {};
|
||||
for(i = 0; i < l.n; i++) {
|
||||
merged.AddPoint(l[i].p);
|
||||
if(withbridge || (i != thisp)) {
|
||||
merged.AddPoint(l[i].p);
|
||||
}
|
||||
if(i == thisp) {
|
||||
// less than or equal; need to duplicate the join point
|
||||
for(j = 0; j <= (sc->l.n - 1); j++) {
|
||||
|
@ -198,14 +202,18 @@ haveEdge:
|
|||
merged.AddPoint((sc->l[jp]).p);
|
||||
}
|
||||
// and likewise duplicate join point for the outer curve
|
||||
merged.AddPoint(l[i].p);
|
||||
if(withbridge) {
|
||||
merged.AddPoint(l[i].p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// and future bridges mustn't cross our bridge, and it's tricky to get
|
||||
// things right if two bridges come from the same point
|
||||
avoidEdges->AddEdge(a, b);
|
||||
avoidPts->Add(&a);
|
||||
if(withbridge) {
|
||||
avoidEdges->AddEdge(a, b);
|
||||
avoidPts->Add(&a);
|
||||
}
|
||||
avoidPts->Add(&b);
|
||||
|
||||
l.Clear();
|
||||
|
@ -213,6 +221,63 @@ haveEdge:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SContour::IsEmptyTriangle(int ap, int bp, int cp, double scaledEPS) const {
|
||||
|
||||
STriangle tr = {};
|
||||
tr.a = l[ap].p;
|
||||
tr.b = l[bp].p;
|
||||
tr.c = l[cp].p;
|
||||
|
||||
// Accelerate with an axis-aligned bounding box test
|
||||
Vector maxv = tr.a, minv = tr.a;
|
||||
(tr.b).MakeMaxMin(&maxv, &minv);
|
||||
(tr.c).MakeMaxMin(&maxv, &minv);
|
||||
|
||||
Vector n = Vector::From(0, 0, -1);
|
||||
|
||||
int i;
|
||||
for(i = 0; i < l.n; i++) {
|
||||
if(i == ap || i == bp || i == cp) continue;
|
||||
|
||||
Vector p = l[i].p;
|
||||
if(p.OutsideAndNotOn(maxv, minv)) continue;
|
||||
|
||||
// A point on the edge of the triangle is considered to be inside,
|
||||
// and therefore makes it a non-ear; but a point on the vertex is
|
||||
// "outside", since that's necessary to make bridges work.
|
||||
if(p.EqualsExactly(tr.a)) continue;
|
||||
if(p.EqualsExactly(tr.b)) continue;
|
||||
if(p.EqualsExactly(tr.c)) continue;
|
||||
|
||||
if(tr.ContainsPointProjd(n, p)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Test if ray b->d passes through triangle a,b,c
|
||||
static bool RayIsInside(Vector a, Vector c, Vector b, Vector d) {
|
||||
// coincident edges are not considered to intersect the triangle
|
||||
if (d.Equals(a)) return false;
|
||||
if (d.Equals(c)) return false;
|
||||
// if d and c are on opposite sides of ba, we are ok
|
||||
// likewise if d and a are on opposite sides of bc
|
||||
Vector ba = a.Minus(b);
|
||||
Vector bc = c.Minus(b);
|
||||
Vector bd = d.Minus(b);
|
||||
|
||||
// perpendicular to (x,y) is (x,-y) so dot that with the two points. If they
|
||||
// have opposite signs their product will be negative. If bd and bc are on
|
||||
// opposite sides of ba the ray does not intersect. Likewise for bd,ba and bc.
|
||||
if ( (bd.x*(ba.y) + (bd.y * (-ba.x))) * ( bc.x*(ba.y) + (bc.y * (-ba.x))) < LENGTH_EPS)
|
||||
return false;
|
||||
if ( (bd.x*(bc.y) + (bd.y * (-bc.x))) * ( ba.x*(bc.y) + (ba.y * (-bc.x))) < LENGTH_EPS)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SContour::IsEar(int bp, double scaledEps) const {
|
||||
int ap = WRAP(bp-1, l.n),
|
||||
cp = WRAP(bp+1, l.n);
|
||||
|
@ -251,8 +316,21 @@ bool SContour::IsEar(int bp, double scaledEps) const {
|
|||
// and therefore makes it a non-ear; but a point on the vertex is
|
||||
// "outside", since that's necessary to make bridges work.
|
||||
if(p.EqualsExactly(tr.a)) continue;
|
||||
if(p.EqualsExactly(tr.b)) continue;
|
||||
if(p.EqualsExactly(tr.c)) continue;
|
||||
// points coincident with bp have to be allowed for bridges but edges
|
||||
// from that other point must not cross through our triangle.
|
||||
if(p.EqualsExactly(tr.b)) {
|
||||
int j = WRAP(i-1, l.n);
|
||||
int k = WRAP(i+1, l.n);
|
||||
Vector jp = l[j].p;
|
||||
Vector kp = l[k].p;
|
||||
|
||||
// two consecutive bridges (A,B,C) and later (C,B,A) are not an ear
|
||||
if (jp.Equals(tr.c) && kp.Equals(tr.a)) return false;
|
||||
// check both edges from the point in question
|
||||
if (!RayIsInside(tr.a, tr.c, p,jp) && !RayIsInside(tr.a, tr.c, p,kp))
|
||||
continue;
|
||||
}
|
||||
|
||||
if(tr.ContainsPointProjd(n, p)) {
|
||||
return false;
|
||||
|
@ -306,8 +384,76 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) {
|
|||
l[i].tag = 1;
|
||||
}
|
||||
}
|
||||
if( (l[0].p).Equals(l[l.n-1].p) ) {
|
||||
l[l.n-1].tag = 1;
|
||||
}
|
||||
l.RemoveTagged();
|
||||
|
||||
// Handle simple triangle fans all at once. This pass is optional.
|
||||
if(srf->degm == 1 && srf->degn == 1) {
|
||||
l.ClearTags();
|
||||
int j=0;
|
||||
int pstart = 0;
|
||||
double elen = -1.0;
|
||||
double oldspan = 0.0;
|
||||
for(i = 1; i < l.n; i++) {
|
||||
Vector ab = l[i].p.Minus(l[i-1].p);
|
||||
// first time just measure the segment
|
||||
if (elen < 0.0) {
|
||||
elen = ab.Dot(ab);
|
||||
oldspan = elen;
|
||||
j = 1;
|
||||
continue;
|
||||
}
|
||||
// check for consecutive segments of similar size which are also
|
||||
// ears and where the group forms a convex ear
|
||||
bool end = false;
|
||||
double ratio = ab.Dot(ab) / elen;
|
||||
if ((ratio < 0.25) || (ratio > 4.0)) end = true;
|
||||
|
||||
double slen = l[pstart].p.Minus(l[i].p).MagSquared();
|
||||
if (slen < oldspan) end = true;
|
||||
|
||||
if (!IsEar(i-1, scaledEps) ) end = true;
|
||||
// if ((j>0) && !IsEar(pstart, i-1, i, scaledEps)) end = true;
|
||||
if ((j>0) && !IsEmptyTriangle(pstart, i-1, i, scaledEps)) end = true;
|
||||
// the new segment is valid so add to the fan
|
||||
if (!end) {
|
||||
j++;
|
||||
oldspan = slen;
|
||||
}
|
||||
// we need to stop at the end of polygon but may still
|
||||
if (i == l.n-1) {
|
||||
end = true;
|
||||
}
|
||||
if (end) { // triangulate the fan and tag the verticies
|
||||
if (j > 3) {
|
||||
Vector center = l[pstart+1].p.Plus(l[pstart+j-1].p).ScaledBy(0.5);
|
||||
for (int x=0; x<j; x++) {
|
||||
STriangle tr = {};
|
||||
tr.a = center;
|
||||
tr.b = l[pstart+x].p;
|
||||
tr.c = l[pstart+x+1].p;
|
||||
m->AddTriangle(&tr);
|
||||
}
|
||||
for (int x=1; x<j; x++) {
|
||||
l[pstart+x].tag = 1;
|
||||
}
|
||||
STriangle tr = {};
|
||||
tr.a = center;
|
||||
tr.b = l[pstart+j].p;
|
||||
tr.c = l[pstart].p;
|
||||
m->AddTriangle(&tr);
|
||||
}
|
||||
pstart = i-1;
|
||||
elen = ab.Dot(ab);
|
||||
oldspan = elen;
|
||||
j = 1;
|
||||
}
|
||||
}
|
||||
l.RemoveTagged();
|
||||
} // end optional fan creation pass
|
||||
|
||||
bool toggle = false;
|
||||
while(l.n > 3) {
|
||||
int bestEar = -1;
|
||||
|
|
|
@ -375,6 +375,7 @@ void TextWindow::ScreenShowListOfStyles(int link, uint32_t v) {
|
|||
SS.TW.GoToScreen(Screen::LIST_OF_STYLES);
|
||||
}
|
||||
void TextWindow::ScreenShowStyleInfo(int link, uint32_t v) {
|
||||
GraphicsWindow::MenuEdit(Command::UNSELECT_ALL);
|
||||
SS.TW.GoToScreen(Screen::STYLE_INFO);
|
||||
SS.TW.shown.style.v = v;
|
||||
}
|
||||
|
|
|
@ -355,10 +355,17 @@ void System::WriteEquationsExceptFor(hConstraint hc, Group *g) {
|
|||
}
|
||||
|
||||
void System::FindWhichToRemoveToFixJacobian(Group *g, List<hConstraint> *bad, bool forceDofCheck) {
|
||||
auto time = GetMilliseconds();
|
||||
g->solved.timeout = false;
|
||||
int a;
|
||||
|
||||
for(a = 0; a < 2; a++) {
|
||||
for(auto &con : SK.constraint) {
|
||||
if((GetMilliseconds() - time) > g->solved.findToFixTimeout) {
|
||||
g->solved.timeout = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ConstraintBase *c = &con;
|
||||
if(c->group != g->h) continue;
|
||||
if((c->type == Constraint::Type::POINTS_COINCIDENT && a == 0) ||
|
||||
|
|
|
@ -44,6 +44,7 @@ void TextWindow::ShowHeader(bool withNav) {
|
|||
// to hide or show them, and to view them in detail. This is our home page.
|
||||
//-----------------------------------------------------------------------------
|
||||
void TextWindow::ScreenSelectGroup(int link, uint32_t v) {
|
||||
GraphicsWindow::MenuEdit(Command::UNSELECT_ALL);
|
||||
SS.TW.GoToScreen(Screen::GROUP_INFO);
|
||||
SS.TW.shown.group.v = v;
|
||||
}
|
||||
|
@ -167,12 +168,16 @@ void TextWindow::ShowListOfGroups() {
|
|||
// The screen that shows information about a specific group, and allows the
|
||||
// user to edit various things about it.
|
||||
//-----------------------------------------------------------------------------
|
||||
void TextWindow::ScreenHoverConstraint(int link, uint32_t v) {
|
||||
if(!SS.GW.showConstraints) return;
|
||||
|
||||
hConstraint hc = { v };
|
||||
void TextWindow::ScreenHoverGroupWorkplane(int link, uint32_t v) {
|
||||
SS.GW.hover.Clear();
|
||||
SS.GW.hover.constraint = hc;
|
||||
hGroup hg = { v };
|
||||
SS.GW.hover.entity = hg.entity(0);
|
||||
SS.GW.hover.emphasized = true;
|
||||
}
|
||||
void TextWindow::ScreenHoverEntity(int link, uint32_t v) {
|
||||
SS.GW.hover.Clear();
|
||||
hEntity he = { v };
|
||||
SS.GW.hover.entity = he;
|
||||
SS.GW.hover.emphasized = true;
|
||||
}
|
||||
void TextWindow::ScreenHoverRequest(int link, uint32_t v) {
|
||||
|
@ -181,10 +186,19 @@ void TextWindow::ScreenHoverRequest(int link, uint32_t v) {
|
|||
SS.GW.hover.entity = hr.entity(0);
|
||||
SS.GW.hover.emphasized = true;
|
||||
}
|
||||
void TextWindow::ScreenSelectConstraint(int link, uint32_t v) {
|
||||
void TextWindow::ScreenHoverConstraint(int link, uint32_t v) {
|
||||
if(!SS.GW.showConstraints) return;
|
||||
|
||||
hConstraint hc = { v };
|
||||
SS.GW.hover.Clear();
|
||||
SS.GW.hover.constraint = hc;
|
||||
SS.GW.hover.emphasized = true;
|
||||
}
|
||||
void TextWindow::ScreenSelectEntity(int link, uint32_t v) {
|
||||
SS.GW.ClearSelection();
|
||||
GraphicsWindow::Selection sel = {};
|
||||
sel.constraint.v = v;
|
||||
hEntity he = { v };
|
||||
sel.entity = he;
|
||||
SS.GW.selection.Add(&sel);
|
||||
}
|
||||
void TextWindow::ScreenSelectRequest(int link, uint32_t v) {
|
||||
|
@ -194,6 +208,12 @@ void TextWindow::ScreenSelectRequest(int link, uint32_t v) {
|
|||
sel.entity = hr.entity(0);
|
||||
SS.GW.selection.Add(&sel);
|
||||
}
|
||||
void TextWindow::ScreenSelectConstraint(int link, uint32_t v) {
|
||||
SS.GW.ClearSelection();
|
||||
GraphicsWindow::Selection sel = {};
|
||||
sel.constraint.v = v;
|
||||
SS.GW.selection.Add(&sel);
|
||||
}
|
||||
|
||||
void TextWindow::ScreenChangeGroupOption(int link, uint32_t v) {
|
||||
SS.UndoRemember();
|
||||
|
@ -558,6 +578,11 @@ void TextWindow::ShowGroupSolveInfo() {
|
|||
c->DescriptionString().c_str());
|
||||
}
|
||||
|
||||
if(g->solved.timeout) {
|
||||
Printf(true, "%FxSome items in list have been ommitted%Fd");
|
||||
Printf(false, "%Fxbecause the operation timed out.%Fd");
|
||||
}
|
||||
|
||||
Printf(true, "It may be possible to fix the problem ");
|
||||
Printf(false, "by selecting Edit -> Undo.");
|
||||
|
||||
|
|
9
src/ui.h
9
src/ui.h
|
@ -316,6 +316,7 @@ public:
|
|||
G_CODE_PLUNGE_FEED = 115,
|
||||
AUTOSAVE_INTERVAL = 116,
|
||||
LIGHT_AMBIENT = 117,
|
||||
FIND_CONSTRAINT_TIMEOUT = 118,
|
||||
// For TTF text
|
||||
TTF_TEXT = 300,
|
||||
// For the step dimension screen
|
||||
|
@ -397,6 +398,7 @@ public:
|
|||
static void ScreenUnselectAll(int link, uint32_t v);
|
||||
|
||||
// when we're describing a constraint
|
||||
static void ScreenConstraintToggleReference(int link, uint32_t v);
|
||||
static void ScreenConstraintShowAsRadius(int link, uint32_t v);
|
||||
|
||||
// and the rest from the stuff in textscreens.cpp
|
||||
|
@ -407,9 +409,12 @@ public:
|
|||
static void ScreenShowGroupsSpecial(int link, uint32_t v);
|
||||
static void ScreenDeleteGroup(int link, uint32_t v);
|
||||
|
||||
static void ScreenHoverConstraint(int link, uint32_t v);
|
||||
static void ScreenHoverGroupWorkplane(int link, uint32_t v);
|
||||
static void ScreenHoverRequest(int link, uint32_t v);
|
||||
static void ScreenHoverEntity(int link, uint32_t v);
|
||||
static void ScreenHoverConstraint(int link, uint32_t v);
|
||||
static void ScreenSelectRequest(int link, uint32_t v);
|
||||
static void ScreenSelectEntity(int link, uint32_t v);
|
||||
static void ScreenSelectConstraint(int link, uint32_t v);
|
||||
|
||||
static void ScreenChangeGroupOption(int link, uint32_t v);
|
||||
|
@ -430,6 +435,7 @@ public:
|
|||
static void ScreenGoToWebsite(int link, uint32_t v);
|
||||
|
||||
static void ScreenChangeFixExportColors(int link, uint32_t v);
|
||||
static void ScreenChangeExportBackgroundColor(int link, uint32_t v);
|
||||
static void ScreenChangeBackFaces(int link, uint32_t v);
|
||||
static void ScreenChangeShowContourAreas(int link, uint32_t v);
|
||||
static void ScreenChangeCheckClosedContour(int link, uint32_t v);
|
||||
|
@ -483,6 +489,7 @@ public:
|
|||
static void ScreenChangeExportOffset(int link, uint32_t v);
|
||||
static void ScreenChangeGCodeParameter(int link, uint32_t v);
|
||||
static void ScreenChangeAutosaveInterval(int link, uint32_t v);
|
||||
static void ScreenChangeFindConstraintTimeout(int link, uint32_t v);
|
||||
static void ScreenChangeStyleName(int link, uint32_t v);
|
||||
static void ScreenChangeStyleMetric(int link, uint32_t v);
|
||||
static void ScreenChangeStyleTextAngle(int link, uint32_t v);
|
||||
|
|
Loading…
Reference in New Issue