Skip to main content
Technical Manual

Drawing Analysis

CrestApps.Estimator.Drawing reads drawing sheets for takeoff. Like the engine, it is a plain .NET library with no Orchard Core dependency, so it runs the same in the web host, a processing worker and tests. It uses PdfPig (Apache-2.0) to read PDFs.

Coordinates are PDF points (1/72 inch) with the origin at the bottom left of the page, as PDF defines them.

Reading pages​

PdfDrawingReader.Read(byte[] pdf) returns a DrawingPage per page:

PropertyContent
Width, HeightThe page size in points
WordsEach word of vector text, with its bounds and font size (DrawingWord)
SegmentsEvery vector path as line segments. Cubic Bézier curves, such as circles, are flattened into six chords each; closed paths get their closing segment
ImageCountThe number of raster images
KindVector (50 or more segments, as a CAD export has), Raster (images and little linework, as a scan has) or Empty

Raster pages need OCR and image detection, which are not part of this library yet.

Title blocks​

TitleBlockReader.Read(page) returns a TitleBlock with the sheet identity, the title, the scale, the scale note and the revision:

  • The sheet number is the sheet number printed in the largest font in the title block region (the right quarter and the bottom quarter of the sheet), so a reference such as SEE E-101 in small text is not taken for the sheet's own number.
  • The title is the block of large lines nearest the sheet number. A line is large when its font is at least 0.45 of the sheet number's font. Lines of about the same font (within 15%), at most one line apart and overlapping across the page join into one block, so a title set in three lines, such as EXISTING LIGHTING / AND LIFE SAFETY / PLAN, is read whole. Sets often print the project name in the same large type higher up the title block; the nearest block wins, so the project name is not taken for the title. Without a sheet number, the title is the largest line.
  • Words join a line only when their fonts are within 70% of each other and the gap to the line is at most three times the font. So a general note of the drawing area on the same baseline does not join the title.
  • The scale is the first line anywhere on the sheet that contains SCALE and a scale that DrawingScaleParser can read. Many sets print the scale under each view title without the word, such as 1/8"=1'-0". Without the keyword, the reader takes the short lines (at most 24 characters) that hold a scale, and the scale written most often wins, then the largest. A long note that mentions a scale is not a scale.
  • The revision comes from the revision table of the title block: rows of a revision of one to three letters or digits, a date and a description. Columns of the table are far apart, so the words of a row join across gaps of up to 15 times the font. The row with the latest date wins. Dates are read as yyyy-MM-dd, yyyy/MM/dd, M/d/yyyy and their variants.

The values are suggestions: the Sheets page marks them Suggested until somebody checks them.

On a real nine-sheet electrical set (sheets E000 to E401, with the sheet title in up to three lines under the project name), the reader finds every sheet number and title, the scale of the five plans and detail sheets, and the revision of every sheet.

Sheet numbers​

SheetNumberParser.TryParse reads numbers such as E-201, E201, E2.01 and FP-101, and maps the prefix to a SheetDiscipline with the US National CAD Standard designators:

PrefixDisciplinePrefixDiscipline
GGeneralMMechanical
CCivilPPlumbing
LLandscapeFPFire protection
AArchitecturalFAFire alarm
IInteriorsEElectrical
SStructuralTTelecommunications

Scales and measurement​

DrawingScaleParser.TryParse reads architectural (1/8" = 1'-0", 1-1/2" = 1'-0"), engineering (1" = 20'), metric (1:100) and not-to-scale (NTS, NOT TO SCALE, SCALE: NONE) notes. A DrawingScale holds the ratio of real length to paper length (96 for 1/8" = 1'-0"):

MemberWhat it does
ToRealFeet(points)Converts a measured distance to feet. Throws for a not-to-scale drawing
FromCalibration(points, feet)Creates a scale from a known dimension measured on the sheet
IsCloseTo(other)Tells whether two scales agree within 2%, to flag a calibration that disagrees with the printed scale

Vector symbol matching​

VectorSymbolMatcher.Find(symbol, segments, options) finds every placement of a symbol among a page's segments. This is the first detection tier of the plan: CAD exports draw every placement of a block with the same geometry, so placements are found by their exact segments, without image processing or AI.

  1. Create the symbol with VectorSymbol.Create(name, segments), for example from the segments inside the legend entry. The symbol is moved to the origin.
  2. The symbol's longest segment is the anchor. Each drawn segment of the same length is a candidate position, in both directions and at 0, 90, 180 and 270 degrees.
  3. A candidate is a match when the share of the symbol's segments found at their expected positions reaches MinimumScore.
  4. Matches whose bounds overlap by half or more (intersection over union) are one placement; the best score is kept.
SymbolMatchOptionsDefaultMeaning
Tolerance0.75 pointsThe largest distance between an expected and a drawn endpoint
MinimumScore0.9The share of segments that must be found
AllowRotationstrueAlso match placements rotated by 90, 180 and 270 degrees

Each SymbolMatch has its bounds, its rotation and its score, which is the match confidence. Excluding the legend itself, and other regions such as schedules, is the caller's job.

Tests​

tests/CrestApps.Estimator.Drawing.Tests builds PDFs with PdfPig and checks the reader, the title block, the parsers and the matcher. A generated 36" × 24" electrical plan has walls, a legend, 42 receptacles at four rotations, 15 switches and a title block. On it the matcher finds all 42 receptacles with no false positive, and never takes a switch for a receptacle. Real bid packages are needed to measure accuracy on production drawings (plan epic 0.7).