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