GLOffscreen::GLOffscreen() : _pixels(NULL), _pixels_inv(NULL), _width(0), _height(0) { #ifndef __APPLE__ ssassert(glewInit() == GLEW_OK, "Unexpected GLEW failure"); #endif ssassert(GL_EXT_framebuffer_object, "Expected an available FBO extension"); glGenFramebuffersEXT(1, &_framebuffer); glGenRenderbuffersEXT(1, &_color_renderbuffer); glGenRenderbuffersEXT(1, &_depth_renderbuffer); }
void ConstraintBase::ModifyToSatisfy() { if(type == Type::ANGLE) { Vector a = SK.GetEntity(entityA)->VectorGetNum(); Vector b = SK.GetEntity(entityB)->VectorGetNum(); if(other) a = a.ScaledBy(-1); if(workplane.v != EntityBase::FREE_IN_3D.v) { a = a.ProjectVectorInto(workplane); b = b.ProjectVectorInto(workplane); } double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude()); valA = acos(c)*180/PI; } else { // We'll fix these ones up by looking at their symbolic equation; // that means no extra work. IdList<Equation,hEquation> l = {}; // Generate the equations even if this is a reference dimension GenerateReal(&l); ssassert(l.n == 1, "Expected constraint to generate a single equation"); // These equations are written in the form f(...) - d = 0, where // d is the value of the valA. valA += (l.elem[0].e)->Eval(); l.Clear(); } }
void SolveSpaceUI::MenuHelp(Command id) { switch(id) { case Command::WEBSITE: OpenWebsite("http://solvespace.com/helpmenu"); break; case Command::ABOUT: Message( "This is SolveSpace version " PACKAGE_VERSION ".\n" "\n" "For more information, see http://solvespace.com/\n" "\n" "SolveSpace is free software: you are free to modify\n" "and/or redistribute it under the terms of the GNU\n" "General Public License (GPL) version 3 or later.\n" "\n" "There is NO WARRANTY, to the extent permitted by\n" "law. For details, visit http://gnu.org/licenses/\n" "\n" "© 2008-2016 Jonathan Westhues and other authors.\n" ); break; default: ssassert(false, "Unexpected menu ID"); } }
Expr *ConstraintBase::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) { EntityBase *pa = SK.GetEntity(hpa); EntityBase *pb = SK.GetEntity(hpb); ssassert(pa->IsPoint() && pb->IsPoint(), "Expected two points to measure projected distance between"); if(wrkpl.v == EntityBase::FREE_IN_3D.v) { // This is true distance ExprVector ea, eb, eab; ea = pa->PointGetExprs(); eb = pb->PointGetExprs(); eab = ea.Minus(eb); return eab.Magnitude(); } else { // This is projected distance, in the given workplane. Expr *au, *av, *bu, *bv; pa->PointGetExprsInWorkplane(wrkpl, &au, &av); pb->PointGetExprsInWorkplane(wrkpl, &bu, &bv); Expr *du = au->Minus(bu); Expr *dv = av->Minus(bv); return ((du->Square())->Plus(dv->Square()))->Sqrt(); } }
Expr *ConstraintBase::VectorsParallel(int eq, ExprVector a, ExprVector b) { ExprVector r = a.Cross(b); // Hairy ball theorem screws me here. There's no clean solution that I // know, so let's pivot on the initial numerical guess. Our caller // has ensured that if one of our input vectors is already known (e.g. // it's from a previous group), then that one's in a; so that one's // not going to move, and we should pivot on that one. double mx = fabs((a.x)->Eval()); double my = fabs((a.y)->Eval()); double mz = fabs((a.z)->Eval()); // The basis vector in which the vectors have the LEAST energy is the // one that we should look at most (e.g. if both vectors lie in the xy // plane, then the z component of the cross product is most important). // So find the strongest component of a and b, and that's the component // of the cross product to ignore. Expr *e0, *e1; if(mx > my && mx > mz) { e0 = r.y; e1 = r.z; } else if(my > mz) { e0 = r.z; e1 = r.x; } else { e0 = r.x; e1 = r.y; } if(eq == 0) return e0; if(eq == 1) return e1; ssassert(false, "Unexpected index of equation"); }
int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) { ssassert(loop->l.n >= 1, "Expected at least one loop"); List<int> listOfTrims = {}; SBezier *sb = &(loop->l.elem[loop->l.n - 1]); // Generate "exactly closed" contours, with the same vertex id for the // finish of a previous edge and the start of the next one. So we need // the finish of the last Bezier in the loop before we start our process. fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", id, CO(sb->Finish())); fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id); int lastFinish = id + 1, prevFinish = lastFinish; id += 2; for(sb = loop->l.First(); sb; sb = loop->l.NextAfter(sb)) { int curveId = ExportCurve(sb); int thisFinish; if(loop->l.NextAfter(sb) != NULL) { fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", id, CO(sb->Finish())); fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id); thisFinish = id + 1; id += 2; } else { thisFinish = lastFinish; } fprintf(f, "#%d=EDGE_CURVE('',#%d,#%d,#%d,%s);\n", id, prevFinish, thisFinish, curveId, ".T."); fprintf(f, "#%d=ORIENTED_EDGE('',*,*,#%d,.T.);\n", id+1, id); int oe = id+1; listOfTrims.Add(&oe); id += 2; prevFinish = thisFinish; } fprintf(f, "#%d=EDGE_LOOP('',(", id); int *oe; for(oe = listOfTrims.First(); oe; oe = listOfTrims.NextAfter(oe)) { fprintf(f, "#%d", *oe); if(listOfTrims.NextAfter(oe) != NULL) fprintf(f, ","); } fprintf(f, "));\n"); int fb = id + 1; fprintf(f, "#%d=%s('',#%d,.T.);\n", fb, inner ? "FACE_BOUND" : "FACE_OUTER_BOUND", id); id += 2; listOfTrims.Clear(); return fb; }
bool SolveSpaceUI::OkayToStartNewFile() { if(!unsaved) return true; switch(SaveFileYesNoCancel()) { case DIALOG_YES: return GetFilenameAndSave(/*saveAs=*/false); case DIALOG_NO: return true; case DIALOG_CANCEL: return false; } ssassert(false, "Unexpected dialog choice"); }
bool SolveSpaceUI::PruneRequests(hGroup hg) { int i; for(i = 0; i < SK.entity.n; i++) { Entity *e = &(SK.entity.elem[i]); if(e->group.v != hg.v) continue; if(EntityExists(e->workplane)) continue; ssassert(e->h.isFromRequest(), "Only explicitly created entities can be pruned"); (deleted.requests)++; SK.request.RemoveById(e->h.request()); return true; } return false; }
void TextWindow::ScreenChangeStyleColor(int link, uint32_t v) { hStyle hs = { v }; Style *s = Style::Get(hs); // Same function used for stroke and fill colors Edit em; RgbaColor rgb; if(link == 's') { em = Edit::STYLE_COLOR; rgb = s->color; } else if(link == 'f') { em = Edit::STYLE_FILL_COLOR; rgb = s->fillColor; } else ssassert(false, "Unexpected link"); SS.TW.ShowEditControlWithColorPicker(13, rgb); SS.TW.edit.style = hs; SS.TW.edit.meaning = em; }
void SolveSpaceUI::PopOntoCurrentFrom(UndoStack *uk) { int i; ssassert(uk->cnt > 0, "Cannot pop from empty undo stack"); (uk->cnt)--; uk->write = WRAP(uk->write - 1, MAX_UNDO); UndoState *ut = &(uk->d[uk->write]); // Free everything in the main copy of the program before replacing it for(i = 0; i < SK.groupOrder.n; i++) { Group *g = SK.GetGroup(SK.groupOrder.elem[i]); g->Clear(); } SK.group.Clear(); SK.groupOrder.Clear(); SK.request.Clear(); SK.constraint.Clear(); SK.param.Clear(); SK.style.Clear(); // And then do a shallow copy of the state from the undo list ut->group.MoveSelfInto(&(SK.group)); for(i = 0; i < ut->groupOrder.n; i++) SK.groupOrder.Add(&ut->groupOrder.elem[i]); ut->request.MoveSelfInto(&(SK.request)); ut->constraint.MoveSelfInto(&(SK.constraint)); ut->param.MoveSelfInto(&(SK.param)); ut->style.MoveSelfInto(&(SK.style)); SS.GW.activeGroup = ut->activeGroup; // No need to free it, since a shallow copy was made above *ut = {}; // And reset the state everywhere else in the program, since the // sketch just changed a lot. SS.GW.ClearSuper(); SS.TW.ClearSuper(); SS.ReloadAllImported(); SS.GenerateAll(SolveSpaceUI::Generate::ALL); SS.ScheduleShowTW(); // Activate the group that was active before. Group *activeGroup = SK.GetGroup(SS.GW.activeGroup); activeGroup->Activate(); }
void TextWindow::ScreenChangeStyleMetric(int link, uint32_t v) { hStyle hs = { v }; Style *s = Style::Get(hs); double val; Style::UnitsAs units; Edit meaning; int col; switch(link) { case 't': val = s->textHeight; units = s->textHeightAs; col = 10; meaning = Edit::STYLE_TEXT_HEIGHT; break; case 's': val = s->stippleScale; units = s->widthAs; col = 17; meaning = Edit::STYLE_STIPPLE_PERIOD; break; case 'w': case 'W': val = s->width; units = s->widthAs; col = 9; meaning = Edit::STYLE_WIDTH; break; default: ssassert(false, "Unexpected link"); } std::string edit_value; if(units == Style::UnitsAs::PIXELS) { edit_value = ssprintf("%.2f", val); } else { edit_value = SS.MmToString(val); } SS.TW.ShowEditControl(col, edit_value); SS.TW.edit.style = hs; SS.TW.edit.meaning = meaning; }
void *MemAlloc(size_t n) { void *p = malloc(n); ssassert(p != NULL, "Cannot allocate memory"); return p; }
void TtfFont::PlotString(const std::string &str, SBezierList *sbl, Vector origin, Vector u, Vector v) { ssassert(fontFace != NULL, "Expected font face to be loaded"); FT_Pos dx = 0; for(char32_t chr : ReadUTF8(str)) { uint32_t gid = FT_Get_Char_Index(fontFace, chr); if (gid == 0) { dbp("freetype: CID-to-GID mapping for CID 0x%04x failed: %s; using CID as GID", chr, ft_error_string(gid)); } FT_F26Dot6 scale = fontFace->units_per_EM; if(int fterr = FT_Set_Char_Size(fontFace, scale, scale, 72, 72)) { dbp("freetype: cannot set character size: %s", ft_error_string(fterr)); return; } /* * Stupid hacks: * - if we want fake-bold, use FT_Outline_Embolden(). This actually looks * quite good. * - if we want fake-italic, apply a shear transform [1 s s 1 0 0] here using * FT_Set_Transform. This looks decent at small font sizes and bad at larger * ones, antialiasing mitigates this considerably though. */ if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) { dbp("freetype: cannot load glyph (gid %d): %s", gid, ft_error_string(fterr)); return; } /* A point that has x = xMin should be plotted at (dx0 + lsb); fix up * our x-position so that the curve-generating code will put stuff * at the right place. * * There's no point in getting the glyph BBox here - not only can it be * needlessly slow sometimes, but because we're about to render a single glyph, * what we want actually *is* the CBox. * * This is notwithstanding that this makes extremely little sense, this * looks like a workaround for either mishandling the start glyph on a line, * or as a really hacky pseudo-track-kerning (in which case it works better than * one would expect! especially since most fonts don't set track kerning). */ FT_BBox cbox; FT_Outline_Get_CBox(&fontFace->glyph->outline, &cbox); FT_Pos bx = dx - cbox.xMin; // Yes, this is what FreeType calls left-side bearing. // Then interchangeably uses that with "left-side bearing". Sigh. bx += fontFace->glyph->metrics.horiBearingX; OutlineData data = {}; data.origin = origin; data.u = u; data.v = v; data.beziers = sbl; data.factor = 1.0f/(float)scale; data.bx = bx; if(int fterr = FT_Outline_Decompose(&fontFace->glyph->outline, &outline_funcs, &data)) { dbp("freetype: bezier decomposition failed (gid %d): %s", gid, ft_error_string(fterr)); } // And we're done, so advance our position by the requested advance // width, plus the user-requested extra advance. dx += fontFace->glyph->advance.x; } }
FILE *ssfopen(const std::string &filename, const char *mode) { ssassert(filename.length() == strlen(filename.c_str()), "Unexpected null byte in middle of a path"); return fopen(filename.c_str(), mode); }
void SolveSpaceUI::MenuAnalyze(Command id) { SS.GW.GroupSelection(); #define gs (SS.GW.gs) switch(id) { case Command::STEP_DIM: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && !c->reference) { SS.TW.shown.dimFinish = c->valA; SS.TW.shown.dimSteps = 10; SS.TW.shown.dimIsDistance = (c->type != Constraint::Type::ANGLE) && (c->type != Constraint::Type::LENGTH_RATIO) && (c->type != Constraint::Type::LENGTH_DIFFERENCE); SS.TW.shown.constraint = c->h; SS.TW.shown.screen = TextWindow::Screen::STEP_DIMENSION; // The step params are specified in the text window, // so force that to be shown. SS.GW.ForceTextWindowShown(); SS.ScheduleShowTW(); SS.GW.ClearSelection(); } else { Error("Constraint must have a label, and must not be " "a reference dimension."); } } else { Error("Bad selection for step dimension; select a constraint."); } break; case Command::NAKED_EDGES: { SS.nakedEdges.Clear(); Group *g = SK.GetGroup(SS.GW.activeGroup); SMesh *m = &(g->displayMesh); SKdNode *root = SKdNode::From(m); bool inters, leaks; root->MakeCertainEdgesInto(&(SS.nakedEdges), EdgeKind::NAKED_OR_SELF_INTER, /*coplanarIsInter=*/true, &inters, &leaks); InvalidateGraphics(); const char *intersMsg = inters ? "The mesh is self-intersecting (NOT okay, invalid)." : "The mesh is not self-intersecting (okay, valid)."; const char *leaksMsg = leaks ? "The mesh has naked edges (NOT okay, invalid)." : "The mesh is watertight (okay, valid)."; std::string cntMsg = ssprintf("\n\nThe model contains %d triangles, from " "%d surfaces.", g->displayMesh.l.n, g->runningShell.surface.n); if(SS.nakedEdges.l.n == 0) { Message("%s\n\n%s\n\nZero problematic edges, good.%s", intersMsg, leaksMsg, cntMsg.c_str()); } else { Error("%s\n\n%s\n\n%d problematic edges, bad.%s", intersMsg, leaksMsg, SS.nakedEdges.l.n, cntMsg.c_str()); } break; } case Command::INTERFERENCE: { SS.nakedEdges.Clear(); SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); SKdNode *root = SKdNode::From(m); bool inters, leaks; root->MakeCertainEdgesInto(&(SS.nakedEdges), EdgeKind::SELF_INTER, /*coplanarIsInter=*/false, &inters, &leaks); InvalidateGraphics(); if(inters) { Error("%d edges interfere with other triangles, bad.", SS.nakedEdges.l.n); } else { Message("The assembly does not interfere, good."); } break; } case Command::VOLUME: { SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); double vol = 0; int i; for(i = 0; i < m->l.n; i++) { STriangle tr = m->l.elem[i]; // Translate to place vertex A at (x, y, 0) Vector trans = Vector::From(tr.a.x, tr.a.y, 0); tr.a = (tr.a).Minus(trans); tr.b = (tr.b).Minus(trans); tr.c = (tr.c).Minus(trans); // Rotate to place vertex B on the y-axis. Depending on // whether the triangle is CW or CCW, C is either to the // right or to the left of the y-axis. This handles the // sign of our normal. Vector u = Vector::From(-tr.b.y, tr.b.x, 0); u = u.WithMagnitude(1); Vector v = Vector::From(tr.b.x, tr.b.y, 0); v = v.WithMagnitude(1); Vector n = Vector::From(0, 0, 1); tr.a = (tr.a).DotInToCsys(u, v, n); tr.b = (tr.b).DotInToCsys(u, v, n); tr.c = (tr.c).DotInToCsys(u, v, n); n = tr.Normal().WithMagnitude(1); // Triangles on edge don't contribute if(fabs(n.z) < LENGTH_EPS) continue; // The plane has equation p dot n = a dot n double d = (tr.a).Dot(n); // nx*x + ny*y + nz*z = d // nz*z = d - nx*x - ny*y double A = -n.x/n.z, B = -n.y/n.z, C = d/n.z; double mac = tr.c.y/tr.c.x, mbc = (tr.c.y - tr.b.y)/tr.c.x; double xc = tr.c.x, yb = tr.b.y; // I asked Maple for // int(int(A*x + B*y +C, y=mac*x..(mbc*x + yb)), x=0..xc); double integral = (1.0/3)*( A*(mbc-mac)+ (1.0/2)*B*(mbc*mbc-mac*mac) )*(xc*xc*xc)+ (1.0/2)*(A*yb+B*yb*mbc+C*(mbc-mac))*xc*xc+ C*yb*xc+ (1.0/2)*B*yb*yb*xc; vol += integral; } std::string msg = ssprintf("The volume of the solid model is:\n\n"" %.3f %s^3", vol / pow(SS.MmPerUnit(), 3), SS.UnitName()); if(SS.viewUnits == Unit::MM) { msg += ssprintf("\n %.2f mL", vol/(10*10*10)); } msg += "\n\nCurved surfaces have been approximated as triangles.\n" "This introduces error, typically of around 1%."; Message("%s", msg.c_str()); break; } case Command::AREA: { Group *g = SK.GetGroup(SS.GW.activeGroup); if(g->polyError.how != PolyError::GOOD) { Error("This group does not contain a correctly-formed " "2d closed area. It is open, not coplanar, or self-" "intersecting."); break; } SEdgeList sel = {}; g->polyLoops.MakeEdgesInto(&sel); SPolygon sp = {}; sel.AssemblePolygon(&sp, NULL, /*keepDir=*/true); sp.normal = sp.ComputeNormal(); sp.FixContourDirections(); double area = sp.SignedArea(); double scale = SS.MmPerUnit(); Message("The area of the region sketched in this group is:\n\n" " %.3f %s^2\n\n" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%.", area / (scale*scale), SS.UnitName()); sel.Clear(); sp.Clear(); break; } case Command::SHOW_DOF: // This works like a normal solve, except that it calculates // which variables are free/bound at the same time. SS.GenerateAll(SolveSpaceUI::Generate::ALL, true); break; case Command::TRACE_PT: if(gs.points == 1 && gs.n == 1) { SS.traced.point = gs.point[0]; SS.GW.ClearSelection(); } else { Error("Bad selection for trace; select a single point."); } break; case Command::STOP_TRACING: { std::string exportFile; if(GetSaveFile(&exportFile, "", CsvFileFilter)) { FILE *f = ssfopen(exportFile, "wb"); if(f) { int i; SContour *sc = &(SS.traced.path); for(i = 0; i < sc->l.n; i++) { Vector p = sc->l.elem[i].p; double s = SS.exportScale; fprintf(f, "%.10f, %.10f, %.10f\r\n", p.x/s, p.y/s, p.z/s); } fclose(f); } else { Error("Couldn't write to '%s'", exportFile.c_str()); } } // Clear the trace, and stop tracing SS.traced.point = Entity::NO_ENTITY; SS.traced.path.l.Clear(); InvalidateGraphics(); break; } default: ssassert(false, "Unexpected menu ID"); } }
void SolveSpaceUI::MenuFile(Command id) { if((uint32_t)id >= (uint32_t)Command::RECENT_OPEN && (uint32_t)id < ((uint32_t)Command::RECENT_OPEN+MAX_RECENT)) { if(!SS.OkayToStartNewFile()) return; std::string newFile = RecentFile[(uint32_t)id - (uint32_t)Command::RECENT_OPEN]; SS.OpenFile(newFile); return; } switch(id) { case Command::NEW: if(!SS.OkayToStartNewFile()) break; SS.saveFile = ""; SS.NewFile(); SS.AfterNewFile(); break; case Command::OPEN: { if(!SS.OkayToStartNewFile()) break; std::string newFile; if(GetOpenFile(&newFile, "", SlvsFileFilter)) { SS.OpenFile(newFile); } break; } case Command::SAVE: SS.GetFilenameAndSave(/*saveAs=*/false); break; case Command::SAVE_AS: SS.GetFilenameAndSave(/*saveAs=*/true); break; case Command::EXPORT_PNG: { std::string exportFile; if(!GetSaveFile(&exportFile, "", PngFileFilter)) break; SS.ExportAsPngTo(exportFile); break; } case Command::EXPORT_VIEW: { std::string exportFile; if(!GetSaveFile(&exportFile, CnfThawString("", "ViewExportFormat"), VectorFileFilter)) break; CnfFreezeString(Extension(exportFile), "ViewExportFormat"); // If the user is exporting something where it would be // inappropriate to include the constraints, then warn. if(SS.GW.showConstraints && (FilenameHasExtension(exportFile, ".txt") || fabs(SS.exportOffset) > LENGTH_EPS)) { Message("Constraints are currently shown, and will be exported " "in the toolpath. This is probably not what you want; " "hide them by clicking the link at the top of the " "text window."); } SS.ExportViewOrWireframeTo(exportFile, /*exportWireframe*/false); break; } case Command::EXPORT_WIREFRAME: { std::string exportFile; if(!GetSaveFile(&exportFile, CnfThawString("", "WireframeExportFormat"), Vector3dFileFilter)) break; CnfFreezeString(Extension(exportFile), "WireframeExportFormat"); SS.ExportViewOrWireframeTo(exportFile, /*exportWireframe*/true); break; } case Command::EXPORT_SECTION: { std::string exportFile; if(!GetSaveFile(&exportFile, CnfThawString("", "SectionExportFormat"), VectorFileFilter)) break; CnfFreezeString(Extension(exportFile), "SectionExportFormat"); SS.ExportSectionTo(exportFile); break; } case Command::EXPORT_MESH: { std::string exportFile; if(!GetSaveFile(&exportFile, CnfThawString("", "MeshExportFormat"), MeshFileFilter)) break; CnfFreezeString(Extension(exportFile), "MeshExportFormat"); SS.ExportMeshTo(exportFile); break; } case Command::EXPORT_SURFACES: { std::string exportFile; if(!GetSaveFile(&exportFile, CnfThawString("", "SurfacesExportFormat"), SurfaceFileFilter)) break; CnfFreezeString(Extension(exportFile), "SurfacesExportFormat"); StepFileWriter sfw = {}; sfw.ExportSurfacesTo(exportFile); break; } case Command::IMPORT: { std::string importFile; if(!GetOpenFile(&importFile, CnfThawString("", "ImportFormat"), ImportableFileFilter)) break; CnfFreezeString(Extension(importFile), "ImportFormat"); if(Extension(importFile) == "dxf") { ImportDxf(importFile); } else if(Extension(importFile) == "dwg") { ImportDwg(importFile); } else ssassert(false, "Unexpected extension of file to import"); SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE); SS.ScheduleShowTW(); break; } case Command::EXIT: if(!SS.OkayToStartNewFile()) break; SS.Exit(); break; default: ssassert(false, "Unexpected menu ID"); } SS.UpdateWindowTitle(); }
void ssremove(const std::string &filename) { ssassert(filename.length() == strlen(filename.c_str()), "Unexpected null byte in middle of a path"); remove(filename.c_str()); }
void TextWindow::ShowStyleInfo() { Printf(true, "%Fl%f%Ll(back to list of styles)%E", &ScreenShowListOfStyles); Style *s = Style::Get(shown.style); if(s->h.v < Style::FIRST_CUSTOM) { Printf(true, "%FtSTYLE %E%s ", s->DescriptionString().c_str()); } else { Printf(true, "%FtSTYLE %E%s " "[%Fl%Ll%D%frename%E/%Fl%Ll%D%fdel%E]", s->DescriptionString().c_str(), s->h.v, &ScreenChangeStyleName, s->h.v, &ScreenDeleteStyle); } Printf(true, "%Ft line stroke style%E"); Printf(false, "%Ba %Ftcolor %E%Bz %Ba (%@, %@, %@) %D%f%Ls%Fl[change]%E", &s->color, s->color.redF(), s->color.greenF(), s->color.blueF(), s->h.v, ScreenChangeStyleColor); // The line width, and its units if(s->widthAs == Style::UnitsAs::PIXELS) { Printf(false, " %Ftwidth%E %@ %D%f%Lp%Fl[change]%E", s->width, s->h.v, &ScreenChangeStyleMetric, (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W'); } else { Printf(false, " %Ftwidth%E %s %D%f%Lp%Fl[change]%E", SS.MmToString(s->width).c_str(), s->h.v, &ScreenChangeStyleMetric, (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W'); } if(s->widthAs == Style::UnitsAs::PIXELS) { Printf(false, "%Ba %Ftstipple width%E %@ %D%f%Lp%Fl[change]%E", s->stippleScale, s->h.v, &ScreenChangeStyleMetric, 's'); } else { Printf(false, "%Ba %Ftstipple width%E %s %D%f%Lp%Fl[change]%E", SS.MmToString(s->stippleScale).c_str(), s->h.v, &ScreenChangeStyleMetric, 's'); } bool widthpx = (s->widthAs == Style::UnitsAs::PIXELS); if(s->h.v < Style::FIRST_CUSTOM) { Printf(false," %Ftin units of %Fdpixels%E"); } else { Printf(false,"%Ba %Ftin units of %Fd" "%D%f%LW%s pixels%E " "%D%f%Lw%s %s", s->h.v, &ScreenChangeStyleYesNo, widthpx ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, !widthpx ? RADIO_TRUE : RADIO_FALSE, SS.UnitName()); } Printf(false,"%Ba %Ftstipple type:%E"); const size_t patternCount = (size_t)StipplePattern::LAST + 1; const char *patternsSource[patternCount] = { "___________", "- - - - ", "- - - - - -", "__ __ __ __", "-.-.-.-.-.-", "..-..-..-..", "...........", "~~~~~~~~~~~", "__~__~__~__" }; std::string patterns[patternCount]; for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) { const char *str = patternsSource[i]; do { switch(*str) { case ' ': patterns[i] += " "; break; case '.': patterns[i] += "\xEE\x80\x84"; break; case '_': patterns[i] += "\xEE\x80\x85"; break; case '-': patterns[i] += "\xEE\x80\x86"; break; case '~': patterns[i] += "\xEE\x80\x87"; break; default: ssassert(false, "Unexpected stipple pattern element"); } } while(*(++str)); } for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) { const char *radio = s->stippleType == (StipplePattern)i ? RADIO_TRUE : RADIO_FALSE; Printf(false, "%Bp %D%f%Lp%s %s%E", (i % 2 == 0) ? 'd' : 'a', s->h.v, &ScreenChangeStylePatternType, i + 1, radio, patterns[i].c_str()); } if(s->h.v >= Style::FIRST_CUSTOM) { // The fill color, and whether contours are filled Printf(false, ""); Printf(false, "%Ft contour fill style%E"); Printf(false, "%Ba %Ftcolor %E%Bz %Ba (%@, %@, %@) %D%f%Lf%Fl[change]%E", &s->fillColor, s->fillColor.redF(), s->fillColor.greenF(), s->fillColor.blueF(), s->h.v, ScreenChangeStyleColor); Printf(false, "%Bd %D%f%Lf%s contours are filled%E", s->h.v, &ScreenChangeStyleYesNo, s->filled ? CHECK_TRUE : CHECK_FALSE); } // The text height, and its units Printf(false, ""); Printf(false, "%Ft text style%E"); if(s->textHeightAs == Style::UnitsAs::PIXELS) { Printf(false, "%Ba %Ftheight %E%@ %D%f%Lt%Fl%s%E", s->textHeight, s->h.v, &ScreenChangeStyleMetric, "[change]"); } else { Printf(false, "%Ba %Ftheight %E%s %D%f%Lt%Fl%s%E", SS.MmToString(s->textHeight).c_str(), s->h.v, &ScreenChangeStyleMetric, "[change]"); } bool textHeightpx = (s->textHeightAs == Style::UnitsAs::PIXELS); if(s->h.v < Style::FIRST_CUSTOM) { Printf(false,"%Bd %Ftin units of %Fdpixels"); } else { Printf(false,"%Bd %Ftin units of %Fd" "%D%f%LG%s pixels%E " "%D%f%Lg%s %s", s->h.v, &ScreenChangeStyleYesNo, textHeightpx ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, !textHeightpx ? RADIO_TRUE : RADIO_FALSE, SS.UnitName()); } if(s->h.v >= Style::FIRST_CUSTOM) { Printf(false, "%Ba %Ftangle %E%@ %D%f%Ll%Fl[change]%E", s->textAngle, s->h.v, &ScreenChangeStyleTextAngle); Printf(false, ""); Printf(false, "%Ft text comment alignment%E"); bool neither; neither = !((uint32_t)s->textOrigin & ((uint32_t)Style::TextOrigin::LEFT | (uint32_t)Style::TextOrigin::RIGHT)); Printf(false, "%Ba " "%D%f%LL%s left%E " "%D%f%LH%s center%E " "%D%f%LR%s right%E ", s->h.v, &ScreenChangeStyleYesNo, ((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::LEFT) ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, neither ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, ((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::RIGHT) ? RADIO_TRUE : RADIO_FALSE); neither = !((uint32_t)s->textOrigin & ((uint32_t)Style::TextOrigin::BOT | (uint32_t)Style::TextOrigin::TOP)); Printf(false, "%Bd " "%D%f%LB%s bottom%E " "%D%f%LV%s center%E " "%D%f%LT%s top%E ", s->h.v, &ScreenChangeStyleYesNo, ((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::BOT) ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, neither ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, ((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::TOP) ? RADIO_TRUE : RADIO_FALSE); } if(s->h.v >= Style::FIRST_CUSTOM) { Printf(false, ""); Printf(false, " %Fd%D%f%Lv%s show these objects on screen%E", s->h.v, &ScreenChangeStyleYesNo, s->visible ? CHECK_TRUE : CHECK_FALSE); Printf(false, " %Fd%D%f%Le%s export these objects%E", s->h.v, &ScreenChangeStyleYesNo, s->exportable ? CHECK_TRUE : CHECK_FALSE); Printf(false, ""); Printf(false, "To assign lines or curves to this style,"); Printf(false, "right-click them on the drawing."); } }
void ConstraintBase::GenerateReal(IdList<Equation,hEquation> *l) const { Expr *exA = Expr::From(valA); switch(type) { case Type::PT_PT_DISTANCE: AddEq(l, Distance(workplane, ptA, ptB)->Minus(exA), 0); return; case Type::PROJ_PT_DISTANCE: { ExprVector pA = SK.GetEntity(ptA)->PointGetExprs(), pB = SK.GetEntity(ptB)->PointGetExprs(), dp = pB.Minus(pA); ExprVector pp = SK.GetEntity(entityA)->VectorGetExprs(); pp = pp.WithMagnitude(Expr::From(1.0)); AddEq(l, (dp.Dot(pp))->Minus(exA), 0); return; } case Type::PT_LINE_DISTANCE: AddEq(l, PointLineDistance(workplane, ptA, entityA)->Minus(exA), 0); return; case Type::PT_PLANE_DISTANCE: { ExprVector pt = SK.GetEntity(ptA)->PointGetExprs(); AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exA), 0); return; } case Type::PT_FACE_DISTANCE: { ExprVector pt = SK.GetEntity(ptA)->PointGetExprs(); EntityBase *f = SK.GetEntity(entityA); ExprVector p0 = f->FaceGetPointExprs(); ExprVector n = f->FaceGetNormalExprs(); AddEq(l, (pt.Minus(p0)).Dot(n)->Minus(exA), 0); return; } case Type::EQUAL_LENGTH_LINES: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); AddEq(l, Distance(workplane, a->point[0], a->point[1])->Minus( Distance(workplane, b->point[0], b->point[1])), 0); return; } // These work on distance squared, since the pt-line distances are // signed, and we want the absolute value. case Type::EQ_LEN_PT_LINE_D: { EntityBase *forLen = SK.GetEntity(entityA); Expr *d1 = Distance(workplane, forLen->point[0], forLen->point[1]); Expr *d2 = PointLineDistance(workplane, ptA, entityB); AddEq(l, (d1->Square())->Minus(d2->Square()), 0); return; } case Type::EQ_PT_LN_DISTANCES: { Expr *d1 = PointLineDistance(workplane, ptA, entityA); Expr *d2 = PointLineDistance(workplane, ptB, entityB); AddEq(l, (d1->Square())->Minus(d2->Square()), 0); return; } case Type::LENGTH_RATIO: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); Expr *la = Distance(workplane, a->point[0], a->point[1]); Expr *lb = Distance(workplane, b->point[0], b->point[1]); AddEq(l, (la->Div(lb))->Minus(exA), 0); return; } case Type::LENGTH_DIFFERENCE: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); Expr *la = Distance(workplane, a->point[0], a->point[1]); Expr *lb = Distance(workplane, b->point[0], b->point[1]); AddEq(l, (la->Minus(lb))->Minus(exA), 0); return; } case Type::DIAMETER: { EntityBase *circle = SK.GetEntity(entityA); Expr *r = circle->CircleGetRadiusExpr(); AddEq(l, (r->Times(Expr::From(2)))->Minus(exA), 0); return; } case Type::EQUAL_RADIUS: { EntityBase *c1 = SK.GetEntity(entityA); EntityBase *c2 = SK.GetEntity(entityB); AddEq(l, (c1->CircleGetRadiusExpr())->Minus( c2->CircleGetRadiusExpr()), 0); return; } case Type::EQUAL_LINE_ARC_LEN: { EntityBase *line = SK.GetEntity(entityA), *arc = SK.GetEntity(entityB); // Get the line length ExprVector l0 = SK.GetEntity(line->point[0])->PointGetExprs(), l1 = SK.GetEntity(line->point[1])->PointGetExprs(); Expr *ll = (l1.Minus(l0)).Magnitude(); // And get the arc radius, and the cosine of its angle EntityBase *ao = SK.GetEntity(arc->point[0]), *as = SK.GetEntity(arc->point[1]), *af = SK.GetEntity(arc->point[2]); ExprVector aos = (as->PointGetExprs()).Minus(ao->PointGetExprs()), aof = (af->PointGetExprs()).Minus(ao->PointGetExprs()); Expr *r = aof.Magnitude(); ExprVector n = arc->Normal()->NormalExprsN(); ExprVector u = aos.WithMagnitude(Expr::From(1.0)); ExprVector v = n.Cross(u); // so in our new csys, we start at (1, 0, 0) Expr *costheta = aof.Dot(u)->Div(r); Expr *sintheta = aof.Dot(v)->Div(r); double thetas, thetaf, dtheta; arc->ArcGetAngles(&thetas, &thetaf, &dtheta); Expr *theta; if(dtheta < 3*PI/4) { theta = costheta->ACos(); } else if(dtheta < 5*PI/4) { // As the angle crosses pi, cos theta is not invertible; // so use the sine to stop blowing up theta = Expr::From(PI)->Minus(sintheta->ASin()); } else { theta = (Expr::From(2*PI))->Minus(costheta->ACos()); } // And write the equation; r*theta = L AddEq(l, (r->Times(theta))->Minus(ll), 0); return; } case Type::POINTS_COINCIDENT: { EntityBase *a = SK.GetEntity(ptA); EntityBase *b = SK.GetEntity(ptB); if(workplane.v == EntityBase::FREE_IN_3D.v) { ExprVector pa = a->PointGetExprs(); ExprVector pb = b->PointGetExprs(); AddEq(l, pa.x->Minus(pb.x), 0); AddEq(l, pa.y->Minus(pb.y), 1); AddEq(l, pa.z->Minus(pb.z), 2); } else { Expr *au, *av; Expr *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); AddEq(l, au->Minus(bu), 0); AddEq(l, av->Minus(bv), 1); } return; } case Type::PT_IN_PLANE: // This one works the same, whether projected or not. AddEq(l, PointPlaneDistance( SK.GetEntity(ptA)->PointGetExprs(), entityA), 0); return; case Type::PT_ON_FACE: { // a plane, n dot (p - p0) = 0 ExprVector p = SK.GetEntity(ptA)->PointGetExprs(); EntityBase *f = SK.GetEntity(entityA); ExprVector p0 = f->FaceGetPointExprs(); ExprVector n = f->FaceGetNormalExprs(); AddEq(l, (p.Minus(p0)).Dot(n), 0); return; } case Type::PT_ON_LINE: if(workplane.v == EntityBase::FREE_IN_3D.v) { EntityBase *ln = SK.GetEntity(entityA); EntityBase *a = SK.GetEntity(ln->point[0]); EntityBase *b = SK.GetEntity(ln->point[1]); EntityBase *p = SK.GetEntity(ptA); ExprVector ep = p->PointGetExprs(); ExprVector ea = a->PointGetExprs(); ExprVector eb = b->PointGetExprs(); ExprVector eab = ea.Minus(eb); // Construct a vector from the point to either endpoint of // the line segment, and choose the longer of these. ExprVector eap = ea.Minus(ep); ExprVector ebp = eb.Minus(ep); ExprVector elp = (ebp.Magnitude()->Eval() > eap.Magnitude()->Eval()) ? ebp : eap; if(p->group.v == group.v) { AddEq(l, VectorsParallel(0, eab, elp), 0); AddEq(l, VectorsParallel(1, eab, elp), 1); } else { AddEq(l, VectorsParallel(0, elp, eab), 0); AddEq(l, VectorsParallel(1, elp, eab), 1); } } else { AddEq(l, PointLineDistance(workplane, ptA, entityA), 0); } return; case Type::PT_ON_CIRCLE: { // This actually constrains the point to lie on the cylinder. EntityBase *circle = SK.GetEntity(entityA); ExprVector center = SK.GetEntity(circle->point[0])->PointGetExprs(); ExprVector pt = SK.GetEntity(ptA)->PointGetExprs(); EntityBase *normal = SK.GetEntity(circle->normal); ExprVector u = normal->NormalExprsU(), v = normal->NormalExprsV(); Expr *du = (center.Minus(pt)).Dot(u), *dv = (center.Minus(pt)).Dot(v); Expr *r = circle->CircleGetRadiusExpr(); AddEq(l, ((du->Square())->Plus(dv->Square()))->Minus(r->Square()), 0); return; } case Type::AT_MIDPOINT: if(workplane.v == EntityBase::FREE_IN_3D.v) { EntityBase *ln = SK.GetEntity(entityA); ExprVector a = SK.GetEntity(ln->point[0])->PointGetExprs(); ExprVector b = SK.GetEntity(ln->point[1])->PointGetExprs(); ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5)); if(ptA.v) { ExprVector p = SK.GetEntity(ptA)->PointGetExprs(); AddEq(l, (m.x)->Minus(p.x), 0); AddEq(l, (m.y)->Minus(p.y), 1); AddEq(l, (m.z)->Minus(p.z), 2); } else { AddEq(l, PointPlaneDistance(m, entityB), 0); } } else { EntityBase *ln = SK.GetEntity(entityA); EntityBase *a = SK.GetEntity(ln->point[0]); EntityBase *b = SK.GetEntity(ln->point[1]); Expr *au, *av, *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); Expr *mu = Expr::From(0.5)->Times(au->Plus(bu)); Expr *mv = Expr::From(0.5)->Times(av->Plus(bv)); if(ptA.v) { EntityBase *p = SK.GetEntity(ptA); Expr *pu, *pv; p->PointGetExprsInWorkplane(workplane, &pu, &pv); AddEq(l, pu->Minus(mu), 0); AddEq(l, pv->Minus(mv), 1); } else { ExprVector m = PointInThreeSpace(workplane, mu, mv); AddEq(l, PointPlaneDistance(m, entityB), 0); } } return; case Type::SYMMETRIC: if(workplane.v == EntityBase::FREE_IN_3D.v) { EntityBase *plane = SK.GetEntity(entityA); EntityBase *ea = SK.GetEntity(ptA); EntityBase *eb = SK.GetEntity(ptB); ExprVector a = ea->PointGetExprs(); ExprVector b = eb->PointGetExprs(); // The midpoint of the line connecting the symmetric points // lies on the plane of the symmetry. ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5)); AddEq(l, PointPlaneDistance(m, plane->h), 0); // And projected into the plane of symmetry, the points are // coincident. Expr *au, *av, *bu, *bv; ea->PointGetExprsInWorkplane(plane->h, &au, &av); eb->PointGetExprsInWorkplane(plane->h, &bu, &bv); AddEq(l, au->Minus(bu), 1); AddEq(l, av->Minus(bv), 2); } else { EntityBase *plane = SK.GetEntity(entityA); EntityBase *a = SK.GetEntity(ptA); EntityBase *b = SK.GetEntity(ptB); Expr *au, *av, *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); Expr *mu = Expr::From(0.5)->Times(au->Plus(bu)); Expr *mv = Expr::From(0.5)->Times(av->Plus(bv)); ExprVector m = PointInThreeSpace(workplane, mu, mv); AddEq(l, PointPlaneDistance(m, plane->h), 0); // Construct a vector within the workplane that is normal // to the symmetry pane's normal (i.e., that lies in the // plane of symmetry). The line connecting the points is // perpendicular to that constructed vector. EntityBase *w = SK.GetEntity(workplane); ExprVector u = w->Normal()->NormalExprsU(); ExprVector v = w->Normal()->NormalExprsV(); ExprVector pa = a->PointGetExprs(); ExprVector pb = b->PointGetExprs(); ExprVector n; Expr *d; plane->WorkplaneGetPlaneExprs(&n, &d); AddEq(l, (n.Cross(u.Cross(v))).Dot(pa.Minus(pb)), 1); } return; case Type::SYMMETRIC_HORIZ: case Type::SYMMETRIC_VERT: { EntityBase *a = SK.GetEntity(ptA); EntityBase *b = SK.GetEntity(ptB); Expr *au, *av, *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); if(type == Type::SYMMETRIC_HORIZ) { AddEq(l, av->Minus(bv), 0); AddEq(l, au->Plus(bu), 1); } else { AddEq(l, au->Minus(bu), 0); AddEq(l, av->Plus(bv), 1); } return; } case Type::SYMMETRIC_LINE: { EntityBase *pa = SK.GetEntity(ptA); EntityBase *pb = SK.GetEntity(ptB); Expr *pau, *pav, *pbu, *pbv; pa->PointGetExprsInWorkplane(workplane, &pau, &pav); pb->PointGetExprsInWorkplane(workplane, &pbu, &pbv); EntityBase *ln = SK.GetEntity(entityA); EntityBase *la = SK.GetEntity(ln->point[0]); EntityBase *lb = SK.GetEntity(ln->point[1]); Expr *lau, *lav, *lbu, *lbv; la->PointGetExprsInWorkplane(workplane, &lau, &lav); lb->PointGetExprsInWorkplane(workplane, &lbu, &lbv); Expr *dpu = pbu->Minus(pau), *dpv = pbv->Minus(pav); Expr *dlu = lbu->Minus(lau), *dlv = lbv->Minus(lav); // The line through the points is perpendicular to the line // of symmetry. AddEq(l, (dlu->Times(dpu))->Plus(dlv->Times(dpv)), 0); // And the signed distances of the points to the line are // equal in magnitude and opposite in sign, so sum to zero Expr *dista = (dlv->Times(lau->Minus(pau)))->Minus( (dlu->Times(lav->Minus(pav)))); Expr *distb = (dlv->Times(lau->Minus(pbu)))->Minus( (dlu->Times(lav->Minus(pbv)))); AddEq(l, dista->Plus(distb), 1); return; } case Type::HORIZONTAL: case Type::VERTICAL: { hEntity ha, hb; if(entityA.v) { EntityBase *e = SK.GetEntity(entityA); ha = e->point[0]; hb = e->point[1]; } else { ha = ptA; hb = ptB; } EntityBase *a = SK.GetEntity(ha); EntityBase *b = SK.GetEntity(hb); Expr *au, *av, *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); AddEq(l, (type == Type::HORIZONTAL) ? av->Minus(bv) : au->Minus(bu), 0); return; } case Type::SAME_ORIENTATION: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); if(b->group.v != group.v) { swap(a, b); } ExprVector au = a->NormalExprsU(), an = a->NormalExprsN(); ExprVector bu = b->NormalExprsU(), bv = b->NormalExprsV(), bn = b->NormalExprsN(); AddEq(l, VectorsParallel(0, an, bn), 0); AddEq(l, VectorsParallel(1, an, bn), 1); Expr *d1 = au.Dot(bv); Expr *d2 = au.Dot(bu); // Allow either orientation for the coordinate system, depending // on how it was drawn. if(fabs(d1->Eval()) < fabs(d2->Eval())) { AddEq(l, d1, 2); } else { AddEq(l, d2, 2); } return; } case Type::PERPENDICULAR: case Type::ANGLE: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); ExprVector ae = a->VectorGetExprs(); ExprVector be = b->VectorGetExprs(); if(other) ae = ae.ScaledBy(Expr::From(-1)); Expr *c = DirectionCosine(workplane, ae, be); if(type == Type::ANGLE) { // The direction cosine is equal to the cosine of the // specified angle Expr *rads = exA->Times(Expr::From(PI/180)), *rc = rads->Cos(); double arc = fabs(rc->Eval()); // avoid false detection of inconsistent systems by gaining // up as the difference in dot products gets small at small // angles; doubles still have plenty of precision, only // problem is that rank test Expr *mult = Expr::From(arc > 0.99 ? 0.01/(1.00001 - arc) : 1); AddEq(l, (c->Minus(rc))->Times(mult), 0); } else { // The dot product (and therefore the direction cosine) // is equal to zero, perpendicular. AddEq(l, c, 0); } return; } case Type::EQUAL_ANGLE: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); EntityBase *c = SK.GetEntity(entityC); EntityBase *d = SK.GetEntity(entityD); ExprVector ae = a->VectorGetExprs(); ExprVector be = b->VectorGetExprs(); ExprVector ce = c->VectorGetExprs(); ExprVector de = d->VectorGetExprs(); if(other) ae = ae.ScaledBy(Expr::From(-1)); Expr *cab = DirectionCosine(workplane, ae, be); Expr *ccd = DirectionCosine(workplane, ce, de); AddEq(l, cab->Minus(ccd), 0); return; } case Type::ARC_LINE_TANGENT: { EntityBase *arc = SK.GetEntity(entityA); EntityBase *line = SK.GetEntity(entityB); ExprVector ac = SK.GetEntity(arc->point[0])->PointGetExprs(); ExprVector ap = SK.GetEntity(arc->point[other ? 2 : 1])->PointGetExprs(); ExprVector ld = line->VectorGetExprs(); // The line is perpendicular to the radius AddEq(l, ld.Dot(ac.Minus(ap)), 0); return; } case Type::CUBIC_LINE_TANGENT: { EntityBase *cubic = SK.GetEntity(entityA); EntityBase *line = SK.GetEntity(entityB); ExprVector a; if(other) { a = cubic->CubicGetFinishTangentExprs(); } else { a = cubic->CubicGetStartTangentExprs(); } ExprVector b = line->VectorGetExprs(); if(workplane.v == EntityBase::FREE_IN_3D.v) { AddEq(l, VectorsParallel(0, a, b), 0); AddEq(l, VectorsParallel(1, a, b), 1); } else { EntityBase *w = SK.GetEntity(workplane); ExprVector wn = w->Normal()->NormalExprsN(); AddEq(l, (a.Cross(b)).Dot(wn), 0); } return; } case Type::CURVE_CURVE_TANGENT: { bool parallel = true; int i; ExprVector dir[2]; for(i = 0; i < 2; i++) { EntityBase *e = SK.GetEntity((i == 0) ? entityA : entityB); bool oth = (i == 0) ? other : other2; if(e->type == Entity::Type::ARC_OF_CIRCLE) { ExprVector center, endpoint; center = SK.GetEntity(e->point[0])->PointGetExprs(); endpoint = SK.GetEntity(e->point[oth ? 2 : 1])->PointGetExprs(); dir[i] = endpoint.Minus(center); // We're using the vector from the center of the arc to // an endpoint; so that's normal to the tangent, not // parallel. parallel = !parallel; } else if(e->type == Entity::Type::CUBIC) { if(oth) { dir[i] = e->CubicGetFinishTangentExprs(); } else { dir[i] = e->CubicGetStartTangentExprs(); } } else { ssassert(false, "Unexpected entity types for CURVE_CURVE_TANGENT"); } } if(parallel) { EntityBase *w = SK.GetEntity(workplane); ExprVector wn = w->Normal()->NormalExprsN(); AddEq(l, ((dir[0]).Cross(dir[1])).Dot(wn), 0); } else { AddEq(l, (dir[0]).Dot(dir[1]), 0); } return; } case Type::PARALLEL: { EntityBase *ea = SK.GetEntity(entityA), *eb = SK.GetEntity(entityB); if(eb->group.v != group.v) { swap(ea, eb); } ExprVector a = ea->VectorGetExprs(); ExprVector b = eb->VectorGetExprs(); if(workplane.v == EntityBase::FREE_IN_3D.v) { AddEq(l, VectorsParallel(0, a, b), 0); AddEq(l, VectorsParallel(1, a, b), 1); } else { EntityBase *w = SK.GetEntity(workplane); ExprVector wn = w->Normal()->NormalExprsN(); AddEq(l, (a.Cross(b)).Dot(wn), 0); } return; } case Type::WHERE_DRAGGED: { EntityBase *ep = SK.GetEntity(ptA); if(workplane.v == EntityBase::FREE_IN_3D.v) { ExprVector ev = ep->PointGetExprs(); Vector v = ep->PointGetNum(); AddEq(l, ev.x->Minus(Expr::From(v.x)), 0); AddEq(l, ev.y->Minus(Expr::From(v.y)), 1); AddEq(l, ev.z->Minus(Expr::From(v.z)), 2); } else { Expr *u, *v; ep->PointGetExprsInWorkplane(workplane, &u, &v); AddEq(l, u->Minus(Expr::From(u->Eval())), 0); AddEq(l, v->Minus(Expr::From(v->Eval())), 1); } return; } case Type::COMMENT: return; } ssassert(false, "Unexpected constraint ID"); }
void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, SShell *agnstA, SShell *agnstB, SShell *into) { SCurve sc = {}; // Important to keep the order of (surfA, surfB) consistent; when we later // rewrite the identifiers, we rewrite surfA from A and surfB from B. sc.surfA = h; sc.surfB = srfB->h; sc.exact = *sb; sc.isExact = true; // Now we have to piecewise linearize the curve. If there's already an // identical curve in the shell, then follow that pwl exactly, otherwise // calculate from scratch. SCurve split, *existing = NULL, *se; 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; } } } if(existing) { SCurvePt *v; for(v = existing->pts.First(); v; v = existing->pts.NextAfter(v)) { sc.pts.Add(v); } if(backwards) sc.pts.Reverse(); split = sc; sc = {}; } else { sb->MakePwlInto(&(sc.pts)); // and split the line where it intersects our existing surfaces split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, srfB); sc.Clear(); } // Test if the curve lies entirely outside one of the SCurvePt *scpt; bool withinA = false, withinB = false; for(scpt = split.pts.First(); scpt; scpt = split.pts.NextAfter(scpt)) { double tol = 0.01; Point2d puv; ClosestPointTo(scpt->p, &puv); if(puv.x > -tol && puv.x < 1 + tol && puv.y > -tol && puv.y < 1 + tol) { withinA = true; } srfB->ClosestPointTo(scpt->p, &puv); if(puv.x > -tol && puv.x < 1 + tol && puv.y > -tol && puv.y < 1 + tol) { withinB = true; } // Break out early, no sense wasting time if we already have the answer. if(withinA && withinB) break; } if(!(withinA && withinB)) { // Intersection curve lies entirely outside one of the surfaces, so // it's fake. split.Clear(); return; } #if 0 if(sb->deg == 2) { dbp(" "); SCurvePt *prev = NULL, *v; dbp("split.pts.n = %d", split.pts.n); for(v = split.pts.First(); v; v = split.pts.NextAfter(v)) { if(prev) { Vector e = (prev->p).Minus(v->p).WithMagnitude(0); SS.nakedEdges.AddEdge((prev->p).Plus(e), (v->p).Minus(e)); } prev = v; } } #endif // 0 ssassert(!(sb->Start()).Equals(sb->Finish()), "Unexpected zero-length edge"); split.source = SCurve::Source::INTERSECTION; into->curve.AddAndAssignId(&split); }
void Constraint::MenuConstrain(Command id) { Constraint c = {}; c.group = SS.GW.activeGroup; c.workplane = SS.GW.ActiveWorkplane(); SS.GW.GroupSelection(); auto const &gs = SS.GW.gs; switch(id) { case Command::DISTANCE_DIA: case Command::REF_DISTANCE: { if(gs.points == 2 && gs.n == 2) { c.type = Type::PT_PT_DISTANCE; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 1 && gs.n == 1) { c.type = Type::PT_PT_DISTANCE; Entity *e = SK.GetEntity(gs.entity[0]); c.ptA = e->point[0]; c.ptB = e->point[1]; } else if(gs.vectors == 1 && gs.points == 2 && gs.n == 3) { c.type = Type::PROJ_PT_DISTANCE; c.ptA = gs.point[0]; c.ptB = gs.point[1]; c.entityA = gs.vector[0]; } else if(gs.workplanes == 1 && gs.points == 1 && gs.n == 2) { c.type = Type::PT_PLANE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) { c.type = Type::PT_LINE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) { c.type = Type::PT_FACE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.face[0]; } else if(gs.circlesOrArcs == 1 && gs.n == 1) { c.type = Type::DIAMETER; c.entityA = gs.entity[0]; } else { Error(_("Bad selection for distance / diameter constraint. This " "constraint can apply to:\n\n" " * two points (distance between points)\n" " * a line segment (length)\n" " * two points and a line segment or normal (projected distance)\n" " * a workplane and a point (minimum distance)\n" " * a line segment and a point (minimum distance)\n" " * a plane face and a point (minimum distance)\n" " * a circle or an arc (diameter)\n")); return; } if(c.type == Type::PT_PT_DISTANCE || c.type == Type::PROJ_PT_DISTANCE) { Vector n = SS.GW.projRight.Cross(SS.GW.projUp); Vector a = SK.GetEntity(c.ptA)->PointGetNum(); Vector b = SK.GetEntity(c.ptB)->PointGetNum(); c.disp.offset = n.Cross(a.Minus(b)); c.disp.offset = (c.disp.offset).WithMagnitude(50/SS.GW.scale); } else { c.disp.offset = Vector::From(0, 0, 0); } if(id == Command::REF_DISTANCE) { c.reference = true; } c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); break; } case Command::ON_ENTITY: if(gs.points == 2 && gs.n == 2) { c.type = Type::POINTS_COINCIDENT; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) { c.type = Type::PT_IN_PLANE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) { c.type = Type::PT_ON_LINE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) { c.type = Type::PT_ON_CIRCLE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.faces == 1 && gs.n == 2) { c.type = Type::PT_ON_FACE; c.ptA = gs.point[0]; c.entityA = gs.face[0]; } else { Error(_("Bad selection for on point / curve / plane constraint. " "This constraint can apply to:\n\n" " * two points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" " * a point and a plane face (point on face)\n")); return; } AddConstraint(&c); break; case Command::EQUAL: if(gs.lineSegments == 2 && gs.n == 2) { c.type = Type::EQUAL_LENGTH_LINES; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) { c.type = Type::EQ_PT_LN_DISTANCES; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.entityB = gs.entity[1]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) { // The same line segment for the distances, but different // points. c.type = Type::EQ_PT_LN_DISTANCES; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.entityB = gs.entity[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) { c.type = Type::EQ_LEN_PT_LINE_D; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; c.ptA = gs.point[0]; } else if(gs.vectors == 4 && gs.n == 4) { c.type = Type::EQUAL_ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.entityC = gs.vector[2]; c.entityD = gs.vector[3]; } else if(gs.vectors == 3 && gs.n == 3) { c.type = Type::EQUAL_ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.entityC = gs.vector[1]; c.entityD = gs.vector[2]; } else if(gs.circlesOrArcs == 2 && gs.n == 2) { c.type = Type::EQUAL_RADIUS; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) { c.type = Type::EQUAL_LINE_ARC_LEN; if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) { c.entityA = gs.entity[1]; c.entityB = gs.entity[0]; } else { c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } } else { Error(_("Bad selection for equal length / radius constraint. " "This constraint can apply to:\n\n" " * two line segments (equal length)\n" " * two line segments and two points " "(equal point-line distances)\n" " * a line segment and two points " "(equal point-line distances)\n" " * a line segment, and a point and line segment " "(point-line distance equals length)\n" " * four line segments or normals " "(equal angle between A,B and C,D)\n" " * three line segments or normals " "(equal angle between A,B and B,C)\n" " * two circles or arcs (equal radius)\n" " * a line segment and an arc " "(line segment length equals arc length)\n")); return; } if(c.type == Type::EQUAL_ANGLE) { // Infer the nearest supplementary angle from the sketch. Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(), b1 = SK.GetEntity(c.entityB)->VectorGetNum(), a2 = SK.GetEntity(c.entityC)->VectorGetNum(), b2 = SK.GetEntity(c.entityD)->VectorGetNum(); double d1 = a1.Dot(b1), d2 = a2.Dot(b2); if(d1*d2 < 0) { c.other = true; } } AddConstraint(&c); break; case Command::RATIO: if(gs.lineSegments == 2 && gs.n == 2) { c.type = Type::LENGTH_RATIO; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else { Error(_("Bad selection for length ratio constraint. This " "constraint can apply to:\n\n" " * two line segments\n")); return; } c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); break; case Command::DIFFERENCE: if(gs.lineSegments == 2 && gs.n == 2) { c.type = Type::LENGTH_DIFFERENCE; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else { Error(_("Bad selection for length difference constraint. This " "constraint can apply to:\n\n" " * two line segments\n")); return; } c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); break; case Command::AT_MIDPOINT: if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) { c.type = Type::AT_MIDPOINT; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; // If a point is at-midpoint, then no reason to also constrain // it on-line; so auto-remove that. DeleteAllConstraintsFor(Type::PT_ON_LINE, c.entityA, c.ptA); } else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) { c.type = Type::AT_MIDPOINT; int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0; c.entityA = gs.entity[i]; c.entityB = gs.entity[1-i]; } else { Error(_("Bad selection for at midpoint constraint. This " "constraint can apply to:\n\n" " * a line segment and a point " "(point at midpoint)\n" " * a line segment and a workplane " "(line's midpoint on plane)\n")); return; } AddConstraint(&c); break; case Command::SYMMETRIC: if(gs.points == 2 && ((gs.workplanes == 1 && gs.n == 3) || (gs.n == 2))) { if(gs.entities > 0) c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.ptB = gs.point[1]; c.type = Type::SYMMETRIC; } else if(gs.lineSegments == 1 && ((gs.workplanes == 1 && gs.n == 2) || (gs.n == 1))) { Entity *line; if(SK.GetEntity(gs.entity[0])->IsWorkplane()) { line = SK.GetEntity(gs.entity[1]); c.entityA = gs.entity[0]; } else { line = SK.GetEntity(gs.entity[0]); } c.ptA = line->point[0]; c.ptB = line->point[1]; c.type = Type::SYMMETRIC; } else if(SS.GW.LockedInWorkplane() && gs.lineSegments == 2 && gs.n == 2) { Entity *l0 = SK.GetEntity(gs.entity[0]), *l1 = SK.GetEntity(gs.entity[1]); if((l1->group.v != SS.GW.activeGroup.v) || (l1->construction && !(l0->construction))) { swap(l0, l1); } c.ptA = l1->point[0]; c.ptB = l1->point[1]; c.entityA = l0->h; c.type = Type::SYMMETRIC_LINE; } else if(SS.GW.LockedInWorkplane() && gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) { c.ptA = gs.point[0]; c.ptB = gs.point[1]; c.entityA = gs.entity[0]; c.type = Type::SYMMETRIC_LINE; } else { Error(_("Bad selection for symmetric constraint. This constraint " "can apply to:\n\n" " * two points or a line segment " "(symmetric about workplane's coordinate axis)\n" " * line segment, and two points or a line segment " "(symmetric about line segment)\n" " * workplane, and two points or a line segment " "(symmetric about workplane)\n")); return; } if(c.entityA.v == Entity::NO_ENTITY.v) { // Horizontal / vertical symmetry, implicit symmetry plane // normal to the workplane if(c.workplane.v == Entity::FREE_IN_3D.v) { Error(_("A workplane must be active when constraining " "symmetric without an explicit symmetry plane.")); return; } Vector pa = SK.GetEntity(c.ptA)->PointGetNum(); Vector pb = SK.GetEntity(c.ptB)->PointGetNum(); Vector dp = pa.Minus(pb); EntityBase *norm = SK.GetEntity(c.workplane)->Normal();; Vector u = norm->NormalU(), v = norm->NormalV(); if(fabs(dp.Dot(u)) > fabs(dp.Dot(v))) { c.type = Type::SYMMETRIC_HORIZ; } else { c.type = Type::SYMMETRIC_VERT; } if(gs.lineSegments == 1) { // If this line segment is already constrained horiz or // vert, then auto-remove that redundant constraint. DeleteAllConstraintsFor(Type::HORIZONTAL, (gs.entity[0]), Entity::NO_ENTITY); DeleteAllConstraintsFor(Type::VERTICAL, (gs.entity[0]), Entity::NO_ENTITY); } } AddConstraint(&c); break; case Command::VERTICAL: case Command::HORIZONTAL: { hEntity ha, hb; if(c.workplane.v == Entity::FREE_IN_3D.v) { Error(_("Activate a workplane (with Sketch -> In Workplane) before " "applying a horizontal or vertical constraint.")); return; } if(gs.lineSegments == 1 && gs.n == 1) { c.entityA = gs.entity[0]; Entity *e = SK.GetEntity(c.entityA); ha = e->point[0]; hb = e->point[1]; } else if(gs.points == 2 && gs.n == 2) { ha = c.ptA = gs.point[0]; hb = c.ptB = gs.point[1]; } else { Error(_("Bad selection for horizontal / vertical constraint. " "This constraint can apply to:\n\n" " * two points\n" " * a line segment\n")); return; } if(id == Command::HORIZONTAL) { c.type = Type::HORIZONTAL; } else { c.type = Type::VERTICAL; } AddConstraint(&c); break; } case Command::ORIENTED_SAME: { if(gs.anyNormals == 2 && gs.n == 2) { c.type = Type::SAME_ORIENTATION; c.entityA = gs.anyNormal[0]; c.entityB = gs.anyNormal[1]; } else { Error(_("Bad selection for same orientation constraint. This " "constraint can apply to:\n\n" " * two normals\n")); return; } SS.UndoRemember(); Entity *nfree = SK.GetEntity(c.entityA); Entity *nref = SK.GetEntity(c.entityB); if(nref->group.v == SS.GW.activeGroup.v) { swap(nref, nfree); } if(nfree->group.v == SS.GW.activeGroup.v && nref ->group.v != SS.GW.activeGroup.v) { // nfree is free, and nref is locked (since it came from a // previous group); so let's force nfree aligned to nref, // and make convergence easy Vector ru = nref ->NormalU(), rv = nref ->NormalV(); Vector fu = nfree->NormalU(), fv = nfree->NormalV(); if(fabs(fu.Dot(ru)) < fabs(fu.Dot(rv))) { // There might be an odd*90 degree rotation about the // normal vector; allow that, since the numerical // constraint does swap(ru, rv); } fu = fu.Dot(ru) > 0 ? ru : ru.ScaledBy(-1); fv = fv.Dot(rv) > 0 ? rv : rv.ScaledBy(-1); nfree->NormalForceTo(Quaternion::From(fu, fv)); } AddConstraint(&c, /*rememberForUndo=*/false); break; } case Command::OTHER_ANGLE: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->type == Type::ANGLE) { SS.UndoRemember(); c->other = !(c->other); c->ModifyToSatisfy(); break; } if(c->type == Type::EQUAL_ANGLE) { SS.UndoRemember(); c->other = !(c->other); SS.MarkGroupDirty(c->group); break; } } Error(_("Must select an angle constraint.")); return; case Command::REFERENCE: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && c->type != Type::COMMENT) { (c->reference) = !(c->reference); SS.MarkGroupDirty(c->group, /*onlyThis=*/true); break; } } Error(_("Must select a constraint with associated label.")); return; case Command::ANGLE: case Command::REF_ANGLE: { if(gs.vectors == 2 && gs.n == 2) { c.type = Type::ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.valA = 0; } else { Error(_("Bad selection for angle constraint. This constraint " "can apply to:\n\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n")); return; } Entity *ea = SK.GetEntity(c.entityA), *eb = SK.GetEntity(c.entityB); if(ea->type == Entity::Type::LINE_SEGMENT && eb->type == Entity::Type::LINE_SEGMENT) { Vector a0 = SK.GetEntity(ea->point[0])->PointGetNum(), a1 = SK.GetEntity(ea->point[1])->PointGetNum(), b0 = SK.GetEntity(eb->point[0])->PointGetNum(), b1 = SK.GetEntity(eb->point[1])->PointGetNum(); if(a0.Equals(b0) || a1.Equals(b1)) { // okay, vectors should be drawn in same sense } else if(a0.Equals(b1) || a1.Equals(b0)) { // vectors are in opposite sense c.other = true; } else { // no shared point; not clear which intersection to draw } } if(id == Command::REF_ANGLE) { c.reference = true; } c.ModifyToSatisfy(); AddConstraint(&c); break; } case Command::PARALLEL: if(gs.vectors == 2 && gs.n == 2) { c.type = Type::PARALLEL; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; } else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) { Entity *line = SK.GetEntity(gs.entity[0]); Entity *arc = SK.GetEntity(gs.entity[1]); if(line->type == Entity::Type::ARC_OF_CIRCLE) { swap(line, arc); } Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), l1 = SK.GetEntity(line->point[1])->PointGetNum(); Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(), a2 = SK.GetEntity(arc->point[2])->PointGetNum(); if(l0.Equals(a1) || l1.Equals(a1)) { c.other = false; } else if(l0.Equals(a2) || l1.Equals(a2)) { c.other = true; } else { Error(_("The tangent arc and line segment must share an " "endpoint. Constrain them with Constrain -> " "On Point before constraining tangent.")); return; } c.type = Type::ARC_LINE_TANGENT; c.entityA = arc->h; c.entityB = line->h; } else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) { Entity *line = SK.GetEntity(gs.entity[0]); Entity *cubic = SK.GetEntity(gs.entity[1]); if(line->type == Entity::Type::CUBIC) { swap(line, cubic); } Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), l1 = SK.GetEntity(line->point[1])->PointGetNum(); Vector as = cubic->CubicGetStartNum(), af = cubic->CubicGetFinishNum(); if(l0.Equals(as) || l1.Equals(as)) { c.other = false; } else if(l0.Equals(af) || l1.Equals(af)) { c.other = true; } else { Error(_("The tangent cubic and line segment must share an " "endpoint. Constrain them with Constrain -> " "On Point before constraining tangent.")); return; } c.type = Type::CUBIC_LINE_TANGENT; c.entityA = cubic->h; c.entityB = line->h; } else if(gs.cubics + gs.arcs == 2 && gs.n == 2) { if(!SS.GW.LockedInWorkplane()) { Error(_("Curve-curve tangency must apply in workplane.")); return; } Entity *eA = SK.GetEntity(gs.entity[0]), *eB = SK.GetEntity(gs.entity[1]); Vector as = eA->EndpointStart(), af = eA->EndpointFinish(), bs = eB->EndpointStart(), bf = eB->EndpointFinish(); if(as.Equals(bs)) { c.other = false; c.other2 = false; } else if(as.Equals(bf)) { c.other = false; c.other2 = true; } else if(af.Equals(bs)) { c.other = true; c.other2 = false; } else if(af.Equals(bf)) { c.other = true; c.other2 = true; } else { Error(_("The curves must share an endpoint. Constrain them " "with Constrain -> On Point before constraining " "tangent.")); return; } c.type = Type::CURVE_CURVE_TANGENT; c.entityA = eA->h; c.entityB = eB->h; } else { Error(_("Bad selection for parallel / tangent constraint. This " "constraint can apply to:\n\n" " * two line segments (parallel)\n" " * a line segment and a normal (parallel)\n" " * two normals (parallel)\n" " * two line segments, arcs, or beziers, that share " "an endpoint (tangent)\n")); return; } AddConstraint(&c); break; case Command::PERPENDICULAR: if(gs.vectors == 2 && gs.n == 2) { c.type = Type::PERPENDICULAR; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; } else { Error(_("Bad selection for perpendicular constraint. This " "constraint can apply to:\n\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n")); return; } AddConstraint(&c); break; case Command::WHERE_DRAGGED: if(gs.points == 1 && gs.n == 1) { c.type = Type::WHERE_DRAGGED; c.ptA = gs.point[0]; } else { Error(_("Bad selection for lock point where dragged constraint. " "This constraint can apply to:\n\n" " * a point\n")); return; } AddConstraint(&c); break; case Command::COMMENT: SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND; SS.GW.pending.command = Command::COMMENT; SS.GW.pending.description = _("click center of comment text"); SS.ScheduleShowTW(); break; default: ssassert(false, "Unexpected menu ID"); } if(SK.constraint.FindByIdNoOops(c.h)) { Constraint *constraint = SK.GetConstraint(c.h); if(SS.TestRankForGroup(c.group) == SolveResult::REDUNDANT_OKAY && !SK.GetGroup(SS.GW.activeGroup)->allowRedundant && constraint->HasLabel()) { constraint->reference = true; } } SS.GW.ClearSelection(); InvalidateGraphics(); }