| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | # include <iomanip> |
| | # include <limits> |
| | # include <sstream> |
| |
|
| | #include <Bnd_Box.hxx> |
| | #include <BRep_Builder.hxx> |
| | #include <Mod/Part/App/FCBRepAlgoAPI_Common.h> |
| | #include <BRepBndLib.hxx> |
| | #include <BRepBuilderAPI_MakeEdge.hxx> |
| | #include <BRepBuilderAPI_MakeFace.hxx> |
| | #include <BRepBuilderAPI_MakeVertex.hxx> |
| | #include <BRepBuilderAPI_Transform.hxx> |
| | #include <gp_Pln.hxx> |
| | #include <gp_Pnt.hxx> |
| | #include <Precision.hxx> |
| | #include <TopExp.hxx> |
| | #include <TopoDS_Vertex.hxx> |
| | #include <TopoDS_Wire.hxx> |
| | #include <TopoDS_Edge.hxx> |
| | #include <TopoDS_Face.hxx> |
| | #include <TopoDS_Compound.hxx> |
| | #include <TopTools_IndexedMapOfShape.hxx> |
| |
|
| | #include <App/Application.h> |
| | #include <App/Document.h> |
| | #include <Base/Console.h> |
| | #include <Base/Converter.h> |
| | #include <Base/FileInfo.h> |
| | #include <Base/Parameter.h> |
| | #include <Base/Tools.h> |
| |
|
| | #include "DrawGeomHatch.h" |
| | #include "DrawGeomHatchPy.h" |
| | #include "DrawUtil.h" |
| | #include "DrawViewPart.h" |
| | #include "DrawViewSection.h" |
| | #include "Geometry.h" |
| | #include "GeometryObject.h" |
| | #include "HatchLine.h" |
| | #include "Preferences.h" |
| |
|
| |
|
| | using namespace TechDraw; |
| | using DU = DrawUtil; |
| |
|
| | App::PropertyFloatConstraint::Constraints DrawGeomHatch::scaleRange = { |
| | Precision::Confusion(), std::numeric_limits<double>::max(), (0.1)}; |
| |
|
| | PROPERTY_SOURCE(TechDraw::DrawGeomHatch, App::DocumentObject) |
| |
|
| | DrawGeomHatch::DrawGeomHatch() |
| | { |
| | static const char *vgroup = "GeomHatch"; |
| |
|
| | ADD_PROPERTY_TYPE(Source, (nullptr), vgroup, App::PropertyType::Prop_None, |
| | "The View + Face to be crosshatched"); |
| | Source.setScope(App::LinkScope::Global); |
| | ADD_PROPERTY_TYPE(FilePattern, (prefGeomHatchFile()), vgroup, App::Prop_None, |
| | "The crosshatch pattern file for this area"); |
| | ADD_PROPERTY_TYPE(PatIncluded, (""), vgroup, App::Prop_None, |
| | "Embedded Pat hatch file. System use only."); |
| | ADD_PROPERTY_TYPE(NamePattern, (prefGeomHatchName()), vgroup, App::Prop_None, |
| | "The name of the pattern"); |
| | ADD_PROPERTY_TYPE(ScalePattern, (1.0), vgroup, App::Prop_None, |
| | "GeomHatch pattern size adjustment"); |
| | ScalePattern.setConstraints(&scaleRange); |
| | ADD_PROPERTY_TYPE(PatternRotation, (0.0), vgroup, App::Prop_None, |
| | "Pattern rotation in degrees anticlockwise"); |
| | ADD_PROPERTY_TYPE(PatternOffset, (0.0, 0.0, 0.0), vgroup, App::Prop_None, |
| | "Pattern offset"); |
| |
|
| | m_saveFile = ""; |
| | m_saveName = ""; |
| |
|
| | std::string patFilter("pat files (*.pat *.PAT);;All files (*)"); |
| | FilePattern.setFilter(patFilter); |
| | } |
| |
|
| | void DrawGeomHatch::onChanged(const App::Property* prop) |
| | { |
| | if (isRestoring()) { |
| | App::DocumentObject::onChanged(prop); |
| | return; |
| | } |
| |
|
| | if (prop == &Source) { |
| | |
| | makeLineSets(); |
| | } |
| | if (prop == &FilePattern) { |
| | replacePatIncluded(FilePattern.getValue()); |
| | makeLineSets(); |
| | } |
| | if (prop == &NamePattern) { |
| | makeLineSets(); |
| | } |
| |
|
| | App::DocumentObject::onChanged(prop); |
| | } |
| |
|
| | App::DocumentObjectExecReturn *DrawGeomHatch::execute() |
| | { |
| | |
| | |
| | DrawViewPart* parent = getSourceView(); |
| | if (parent) { |
| | parent->requestPaint(); |
| | } |
| | return App::DocumentObject::StdReturn; |
| | } |
| |
|
| | void DrawGeomHatch::onDocumentRestored() |
| | { |
| | |
| | makeLineSets(); |
| |
|
| | App::DocumentObject::onDocumentRestored(); |
| | } |
| |
|
| | void DrawGeomHatch::replacePatIncluded(std::string newHatchFileName) |
| | { |
| | |
| | if (newHatchFileName.empty()) { |
| | return; |
| | } |
| |
|
| | Base::FileInfo tfi(newHatchFileName); |
| | if (tfi.isReadable()) { |
| | PatIncluded.setValue(newHatchFileName.c_str()); |
| | } else { |
| | throw Base::RuntimeError("Could not read the new PAT file"); |
| | } |
| | } |
| |
|
| | void DrawGeomHatch::setupObject() |
| | { |
| | |
| | replacePatIncluded(FilePattern.getValue()); |
| | } |
| |
|
| | void DrawGeomHatch::unsetupObject() |
| | { |
| | |
| | App::DocumentObject* source = Source.getValue(); |
| | DrawView* dv = freecad_cast<DrawView*>(source); |
| | if (dv) { |
| | dv->requestPaint(); |
| | } |
| | App::DocumentObject::unsetupObject(); |
| | } |
| |
|
| | |
| |
|
| | void DrawGeomHatch::makeLineSets() |
| | { |
| | |
| | if (!PatIncluded.isEmpty() && |
| | !NamePattern.isEmpty()) { |
| | m_lineSets.clear(); |
| | m_lineSets = makeLineSets(PatIncluded.getValue(), |
| | NamePattern.getValue()); |
| | } |
| | } |
| |
|
| | |
| | std::vector<LineSet> DrawGeomHatch::makeLineSets(std::string fileSpec, std::string myPattern) |
| | { |
| | std::vector<LineSet> lineSets; |
| | if (fileSpec.empty() && myPattern.empty()) { |
| | return lineSets; |
| | } |
| |
|
| | std::vector<PATLineSpec> specs = |
| | DrawGeomHatch::getDecodedSpecsFromFile(fileSpec, |
| | myPattern); |
| | for (auto& hl: specs) { |
| | |
| | LineSet ls; |
| | ls.setPATLineSpec(hl); |
| | lineSets.push_back(ls); |
| | } |
| | return lineSets; |
| | } |
| |
|
| | DrawViewPart* DrawGeomHatch::getSourceView() const |
| | { |
| | App::DocumentObject* obj = Source.getValue(); |
| | DrawViewPart* result = freecad_cast<DrawViewPart*>(obj); |
| | return result; |
| | } |
| |
|
| | std::vector<PATLineSpec> DrawGeomHatch::getDecodedSpecsFromFile() |
| | { |
| | std::string fileSpec = PatIncluded.getValue(); |
| | std::string myPattern = NamePattern.getValue(); |
| | return getDecodedSpecsFromFile(fileSpec, myPattern); |
| | } |
| |
|
| |
|
| | |
| | |
| | std::vector<PATLineSpec> DrawGeomHatch::getDecodedSpecsFromFile(std::string fileSpec, std::string myPattern) |
| | { |
| | Base::FileInfo fi(fileSpec); |
| | if (!fi.isReadable()) { |
| | Base::Console().error("DrawGeomHatch::getDecodedSpecsFromFile not able to open %s!\n", fileSpec.c_str()); |
| | return std::vector<PATLineSpec>(); |
| | } |
| | return PATLineSpec::getSpecsForPattern(fileSpec, myPattern); |
| | } |
| |
|
| | std::vector<LineSet> DrawGeomHatch::getTrimmedLines(int iFace) |
| | { |
| | if (m_lineSets.empty()) { |
| | makeLineSets(); |
| | } |
| |
|
| | DrawViewPart* source = getSourceView(); |
| | if (!source || |
| | !source->hasGeometry()) { |
| | return std::vector<LineSet>(); |
| | } |
| | return getTrimmedLines(source, m_lineSets, iFace, ScalePattern.getValue(), |
| | PatternRotation.getValue(), PatternOffset.getValue()); |
| | } |
| |
|
| | |
| | std::vector<LineSet> DrawGeomHatch::getTrimmedLinesSection(DrawViewSection* source, |
| | std::vector<LineSet> lineSets, |
| | TopoDS_Face f, |
| | double scale, |
| | double hatchRotation, |
| | Base::Vector3d hatchOffset) |
| | { |
| | std::vector<LineSet> result; |
| | gp_Pln p; |
| | Base::Vector3d vfc = DrawUtil::getFaceCenter(f); |
| | gp_Pnt fc(vfc.x, vfc.y, vfc.z); |
| | double dir = -1.0; |
| | if (fc.Z() < 0.0) { |
| | dir = -dir; |
| | } |
| | Base::Vector3d stdZ(0.0, 0.0, 1.0); |
| | Base::Vector3d offset = stdZ * p.Distance(fc) * dir; |
| |
|
| | |
| | |
| | TopoDS_Shape moved = ShapeUtils::moveShape(f, |
| | offset); |
| | TopoDS_Face fMoved = TopoDS::Face(ShapeUtils::invertGeometry(moved)); |
| | return getTrimmedLines( |
| | source, |
| | lineSets, |
| | fMoved, |
| | scale, |
| | hatchRotation, |
| | hatchOffset |
| | ); |
| | } |
| |
|
| | |
| | std::vector<LineSet> DrawGeomHatch::getTrimmedLines(DrawViewPart* source, std::vector<LineSet> lineSets, |
| | int iface, double scale, double hatchRotation , |
| | Base::Vector3d hatchOffset) |
| | { |
| | TopoDS_Face face = extractFace(source, iface); |
| | return getTrimmedLines( |
| | source, |
| | lineSets, |
| | face, |
| | scale, |
| | hatchRotation, |
| | hatchOffset |
| | ); |
| | } |
| |
|
| | std::vector<LineSet> DrawGeomHatch::getTrimmedLines(DrawViewPart* source, |
| | std::vector<LineSet> lineSets, |
| | TopoDS_Face f, |
| | double scale, |
| | double hatchRotation, |
| | Base::Vector3d hatchOffset) |
| | { |
| | |
| | (void)source; |
| | std::vector<LineSet> result; |
| |
|
| | if (lineSets.empty()) { |
| | return result; |
| | } |
| |
|
| | TopoDS_Face face = f; |
| |
|
| | Bnd_Box bBox; |
| | BRepBndLib::AddOptimal(face, bBox); |
| | bBox.SetGap(0.0); |
| | gp_Vec translateVector(hatchOffset.x, hatchOffset.y, 0.); |
| | auto cornerMin = bBox.CornerMin().Translated(-translateVector); |
| | auto cornerMax = bBox.CornerMax().Translated(-translateVector); |
| | bBox = Bnd_Box(cornerMin, cornerMax); |
| |
|
| | for (auto& ls: lineSets) { |
| | PATLineSpec hl = ls.getPATLineSpec(); |
| | std::vector<TopoDS_Edge> candidates = DrawGeomHatch::makeEdgeOverlay(hl, bBox, scale, hatchRotation); |
| |
|
| | |
| | BRep_Builder builder; |
| | TopoDS_Compound gridComp; |
| | builder.MakeCompound(gridComp); |
| | for (auto& c: candidates) { |
| | builder.Add(gridComp, c); |
| | } |
| |
|
| | TopoDS_Shape grid = gridComp; |
| | gp_Trsf xGridTranslate; |
| | xGridTranslate.SetTranslation(Base::convertTo<gp_Vec>(hatchOffset)); |
| | BRepBuilderAPI_Transform mkTransTranslate(grid, xGridTranslate, true); |
| | grid = mkTransTranslate.Shape(); |
| |
|
| | |
| | FCBRepAlgoAPI_Common mkCommon(face, grid); |
| | if (!mkCommon.IsDone() || |
| | mkCommon.Shape().IsNull()) { |
| | return result; |
| | } |
| | TopoDS_Shape common = mkCommon.Shape(); |
| |
|
| | |
| | Bnd_Box overlayBox; |
| | overlayBox.SetGap(0.0); |
| | BRepBndLib::AddOptimal(common, overlayBox); |
| | ls.setBBox(overlayBox); |
| |
|
| | |
| | std::vector<TopoDS_Edge> resultEdges; |
| | TopTools_IndexedMapOfShape mapOfEdges; |
| | TopExp::MapShapes(common, TopAbs_EDGE, mapOfEdges); |
| | for ( int i = 1 ; i <= mapOfEdges.Extent() ; i++ ) { |
| | const TopoDS_Edge& edge = TopoDS::Edge(mapOfEdges(i)); |
| | if (edge.IsNull()) { |
| | continue; |
| | } |
| | resultEdges.push_back(edge); |
| | } |
| |
|
| | std::vector<TechDraw::BaseGeomPtr> resultGeoms; |
| | for (auto& e: resultEdges) { |
| | TechDraw::BaseGeomPtr base = BaseGeom::baseFactory(e); |
| | if (!base) { |
| | throw Base::ValueError("DGH::getTrimmedLines - baseFactory failed"); |
| | } |
| | resultGeoms.push_back(base); |
| | } |
| | ls.setEdges(resultEdges); |
| | ls.setGeoms(resultGeoms); |
| | result.push_back(ls); |
| | } |
| | return result; |
| | } |
| |
|
| | |
| | std::vector<TopoDS_Edge> DrawGeomHatch::makeEdgeOverlay(PATLineSpec hatchLine, Bnd_Box bBox, double scale, double rotation) |
| | { |
| | const size_t MaxNumberOfEdges = Preferences::getPreferenceGroup("PAT")->GetInt("MaxSeg", 10000l); |
| |
|
| | std::vector<TopoDS_Edge> result; |
| | double minX, maxX, minY, maxY, minZ, maxZ; |
| | bBox.Get(minX, minY, minZ, maxX, maxY, maxZ); |
| | Base::Vector3d topLeft(minX, maxY, 0.); |
| | Base::Vector3d topRight(maxX, maxY, 0.); |
| | Base::Vector3d bottomLeft(minX, minY, 0.); |
| | Base::Vector3d bottomRight(maxX, minY, 0.); |
| |
|
| | Base::Vector3d origin = hatchLine.getOrigin() * scale; |
| | double interval = hatchLine.getInterval() * scale; |
| | double offset = hatchLine.getOffset() * scale; |
| | double angle = hatchLine.getAngle() + rotation; |
| | origin.RotateZ(Base::toRadians(rotation)); |
| |
|
| | if (scale == 0. || interval == 0.) |
| | return {}; |
| |
|
| | const double hatchAngle = Base::toRadians(angle); |
| | Base::Vector3d hatchDirection(cos(hatchAngle), sin(hatchAngle), 0.); |
| | Base::Vector3d hatchPerpendicular(-hatchDirection.y, hatchDirection.x, 0.); |
| | Base::Vector3d hatchIntervalAndOffset = offset * hatchDirection + interval * hatchPerpendicular; |
| |
|
| | std::array<double, 4> orthogonalProjections = { |
| | (topLeft - origin).Dot(hatchPerpendicular / interval), |
| | (topRight - origin).Dot(hatchPerpendicular / interval), |
| | (bottomLeft - origin).Dot(hatchPerpendicular / interval), |
| | (bottomRight - origin).Dot(hatchPerpendicular / interval) |
| | }; |
| | auto minMaxIterators = std::minmax_element(orthogonalProjections.begin(), orthogonalProjections.end()); |
| | int firstRepeatIndex = ceil(*minMaxIterators.first); |
| | int lastRepeatIndex = floor(*minMaxIterators.second); |
| |
|
| | std::vector<double> dashParams = hatchLine.getDashParms().get(); |
| | double globalDashStep = 0.; |
| | if (dashParams.empty()) { |
| | |
| | double diagonalLength = (topRight - bottomLeft).Length(); |
| | dashParams.push_back(2. * diagonalLength); |
| | globalDashStep = diagonalLength; |
| | } |
| | else { |
| | for (auto& x : dashParams) { |
| | x *= scale; |
| | globalDashStep += std::abs(x); |
| | } |
| | } |
| | if (globalDashStep == 0.) { |
| | return {}; |
| | } |
| |
|
| | |
| | for (int i = firstRepeatIndex ; i <= lastRepeatIndex ; ++i) { |
| | Base::Vector3d currentOrigin = origin + static_cast<double>(i) * hatchIntervalAndOffset; |
| |
|
| | int firstDashIndex, lastDashIndex; |
| | if (std::abs(hatchDirection.x) > std::abs(hatchDirection.y)) { |
| | firstDashIndex = (hatchDirection.x > 0.) |
| | ? std::floor((minX - currentOrigin.x) / (globalDashStep * hatchDirection.x)) |
| | : std::floor((maxX - currentOrigin.x) / (globalDashStep * hatchDirection.x)); |
| | lastDashIndex = (hatchDirection.x > 0.) |
| | ? std::ceil((maxX - currentOrigin.x) / (globalDashStep * hatchDirection.x)) |
| | : std::ceil((minX - currentOrigin.x) / (globalDashStep * hatchDirection.x)); |
| | } |
| | else { |
| | firstDashIndex = (hatchDirection.y > 0.) |
| | ? std::floor((minY - currentOrigin.y) / (globalDashStep * hatchDirection.y)) |
| | : std::floor((maxY - currentOrigin.y) / (globalDashStep * hatchDirection.y)); |
| | lastDashIndex = (hatchDirection.y > 0.) |
| | ? std::ceil((maxY - currentOrigin.y) / (globalDashStep * hatchDirection.y)) |
| | : std::ceil((minY - currentOrigin.y) / (globalDashStep * hatchDirection.y)); |
| | } |
| |
|
| | for (int j = firstDashIndex ; j < lastDashIndex ; ++j) { |
| | Base::Vector3d current = currentOrigin + static_cast<double>(j) * globalDashStep * hatchDirection; |
| | for (auto dashParamsIterator = dashParams.begin() ; dashParamsIterator != dashParams.end() ; ++dashParamsIterator) { |
| | double len = *dashParamsIterator; |
| | Base::Vector3d next = current + std::abs(len) * hatchDirection; |
| | if (len > 0. && (current.x >= minX || next.x >= minX) && (current.x <= maxX || next.x <= maxX) |
| | && (current.y >= minY || next.y >= minY) && (current.y <= maxY || next.y <= maxY)) { |
| | TopoDS_Edge newLine = makeLine(current, next); |
| | result.push_back(newLine); |
| | } |
| | std::swap(current, next); |
| | } |
| | } |
| |
|
| | if (result.size() > MaxNumberOfEdges) { |
| | return {}; |
| | } |
| | } |
| |
|
| | return result; |
| | } |
| |
|
| | TopoDS_Edge DrawGeomHatch::makeLine(const Base::Vector3d& s, const Base::Vector3d& e) |
| | { |
| | gp_Pnt start(s.x, s.y, 0.0); |
| | gp_Pnt end(e.x, e.y, 0.0); |
| | TopoDS_Vertex v1 = BRepBuilderAPI_MakeVertex(start); |
| | TopoDS_Vertex v2 = BRepBuilderAPI_MakeVertex(end); |
| | BRepBuilderAPI_MakeEdge makeEdge1(v1, v2); |
| | return makeEdge1.Edge(); |
| | } |
| |
|
| | |
| | |
| | std::vector<LineSet> DrawGeomHatch::getFaceOverlay(int iFace) |
| | { |
| | |
| | std::vector<LineSet> result; |
| | DrawViewPart* source = getSourceView(); |
| | if (!source || |
| | !source->hasGeometry()) { |
| | return result; |
| | } |
| |
|
| | TopoDS_Face face = extractFace(source, iFace); |
| |
|
| | Bnd_Box bBox; |
| | BRepBndLib::AddOptimal(face, bBox); |
| | bBox.SetGap(0.0); |
| |
|
| | if (m_lineSets.empty()) { |
| | makeLineSets(); |
| | } |
| |
|
| | for (auto& ls: m_lineSets) { |
| | PATLineSpec hl = ls.getPATLineSpec(); |
| | std::vector<TopoDS_Edge> candidates = DrawGeomHatch::makeEdgeOverlay(hl, bBox, ScalePattern.getValue(), PatternRotation.getValue()); |
| | std::vector<TechDraw::BaseGeomPtr> resultGeoms; |
| | for (auto& e: candidates) { |
| | TechDraw::BaseGeomPtr base = BaseGeom::baseFactory(e); |
| | if (!base) { |
| | throw Base::ValueError("DGH::getFaceOverlay - baseFactory failed"); |
| | } |
| | resultGeoms.push_back(base); |
| | } |
| | ls.setEdges(candidates); |
| | ls.setGeoms(resultGeoms); |
| | result.push_back(ls); |
| | } |
| |
|
| | return result; |
| | } |
| |
|
| | |
| | |
| | |
| | TopoDS_Face DrawGeomHatch::extractFace(DrawViewPart* source, int iface ) |
| | { |
| | std::vector<TopoDS_Wire> faceWires = source->getWireForFace(iface); |
| |
|
| | |
| | gp_Pnt gOrg(0.0, 0.0, 0.0); |
| | gp_Dir gDir(0.0, 0.0, 1.0); |
| | gp_Pln plane(gOrg, gDir); |
| |
|
| | BRepBuilderAPI_MakeFace mkFace(plane, faceWires.front(), true); |
| | std::vector<TopoDS_Wire>::iterator itWire = ++faceWires.begin(); |
| | for (; itWire != faceWires.end(); itWire++) { |
| | mkFace.Add(*itWire); |
| | } |
| | if (!mkFace.IsDone()) { |
| | return TopoDS_Face(); |
| | } |
| | TopoDS_Face face = mkFace.Face(); |
| |
|
| | TopoDS_Shape temp; |
| | try { |
| | |
| | gp_Trsf mirrorTransform; |
| | mirrorTransform.SetMirror( gp_Ax2(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0, 1, 0)) ); |
| | BRepBuilderAPI_Transform mkTrf(face, mirrorTransform); |
| | temp = mkTrf.Shape(); |
| | } |
| | catch (...) { |
| | return TopoDS_Face(); |
| | } |
| | return TopoDS::Face(temp); |
| | } |
| |
|
| | |
| | |
| | void DrawGeomHatch::translateLabel(std::string context, std::string baseName, std::string uniqueName) |
| | { |
| | Label.setValue(DU::translateArbitrary(context, baseName, uniqueName)); |
| | } |
| |
|
| | |
| |
|
| | PyObject *DrawGeomHatch::getPyObject() |
| | { |
| | if (PythonObject.is(Py::_None())) { |
| | PythonObject = Py::Object(new DrawGeomHatchPy(this), true); |
| | } |
| | return Py::new_reference_to(PythonObject); |
| | } |
| |
|
| | std::string DrawGeomHatch::prefGeomHatchFile() |
| | { |
| | return Preferences::patFile(); |
| | } |
| |
|
| | std::string DrawGeomHatch::prefGeomHatchName() |
| | { |
| | std::string defaultNamePattern = "Diamond"; |
| | std::string result = Preferences::getPreferenceGroup("PAT")->GetASCII("NamePattern", defaultNamePattern.c_str()); |
| | if (result.empty()) { |
| | return defaultNamePattern; |
| | } |
| | return result; |
| | } |
| |
|
| | Base::Color DrawGeomHatch::prefGeomHatchColor() |
| | { |
| | Base::Color fcColor; |
| | fcColor.setPackedValue(Preferences::getPreferenceGroup("Colors")->GetUnsigned("GeomHatch", 0x00FF0000)); |
| | return fcColor; |
| | } |
| |
|
| |
|
| |
|
| | |
| |
|
| | namespace App { |
| | |
| | PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawGeomHatchPython, TechDraw::DrawGeomHatch) |
| | template<> const char* TechDraw::DrawGeomHatchPython::getViewProviderName() const { |
| | return "TechDrawGui::ViewProviderGeomHatch"; |
| | } |
| | |
| |
|
| | |
| | template class TechDrawExport FeaturePythonT<TechDraw::DrawGeomHatch>; |
| | } |
| |
|