Most of these were just converting char* into std::string back and
forth; some more used ReadUTF8, which was converted to use nicer
STL-style iterators over UTF-8 text.
The remaining ones are:
* arguments to Expr::From, which we'll change when refactoring
the expression lexer;
* arguments to varargs functions, which we'll change when adding
localization (that requires custom printf-style functions to
allow for changing argument order);
* arguments where only string literals are ever passed, which
are OK;
* in platform-specific code, which is OK.
After commit 2f734d9, inactive groups are no longer regenerated
for trivial changes, e.g. changing parameters, so it's possible to
switch to an earlier group and work on it without incurring
the computational (slowdown) and cognitive (annoyance by red
background) overhead of later groups failing to solve.
However, if a group--any group anywhere--was not solved OK,
the interface reacted accordingly, which diminished usefulness of
the change, especially given that, if we have groups A and B with
B depending on A, if B is broken by a change in A and we activate A
and fix it, B will not be regenerated.
After this commit, only active groups are considered when deciding
if generating the entire sketch would fail.
This font is less complete than our bitmap font, Unifont: Unifont
has essentially complete Unicode coverage and LibreCAD's font only
has Latin, Cyrillic and Japanese, but it can be extended rather
easily, so this should be fine for now.
These embedded fonts fatten glhelper.o quite a bit:
bitmapfont.table.h is about 8M in gzip-compressed bitmaps and
vectorfont.table.h is about 2M in raw vector data.
In spite of that it takes just around five seconds to build
glhelper.c on my laptop, so it should be fine.
The final executable grows from about 2M to about 8M, but this
is a small price to pay for fairly extensive i18n support.
The new font has somewhat different metrics, so the rendering
code has been fudged to make it look good.
Benefits:
* Much simpler code.
* Handles the entire TTF spec, not just a small subset that
only really worked well on Windows fonts.
* Handles all character sets as well as accented characters.
* Much faster parsing, since Freetype lazily loads and
caches glyphs.
* Support for basically every kind of font that was invented,
not just TTF.
Note that OpenType features, e.g. ligatures, are not yet supported.
This means that Arabic and Devanagari scripts, among others, will
not be rendered in their proper form.
RTL scripts are not supported either, neither in TTF nor in
the text window. Adding RTL support is comparatively easy, but
given that Arabic would not be legibly rendered anyway, this is not
done so far.
Commit 89eb208 has improved the overall situation with chord
tolerance, but it changed the display chord tolerance to use
an absolute value in millimeters as a stopgap measure.
This commit changes the display chord tolerance to be specified
in percents of entity bounding box instead of millimeters.
As a result, the linearized curves are both zoom level and sketch
scale independent.
In order to compute the bounding box, all entities are generated
twice. However, this shouldn't result in a noticeable slowdown,
since the bounding box calculation does not need the expensive
triangle mesh generation and the solver will converge immediately
on the second run.
Since the meaning of the preference has changed, a new name is
used (ChordTolerancePct instead of ChordTolerance), so that it
would be reset to the default value after updating SolveSpace.
The default value, 0.5%, was selected using trial and error by
judging whether cylinders of moderate dimensions were looking
aesthetically pleasing enough.
After this change, the only real function of the spacebar
shortcut is to reload imported groups, since manual regeneration
should not change anything anymore unless there is a bug.
Before this commit, a single chord tolerance was used for both
displaying and exporting geometry. Moreover, this chord tolerance
was specified in screen pixels, and as such depended on zoom level.
This was inconvenient: exporting geometry with a required level of
precision required awkward manipulations of viewport. Moreover,
since some operations, e.g. mesh watertightness checking, were done
on triangle meshes which are generated differently depending on
the zoom level, these operations could report wildly different
and quite confusing results depending on zoom level.
The chord tolerance for display and export pursue completely distinct
goals: display chord tolerance should be set high enough to achieve
both fast regeneration and legible rendering, whereas export chord
tolerance should be set to match the dimension tolerance of
the fabrication process.
This commit introduces two distinct chord tolerances: a display
and an export one. Both chord tolerances are absolute and expressed
in millimeters; this is inappropriate for display purposes but
will be fixed in the next commits.
After exporting, the geometry is redrawn with the chord tolerance
configured for the export and an overlay message is displayed;
pressing Esc clears the message and returns the display back to
normal.
When a solver error arises after a change to the sketch, it should
be easy to understand exactly why it happened. Before this change,
two functionally distinct modes of failure were lumped into one:
the same "redundant constraints" message was displayed when all
degrees of freedom were exhausted and the had a solution, but also
when it had not.
To understand why this is problematic, let's examine several ways
in which we can end up with linearly dependent equations in our
system:
0) create a triangle, then constrain two different pairs of edges
to be perpendicular
1) add two distinct distance constraints on the same segment
2) add two identical distance constraints on the same segment
3) create a triangle, then constrain edges to lengths a, b, and c
so that a+b=c
The case (0) is our baseline case: the constraints in it make
the system unsolvable yet they do not remove more degrees of freedom
than the amount we started with. So the displayed error is
"unsolvable constraints".
The constraints in case (1) remove one too many degrees of freedom,
but otherwise are quite like the case (0): the cause of failure that
is useful to the user is that the constraints are mutually
incompatible.
The constraints in cases (2) and (3) however are not like the others:
there is a set of parameters that satisfies all of the constraints,
but the constraints still remove one degree of freedom too many.
It makes sense to display a different error message for cases (2)
and (3) because in practice, cases like this are likely to arise from
adjustment of constraint values on sketches corresponding to systems
that have a small amount of degenerate solutions, and this is very
different from systems arising in cases like (0) where no adjustment
of constraint values will ever result in a successful solution.
So the error message displayed is "redundant constraints".
At last, this commit makes cases (0) and (1) display a message
with only a minor difference in wording. This is deliberate.
The reason is that the facts "the system is unsolvable" and
"the system is unsolvable and also has linearly dependent equations"
present no meaningful, actionable difference to the user, and placing
emphasis on it would only cause confusion.
However, they are still distinguished, because in case (0) we
list all relevant constraints (and thus we say they are "mutually
incompatible") but in case (1) we only list the ones that constrain
the sketch further than some valid solution (and we say they are
"unsatisfied").
The current messages accurately reflect what happens to the system
of equations that represents the sketch, but can be quite confusing
to users that only think in terms of the constraints.
We use "unsolvable" and not "impossible" because while most of
the cases that result in this error message will indeed stem from
mutually exclusive sets of constraints, it is still possible that
there is some solution that our solver is unable to find using
numeric methods.
Most people just want a single self-contained .html file, but more
advanced usage will involve embedding in a webpage, where the default
viewer would be copied and customized, and fed with bare mesh export.
It works. Mostly. Sort of. Only on Windows fonts. Sometimes it
randomly refuses to render glyphs (try `х`, that's not a latin ex).
I'm not really sure why, the logic seems right.
Why do we have a homegrown TTF parser anyway? It's kind of awful.
It breaks on any slightly unusual input. It plows through UTF-16BE
font names like a nuclear-powered steamroller. It outright ignores
composite glyphs (is that why it's broken this time?). The kerning
is seizure-inducing. It ignores any characters outside BMP by design.
Maybe we should just replace it with freetype.
This removes the arbitrary 64 byte restriction (which effectively
limits us to as little as 16 Unicode characters with CJK encodings),
makes classes smaller, and is easier to use.
As a consequence of making the length of all ex-NameStr fields
unbounded, all functions that returned a buffer derived from those
were changed to return std::string. Then, functions that are
contextually similar to the ones described above were changed
to return std::string. Then, functions that now happened to mostly
take an std::string argument converted to a C string were changed
to accept std::string.
This has produced a bit of churn, but is probably for the better.
Now it is possible to give non-ASCII names to groups
as well as see non-ASCII filenames of imported files.
In the future this makes localization possible.
This works for LTR languages, such as European and CJK,
but not RTL such as Arabic. Does Arabic even exist in
monospaced form? I have no idea.
After this commit, SolveSpace can robustly handle non-ASCII filenames
on every OS. Additionally, on Windows, filenames longer than 260
characeters can be used, and files on network shares can be opened
directly, without mounting them as a network drive.
After this commit, SolveSpace always uses UTF-8 strings internally
(represented as char* and std::string) on every OS, for everything:
UI labels, paths and user input. OS X always uses UTF-8; on Windows,
strings are converted at boundary; and on Linux/BSD/etc SolveSpace
refuses to start unless the current locale is UTF-8. This will
negatively affect literally no one.
This commit does not bring forth any user-visible improvement yet;
in order to correctly handle Unicode in filenames, it is still
necessary to change the fopen calls to _wfopen on Windows.
After this commit, SolveSpace deals with paths as follows:
* Paths are generally treated as opaque platform-specific strings.
This helps on Linux, because paths on Linux don't have any
specific encoding and it helps to avoid any operations on them.
* The UI in some places wants to get a basename. In this case,
the newly introduced PATH_SEP is used. This allows to treat
backslash as a regular character, which it is on Linux and OS X.
* The only place where any nontrivial operations on paths are
performed is the g->impFile/impFileRel logic.
Specifically, when saved, g->impFile always contains an absolute
path with separators of the current platform, and g->impFileRel
always contains a relative path with UNIX separators. This allows
to treat backslash as a regular character.
Old files will contain g->impFileRel with Windows separators;
these are detected by looking for a drive letter in g->impFile
and in that case mapping Windows separators to UNIX ones.
There is no need to treat UNIX separators (forward slashes) in
any special way on Windows because there is no way on Windows,
not even via UNC paths, to create or address a directory entry
with a forward slash in its name.
What do we gain from this? Several things.
* First, usage of PATH_MAX (the POSIX constant) is eliminated.
PATH_MAX is actually a lie; Linux and OS X (and probably other BSDs
too) do not have an actual path length limit. Linux claims 4096,
OS X claims 1024, but it is trivial to construct paths that are
longer.
* Second, while Windows does enforce a limit of MAX_PATH (the Win32
constant) for its ASCII functions, the Unicode variants, when
used with UNC paths, do not have this restriction.
The capability to use UNC paths is useful by itself, as it allows
to access files on network shares directly.
* Third, representing paths as std::string will make it easier to
interoperate with *W WinAPI functions later.
On Windows, freeze.{cpp,h} was factored into w32main.cpp.
The old implementation was too redundant and leaked registry
key handles.
On all platforms, Cnf* functions now use std::string.
This simplifies code everywhere, but will be particularly useful
when the Windows port switches to the *W WinAPI functions.
This is necessary to store non-POD classes in these containers.
Note that List and IdList do not use MemRealloc anymore;
this is necessarily (slightly) less efficient, but is the right
semantics, as you cannot just move non-POD types, e.g. std::string,
around in memory. All STL containers provide the same guarantees
and share the performance hit.
The slowdown is mostly mitigated by moving the contained objects,
so that no additional heap allocations or copies occur beyond
that of the object itself.
This will allow us to use non-POD classes inside these objects
in future and is otherwise functionally equivalent, as well
as more concise.
Note that there are some subtleties with handling of
brace-initialization. Specifically:
On aggregates (e.g. simple C-style structures) using an empty
brace-initializer zero-initializes the aggregate, i.e. it makes
all members zero.
On non-aggregates an empty brace-initializer calls the default
constructor. And if the constructor doesn't explicitly initialize
the members (which the auto-generated constructor doesn't) then
the members will be constructed but otherwise uninitialized.
So, what is an aggregate class? To quote the C++ standard
(C++03 8.5.1 §1):
An aggregate is an array or a class (clause 9) with no
user-declared constructors (12.1), no private or protected
non-static data members (clause 11), no base classes (clause 10),
and no virtual functions (10.3).
In SolveSpace, we only have to handle the case of base classes;
Constraint and Entity have those. Thus, they had to gain a default
constructor that does nothing but initializes the members to zero.
The main benefit is that std::swap will ensure that the type
of arguments is copy-constructible and move-constructible.
It is more concise as well.
When min and max are defined as macros, they will conflict
with STL header files included by other C++ libraries;
in this case STL will #undef any other definition.
Some extra code is necessary to determine that the back faces
should not be drawn in red for transparent solids. It is expected
that the user will first ensure that the shell is watertight
and then set the opacity; back faces are still drawn if
the opacity is exactly 1.
The savefile format is changed backwards-compatibly by stashing
the alpha value in uppermost byte of 4-byte hex color value
in Surface and Triangle clauses. The existing files have 00
in the high byte, so RgbColor::FromPackedInt treats that
as "opaque".
This is required to avoid name conflicts with the Cocoa libraries
on OS X.
I renamed the `class SolveSpace` to `class SolveSpaceUI`, because
that's what it does, and because otherwise the namespace would
have to be called something else than `namespace SolveSpace`.
In principle, GTK3 is the way forward, and GTK2 is officially
deprecated, though still maintained. In practice however, GTK3
is often unbearably buggy; e.g. on my system, combo boxes
don't ever roll up in GTK3 windows. So I have added support
for both.
This required a few minor changes to the core, namely:
* GTK wants to know beforehand whether a menu item is a check
menu item or a regular one.
* GTK doesn't give us an easy way to execute something after
any event is processed, so an explicit idle timer is added.
This is a no-op on Win32.
* A few function signatures were const'ed, since GTK expects
immutable strings when converting to Glib::ustring.
However, don't use ssglLineWidth for UI drawing operations.
These only draw horizontal or vertical lines that don't need to
be antialiased, and thus don't require the workaround. In fact
the workaround would make them thicker than needed.
Writing to NULL is undefined behavior and it is legal for the
compiler to simply remove it; clang will do so. abort()
or __builtin_trap() would produce the desirable result.
abort() is used as it is more portable.
The SolveSpace top-level directory was getting a bit cluttered, so
following the example of numerous other free-software projects, we move the
main application source into a subdirectory and adjust the build systems
accordingly.
Also, got rid of the obj/ directory in favor of creating it on the fly in
Makefile.msvc.