Merge pull request #7 from solvespace/master

Merge from origin.
pull/493/head
Yuan 2020-10-05 11:42:48 +08:00 committed by GitHub
commit a02a157920
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 5334 additions and 3476 deletions

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -175,6 +175,7 @@ set(solvespace_core_SOURCES
group.cpp
groupmesh.cpp
importdxf.cpp
importidf.cpp
mesh.cpp
modify.cpp
mouse.cpp

View File

@ -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;
}

View File

@ -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)));
}

View File

@ -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++;
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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));

View File

@ -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);

View File

@ -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) {
}

View File

@ -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: {

View File

@ -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),

View File

@ -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;
}

View File

@ -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;

View File

@ -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>

View File

@ -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);
});
}

500
src/importidf.cpp Normal file
View File

@ -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;
}
}

View File

@ -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, &parallelInfo);
if (info.count != parallelInfo.count) {
sel->AddEdge(a, b, auxA);
if(leaky) *leaky = true;
}
}
if(info.intersectsMesh) {
sel->AddEdge(a, b, auxA);

View File

@ -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);
};

View File

@ -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" } },
};

View File

@ -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

View File

@ -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 {

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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, &parallel);
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], &parallel);
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)");
}

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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) ||

View File

@ -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.");

View File

@ -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);