Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: DXF Translator
4 : * Purpose: Implements translation support for DIMENSION elements as a part
5 : * of the OGRDXFLayer class.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
10 : * Copyright (c) 2010, Even Rouault <even dot rouault at spatialys.com>
11 : * Copyright (c) 2017, Alan Thomas <alant@outlook.com.au>
12 : *
13 : * Permission is hereby granted, free of charge, to any person obtaining a
14 : * copy of this software and associated documentation files (the "Software"),
15 : * to deal in the Software without restriction, including without limitation
16 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 : * and/or sell copies of the Software, and to permit persons to whom the
18 : * Software is furnished to do so, subject to the following conditions:
19 : *
20 : * The above copyright notice and this permission notice shall be included
21 : * in all copies or substantial portions of the Software.
22 : *
23 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 : * DEALINGS IN THE SOFTWARE.
30 : ****************************************************************************/
31 :
32 : #include "ogr_dxf.h"
33 : #include "cpl_conv.h"
34 :
35 : #include <stdexcept>
36 :
37 : /************************************************************************/
38 : /* PointDist() */
39 : /************************************************************************/
40 :
41 : #ifndef PointDist_defined
42 : #define PointDist_defined
43 :
44 28 : inline static double PointDist(double x1, double y1, double x2, double y2)
45 : {
46 28 : return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
47 : }
48 : #endif
49 :
50 : /************************************************************************/
51 : /* TranslateDIMENSION() */
52 : /************************************************************************/
53 :
54 30 : OGRDXFFeature *OGRDXFLayer::TranslateDIMENSION()
55 :
56 : {
57 : char szLineBuf[257];
58 30 : int nCode = 0;
59 : // int nDimType = 0;
60 30 : OGRDXFFeature *poFeature = new OGRDXFFeature(poFeatureDefn);
61 30 : double dfArrowX1 = 0.0;
62 30 : double dfArrowY1 = 0.0;
63 : // double dfArrowZ1 = 0.0;
64 30 : double dfTargetX1 = 0.0;
65 30 : double dfTargetY1 = 0.0;
66 : // double dfTargetZ1 = 0.0;
67 30 : double dfTargetX2 = 0.0;
68 30 : double dfTargetY2 = 0.0;
69 : // double dfTargetZ2 = 0.0;
70 30 : double dfTextX = 0.0;
71 30 : double dfTextY = 0.0;
72 : // double dfTextZ = 0.0;
73 :
74 30 : bool bReadyForDimstyleOverride = false;
75 :
76 30 : bool bHaveBlock = false;
77 60 : CPLString osBlockName;
78 60 : CPLString osText;
79 :
80 60 : std::map<CPLString, CPLString> oDimStyleProperties;
81 30 : poDS->PopulateDefaultDimStyleProperties(oDimStyleProperties);
82 :
83 1105 : while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
84 : {
85 1075 : switch (nCode)
86 : {
87 19 : case 2:
88 19 : bHaveBlock = true;
89 19 : osBlockName = szLineBuf;
90 19 : break;
91 :
92 28 : case 3:
93 : // 3 is the dimension style name. We don't need to store it,
94 : // let's just fetch the dimension style properties
95 28 : poDS->LookupDimStyle(szLineBuf, oDimStyleProperties);
96 28 : break;
97 :
98 28 : case 10:
99 28 : dfArrowX1 = CPLAtof(szLineBuf);
100 28 : break;
101 :
102 28 : case 20:
103 28 : dfArrowY1 = CPLAtof(szLineBuf);
104 28 : break;
105 :
106 28 : case 30:
107 : /* dfArrowZ1 = CPLAtof(szLineBuf); */
108 28 : break;
109 :
110 28 : case 11:
111 28 : dfTextX = CPLAtof(szLineBuf);
112 28 : break;
113 :
114 28 : case 21:
115 28 : dfTextY = CPLAtof(szLineBuf);
116 28 : break;
117 :
118 28 : case 31:
119 : /* dfTextZ = CPLAtof(szLineBuf); */
120 28 : break;
121 :
122 28 : case 13:
123 28 : dfTargetX2 = CPLAtof(szLineBuf);
124 28 : break;
125 :
126 28 : case 23:
127 28 : dfTargetY2 = CPLAtof(szLineBuf);
128 28 : break;
129 :
130 28 : case 33:
131 : /* dfTargetZ2 = CPLAtof(szLineBuf); */
132 28 : break;
133 :
134 28 : case 14:
135 28 : dfTargetX1 = CPLAtof(szLineBuf);
136 28 : break;
137 :
138 28 : case 24:
139 28 : dfTargetY1 = CPLAtof(szLineBuf);
140 28 : break;
141 :
142 28 : case 34:
143 : /* dfTargetZ1 = CPLAtof(szLineBuf); */
144 28 : break;
145 :
146 26 : case 70:
147 : /* nDimType = atoi(szLineBuf); */
148 26 : break;
149 :
150 25 : case 1:
151 25 : osText = szLineBuf;
152 25 : break;
153 :
154 15 : case 1001:
155 15 : bReadyForDimstyleOverride = EQUAL(szLineBuf, "ACAD");
156 15 : break;
157 :
158 160 : case 1070:
159 160 : if (bReadyForDimstyleOverride)
160 : {
161 : // Store DIMSTYLE override values in the dimension
162 : // style property map. The nInnerCode values match the
163 : // group codes used in the DIMSTYLE table.
164 160 : const int nInnerCode = atoi(szLineBuf);
165 : const char *pszProperty =
166 160 : ACGetDimStylePropertyName(nInnerCode);
167 160 : if (pszProperty)
168 : {
169 74 : nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf));
170 74 : if (nCode == 1005 || nCode == 1040 || nCode == 1070)
171 74 : oDimStyleProperties[pszProperty] = szLineBuf;
172 : }
173 : }
174 160 : break;
175 :
176 466 : default:
177 466 : TranslateGenericProperty(poFeature, nCode, szLineBuf);
178 466 : break;
179 : }
180 : }
181 30 : if (nCode < 0)
182 : {
183 0 : DXF_LAYER_READER_ERROR();
184 0 : delete poFeature;
185 0 : return nullptr;
186 : }
187 30 : if (nCode == 0)
188 30 : poDS->UnreadValue();
189 :
190 : // If osBlockName (group code 2) refers to a valid block, we can just insert
191 : // that block - that should give us the correctly exploded geometry of this
192 : // dimension. If this value is missing, or doesn't refer to a valid block,
193 : // we will need to use our own logic to generate the dimension lines.
194 30 : if (bHaveBlock && osBlockName.length() > 0)
195 : {
196 : // Always inline the block, because this is an anonymous block that the
197 : // user likely doesn't know or care about
198 : try
199 : {
200 19 : OGRDXFFeature *poBlockFeature = InsertBlockInline(
201 22 : CPLGetErrorCounter(), osBlockName, OGRDXFInsertTransformer(),
202 19 : poFeature, apoPendingFeatures, true, false);
203 :
204 16 : return poBlockFeature; // may be NULL but that is OK
205 : }
206 3 : catch (const std::invalid_argument &)
207 : {
208 : }
209 : }
210 :
211 : // Unpack the dimension style
212 14 : const double dfScale = CPLAtof(oDimStyleProperties["DIMSCALE"]);
213 14 : const double dfArrowheadSize = CPLAtof(oDimStyleProperties["DIMASZ"]);
214 14 : const double dfExtLineExtendLength = CPLAtof(oDimStyleProperties["DIMEXE"]);
215 14 : const double dfExtLineOffset = CPLAtof(oDimStyleProperties["DIMEXO"]);
216 14 : const bool bWantExtLine1 = atoi(oDimStyleProperties["DIMSE1"]) == 0;
217 14 : const bool bWantExtLine2 = atoi(oDimStyleProperties["DIMSE2"]) == 0;
218 14 : const double dfTextHeight = CPLAtof(oDimStyleProperties["DIMTXT"]);
219 14 : const int nUnitsPrecision = atoi(oDimStyleProperties["DIMDEC"]);
220 : const bool bTextSupposedlyCentered =
221 14 : atoi(oDimStyleProperties["DIMTAD"]) == 0;
222 28 : const CPLString osTextColor = oDimStyleProperties["DIMCLRT"];
223 :
224 : /*************************************************************************
225 :
226 : DIMENSION geometry layout
227 :
228 : (11,21)(text center point)
229 : | DimText |
230 : (10,20) X<--------------------------------->X (Arrow2 - computed)
231 : (Arrow1)| |
232 : | |
233 : | X (13,23) (Target2)
234 : |
235 : X (14,24) (Target1)
236 :
237 : Given:
238 : Locations Arrow1, Target1, and Target2 we need to compute Arrow2.
239 :
240 : Steps:
241 : 1) Compute direction vector from Target1 to Arrow1 (Vec1).
242 : 2) Compute direction vector for arrow as perpendicular to Vec1 (call Vec2).
243 : 3) Compute Arrow2 location as intersection between line defined by
244 : Vec2 and Arrow1 and line defined by Target2 and direction Vec1 (call
245 : Arrow2)
246 :
247 : Then we can draw lines for the various components.
248 :
249 : Note that Vec1 and Vec2 may be horizontal, vertical or on an angle but
250 : the approach is as above in all these cases.
251 :
252 : *************************************************************************/
253 :
254 : /* -------------------------------------------------------------------- */
255 : /* Step 1, compute direction vector between Target1 and Arrow1. */
256 : /* -------------------------------------------------------------------- */
257 14 : double dfVec1X = dfArrowX1 - dfTargetX1;
258 14 : double dfVec1Y = dfArrowY1 - dfTargetY1;
259 :
260 : // make Vec1 a unit vector
261 14 : double dfVec1Length = PointDist(0, 0, dfVec1X, dfVec1Y);
262 14 : if (dfVec1Length > 0.0)
263 : {
264 12 : dfVec1X /= dfVec1Length;
265 12 : dfVec1Y /= dfVec1Length;
266 : }
267 :
268 : /* -------------------------------------------------------------------- */
269 : /* Step 2, compute the direction vector from Arrow1 to Arrow2 */
270 : /* as a perpendicular to Vec1. */
271 : /* -------------------------------------------------------------------- */
272 14 : double dfVec2X = dfVec1Y;
273 14 : double dfVec2Y = -dfVec1X;
274 :
275 : /* -------------------------------------------------------------------- */
276 : /* Step 3, compute intersection of line from target2 along */
277 : /* direction vector 1, with the line through Arrow1 and */
278 : /* direction vector 2. */
279 : /* -------------------------------------------------------------------- */
280 14 : double dfArrowX2 = 0.0;
281 14 : double dfArrowY2 = 0.0;
282 :
283 : // special case if vec1 is zero, which means the arrow and target
284 : // points coincide.
285 14 : if (dfVec1X == 0.0 && dfVec1Y == 0.0)
286 : {
287 2 : dfArrowX2 = dfTargetX2;
288 2 : dfArrowY2 = dfTargetY2;
289 : }
290 :
291 : // special case if vec1 is vertical.
292 12 : else if (dfVec1X == 0.0)
293 : {
294 5 : dfArrowX2 = dfTargetX2;
295 5 : dfArrowY2 = dfArrowY1;
296 : }
297 :
298 : // special case if vec1 is horizontal.
299 7 : else if (dfVec1Y == 0.0)
300 : {
301 3 : dfArrowX2 = dfArrowX1;
302 3 : dfArrowY2 = dfTargetY2;
303 : }
304 :
305 : else // General case for diagonal vectors.
306 : {
307 : // first convert vec1 + target2 into y = mx + b format: call this L1
308 :
309 4 : const double dfL1M = dfVec1Y / dfVec1X;
310 4 : const double dfL1B = dfTargetY2 - dfL1M * dfTargetX2;
311 :
312 : // convert vec2 + Arrow1 into y = mx + b format, call this L2
313 :
314 4 : const double dfL2M = dfVec2Y / dfVec2X;
315 4 : const double dfL2B = dfArrowY1 - dfL2M * dfArrowX1;
316 :
317 : // Compute intersection x = (b2-b1) / (m1-m2)
318 :
319 4 : dfArrowX2 = (dfL2B - dfL1B) / (dfL1M - dfL2M);
320 4 : dfArrowY2 = dfL2M * dfArrowX2 + dfL2B;
321 : }
322 :
323 : /* -------------------------------------------------------------------- */
324 : /* Create geometries for the different components of the */
325 : /* dimension object. */
326 : /* -------------------------------------------------------------------- */
327 14 : OGRMultiLineString *poMLS = new OGRMultiLineString();
328 28 : OGRLineString oLine;
329 :
330 : // Main arrow line between Arrow1 and Arrow2.
331 14 : oLine.setPoint(0, dfArrowX1, dfArrowY1);
332 14 : oLine.setPoint(1, dfArrowX2, dfArrowY2);
333 14 : poMLS->addGeometry(&oLine);
334 :
335 : // Insert default arrowheads.
336 14 : InsertArrowhead(poFeature, "", &oLine, dfArrowheadSize * dfScale);
337 14 : InsertArrowhead(poFeature, "", &oLine, dfArrowheadSize * dfScale, true);
338 :
339 : // Dimension line from Target1 to Arrow1 with a small extension.
340 14 : oLine.setPoint(0, dfTargetX1 + dfVec1X * dfExtLineOffset,
341 14 : dfTargetY1 + dfVec1Y * dfExtLineOffset);
342 14 : oLine.setPoint(1, dfArrowX1 + dfVec1X * dfExtLineExtendLength,
343 14 : dfArrowY1 + dfVec1Y * dfExtLineExtendLength);
344 14 : if (bWantExtLine1 && oLine.get_Length() > 0.0)
345 : {
346 12 : poMLS->addGeometry(&oLine);
347 : }
348 :
349 : // Dimension line from Target2 to Arrow2 with a small extension.
350 14 : oLine.setPoint(0, dfTargetX2 + dfVec1X * dfExtLineOffset,
351 14 : dfTargetY2 + dfVec1Y * dfExtLineOffset);
352 14 : oLine.setPoint(1, dfArrowX2 + dfVec1X * dfExtLineExtendLength,
353 14 : dfArrowY2 + dfVec1Y * dfExtLineExtendLength);
354 14 : if (bWantExtLine2 && oLine.get_Length() > 0.0)
355 : {
356 12 : poMLS->addGeometry(&oLine);
357 : }
358 :
359 14 : poFeature->SetGeometryDirectly(poMLS);
360 :
361 14 : PrepareLineStyle(poFeature);
362 :
363 : /* -------------------------------------------------------------------- */
364 : /* Prepare a new feature to serve as the dimension text label */
365 : /* feature. We will push it onto the layer as a pending */
366 : /* feature for the next feature read. */
367 : /* */
368 : /* The DXF format supports a myriad of options for dimension */
369 : /* text placement, some of which involve the drawing of */
370 : /* additional lines and the like. For now we ignore most of */
371 : /* those properties and place the text alongside the dimension */
372 : /* line. */
373 : /* -------------------------------------------------------------------- */
374 :
375 : // a single space suppresses labeling.
376 14 : if (osText == " ")
377 0 : return poFeature;
378 :
379 14 : OGRDXFFeature *poLabelFeature = poFeature->CloneDXFFeature();
380 :
381 14 : poLabelFeature->SetGeometryDirectly(new OGRPoint(dfTextX, dfTextY));
382 :
383 14 : if (osText.empty())
384 13 : osText = "<>";
385 :
386 : // Do we need to compute the dimension value?
387 14 : size_t nDimensionPos = osText.find("<>");
388 14 : if (nDimensionPos == std::string::npos)
389 : {
390 0 : poLabelFeature->SetField("Text", TextUnescape(osText, true));
391 : }
392 : else
393 : {
394 : // Replace the first occurrence of <> with the dimension
395 14 : CPLString osDimensionText;
396 14 : FormatDimension(osDimensionText,
397 : PointDist(dfArrowX1, dfArrowY1, dfArrowX2, dfArrowY2),
398 : nUnitsPrecision);
399 14 : osText.replace(nDimensionPos, 2, osDimensionText);
400 14 : poLabelFeature->SetField("Text", TextUnescape(osText, true));
401 : }
402 :
403 14 : CPLString osStyle;
404 : char szBuffer[64];
405 :
406 : osStyle.Printf("LABEL(f:\"Arial\",t:\"%s\"",
407 14 : TextUnescape(osText.c_str(), true).c_str());
408 :
409 : // If the text is supposed to be centered on the line, we align
410 : // it above the line. Drawing it properly would require us to
411 : // work out the width of the text, which seems like too much
412 : // effort for what is just a fallback renderer.
413 14 : if (bTextSupposedlyCentered)
414 4 : osStyle += ",p:11";
415 : else
416 10 : osStyle += ",p:5";
417 :
418 : // Compute the text angle. Use atan to avoid upside-down text
419 14 : const double dfTextAngle =
420 : (dfArrowX1 == dfArrowX2)
421 14 : ? -90.0
422 10 : : atan((dfArrowY1 - dfArrowY2) / (dfArrowX1 - dfArrowX2)) * 180.0 /
423 : M_PI;
424 :
425 14 : if (dfTextAngle != 0.0)
426 : {
427 9 : CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextAngle);
428 9 : osStyle += CPLString().Printf(",a:%s", szBuffer);
429 : }
430 :
431 14 : if (dfTextHeight != 0.0)
432 : {
433 14 : CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextHeight * dfScale);
434 14 : osStyle += CPLString().Printf(",s:%sg", szBuffer);
435 : }
436 :
437 14 : poLabelFeature->oStyleProperties["Color"] = osTextColor;
438 14 : osStyle += ",c:";
439 14 : osStyle += poLabelFeature->GetColor(poDS, poFeature);
440 :
441 14 : osStyle += ")";
442 :
443 14 : poLabelFeature->SetStyleString(osStyle);
444 :
445 14 : apoPendingFeatures.push(poLabelFeature);
446 :
447 14 : return poFeature;
448 : }
449 :
450 : /************************************************************************/
451 : /* FormatDimension() */
452 : /* */
453 : /* Format a dimension number according to the current files */
454 : /* formatting conventions. */
455 : /************************************************************************/
456 :
457 14 : void OGRDXFLayer::FormatDimension(CPLString &osText, const double dfValue,
458 : int nPrecision)
459 :
460 : {
461 14 : if (nPrecision < 0)
462 0 : nPrecision = 0;
463 14 : else if (nPrecision > 20)
464 0 : nPrecision = 20;
465 :
466 : // We could do a significantly more precise formatting if we want
467 : // to spend the effort. See QCAD's rs_dimlinear.cpp and related files
468 : // for example.
469 :
470 : char szFormat[32];
471 14 : snprintf(szFormat, sizeof(szFormat), "%%.%df", nPrecision);
472 :
473 : char szBuffer[64];
474 14 : CPLsnprintf(szBuffer, sizeof(szBuffer), szFormat, dfValue);
475 :
476 14 : osText = szBuffer;
477 14 : }
|