diff --git a/src/drawentity.cpp b/src/drawentity.cpp index 28647e4..c007457 100644 --- a/src/drawentity.cpp +++ b/src/drawentity.cpp @@ -23,15 +23,7 @@ void Entity::LineDrawOrGetDistance(Vector a, Vector b, bool maybeFat) { ssglDepthRangeOffset((group.v == SS.GW.activeGroup.v) ? 4 : 3); // Narrow lines are drawn as lines, but fat lines must be drawn as // filled polygons, to get the line join style right. - if(!maybeFat || dogd.lineWidth < 3) { - glBegin(GL_LINES); - ssglVertex3v(a); - ssglVertex3v(b); - glEnd(); - } else { - ssglFatLine(a, b, dogd.lineWidth/SS.GW.scale); - } - + ssglStippledLine(a, b, dogd.lineWidth, dogd.stippleType, dogd.stippleScale); ssglDepthRangeOffset(0); } else { Point2d ap = SS.GW.ProjectPoint(a); @@ -114,6 +106,8 @@ void Entity::DrawAll(void) { void Entity::Draw(void) { hStyle hs = Style::ForEntity(h); dogd.lineWidth = Style::Width(hs); + dogd.stippleType = Style::PatternType(hs); + dogd.stippleScale = Style::StippleScaleMm(hs); ssglLineWidth((float)dogd.lineWidth); ssglColorRGB(Style::Color(hs)); diff --git a/src/file.cpp b/src/file.cpp index d5b87b0..3bbe62a 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -198,6 +198,8 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = { { 's', "Style.filled", 'b', &(SS.sv.s.filled) }, { 's', "Style.visible", 'b', &(SS.sv.s.visible) }, { 's', "Style.exportable", 'b', &(SS.sv.s.exportable) }, + { 's', "Style.stippleType", 'd', &(SS.sv.s.stippleType) }, + { 's', "Style.stippleScale", 'f', &(SS.sv.s.stippleScale) }, { 0, NULL, 0, NULL } }; diff --git a/src/fonts/private/4-stipple-dot.png b/src/fonts/private/4-stipple-dot.png new file mode 100644 index 0000000..52fc219 Binary files /dev/null and b/src/fonts/private/4-stipple-dot.png differ diff --git a/src/fonts/private/5-stipple-dash-long.png b/src/fonts/private/5-stipple-dash-long.png new file mode 100644 index 0000000..b73853c Binary files /dev/null and b/src/fonts/private/5-stipple-dash-long.png differ diff --git a/src/fonts/private/6-stipple-dash.png b/src/fonts/private/6-stipple-dash.png new file mode 100644 index 0000000..1cec678 Binary files /dev/null and b/src/fonts/private/6-stipple-dash.png differ diff --git a/src/fonts/private/7-stipple-zigzag.png b/src/fonts/private/7-stipple-zigzag.png new file mode 100644 index 0000000..470a7f2 Binary files /dev/null and b/src/fonts/private/7-stipple-zigzag.png differ diff --git a/src/glhelper.cpp b/src/glhelper.cpp index 876bbed..29f6b89 100644 --- a/src/glhelper.cpp +++ b/src/glhelper.cpp @@ -200,6 +200,128 @@ static void FatLineEndcap(Vector p, Vector u, Vector v) glEnd(); } +void ssglLine(const Vector &a, const Vector &b, double pixelWidth, bool maybeFat = true) { + if(!maybeFat || pixelWidth < 3.0) { + glBegin(GL_LINES); + ssglVertex3v(a); + ssglVertex3v(b); + glEnd(); + } else { + ssglFatLine(a, b, pixelWidth / SS.GW.scale); + } +} + +void ssglPoint(Vector p, double pixelSize) +{ + if(/*!maybeFat || */pixelSize < 3.0) { + glBegin(GL_LINES); + Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0); + ssglVertex3v(p.Minus(u)); + ssglVertex3v(p.Plus(u)); + glEnd(); + } else { + Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0); + Vector v = SS.GW.projUp.WithMagnitude(pixelSize / SS.GW.scale / 2.0); + + FatLineEndcap(p, u, v); + FatLineEndcap(p, u.ScaledBy(-1.0), v); + } +} + +void ssglStippledLine(Vector a, Vector b, double width, + int stippleType, double stippleScale) +{ + const char *stipplePattern; + switch(stippleType) { + case Style::STIPPLE_CONTINUOUS: ssglLine(a, b, width); return; + case Style::STIPPLE_DASH: stipplePattern = "- "; break; + case Style::STIPPLE_LONG_DASH: stipplePattern = "_ "; break; + case Style::STIPPLE_DASH_DOT: stipplePattern = "-."; break; + case Style::STIPPLE_DASH_DOT_DOT: stipplePattern = "-.."; break; + case Style::STIPPLE_DOT: stipplePattern = "."; break; + case Style::STIPPLE_FREEHAND: stipplePattern = "~"; break; + case Style::STIPPLE_ZIGZAG: stipplePattern = "~__"; break; + default: oops(); + } + ssglStippledLine(a, b, width, stipplePattern, stippleScale); +} + +void ssglStippledLine(Vector a, Vector b, double width, + const char *stipplePattern, double stippleScale) +{ + if(stipplePattern == NULL || *stipplePattern == 0) oops(); + + Vector dir = b.Minus(a); + double len = dir.Magnitude(); + dir = dir.WithMagnitude(1.0); + + const char *si = stipplePattern; + double end = len; + double ss = stippleScale / 2.0; + do { + double start = end; + switch(*si) { + case ' ': + end -= 1.0 * ss; + break; + + case '-': + start = max(start - 0.5 * ss, 0.0); + end = max(start - 2.0 * ss, 0.0); + if(start == end) break; + ssglLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), width); + end = max(end - 0.5 * ss, 0.0); + break; + + case '_': + end = max(end - 4.0 * ss, 0.0); + ssglLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), width); + break; + + case '.': + end = max(end - 0.5 * ss, 0.0); + if(end == 0.0) break; + ssglPoint(a.Plus(dir.ScaledBy(end)), width); + end = max(end - 0.5 * ss, 0.0); + break; + + case '~': { + Vector ab = b.Minus(a); + Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); + Vector abn = (ab.Cross(gn)).WithMagnitude(1); + abn = abn.Minus(gn.ScaledBy(gn.Dot(abn))); + double pws = 2.0 * width / SS.GW.scale; + + end = max(end - 0.5 * ss, 0.0); + Vector aa = a.Plus(dir.ScaledBy(start)); + Vector bb = a.Plus(dir.ScaledBy(end)) + .Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss))); + ssglLine(aa, bb, width); + if(end == 0.0) break; + + start = end; + end = max(end - 1.0 * ss, 0.0); + aa = a.Plus(dir.ScaledBy(end)) + .Plus(abn.ScaledBy(pws)) + .Minus(abn.ScaledBy(2.0 * pws * (start - end) / ss)); + ssglLine(bb, aa, width); + if(end == 0.0) break; + + start = end; + end = max(end - 0.5 * ss, 0.0); + bb = a.Plus(dir.ScaledBy(end)) + .Minus(abn.ScaledBy(pws)) + .Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss))); + ssglLine(aa, bb, width); + break; + } + + default: oops(); + } + if(*(++si) == 0) si = stipplePattern; + } while(end > 0.0); +} + void ssglFatLine(Vector a, Vector b, double width) { // The half-width of the line we're drawing. diff --git a/src/sketch.h b/src/sketch.h index 6c410ae..6b9f95a 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -481,6 +481,8 @@ public: double dmin; Vector refp; double lineWidth; + double stippleScale; + int stippleType; } dogd; // state for drawing or getting distance (for hit testing) void LineDrawOrGetDistance(Vector a, Vector b, bool maybeFat=false); void DrawOrGetDistance(void); @@ -711,6 +713,19 @@ public: int tag; hStyle h; + enum { + STIPPLE_CONTINUOUS = 0, + STIPPLE_DASH = 1, + STIPPLE_LONG_DASH = 2, + STIPPLE_DASH_DOT = 3, + STIPPLE_DASH_DOT_DOT = 4, + STIPPLE_DOT = 5, + STIPPLE_FREEHAND = 6, + STIPPLE_ZIGZAG = 7, + + LAST_STIPPLE = STIPPLE_ZIGZAG + }; + enum { // If an entity has no style, then it will be colored according to // whether the group that it's in is active or not, whether it's @@ -757,6 +772,8 @@ public: RgbaColor fillColor; bool visible; bool exportable; + int stippleType; + double stippleScale; // The default styles, for entities that don't have a style assigned yet, // and for datums and such. @@ -793,6 +810,8 @@ public: static double TextHeight(hStyle hs); static bool Exportable(int hs); static hStyle ForEntity(hEntity he); + static int PatternType(hStyle hs); + static double StippleScaleMm(hStyle hs); std::string DescriptionString(void); diff --git a/src/solvespace.h b/src/solvespace.h index c52733a..efc9c2b 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -357,6 +357,10 @@ void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u double ssglStrWidth(const std::string &str, double h); double ssglStrHeight(double h); void ssglLockColorTo(RgbaColor rgb); +void ssglStippledLine(Vector a, Vector b, double width, + int stippleType, double stippleScale); +void ssglStippledLine(Vector a, Vector b, double width, + const char *stipplePattern, double stippleScale); void ssglFatLine(Vector a, Vector b, double width); void ssglUnlockColor(void); void ssglColorRGB(RgbaColor rgb); diff --git a/src/style.cpp b/src/style.cpp index d18e9a0..0bb16c8 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -78,17 +78,19 @@ void Style::CreateDefaultStyle(hStyle h) { void Style::FillDefaultStyle(Style *s, const Default *d) { if(d == NULL) d = &Defaults[0]; - s->color = CnfThawColor(d->color, CnfColor(d->cnfPrefix)); - s->width = CnfThawFloat((float)(d->width), CnfWidth(d->cnfPrefix)); - s->widthAs = UNITS_AS_PIXELS; - s->textHeight = DEFAULT_TEXT_HEIGHT; - s->textHeightAs = UNITS_AS_PIXELS; - s->textOrigin = 0; - s->textAngle = 0; - s->visible = true; - s->exportable = true; - s->filled = false; - s->fillColor = RGBf(0.3, 0.3, 0.3); + s->color = CnfThawColor(d->color, CnfColor(d->cnfPrefix)); + s->width = CnfThawFloat((float)(d->width), CnfWidth(d->cnfPrefix)); + s->widthAs = UNITS_AS_PIXELS; + s->textHeight = DEFAULT_TEXT_HEIGHT; + s->textHeightAs = UNITS_AS_PIXELS; + s->textOrigin = 0; + s->textAngle = 0; + s->visible = true; + s->exportable = true; + s->filled = false; + s->fillColor = RGBf(0.3, 0.3, 0.3); + s->stippleType = Style::STIPPLE_CONTINUOUS; + s->stippleScale = 15.0; } void Style::LoadFactoryDefaults(void) { @@ -96,18 +98,20 @@ void Style::LoadFactoryDefaults(void) { for(d = &(Defaults[0]); d->h.v; d++) { Style *s = Get(d->h); - s->color = d->color; - s->width = d->width; - s->widthAs = UNITS_AS_PIXELS; - s->textHeight = DEFAULT_TEXT_HEIGHT; - s->textHeightAs = UNITS_AS_PIXELS; - s->textOrigin = 0; - s->textAngle = 0; - s->visible = true; - s->exportable = true; - s->filled = false; - s->fillColor = RGBf(0.3, 0.3, 0.3); - s->name = CnfPrefixToName(d->cnfPrefix); + s->color = d->color; + s->width = d->width; + s->widthAs = UNITS_AS_PIXELS; + s->textHeight = DEFAULT_TEXT_HEIGHT; + s->textHeightAs = UNITS_AS_PIXELS; + s->textOrigin = 0; + s->textAngle = 0; + s->visible = true; + s->exportable = true; + s->filled = false; + s->fillColor = RGBf(0.3, 0.3, 0.3); + s->stippleType = Style::STIPPLE_CONTINUOUS; + s->stippleScale = 15.0; + s->name = CnfPrefixToName(d->cnfPrefix); } SS.backgroundColor = RGBi(0, 0, 0); if(SS.bgImage.fromFile) MemFree(SS.bgImage.fromFile); @@ -316,6 +320,21 @@ hStyle Style::ForEntity(hEntity he) { return hs; } +int Style::PatternType(hStyle hs) { + Style *s = Get(hs); + return s->stippleType; +} + +double Style::StippleScaleMm(hStyle hs) { + Style *s = Get(hs); + if(s->widthAs == UNITS_AS_MM) { + return s->stippleScale; + } else if(s->widthAs == UNITS_AS_PIXELS) { + return s->stippleScale / SS.GW.scale; + } + return 1.0; +} + std::string Style::DescriptionString(void) { if(name.empty()) { return ssprintf("s%03x-(unnamed)", h.v); @@ -499,11 +518,42 @@ void TextWindow::ScreenDeleteStyle(int link, uint32_t v) { InvalidateGraphics(); } -void TextWindow::ScreenChangeStyleWidthOrTextHeight(int link, uint32_t v) { +void TextWindow::ScreenChangeStylePatternType(int link, uint32_t v) { hStyle hs = { v }; Style *s = Style::Get(hs); - double val = (link == 't') ? s->textHeight : s->width; - int units = (link == 't') ? s->textHeightAs : s->widthAs; + s->stippleType = link - 1; +} + +void TextWindow::ScreenChangeStyleMetric(int link, uint32_t v) { + hStyle hs = { v }; + Style *s = Style::Get(hs); + double val; + int units, meaning, 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: oops(); + } std::string edit_value; if(units == Style::UNITS_AS_PIXELS) { @@ -511,12 +561,9 @@ void TextWindow::ScreenChangeStyleWidthOrTextHeight(int link, uint32_t v) { } else { edit_value = SS.MmToString(val); } - int col = 9; - if(link == 't') col++; SS.TW.ShowEditControl(col, edit_value); SS.TW.edit.style = hs; - SS.TW.edit.meaning = (link == 't') ? EDIT_STYLE_TEXT_HEIGHT : - EDIT_STYLE_WIDTH; + SS.TW.edit.meaning = meaning; } void TextWindow::ScreenChangeStyleTextAngle(int link, uint32_t v) { @@ -557,12 +604,14 @@ void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) { if(s->widthAs != Style::UNITS_AS_MM) { s->widthAs = Style::UNITS_AS_MM; s->width /= SS.GW.scale; + s->stippleScale /= SS.GW.scale; } break; case 'W': if(s->widthAs != Style::UNITS_AS_PIXELS) { s->widthAs = Style::UNITS_AS_PIXELS; s->width *= SS.GW.scale; + s->stippleScale *= SS.GW.scale; } break; @@ -627,6 +676,7 @@ void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) { bool TextWindow::EditControlDoneForStyles(const char *str) { Style *s; switch(edit.meaning) { + case EDIT_STYLE_STIPPLE_PERIOD: case EDIT_STYLE_TEXT_HEIGHT: case EDIT_STYLE_WIDTH: { SS.UndoRemember(); @@ -643,6 +693,8 @@ bool TextWindow::EditControlDoneForStyles(const char *str) { v = max(0.0, v); if(edit.meaning == EDIT_STYLE_TEXT_HEIGHT) { s->textHeight = v; + } else if(edit.meaning == EDIT_STYLE_STIPPLE_PERIOD) { + s->stippleScale = v; } else { s->width = v; } @@ -728,20 +780,32 @@ void TextWindow::ShowStyleInfo(void) { if(s->widthAs == Style::UNITS_AS_PIXELS) { Printf(false, " %Ftwidth%E %@ %D%f%Lp%Fl[change]%E", s->width, - s->h.v, &ScreenChangeStyleWidthOrTextHeight, + 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, &ScreenChangeStyleWidthOrTextHeight, + s->h.v, &ScreenChangeStyleMetric, (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W'); } + if(s->h.v >= Style::FIRST_CUSTOM) { + if(s->widthAs == Style::UNITS_AS_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::UNITS_AS_PIXELS); if(s->h.v < Style::FIRST_CUSTOM) { - Printf(false,"%Ba %Ftin units of %Fdpixels%E"); + Printf(false," %Ftin units of %Fdpixels%E"); } else { - Printf(false,"%Ba %Ftin units of %Fd" + Printf(false," %Ftin units of %Fd" "%D%f%LW%s pixels%E " "%D%f%Lw%s %s", s->h.v, &ScreenChangeStyleYesNo, @@ -751,6 +815,45 @@ void TextWindow::ShowStyleInfo(void) { SS.UnitName()); } + if(s->h.v >= Style::FIRST_CUSTOM) { + Printf(false,"%Ba %Ftstipple type:%E"); + + const size_t patternCount = Style::LAST_STIPPLE + 1; + const char *patternsSource[patternCount] = { + "___________", + "- - - - - -", + "__ __ __ __", + "-.-.-.-.-.-", + "..-..-..-..", + "...........", + "~~~~~~~~~~~", + "__~__~__~__" + }; + std::string patterns[patternCount]; + + for(int i = 0; i <= Style::LAST_STIPPLE; 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: oops(); + } + } while(*(++str)); + } + + for(int i = 0; i <= Style::LAST_STIPPLE; i++) { + const char *radio = s->stippleType == 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 @@ -775,12 +878,12 @@ void TextWindow::ShowStyleInfo(void) { if(s->textHeightAs == Style::UNITS_AS_PIXELS) { Printf(false, "%Ba %Ftheight %E%@ %D%f%Lt%Fl%s%E", s->textHeight, - s->h.v, &ScreenChangeStyleWidthOrTextHeight, + s->h.v, &ScreenChangeStyleMetric, chng); } else { Printf(false, "%Ba %Ftheight %E%s %D%f%Lt%Fl%s%E", SS.MmToString(s->textHeight).c_str(), - s->h.v, &ScreenChangeStyleWidthOrTextHeight, + s->h.v, &ScreenChangeStyleMetric, chng); } diff --git a/src/ui.h b/src/ui.h index 8dfbea9..719b595 100644 --- a/src/ui.h +++ b/src/ui.h @@ -171,6 +171,7 @@ public: EDIT_STYLE_NAME = 505, EDIT_BACKGROUND_COLOR = 506, EDIT_BACKGROUND_IMG_SCALE = 507, + EDIT_STYLE_STIPPLE_PERIOD = 508, // For paste transforming EDIT_PASTE_TIMES_REPEATED = 600, EDIT_PASTE_ANGLE = 601, @@ -260,6 +261,7 @@ public: static void ScreenShowListOfStyles(int link, uint32_t v); static void ScreenShowStyleInfo(int link, uint32_t v); static void ScreenDeleteStyle(int link, uint32_t v); + static void ScreenChangeStylePatternType(int link, uint32_t v); static void ScreenChangeStyleYesNo(int link, uint32_t v); static void ScreenCreateCustomStyle(int link, uint32_t v); static void ScreenLoadFactoryDefaultStyles(int link, uint32_t v); @@ -309,7 +311,7 @@ public: static void ScreenChangeGCodeParameter(int link, uint32_t v); static void ScreenChangeAutosaveInterval(int link, uint32_t v); static void ScreenChangeStyleName(int link, uint32_t v); - static void ScreenChangeStyleWidthOrTextHeight(int link, uint32_t v); + static void ScreenChangeStyleMetric(int link, uint32_t v); static void ScreenChangeStyleTextAngle(int link, uint32_t v); static void ScreenChangeStyleColor(int link, uint32_t v); static void ScreenChangeBackgroundColor(int link, uint32_t v);