Add ability to represent our surfaces as either a shell or a mesh,
according to the user's preference. I templated the housekeeping stuff for Boolean operations and step and repeat, so it's relatively clean. Still need to add the stuff to make a mesh vertex-to-vertex, and to export sections of a mesh. [git-p4: depot-paths = "//depot/solvespace/": change = 1959]
This commit is contained in:
parent
03ecbad981
commit
ddbd0ff77b
2
draw.cpp
2
draw.cpp
|
@ -1026,7 +1026,7 @@ void GraphicsWindow::Paint(int w, int h) {
|
|||
// And the naked edges, if the user did Analyze -> Show Naked Edges.
|
||||
glLineWidth(7);
|
||||
glColor3d(1, 0, 0);
|
||||
glxDrawEdges(&(SS.nakedEdges));
|
||||
glxDrawEdges(&(SS.nakedEdges), true);
|
||||
|
||||
// Then redraw whatever the mouse is hovering over, highlighted.
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
|
|
@ -7,7 +7,7 @@ void SolveSpace::ExportSectionTo(char *filename) {
|
|||
|
||||
Group *g = SK.GetGroup(SS.GW.activeGroup);
|
||||
g->GenerateDisplayItems();
|
||||
if(g->displayMesh.l.n == 0) {
|
||||
if(g->displayMesh.IsEmpty()) {
|
||||
Error("No solid model present; draw one with extrudes and revolves, "
|
||||
"or use Export 2d View to export bare lines and curves.");
|
||||
return;
|
||||
|
@ -100,7 +100,7 @@ void SolveSpace::ExportViewTo(char *filename) {
|
|||
g->GenerateDisplayItems();
|
||||
sm = &(g->displayMesh);
|
||||
}
|
||||
if(sm->l.n == 0) {
|
||||
if(sm->IsEmpty()) {
|
||||
sm = NULL;
|
||||
}
|
||||
|
||||
|
@ -979,7 +979,7 @@ void HpglFileWriter::FinishAndCloseFile(void) {
|
|||
//-----------------------------------------------------------------------------
|
||||
void SolveSpace::ExportMeshTo(char *filename) {
|
||||
SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh);
|
||||
if(m->l.n == 0) {
|
||||
if(m->IsEmpty()) {
|
||||
Error("Active group mesh is empty; nothing to export.");
|
||||
return;
|
||||
}
|
||||
|
|
17
glhelper.cpp
17
glhelper.cpp
|
@ -293,7 +293,7 @@ void glxDebugPolygon(SPolygon *p)
|
|||
}
|
||||
}
|
||||
|
||||
void glxDrawEdges(SEdgeList *el)
|
||||
void glxDrawEdges(SEdgeList *el, bool endpointsToo)
|
||||
{
|
||||
SEdge *se;
|
||||
glBegin(GL_LINES);
|
||||
|
@ -302,13 +302,16 @@ void glxDrawEdges(SEdgeList *el)
|
|||
glxVertex3v(se->b);
|
||||
}
|
||||
glEnd();
|
||||
glPointSize(12);
|
||||
glBegin(GL_POINTS);
|
||||
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
|
||||
glxVertex3v(se->a);
|
||||
glxVertex3v(se->b);
|
||||
|
||||
if(endpointsToo) {
|
||||
glPointSize(12);
|
||||
glBegin(GL_POINTS);
|
||||
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
|
||||
glxVertex3v(se->a);
|
||||
glxVertex3v(se->b);
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
|
||||
void glxDebugMesh(SMesh *m)
|
||||
|
|
188
groupmesh.cpp
188
groupmesh.cpp
|
@ -51,15 +51,35 @@ void Group::GenerateLoops(void) {
|
|||
}
|
||||
}
|
||||
|
||||
void Group::GenerateShellForStepAndRepeat(void) {
|
||||
Group *src = SK.GetGroup(opA);
|
||||
SShell *srcs = &(src->thisShell); // the shell to step and repeat
|
||||
void SShell::RemapFaces(Group *g, int remap) {
|
||||
SSurface *ss;
|
||||
for(ss = surface.First(); ss; ss = surface.NextAfter(ss)){
|
||||
hEntity face = { ss->face };
|
||||
if(face.v == Entity::NO_ENTITY.v) continue;
|
||||
|
||||
SShell workA, workB;
|
||||
face = g->Remap(face, remap);
|
||||
ss->face = face.v;
|
||||
}
|
||||
}
|
||||
|
||||
void SMesh::RemapFaces(Group *g, int remap) {
|
||||
STriangle *tr;
|
||||
for(tr = l.First(); tr; tr = l.NextAfter(tr)) {
|
||||
hEntity face = { tr->meta.face };
|
||||
if(face.v == Entity::NO_ENTITY.v) continue;
|
||||
|
||||
face = g->Remap(face, remap);
|
||||
tr->meta.face = face.v;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Group::GenerateForStepAndRepeat(T *prevs, T *steps, T *outs, int how) {
|
||||
T workA, workB;
|
||||
ZERO(&workA);
|
||||
ZERO(&workB);
|
||||
SShell *soFar = &workA, *scratch = &workB;
|
||||
soFar->MakeFromCopyOf(&(src->PreviousGroup()->runningShell));
|
||||
T *soFar = &workA, *scratch = &workB;
|
||||
soFar->MakeFromCopyOf(prevs);
|
||||
|
||||
int n = (int)valA, a0 = 0;
|
||||
if(subtype == ONE_SIDED && skipFirst) {
|
||||
|
@ -70,13 +90,12 @@ void Group::GenerateShellForStepAndRepeat(void) {
|
|||
int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1));
|
||||
int remap = (a == (n - 1)) ? REMAP_LAST : a;
|
||||
|
||||
SShell transd;
|
||||
T transd;
|
||||
ZERO(&transd);
|
||||
if(type == TRANSLATE) {
|
||||
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
|
||||
trans = trans.ScaledBy(ap);
|
||||
Quaternion q = Quaternion::From(1, 0, 0, 0);
|
||||
transd.MakeFromTransformationOf(srcs, trans, q);
|
||||
transd.MakeFromTransformationOf(steps, trans, Quaternion::IDENTITY);
|
||||
} else {
|
||||
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
|
||||
double theta = ap * SK.GetParam(h.param(3))->val;
|
||||
|
@ -84,43 +103,88 @@ void Group::GenerateShellForStepAndRepeat(void) {
|
|||
Vector axis = Vector::From(h.param(4), h.param(5), h.param(6));
|
||||
Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z);
|
||||
// Rotation is centered at t; so A(x - t) + t = Ax + (t - At)
|
||||
transd.MakeFromTransformationOf(srcs,
|
||||
transd.MakeFromTransformationOf(steps,
|
||||
trans.Minus(q.Rotate(trans)), q);
|
||||
}
|
||||
|
||||
// We need to rewrite any plane face entities to the transformed ones.
|
||||
SSurface *ss;
|
||||
for(ss = transd.surface.First(); ss; ss = transd.surface.NextAfter(ss)){
|
||||
hEntity face = { ss->face };
|
||||
if(face.v == Entity::NO_ENTITY.v) continue;
|
||||
transd.RemapFaces(this, remap);
|
||||
|
||||
face = Remap(face, remap);
|
||||
ss->face = face.v;
|
||||
}
|
||||
|
||||
if(src->meshCombine == COMBINE_AS_DIFFERENCE) {
|
||||
if(how == COMBINE_AS_DIFFERENCE) {
|
||||
scratch->MakeFromDifferenceOf(soFar, &transd);
|
||||
} else if(src->meshCombine == COMBINE_AS_UNION) {
|
||||
} else if(how == COMBINE_AS_UNION) {
|
||||
scratch->MakeFromUnionOf(soFar, &transd);
|
||||
} else {
|
||||
scratch->MakeFromAssemblyOf(soFar, &transd);
|
||||
}
|
||||
SWAP(SShell *, scratch, soFar);
|
||||
SWAP(T *, scratch, soFar);
|
||||
|
||||
scratch->Clear();
|
||||
transd.Clear();
|
||||
}
|
||||
|
||||
runningShell.Clear();
|
||||
runningShell = *soFar;
|
||||
outs->Clear();
|
||||
*outs = *soFar;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Group::GenerateForBoolean(T *prevs, T *thiss, T *outs) {
|
||||
// If this group contributes no new mesh, then our running mesh is the
|
||||
// same as last time, no combining required. Likewise if we have a mesh
|
||||
// but it's suppressed.
|
||||
if(thiss->IsEmpty() || suppress) {
|
||||
outs->MakeFromCopyOf(prevs);
|
||||
return;
|
||||
}
|
||||
|
||||
// So our group's shell appears in thisShell. Combine this with the
|
||||
// previous group's shell, using the requested operation.
|
||||
if(meshCombine == COMBINE_AS_UNION) {
|
||||
outs->MakeFromUnionOf(prevs, thiss);
|
||||
} else if(meshCombine == COMBINE_AS_DIFFERENCE) {
|
||||
outs->MakeFromDifferenceOf(prevs, thiss);
|
||||
} else {
|
||||
outs->MakeFromAssemblyOf(prevs, thiss);
|
||||
}
|
||||
}
|
||||
|
||||
void Group::GenerateShellAndMesh(void) {
|
||||
thisShell.Clear();
|
||||
thisMesh.Clear();
|
||||
runningShell.Clear();
|
||||
runningMesh.Clear();
|
||||
|
||||
if(type == TRANSLATE || type == ROTATE) {
|
||||
GenerateShellForStepAndRepeat();
|
||||
goto done;
|
||||
Group *src = SK.GetGroup(opA);
|
||||
Group *pg = src->PreviousGroup();
|
||||
|
||||
if(src->thisMesh.IsEmpty() && pg->runningMesh.IsEmpty() && !forceToMesh)
|
||||
{
|
||||
SShell *toStep = &(src->thisShell),
|
||||
*prev = &(pg->runningShell);
|
||||
|
||||
GenerateForStepAndRepeat<SShell>
|
||||
(prev, toStep, &runningShell, src->meshCombine);
|
||||
} else {
|
||||
SMesh prevm, stepm;
|
||||
ZERO(&prevm);
|
||||
ZERO(&stepm);
|
||||
|
||||
prevm.MakeFromCopyOf(&(pg->runningMesh));
|
||||
pg->runningShell.TriangulateInto(&prevm);
|
||||
|
||||
stepm.MakeFromCopyOf(&(src->thisMesh));
|
||||
src->thisShell.TriangulateInto(&stepm);
|
||||
|
||||
GenerateForStepAndRepeat<SMesh>
|
||||
(&prevm, &stepm, &runningMesh, src->meshCombine);
|
||||
|
||||
stepm.Clear();
|
||||
prevm.Clear();
|
||||
}
|
||||
|
||||
displayDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(type == EXTRUDE) {
|
||||
|
@ -205,51 +269,38 @@ void Group::GenerateShellAndMesh(void) {
|
|||
SK.GetParam(h.param(5))->val,
|
||||
SK.GetParam(h.param(6))->val };
|
||||
|
||||
for(int i = 0; i < impMesh.l.n; i++) {
|
||||
STriangle st = impMesh.l.elem[i];
|
||||
|
||||
if(st.meta.face != 0) {
|
||||
hEntity he = { st.meta.face };
|
||||
st.meta.face = Remap(he, 0).v;
|
||||
}
|
||||
st.a = q.Rotate(st.a).Plus(offset);
|
||||
st.b = q.Rotate(st.b).Plus(offset);
|
||||
st.c = q.Rotate(st.c).Plus(offset);
|
||||
}
|
||||
thisMesh.MakeFromTransformationOf(&impMesh, offset, q);
|
||||
thisMesh.RemapFaces(this, 0);
|
||||
|
||||
thisShell.MakeFromTransformationOf(&impShell, offset, q);
|
||||
SSurface *srf;
|
||||
IdList<SSurface,hSSurface> *sl = &(thisShell.surface);
|
||||
for(srf = sl->First(); srf; srf = sl->NextAfter(srf)) {
|
||||
if(srf->face != 0) {
|
||||
hEntity he = { srf->face };
|
||||
srf->face = Remap(he, 0).v;
|
||||
}
|
||||
}
|
||||
thisShell.RemapFaces(this, 0);
|
||||
}
|
||||
|
||||
runningShell.Clear();
|
||||
// So now we've got the mesh or shell for this group. Combine it with
|
||||
// the previous group's mesh or shell with the requested Boolean, and
|
||||
// we're done.
|
||||
|
||||
// If this group contributes no new mesh, then our running mesh is the
|
||||
// same as last time, no combining required. Likewise if we have a mesh
|
||||
// but it's suppressed.
|
||||
if(suppress) {
|
||||
runningShell.MakeFromCopyOf(&(PreviousGroup()->runningShell));
|
||||
goto done;
|
||||
}
|
||||
|
||||
// So our group's shell appears in thisShell. Combine this with the
|
||||
// previous group's shell, using the requested operation.
|
||||
SShell *a = &(PreviousGroup()->runningShell);
|
||||
if(meshCombine == COMBINE_AS_UNION) {
|
||||
runningShell.MakeFromUnionOf(a, &thisShell);
|
||||
} else if(meshCombine == COMBINE_AS_DIFFERENCE) {
|
||||
runningShell.MakeFromDifferenceOf(a, &thisShell);
|
||||
Group *pg = PreviousGroup();
|
||||
if(pg->runningMesh.IsEmpty() && thisMesh.IsEmpty() && !forceToMesh) {
|
||||
SShell *prevs = &(pg->runningShell);
|
||||
GenerateForBoolean<SShell>(prevs, &thisShell, &runningShell);
|
||||
} else {
|
||||
runningShell.MakeFromAssemblyOf(a, &thisShell);
|
||||
SMesh prevm, thism;
|
||||
ZERO(&prevm);
|
||||
ZERO(&thism);
|
||||
|
||||
prevm.MakeFromCopyOf(&(pg->runningMesh));
|
||||
pg->runningShell.TriangulateInto(&prevm);
|
||||
|
||||
thism.MakeFromCopyOf(&thisMesh);
|
||||
thisShell.TriangulateInto(&thism);
|
||||
|
||||
GenerateForBoolean<SMesh>(&prevm, &thism, &runningMesh);
|
||||
|
||||
thism.Clear();
|
||||
prevm.Clear();
|
||||
}
|
||||
|
||||
done:
|
||||
displayDirty = true;
|
||||
}
|
||||
|
||||
|
@ -257,8 +308,19 @@ void Group::GenerateDisplayItems(void) {
|
|||
if(displayDirty) {
|
||||
displayMesh.Clear();
|
||||
runningShell.TriangulateInto(&displayMesh);
|
||||
STriangle *tr;
|
||||
for(tr = runningMesh.l.First(); tr; tr = runningMesh.l.NextAfter(tr)) {
|
||||
STriangle trn = *tr;
|
||||
Vector n = trn.Normal();
|
||||
trn.an = n;
|
||||
trn.bn = n;
|
||||
trn.cn = n;
|
||||
displayMesh.AddTriangle(&trn);
|
||||
}
|
||||
|
||||
displayEdges.Clear();
|
||||
runningShell.MakeEdgesInto(&displayEdges);
|
||||
|
||||
displayDirty = false;
|
||||
}
|
||||
}
|
||||
|
@ -314,7 +376,7 @@ void Group::Draw(void) {
|
|||
glxColor3d(REDf (SS.edgeColor),
|
||||
GREENf(SS.edgeColor),
|
||||
BLUEf (SS.edgeColor));
|
||||
glxDrawEdges(&displayEdges);
|
||||
glxDrawEdges(&displayEdges, false);
|
||||
}
|
||||
|
||||
if(SS.GW.showMesh) glxDebugMesh(&displayMesh);
|
||||
|
|
53
mesh.cpp
53
mesh.cpp
|
@ -206,7 +206,7 @@ void SMesh::AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3) {
|
|||
}
|
||||
}
|
||||
|
||||
void SMesh::MakeFromUnion(SMesh *a, SMesh *b) {
|
||||
void SMesh::MakeFromUnionOf(SMesh *a, SMesh *b) {
|
||||
SBsp3 *bspa = SBsp3::FromMesh(a);
|
||||
SBsp3 *bspb = SBsp3::FromMesh(b);
|
||||
|
||||
|
@ -219,7 +219,7 @@ void SMesh::MakeFromUnion(SMesh *a, SMesh *b) {
|
|||
AddAgainstBsp(a, bspb);
|
||||
}
|
||||
|
||||
void SMesh::MakeFromDifference(SMesh *a, SMesh *b) {
|
||||
void SMesh::MakeFromDifferenceOf(SMesh *a, SMesh *b) {
|
||||
SBsp3 *bspa = SBsp3::FromMesh(a);
|
||||
SBsp3 *bspb = SBsp3::FromMesh(b);
|
||||
|
||||
|
@ -232,40 +232,33 @@ void SMesh::MakeFromDifference(SMesh *a, SMesh *b) {
|
|||
AddAgainstBsp(a, bspb);
|
||||
}
|
||||
|
||||
bool SMesh::MakeFromInterferenceCheck(SMesh *srca, SMesh *srcb, SMesh *error) {
|
||||
SBsp3 *bspa = SBsp3::FromMesh(srca);
|
||||
SBsp3 *bspb = SBsp3::FromMesh(srcb);
|
||||
|
||||
error->Clear();
|
||||
error->flipNormal = true;
|
||||
error->keepCoplanar = false;
|
||||
|
||||
error->AddAgainstBsp(srcb, bspa);
|
||||
error->AddAgainstBsp(srca, bspb);
|
||||
// Now we have a list of all the triangles (or fragments thereof) from
|
||||
// A that lie inside B, or vice versa. That's the interference, and
|
||||
// we report it so that it can be flagged.
|
||||
|
||||
// For the actual output, take the union.
|
||||
flipNormal = false;
|
||||
keepCoplanar = false;
|
||||
AddAgainstBsp(srcb, bspa);
|
||||
|
||||
flipNormal = false;
|
||||
keepCoplanar = true;
|
||||
AddAgainstBsp(srca, bspb);
|
||||
|
||||
// And we're successful if the intersection was empty.
|
||||
return (error->l.n == 0);
|
||||
}
|
||||
|
||||
void SMesh::MakeFromCopy(SMesh *a) {
|
||||
void SMesh::MakeFromCopyOf(SMesh *a) {
|
||||
int i;
|
||||
for(i = 0; i < a->l.n; i++) {
|
||||
AddTriangle(&(a->l.elem[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void SMesh::MakeFromAssemblyOf(SMesh *a, SMesh *b) {
|
||||
MakeFromCopyOf(a);
|
||||
MakeFromCopyOf(b);
|
||||
}
|
||||
|
||||
void SMesh::MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q) {
|
||||
STriangle *tr;
|
||||
for(tr = a->l.First(); tr; tr = a->l.NextAfter(tr)) {
|
||||
STriangle tt = *tr;
|
||||
tt.a = (q.Rotate(tt.a)).Plus(trans);
|
||||
tt.b = (q.Rotate(tt.b)).Plus(trans);
|
||||
tt.c = (q.Rotate(tt.c)).Plus(trans);
|
||||
AddTriangle(&tt);
|
||||
}
|
||||
}
|
||||
|
||||
bool SMesh::IsEmpty(void) {
|
||||
return (l.n == 0);
|
||||
}
|
||||
|
||||
DWORD SMesh::FirstIntersectionWith(Point2d mp) {
|
||||
Vector p0 = Vector::From(mp.x, mp.y, 0);
|
||||
Vector gn = Vector::From(0, 0, 1);
|
||||
|
|
13
polygon.h
13
polygon.h
|
@ -184,10 +184,15 @@ public:
|
|||
void Simplify(int start);
|
||||
|
||||
void AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3);
|
||||
void MakeFromUnion(SMesh *a, SMesh *b);
|
||||
void MakeFromDifference(SMesh *a, SMesh *b);
|
||||
bool MakeFromInterferenceCheck(SMesh *srca, SMesh *srcb, SMesh *errorAt);
|
||||
void MakeFromCopy(SMesh *a);
|
||||
void MakeFromUnionOf(SMesh *a, SMesh *b);
|
||||
void MakeFromDifferenceOf(SMesh *a, SMesh *b);
|
||||
|
||||
void MakeFromCopyOf(SMesh *a);
|
||||
void MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q);
|
||||
void MakeFromAssemblyOf(SMesh *a, SMesh *b);
|
||||
|
||||
bool IsEmpty(void);
|
||||
void RemapFaces(Group *g, int remap);
|
||||
|
||||
DWORD FirstIntersectionWith(Point2d mp);
|
||||
};
|
||||
|
|
3
sketch.h
3
sketch.h
|
@ -208,9 +208,10 @@ public:
|
|||
void GenerateLoops(void);
|
||||
// And the mesh stuff
|
||||
Group *PreviousGroup(void);
|
||||
void GenerateShellForStepAndRepeat(void);
|
||||
void GenerateDisplayItems(void);
|
||||
void GenerateShellAndMesh(void);
|
||||
template<class T> void GenerateForStepAndRepeat(T *a, T *b, T *o, int how);
|
||||
template<class T> void GenerateForBoolean(T *a, T *b, T *o);
|
||||
void Draw(void);
|
||||
|
||||
SPolygon GetPolygon(void);
|
||||
|
|
|
@ -149,7 +149,7 @@ void vl(void); // debug function to validate heaps
|
|||
// End of platform-specific functions
|
||||
//================
|
||||
|
||||
|
||||
class Group;
|
||||
class SSurface;
|
||||
#include "dsc.h"
|
||||
#include "polygon.h"
|
||||
|
@ -175,7 +175,7 @@ void glxTesselatePolygon(GLUtesselator *gt, SPolygon *p);
|
|||
void glxFillPolygon(SPolygon *p);
|
||||
void glxFillMesh(int color, SMesh *m, DWORD h, DWORD s1, DWORD s2);
|
||||
void glxDebugPolygon(SPolygon *p);
|
||||
void glxDrawEdges(SEdgeList *l);
|
||||
void glxDrawEdges(SEdgeList *l, bool endpointsToo);
|
||||
void glxDebugMesh(SMesh *m);
|
||||
void glxMarkPolygonNormal(SPolygon *p);
|
||||
void glxWriteText(char *str);
|
||||
|
|
|
@ -666,6 +666,10 @@ void SShell::TriangulateInto(SMesh *sm) {
|
|||
}
|
||||
}
|
||||
|
||||
bool SShell::IsEmpty(void) {
|
||||
return (surface.n == 0);
|
||||
}
|
||||
|
||||
void SShell::Clear(void) {
|
||||
SSurface *s;
|
||||
for(s = surface.First(); s; s = surface.NextAfter(s)) {
|
||||
|
|
|
@ -319,6 +319,8 @@ public:
|
|||
void MakeEdgesInto(SEdgeList *sel);
|
||||
void MakeSectionEdgesInto(Vector n, double d,
|
||||
SEdgeList *sel, SBezierList *sbl);
|
||||
bool IsEmpty(void);
|
||||
void RemapFaces(Group *g, int remap);
|
||||
void Clear(void);
|
||||
};
|
||||
|
||||
|
|
|
@ -463,7 +463,7 @@ void TextWindow::ShowGroupInfo(void) {
|
|||
g->runningShell.surface.n > 0))
|
||||
{
|
||||
Group *pg = g->PreviousGroup();
|
||||
if(pg->runningMesh.l.n == 0 && g->thisMesh.l.n == 0) {
|
||||
if(pg->runningMesh.IsEmpty() && g->thisMesh.IsEmpty()) {
|
||||
bool fm = g->forceToMesh;
|
||||
Printf(true,
|
||||
"%FtSURFACES%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
|
||||
|
@ -473,7 +473,7 @@ void TextWindow::ShowGroupInfo(void) {
|
|||
(fm ? "" : "as mesh"), (fm ? "as mesh" : ""));
|
||||
} else {
|
||||
Printf(false,
|
||||
"%FtSURFACES%E %Fas mesh%FE");
|
||||
"%FtSURFACES%E %Fsas mesh%E");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user