Commit 5887006c authored by Christophe Geuzaine's avatar Christophe Geuzaine

rewrote surface representation cross code

parent 6a1d3f5b
Pipeline #1494 passed with stage
in 37 minutes and 1 second
......@@ -867,7 +867,7 @@ StringXNumber GeometryOptions_Number[] = {
{ F|O, "Normals" , opt_geometry_normals , 0. ,
"Display size of normal vectors (in pixels)" },
{ F|O, "NumSubEdges" , opt_geometry_num_sub_edges , 20. ,
{ F|O, "NumSubEdges" , opt_geometry_num_sub_edges , 40. ,
"Number of edge subdivisions between control points when displaying curves" },
{ F|O, "OCCAutoFix" , opt_geometry_occ_auto_fix , 1. ,
......
......@@ -1139,77 +1139,84 @@ SVector3 GFace::normal(const SPoint2 &param) const
bool GFace::buildRepresentationCross(bool force)
{
if(cross.size()){
if(force)
cross.clear();
if(cross[0].size()){
if(force){
cross[0].clear();
cross[0].clear();
}
else
return true;
}
if(geomType() != Plane){
cross.clear();
cross.push_back(SPoint3(0., 0., 0.));
return false;
}
std::list<GEdge*> ed = edges();
SBoundingBox3d bb;
for(std::list<GEdge*>::iterator it = ed.begin(); it != ed.end(); it++){
GEdge *ge = *it;
if(ge->geomType() == GEntity::DiscreteCurve ||
ge->geomType() == GEntity::BoundaryLayerCurve){
cross.clear();
cross.push_back(SPoint3(0., 0., 0.));
return false;
}
else{
Range<double> t_bounds = ge->parBounds(0);
GPoint p[3] = {ge->point(t_bounds.low()),
ge->point(0.5 * (t_bounds.low() + t_bounds.high())),
ge->point(t_bounds.high())};
for(int i = 0; i < 3; i++){
SPoint2 uv = parFromPoint(SPoint3(p[i].x(), p[i].y(), p[i].z()));
bb += SPoint3(uv.x(), uv.y(), 0.);
Range<double> ubounds = parBounds(0);
Range<double> vbounds = parBounds(1);
// try to compute something better for Gmsh surfaces
if(getNativeType() == GmshModel && geomType() == Plane){
SBoundingBox3d bb;
std::list<GEdge*> ed = edges();
for(std::list<GEdge*>::iterator it = ed.begin(); it != ed.end(); it++){
GEdge *ge = *it;
if(ge->geomType() != DiscreteCurve &&
ge->geomType() != BoundaryLayerCurve){
Range<double> t_bounds = ge->parBounds(0);
const int N = 5;
double t0 = t_bounds.low(), dt = t_bounds.high() - t_bounds.low();
for(int i = 0; i < N; i++){
double t = t0 + dt / (double)(N - 1) * i;
GPoint p = ge->point(t);
SPoint2 uv = parFromPoint(SPoint3(p.x(), p.y(), p.z()));
bb += SPoint3(uv.x(), uv.y(), 0.);
}
}
}
if(!bb.empty()){
ubounds = Range<double>(bb.min().x(), bb.max().x());
vbounds = Range<double>(bb.min().y(), bb.max().y());
}
}
bb *= 1.1;
GPoint v0 = point(bb.min().x(), bb.min().y());
GPoint v1 = point(bb.max().x(), bb.min().y());
GPoint v2 = point(bb.max().x(), bb.max().y());
GPoint v3 = point(bb.min().x(), bb.max().y());
bool tri = (geomType() == RuledSurface && edges().size() == 3);
if(CTX::instance()->geom.oldRuledSurface) tri = false;
double c = tri ? 0.75 : 0.5;
double uav = c * (ubounds.high() + ubounds.low());
double vav = (1-c) * (vbounds.high() + vbounds.low());
double u2 = 0.5 * (ubounds.high() + ubounds.low());
double v2 = 0.5 * (vbounds.high() + vbounds.low());
double ud = (ubounds.high() - ubounds.low());
double vd = (vbounds.high() - vbounds.low());
const int N = 100;
for(int dir = 0; dir < 2; dir++) {
int end_line = 0;
SPoint3 pt, pt_last_inside;
cross[dir].push_back(std::vector<SPoint3>());
for(int i = 0; i < N; i++) {
double t = (double)i / (double)(N - 1);
double x, y, z;
SPoint2 uv;
if(!dir){
x = 0.5 * (t * (v0.x() + v1.x()) + (1. - t) * (v2.x() + v3.x()));
y = 0.5 * (t * (v0.y() + v1.y()) + (1. - t) * (v2.y() + v3.y()));
z = 0.5 * (t * (v0.z() + v1.z()) + (1. - t) * (v2.z() + v3.z()));
if(tri)
uv.setPosition(u2 + u2 * t, vbounds.low() + v2 * t);
else
uv.setPosition(ubounds.low() + ud * t, vav);
}
else{
x = 0.5 * (t * (v0.x() + v3.x()) + (1. - t) * (v2.x() + v1.x()));
y = 0.5 * (t * (v0.y() + v3.y()) + (1. - t) * (v2.y() + v1.y()));
z = 0.5 * (t * (v0.z() + v3.z()) + (1. - t) * (v2.z() + v1.z()));
if(tri)
uv.setPosition(u2 + u2 * t, v2 - v2 * t);
else
uv.setPosition(uav, vbounds.low() + vd * t);
}
pt.setPosition(x, y, z);
if(containsPoint(pt)){
pt_last_inside.setPosition(x, y, z);
if(!end_line) { cross.push_back(pt); end_line = 1; }
GPoint p = point(uv);
SPoint3 pt(p.x(), p.y(), p.z());
bool inside = (geomType() == Plane) ? containsPoint(pt) : containsParam(uv);
if(inside){
cross[dir].back().push_back(pt);
}
else {
if(end_line) { cross.push_back(pt_last_inside); end_line = 0; }
else{
if(cross[dir].back().size()) cross[dir].push_back(std::vector<SPoint3>());
}
}
if(end_line) cross.push_back(pt_last_inside);
}
// if we couldn't determine a cross, add a dummy point so that we
// won't try again unless we force the recomputation
if(!cross.size()){
cross.push_back(SPoint3(0., 0., 0.));
// if we couldn't determine a cross, add a dummy one so that we won't try
// again unless we force the recomputation
if(cross[0].empty()){
cross[0].push_back(std::vector<SPoint3>());
return false;
}
return true;
......@@ -1679,9 +1686,9 @@ void GFace::setMeshMaster(GFace* master, const std::vector<double>& tfo)
}
gEdgeCounterparts[localEdge] = std::make_pair(masterEdge,sign);
}
// complete the information at the edge level
edgeCounterparts = gEdgeCounterparts;
// complete the information at the edge level
edgeCounterparts = gEdgeCounterparts;
vertexCounterparts = gVertexCounterparts;
GEntity::setMeshMaster(master,tfo);
}
......
......@@ -310,9 +310,9 @@ class GFace : public GEntity {
int nbGoodQuality, nbGoodLength;
} meshStatistics;
// a crude graphical representation using a "cross" defined by pairs
// of start/end points
std::vector<SPoint3> cross;
// a crude graphical representation using a "cross" represented by points
// along u and v parameter lines
std::vector<std::vector<SPoint3> > cross[2];
// the STL mesh
std::vector<SPoint2> stl_vertices;
......
......@@ -188,16 +188,10 @@ GPoint OCCEdge::closestPoint(const SPoint3 &qp, double &param) const
// True if the edge is a seam for the given face
bool OCCEdge::isSeam(const GFace *face) const
{
if (face->geomType() == GEntity::CompoundSurface) return false;
if (face->getNativeType() != GEntity::OpenCascadeModel) return false;
// this breaks demos/thrusections.geo:
//if (!face->periodic(0) && !face->periodic(1)) return false;
bool ret;
if(face->geomType() == GEntity::CompoundSurface) return false;
if(face->getNativeType() != GEntity::OpenCascadeModel) return false;
const TopoDS_Face *s = (TopoDS_Face*) face->getNativePtr();
// This function was horribly slow... until ...
// BRepAdaptor_Surface surface(*s);
ret = BRep_Tool::IsClosed(c, *s);
bool ret = BRep_Tool::IsClosed(c, *s);
return ret;
}
......
......@@ -118,8 +118,7 @@ void OCCFace::setup()
ShapeAnalysis::GetFaceUVBounds(s, umin, umax, vmin, vmax);
Msg::Debug("OCC Face %d with %d parameter bounds (%g,%g)(%g,%g)",
tag(), l_edges.size(), umin, umax, vmin, vmax);
// we do that for the projections to converge on the borders of the
// surface
// we do that for the projections to converge on the borders of the surface
const double du = umax - umin;
const double dv = vmax - vmin;
umin -= fabs(du) / 100.0;
......@@ -128,7 +127,8 @@ void OCCFace::setup()
vmax += fabs(dv) / 100.0;
occface = BRep_Tool::Surface(s);
for(exp2.Init(s.Oriented(TopAbs_FORWARD), TopAbs_VERTEX, TopAbs_EDGE); exp2.More(); exp2.Next()){
for(exp2.Init(s.Oriented(TopAbs_FORWARD), TopAbs_VERTEX, TopAbs_EDGE);
exp2.More(); exp2.Next()){
TopoDS_Vertex vertex = TopoDS::Vertex(exp2.Current());
GVertex *v = 0;
if(model()->getOCCInternals())
......@@ -411,7 +411,7 @@ bool OCCFace::buildSTLTriangulation(bool force)
return true;
}
if(!model()->getOCCInternals()->makeFaceSTL(s, stl_vertices, stl_triangles)){
Msg::Warning("OpenCASCADE triangulation of surface %d failed", tag());
Msg::Info("OpenCASCADE triangulation of surface %d failed", tag());
// add a dummy triangle so that we won't try again
stl_vertices.push_back(SPoint2(0., 0.));
stl_triangles.push_back(0);
......@@ -455,10 +455,8 @@ bool OCCFace::isSphere (double &radius, SPoint3 &center) const
{
switch(geomType()){
case GEntity::Sphere:
{
radius = _radius;
center = _center;
}
radius = _radius;
center = _center;
return true;
default:
return false;
......@@ -467,43 +465,21 @@ bool OCCFace::isSphere (double &radius, SPoint3 &center) const
bool OCCFace::containsParam(const SPoint2 &pt)
{
// return GFace::containsParam(pt);
if(!buildSTLTriangulation(false)){
Msg::Warning ("Inacurate computation in OCCFace::containsParam");
Msg::Info("Inacurate computation in OCCFace::containsParam");
return GFace::containsParam(pt);
}
// FILE *F = fopen("HOP.pos","w");
// fprintf(F,"View \" \"{\n");
/// fprintf(F,"SP(%g,%g,%g){2,2,2};\n",pt.x(),pt.y(),1.0);
SPoint2 mine = pt;
// bool ok = false;
for(unsigned int i = 0; i < stl_triangles.size(); i += 3){
SPoint2 gp1 = stl_vertices[stl_triangles[i]];
SPoint2 gp2 = stl_vertices[stl_triangles[i + 1]];
SPoint2 gp3 = stl_vertices[stl_triangles[i + 2]];
double s1 = robustPredicates::orient2d(gp1, gp2, mine);
double s2 = robustPredicates::orient2d(gp2, gp3, mine);
double s3 = robustPredicates::orient2d(gp3, gp1, mine);
/*
fprintf(F,"ST(%g,%g,%g,%g,%g,%g,%g,%g,%g){1,1,1};\n",
gp1.x(),gp1.y(),0.0,
gp2.x(),gp2.y(),0.0,
gp3.x(),gp3.y(),0.0);
printf("%g %g %g\n",s1,s2,s3);
*/
if (s1*s2 >= 0 && s1*s3 >=0){
//ok = true;
if(s1*s2 >= 0 && s1*s3 >=0)
return true;
}
}
// printf("gasp\n");
// fprintf(F,"};\n");
// fclose(F);
// return ok;
return false;
}
......
......@@ -19,8 +19,10 @@
// A bounding box class - add points and it grows to be the bounding
// box of the point set
class SBoundingBox3d {
private:
SPoint3 MinPt, MaxPt;
public:
SBoundingBox3d()
SBoundingBox3d()
: MinPt(DBL_MAX,DBL_MAX,DBL_MAX), MaxPt(-DBL_MAX,-DBL_MAX,-DBL_MAX) {}
SBoundingBox3d(const SPoint3 &pt)
: MinPt(pt), MaxPt(pt) {}
......@@ -30,7 +32,7 @@ class SBoundingBox3d {
bool empty()
{
if(MinPt.x() == DBL_MAX || MinPt.y() == DBL_MAX || MinPt.z() == DBL_MAX ||
MaxPt.x() == -DBL_MAX || MaxPt.y() == -DBL_MAX || MaxPt.z() == -DBL_MAX)
MaxPt.x() == -DBL_MAX || MaxPt.y() == -DBL_MAX || MaxPt.z() == -DBL_MAX)
return true;
return false;
}
......@@ -47,12 +49,12 @@ class SBoundingBox3d {
MinPt[0] = pt[0];
if (pt[0] > MaxPt[0])
MaxPt[0] = pt[0];
if(pt[1] < MinPt[1])
MinPt[1] = pt[1];
if (pt[1] > MaxPt[1])
MaxPt[1] = pt[1];
if(pt[2] < MinPt[2])
MinPt[2] = pt[2];
if (pt[2] > MaxPt[2])
......@@ -91,10 +93,10 @@ class SBoundingBox3d {
{
SVector3 len = MaxPt - MinPt;
SPoint3 cc = center();
MaxPt = cc + SPoint3(1,1,1);
MinPt = cc + SPoint3(-1,-1,-1);
MaxPt = cc + SPoint3(1, 1, 1);
MinPt = cc + SPoint3(-1, -1, -1);
double sc = len.norm() * 0.5;
scale (sc,sc,sc);
scale(sc, sc, sc);
}
bool contains(const SBoundingBox3d &bound)
{
......@@ -104,11 +106,28 @@ class SBoundingBox3d {
bound.MaxPt.x() <= MaxPt.x() &&
bound.MaxPt.y() <= MaxPt.y() &&
bound.MaxPt.z() <= MaxPt.z()) return true;
return false;
}
private:
SPoint3 MinPt, MaxPt;
bool contains(const SPoint3 &p)
{
if(p.x() >= MinPt.x() &&
p.y() >= MinPt.y() &&
p.z() >= MinPt.z() &&
p.x() <= MaxPt.x() &&
p.y() <= MaxPt.y() &&
p.z() <= MaxPt.z()) return true;
return false;
}
bool contains(double x, double y, double z)
{
if(x >= MinPt.x() &&
y >= MinPt.y() &&
z >= MinPt.z() &&
x <= MaxPt.x() &&
y <= MaxPt.y() &&
z <= MaxPt.z()) return true;
return false;
}
};
#endif
......@@ -274,151 +274,6 @@ class drawGFace {
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
void _drawParametricGFace(GFace *f)
{
if(CTX::instance()->geom.surfaceType > 0)
f->fillVertexArray(f->geomType() == GEntity::ProjectionFace);
Range<double> ubounds = f->parBounds(0);
Range<double> vbounds = f->parBounds(1);
bool tri = (f->geomType() == GEntity::RuledSurface && f->edges().size() == 3);
if(CTX::instance()->geom.oldRuledSurface) tri = false;
double c = tri ? 0.75 : 0.5;
double uav = c * (ubounds.high() + ubounds.low());
double vav = (1-c) * (vbounds.high() + vbounds.low());
double u2 = 0.5 * (ubounds.high() + ubounds.low());
double v2 = 0.5 * (vbounds.high() + vbounds.low());
if(CTX::instance()->geom.surfaces || f->getSelection() > 1){
if(CTX::instance()->geom.surfaceType > 0 && f->va_geom_triangles){
bool selected = false;
if (f->getSelection())
selected = true;
_drawVertexArray
(f->va_geom_triangles, CTX::instance()->geom.light,
(f->geomType() == GEntity::ProjectionFace) ? true : selected,
(f->geomType() == GEntity::ProjectionFace) ?
CTX::instance()->color.geom.projection :
CTX::instance()->color.geom.selection);
}
else{
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, 0x1F1F);
gl2psEnable(GL2PS_LINE_STIPPLE);
const double ud = (ubounds.high() - ubounds.low());
const double vd = (vbounds.high() - vbounds.low());
int N = 20;
GPoint p;
glBegin(GL_LINE_STRIP);
for(int i = 0; i < N; i++) {
if(tri)
p = f->point(u2 + u2 * (double)i / (double)(N - 1),
vbounds.low() + v2 * (double)i / (double)(N - 1));
else
p = f->point(ubounds.low() + ud * (double)i / (double)(N - 1), vav);
double x = p.x(), y = p.y(), z = p.z();
_ctx->transform(x, y, z);
glVertex3d(x, y, z);
}
glEnd();
glBegin(GL_LINE_STRIP);
for(int i = 0; i < N; i++) {
if(tri)
p = f->point(u2 + u2 * (double)i / (double)(N - 1),
v2 - v2 * (double)i / (double)(N - 1));
else
p = f->point(uav, vbounds.low() + vd * (double)i / (double)(N - 1));
double x = p.x(), y = p.y(), z = p.z();
_ctx->transform(x, y, z);
glVertex3d(x, y, z);
}
glEnd();
glDisable(GL_LINE_STIPPLE);
gl2psDisable(GL2PS_LINE_STIPPLE);
}
}
if(CTX::instance()->geom.surfacesNum || f->getSelection() > 1) {
GPoint p = f->point(uav, vav);
double offset = 0.1 * CTX::instance()->glFontSize * _ctx->pixel_equiv_x;
double x = p.x(), y = p.y(), z = p.z();
_ctx->transform(x, y, z);
drawEntityLabel(_ctx, f, x, y, z, offset);
}
if(CTX::instance()->geom.normals) {
GPoint p = f->point(uav, vav);
SVector3 n = f->normal(SPoint2(uav, vav));
for(int i = 0; i < 3; i++)
n[i] *= CTX::instance()->geom.normals * _ctx->pixel_equiv_x / _ctx->s[i];
glColor4ubv((GLubyte *) & CTX::instance()->color.geom.normals);
double x = p.x(), y = p.y(), z = p.z();
_ctx->transform(x, y, z);
_ctx->transformTwoForm(n[0], n[1], n[2]);
_ctx->drawVector(CTX::instance()->vectorType, 0, x, y, z, n[0], n[1], n[2],
CTX::instance()->geom.light);
}
}
void _drawPlaneGFace(GFace *f)
{
if(CTX::instance()->geom.surfaceType > 0)
f->fillVertexArray();
if(!CTX::instance()->geom.surfaceType || !f->va_geom_triangles ||
CTX::instance()->geom.surfacesNum || CTX::instance()->geom.normals)
f->buildRepresentationCross();
if(CTX::instance()->geom.surfaces || f->getSelection() > 1){
//bool selected = false;
if(CTX::instance()->geom.surfaceType > 0 && f->va_geom_triangles){
_drawVertexArray(f->va_geom_triangles, CTX::instance()->geom.light,
f->getSelection(), CTX::instance()->color.geom.selection);
}
else{
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, 0x1F1F);
gl2psEnable(GL2PS_LINE_STIPPLE);
glBegin(GL_LINES);
for(unsigned int i = 0; i < f->cross.size(); i++){
double x = f->cross[i].x(), y = f->cross[i].y(), z = f->cross[i].z();
_ctx->transform(x, y, z);
glVertex3d(x, y, z);
}
glEnd();
glDisable(GL_LINE_STIPPLE);
gl2psDisable(GL2PS_LINE_STIPPLE);
}
}
if(f->cross.size() < 2) return;
if(CTX::instance()->geom.surfacesNum || f->getSelection() > 1) {
double offset = 0.1 * CTX::instance()->glFontSize * _ctx->pixel_equiv_x;
double x = 0.5 * (f->cross[0].x() + f->cross[1].x());
double y = 0.5 * (f->cross[0].y() + f->cross[1].y());
double z = 0.5 * (f->cross[0].z() + f->cross[1].z());
_ctx->transform(x, y, z);
drawEntityLabel(_ctx, f, x, y, z, offset);
}
if(CTX::instance()->geom.normals) {
SPoint3 p(0.5 * (f->cross[0].x() + f->cross[1].x()),
0.5 * (f->cross[0].y() + f->cross[1].y()),
0.5 * (f->cross[0].z() + f->cross[1].z()));
SPoint2 uv = f->parFromPoint(p);
SVector3 n = f->normal(uv);
for(int i = 0; i < 3; i++)
n[i] *= CTX::instance()->geom.normals * _ctx->pixel_equiv_x / _ctx->s[i];
glColor4ubv((GLubyte *) & CTX::instance()->color.geom.normals);
double x = p.x(), y = p.y(), z = p.z();
_ctx->transform(x, y, z);
_ctx->transformTwoForm(n[0], n[1], n[2]);
_ctx->drawVector(CTX::instance()->vectorType, 0, x, y, z, n[0], n[1], n[2],
CTX::instance()->geom.light);
}
}
public :
drawGFace(drawContext *ctx) : _ctx(ctx) {}
void operator () (GFace *f)
......@@ -453,10 +308,74 @@ class drawGFace {
else
glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
if(f->geomType() == GEntity::Plane)
_drawPlaneGFace(f);
else
_drawParametricGFace(f);
if(CTX::instance()->geom.surfaceType > 0)
f->fillVertexArray(f->geomType() == GEntity::ProjectionFace);
if(!CTX::instance()->geom.surfaceType || !f->va_geom_triangles ||
CTX::instance()->geom.surfacesNum || CTX::instance()->geom.normals)
f->buildRepresentationCross();
if(CTX::instance()->geom.surfaces || f->getSelection() > 1){
if(CTX::instance()->geom.surfaceType > 0 && f->va_geom_triangles){
bool selected = false;
if (f->getSelection())
selected = true;
_drawVertexArray
(f->va_geom_triangles, CTX::instance()->geom.light,
(f->geomType() == GEntity::ProjectionFace) ? true : selected,
(f->geomType() == GEntity::ProjectionFace) ?
CTX::instance()->color.geom.projection :
CTX::instance()->color.geom.selection);
}
else{
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, 0x1F1F);
gl2psEnable(GL2PS_LINE_STIPPLE);
for(int dim = 0; dim < 2; dim++){
for(unsigned int i = 0; i < f->cross[dim].size(); i++){
glBegin(GL_LINE_STRIP);
for(unsigned int j = 0; j < f->cross[dim][i].size(); j++){
double x = f->cross[dim][i][j].x();
double y = f->cross[dim][i][j].y();
double z = f->cross[dim][i][j].z();
_ctx->transform(x, y, z);
glVertex3d(x, y, z);
}
glEnd();
}
}
glDisable(GL_LINE_STIPPLE);
gl2psDisable(GL2PS_LINE_STIPPLE);
}
}
if(f->cross[0].size() && f->cross[0][0].size() >= 2){
int idx = f->cross[0][0].size() / 2;
if(CTX::instance()->geom.surfacesNum || f->getSelection() > 1) {
double offset = 0.1 * CTX::instance()->glFontSize * _ctx->pixel_equiv_x;
double x = f->cross[0][0][idx].x();
double y = f->cross[0][0][idx].y();
double z = f->cross[0][0][idx].z();
_ctx->transform(x, y, z);
drawEntityLabel(_ctx, f, x, y, z, offset);
}
if(CTX::instance()->geom.normals) {
SPoint3 p(f->cross[0][0][idx].x(),
f->cross[0][0][idx].y(),
f->cross[0][0][idx].z());
SPoint2 uv = f->parFromPoint(p);
SVector3 n = f->normal(uv);
for(int i = 0; i < 3; i++)
n[i] *= CTX::instance()->geom.normals * _ctx->pixel_equiv_x / _ctx->s[i];
glColor4ubv((GLubyte *) & CTX::instance()->color.geom.normals);
double x = p.x(), y = p.y(), z = p.z();
_ctx->transform(x, y, z);
_ctx->transformTwoForm(n[0], n[1], n[2]);
_ctx->drawVector(CTX::instance()->vectorType, 0, x, y, z, n[0], n[1], n[2],
CTX::instance()->geom.light);
}
}
if(select) {
glPopName();
......
......@@ -118,7 +118,7 @@ Saved in: @code{General.OptionsFileName}
@item Geometry.NumSubEdges
Number of edge subdivisions between control points when displaying curves@*
Default value: @code{20}@*
Default value: @code{40}@*
Saved in: @code{General.OptionsFileName}
@item Geometry.OCCAutoFix
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment