diff --git "a/Code_2_7.py" "b/Code_2_7.py" --- "a/Code_2_7.py" +++ "b/Code_2_7.py" @@ -1,2547 +1,2565 @@ -# -*- coding: utf-8 -*- -"""2.7 Code to be deployed 21.02.2025 - -Automatically generated by Colab. -Original file is located at - https://colab.research.google.com/drive/1RWSQn0GW_KXoHkJLcbYzLAGGyc0tiDWl -""" - -"""## Imports""" -import sys -import math -import random -import string -import zlib -import base64 -import datetime -import uuid -import re -from io import BytesIO -from ctypes import sizeof -from collections import Counter -from typing import NewType -import xml.etree.ElementTree as ET -from xml.etree.ElementTree import Element, SubElement, tostring, ElementTree -from xml.dom.minidom import parseString - -import numpy as np -import cv2 -from matplotlib import pyplot as plt -from matplotlib.patches import Polygon -from shapely.geometry import Point, Polygon as ShapelyPolygon -from shapely.ops import unary_union -from PIL import Image, ImageDraw, ImageFont, ImageColor - -import fitz -import ezdxf -from ezdxf import units, bbox -from ezdxf.colors import aci2rgb -from ezdxf.math import OCS, Matrix44, Vec3, Vec2 - -import pandas as pd -import google_sheet_Legend -import tsadropboxretrieval - -from PyPDF2 import PdfReader, PdfWriter -from PyPDF2.generic import ( - NameObject, - TextStringObject, - DictionaryObject, - ArrayObject, - FloatObject, - NumberObject, -) - -from math import sin, cos, radians, isclose - - - -def normalize_vertices(vertices): - """Sort vertices to ensure consistent order.""" - return tuple(sorted(tuple(v) for v in vertices)) - -def areas_are_similar(area1, area2, tolerance=0.2): - """Check if two areas are within a given tolerance.""" - return abs(area1 - area2) <= tolerance - - -# -*- coding: utf-8 -*-wj -"""Version to be deployed of 3.2 Calculating area/perimeter - -Automatically generated by Colab. - -Original file is located at - https://colab.research.google.com/drive/1XPeCoTBgWSNBYZ3aMKBteP4YG3w4bORs -""" -import ezdxf -from ezdxf.bbox import extents - -def detect_scale_from_page(dxf_path, page_pixel_width, m_per_pixel_from_pdf): - """ - Detects mm/px scale factor using the bounding box of the entire DXF content. - """ - doc = ezdxf.readfile(dxf_path) - - #getting the bounding box from modelspace - msp = doc.modelspace() - bbox_msp = extents(msp, fast=True) - - if bbox_msp.has_data: #not empty - min_x, min_y, max_x, max_y = bbox_msp.extmin.x, bbox_msp.extmin.y, bbox_msp.extmax.x, bbox_msp.extmax.y - else: - # Try paperspace as a fallback - psp = doc.layout("Layout1") - bbox_psp = extents(psp, fast=True) - if not bbox_psp.has_data: - raise ValueError("No bounding box data found in modelspace or paperspace.") - min_x, min_y, max_x, max_y = bbox_psp.extmin.x, bbox_psp.extmin.y, bbox_psp.extmax.x, bbox_psp.extmax.y - - # DXF width - dxf_width = max_x - min_x - - # PDF width in m - pdf_metric_width = page_pixel_width * m_per_pixel_from_pdf - - # Correction factor - correction_factor = dxf_width / pdf_metric_width - # final_scale = mm_per_pixel_from_pdf * correction_factor - - return correction_factor - - -"""## Notes""" - -#new approach to get width and height of dxf plan -''' -This portion is used to convert vertices read from dxf to pixels in order to accurately locate shapes in the image and pdf - ratio : - MeasuredMetric* PixelValue/ DxfMetric = MeasuredPixel - PixelValue: get from pixel conversion code , second number in the bracker represents the perimeter - DxfMetric: measured perimeter from foxit - - divide pixelvalue by dxfmetric, will give u a ratio , this is ur dxfratio - - -''' - -AllhatchesCodes= { - 'Brick':'<>/Matrix[1 0 0 1 0 0]/BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n{fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 18 m 19.00001 18 l 9 18 m 9 9 l -1 9 m 19.00001 9 l 0 9 m 0 0 l -1 0 m 19.00001 0 l 18 0 m 18 9 l S \nendstream' -, -'DiagonalBrick': '''<> - /Matrix [1 0 0 1 0 0] - /BBox [0 0 18 18] - /XStep 18/YStep 18>>stream - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 -1 m 19.00001 19.00001 l 9 9 m 0 18 l -1 17 m 1 19.00001 l 17 -1 m 19.00001 1 l S - endstream''' -, -'Horizontal':'''<> - /Matrix[1 0 0 1 0 0] - /BBox[0 0 18 18] - /XStep 18/YStep 18>>\nstream\n - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 13.5 m 19.00001 13.5 l -1 4.5 m 19.00001 4.5 l S - endstream''' -, -'Vertical':'''<> - /Matrix[1 0 0 1 0 0] - /BBox[0 0 18 18] - /XStep 18/YStep 18>>\nstream\n - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w 4.5 19.00001 m 4.5 -1 l 13.5 19.00001 m 13.5 -1 l S - endstream''' -, -'DiagonalDown':'''<>/Matrix[1 0 0 1 0 0] - /BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 19.00001 m 19.00001 -1 l -1 1 m 1 -1 l 17 19.00001 m 19.00001 17 l S - endstream''' -, -'DiagonalUp':'''<>/Matrix[1 0 0 1 0 0] - /BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 17 m 1 19.00001 l -1 -1 m 19.00001 19.00001 l 17 -1 m 19.00001 1 l S - endstream''' -, -'Grid':'''<>/Matrix[1 0 0 1 0 0] - /BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w 4.5 19.00001 m 4.5 -1 l 13.5 19.00001 m 13.5 -1 l -1 13.5 m 19.00001 13.5 l -1 4.5 m 19.00001 4.5 l S - endstream''' -, -'Weave':'''<>/Matrix[1 0 0 1 0 0] - /BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 19.00001 m 4.5 13.5 l -1 7.999999 m 10 19.00001 l 7.999999 19.00001 m 19.00001 7.999999 l 17 19.00001 m 19.00001 17 l -1 -1 m 13.5 13.5 l 4.5 4.5 m 10 -1 l 9 9 m 19.00001 -1 l 18 9 m 13.5 4.5 l S - endstream''' -, -'10Dots':'''<> - /Matrix[1 0 0 1 0 0]/BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w 0 -0.5 m 0.2761424 -0.5 0.5 -0.2761424 0.5 0 c 0.5 0.2761424 0.2761424 0.5 0 0.5 c -0.2761424 0.5 -0.5 0.2761424 -0.5 0 c -0.5 -0.2761424 -0.2761424 -0.5 0 -0.5 c h 0 4 m 0.2761424 4 0.5 4.223857 0.5 4.5 c 0.5 4.776143 0.2761424 5 0 5 c -0.2761424 5 -0.5 4.776143 -0.5 4.5 c -0.5 4.223857 -0.2761424 4 0 4 c h 0 8.5 m 0.2761424 8.5 0.5 8.723858 0.5 9 c 0.5 9.276142 0.2761424 9.5 0 9.5 c -0.2761424 9.5 -0.5 9.276142 -0.5 9 c -0.5 8.723858 -0.2761424 8.5 0 8.5 c h 0 13 m 0.2761424 13 0.5 13.22386 0.5 13.5 c 0.5 13.77614 0.2761424 14 0 14 c -0.2761424 14 -0.5 13.77614 -0.5 13.5 c -0.5 13.22386 -0.2761424 13 0 13 c h 0 17.5 m 0.2761424 17.5 0.5 17.72386 0.5 18 c 0.5 18.27614 0.2761424 18.5 0 18.5 c -0.2761424 18.5 -0.5 18.27614 -0.5 18 c -0.5 17.72386 -0.2761424 17.5 0 17.5 c h 4.5 -0.5 m 4.776143 -0.5 5 -0.2761424 5 0 c 5 0.2761424 4.776143 0.5 4.5 0.5 c 4.223857 0.5 4 0.2761424 4 0 c 4 -0.2761424 4.223857 -0.5 4.5 -0.5 c h 4.5 4 m 4.776143 4 5 4.223857 5 4.5 c 5 4.776143 4.776143 5 4.5 5 c 4.223857 5 4 4.776143 4 4.5 c 4 4.223857 4.223857 4 4.5 4 c h 4.5 8.5 m 4.776143 8.5 5 8.723858 5 9 c 5 9.276142 4.776143 9.5 4.5 9.5 c 4.223857 9.5 4 9.276142 4 9 c 4 8.723858 4.223857 8.5 4.5 8.5 c h 4.5 13 m 4.776143 13 5 13.22386 5 13.5 c 5 13.77614 4.776143 14 4.5 14 c 4.223857 14 4 13.77614 4 13.5 c 4 13.22386 4.223857 13 4.5 13 c h 4.5 17.5 m 4.776143 17.5 5 17.72386 5 18 c 5 18.27614 4.776143 18.5 4.5 18.5 c 4.223857 18.5 4 18.27614 4 18 c 4 17.72386 4.223857 17.5 4.5 17.5 c h 9 -0.5 m 9.276142 -0.5 9.5 -0.2761424 9.5 0 c 9.5 0.2761424 9.276142 0.5 9 0.5 c 8.723858 0.5 8.5 0.2761424 8.5 0 c 8.5 -0.2761424 8.723858 -0.5 9 -0.5 c h 9 4 m 9.276142 4 9.5 4.223857 9.5 4.5 c 9.5 4.776143 9.276142 5 9 5 c 8.723858 5 8.5 4.776143 8.5 4.5 c 8.5 4.223857 8.723858 4 9 4 c h 9 8.5 m 9.276142 8.5 9.5 8.723858 9.5 9 c 9.5 9.276142 9.276142 9.5 9 9.5 c 8.723858 9.5 8.5 9.276142 8.5 9 c 8.5 8.723858 8.723858 8.5 9 8.5 c h 9 13 m 9.276142 13 9.5 13.22386 9.5 13.5 c 9.5 13.77614 9.276142 14 9 14 c 8.723858 14 8.5 13.77614 8.5 13.5 c 8.5 13.22386 8.723858 13 9 13 c h 9 17.5 m 9.276142 17.5 9.5 17.72386 9.5 18 c 9.5 18.27614 9.276142 18.5 9 18.5 c 8.723858 18.5 8.5 18.27614 8.5 18 c 8.5 17.72386 8.723858 17.5 9 17.5 c h 13.5 -0.5 m 13.77614 -0.5 14 -0.2761424 14 0 c 14 0.2761424 13.77614 0.5 13.5 0.5 c 13.22386 0.5 13 0.2761424 13 0 c 13 -0.2761424 13.22386 -0.5 13.5 -0.5 c h 13.5 4 m 13.77614 4 14 4.223857 14 4.5 c 14 4.776143 13.77614 5 13.5 5 c 13.22386 5 13 4.776143 13 4.5 c 13 4.223857 13.22386 4 13.5 4 c h 13.5 8.5 m 13.77614 8.5 14 8.723858 14 9 c 14 9.276142 13.77614 9.5 13.5 9.5 c 13.22386 9.5 13 9.276142 13 9 c 13 8.723858 13.22386 8.5 13.5 8.5 c h 13.5 13 m 13.77614 13 14 13.22386 14 13.5 c 14 13.77614 13.77614 14 13.5 14 c 13.22386 14 13 13.77614 13 13.5 c 13 13.22386 13.22386 13 13.5 13 c h 13.5 17.5 m 13.77614 17.5 14 17.72386 14 18 c 14 18.27614 13.77614 18.5 13.5 18.5 c 13.22386 18.5 13 18.27614 13 18 c 13 17.72386 13.22386 17.5 13.5 17.5 c h 18 -0.5 m 18.27614 -0.5 18.5 -0.2761424 18.5 0 c 18.5 0.2761424 18.27614 0.5 18 0.5 c 17.72386 0.5 17.5 0.2761424 17.5 0 c 17.5 -0.2761424 17.72386 -0.5 18 -0.5 c h 18 4 m 18.27614 4 18.5 4.223857 18.5 4.5 c 18.5 4.776143 18.27614 5 18 5 c 17.72386 5 17.5 4.776143 17.5 4.5 c 17.5 4.223857 17.72386 4 18 4 c h 18 8.5 m 18.27614 8.5 18.5 8.723858 18.5 9 c 18.5 9.276142 18.27614 9.5 18 9.5 c 17.72386 9.5 17.5 9.276142 17.5 9 c 17.5 8.723858 17.72386 8.5 18 8.5 c h 18 13 m 18.27614 13 18.5 13.22386 18.5 13.5 c 18.5 13.77614 18.27614 14 18 14 c 17.72386 14 17.5 13.77614 17.5 13.5 c 17.5 13.22386 17.72386 13 18 13 c h 18 17.5 m 18.27614 17.5 18.5 17.72386 18.5 18 c 18.5 18.27614 18.27614 18.5 18 18.5 c 17.72386 18.5 17.5 18.27614 17.5 18 c 17.5 17.72386 17.72386 17.5 18 17.5 c h 2.25 1.75 m 2.526142 1.75 2.75 1.973858 2.75 2.25 c 2.75 2.526142 2.526142 2.75 2.25 2.75 c 1.973858 2.75 1.75 2.526142 1.75 2.25 c 1.75 1.973858 1.973858 1.75 2.25 1.75 c h 2.25 6.25 m 2.526142 6.25 2.75 6.473857 2.75 6.75 c 2.75 7.026143 2.526142 7.25 2.25 7.25 c 1.973858 7.25 1.75 7.026143 1.75 6.75 c 1.75 6.473857 1.973858 6.25 2.25 6.25 c h 2.25 10.75 m 2.526142 10.75 2.75 10.97386 2.75 11.25 c 2.75 11.52614 2.526142 11.75 2.25 11.75 c 1.973858 11.75 1.75 11.52614 1.75 11.25 c 1.75 10.97386 1.973858 10.75 2.25 10.75 c h 2.25 15.25 m 2.526142 15.25 2.75 15.47386 2.75 15.75 c 2.75 16.02614 2.526142 16.25 2.25 16.25 c 1.973858 16.25 1.75 16.02614 1.75 15.75 c 1.75 15.47386 1.973858 15.25 2.25 15.25 c h 6.75 1.75 m 7.026143 1.75 7.25 1.973858 7.25 2.25 c 7.25 2.526142 7.026143 2.75 6.75 2.75 c 6.473857 2.75 6.25 2.526142 6.25 2.25 c 6.25 1.973858 6.473857 1.75 6.75 1.75 c h 6.75 6.25 m 7.026143 6.25 7.25 6.473857 7.25 6.75 c 7.25 7.026143 7.026143 7.25 6.75 7.25 c 6.473857 7.25 6.25 7.026143 6.25 6.75 c 6.25 6.473857 6.473857 6.25 6.75 6.25 c h 6.75 10.75 m 7.026143 10.75 7.25 10.97386 7.25 11.25 c 7.25 11.52614 7.026143 11.75 6.75 11.75 c 6.473857 11.75 6.25 11.52614 6.25 11.25 c 6.25 10.97386 6.473857 10.75 6.75 10.75 c h 6.75 15.25 m 7.026143 15.25 7.25 15.47386 7.25 15.75 c 7.25 16.02614 7.026143 16.25 6.75 16.25 c 6.473857 16.25 6.25 16.02614 6.25 15.75 c 6.25 15.47386 6.473857 15.25 6.75 15.25 c h 11.25 1.75 m 11.52614 1.75 11.75 1.973858 11.75 2.25 c 11.75 2.526142 11.52614 2.75 11.25 2.75 c 10.97386 2.75 10.75 2.526142 10.75 2.25 c 10.75 1.973858 10.97386 1.75 11.25 1.75 c h 11.25 6.25 m 11.52614 6.25 11.75 6.473857 11.75 6.75 c 11.75 7.026143 11.52614 7.25 11.25 7.25 c 10.97386 7.25 10.75 7.026143 10.75 6.75 c 10.75 6.473857 10.97386 6.25 11.25 6.25 c h 11.25 10.75 m 11.52614 10.75 11.75 10.97386 11.75 11.25 c 11.75 11.52614 11.52614 11.75 11.25 11.75 c 10.97386 11.75 10.75 11.52614 10.75 11.25 c 10.75 10.97386 10.97386 10.75 11.25 10.75 c h 11.25 15.25 m 11.52614 15.25 11.75 15.47386 11.75 15.75 c 11.75 16.02614 11.52614 16.25 11.25 16.25 c 10.97386 16.25 10.75 16.02614 10.75 15.75 c 10.75 15.47386 10.97386 15.25 11.25 15.25 c h 15.75 1.75 m 16.02614 1.75 16.25 1.973858 16.25 2.25 c 16.25 2.526142 16.02614 2.75 15.75 2.75 c 15.47386 2.75 15.25 2.526142 15.25 2.25 c 15.25 1.973858 15.47386 1.75 15.75 1.75 c h 15.75 6.25 m 16.02614 6.25 16.25 6.473857 16.25 6.75 c 16.25 7.026143 16.02614 7.25 15.75 7.25 c 15.47386 7.25 15.25 7.026143 15.25 6.75 c 15.25 6.473857 15.47386 6.25 15.75 6.25 c h 15.75 10.75 m 16.02614 10.75 16.25 10.97386 16.25 11.25 c 16.25 11.52614 16.02614 11.75 15.75 11.75 c 15.47386 11.75 15.25 11.52614 15.25 11.25 c 15.25 10.97386 15.47386 10.75 15.75 10.75 c h 15.75 15.25 m 16.02614 15.25 16.25 15.47386 16.25 15.75 c 16.25 16.02614 16.02614 16.25 15.75 16.25 c 15.47386 16.25 15.25 16.02614 15.25 15.75 c 15.25 15.47386 15.47386 15.25 15.75 15.25 c h - {strokecolor} rg f - endstream''' -, -'20Dots':'''<> - /Matrix[1 0 0 1 0 0]/BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w 0 -0.5 m 0.2761424 -0.5 0.5 -0.2761424 0.5 0 c 0.5 0.2761424 0.2761424 0.5 0 0.5 c -0.2761424 0.5 -0.5 0.2761424 -0.5 0 c -0.5 -0.2761424 -0.2761424 -0.5 0 -0.5 c h 0 2.500001 m 0.2761424 2.500001 0.5 2.723858 0.5 3.000001 c 0.5 3.276143 0.2761424 3.500001 0 3.500001 c -0.2761424 3.500001 -0.5 3.276143 -0.5 3.000001 c -0.5 2.723858 -0.2761424 2.500001 0 2.500001 c h 0 5.5 m 0.2761424 5.5 0.5 5.723857 0.5 6 c 0.5 6.276142 0.2761424 6.5 0 6.5 c -0.2761424 6.5 -0.5 6.276142 -0.5 6 c -0.5 5.723857 -0.2761424 5.5 0 5.5 c h 0 8.5 m 0.2761424 8.5 0.5 8.723858 0.5 9 c 0.5 9.276142 0.2761424 9.5 0 9.5 c -0.2761424 9.5 -0.5 9.276142 -0.5 9 c -0.5 8.723858 -0.2761424 8.5 0 8.5 c h 0 11.5 m 0.2761424 11.5 0.5 11.72386 0.5 12 c 0.5 12.27614 0.2761424 12.5 0 12.5 c -0.2761424 12.5 -0.5 12.27614 -0.5 12 c -0.5 11.72386 -0.2761424 11.5 0 11.5 c h 0 14.5 m 0.2761424 14.5 0.5 14.72386 0.5 15 c 0.5 15.27614 0.2761424 15.5 0 15.5 c -0.2761424 15.5 -0.5 15.27614 -0.5 15 c -0.5 14.72386 -0.2761424 14.5 0 14.5 c h 0 17.5 m 0.2761424 17.5 0.5 17.72386 0.5 18 c 0.5 18.27614 0.2761424 18.5 0 18.5 c -0.2761424 18.5 -0.5 18.27614 -0.5 18 c -0.5 17.72386 -0.2761424 17.5 0 17.5 c h 3.000001 -0.5 m 3.276143 -0.5 3.500001 -0.2761424 3.500001 0 c 3.500001 0.2761424 3.276143 0.5 3.000001 0.5 c 2.723858 0.5 2.500001 0.2761424 2.500001 0 c 2.500001 -0.2761424 2.723858 -0.5 3.000001 -0.5 c h 3.000001 2.500001 m 3.276143 2.500001 3.500001 2.723858 3.500001 3.000001 c 3.500001 3.276143 3.276143 3.500001 3.000001 3.500001 c 2.723858 3.500001 2.500001 3.276143 2.500001 3.000001 c 2.500001 2.723858 2.723858 2.500001 3.000001 2.500001 c h 3.000001 5.5 m 3.276143 5.5 3.500001 5.723857 3.500001 6 c 3.500001 6.276142 3.276143 6.5 3.000001 6.5 c 2.723858 6.5 2.500001 6.276142 2.500001 6 c 2.500001 5.723857 2.723858 5.5 3.000001 5.5 c h 3.000001 8.5 m 3.276143 8.5 3.500001 8.723858 3.500001 9 c 3.500001 9.276142 3.276143 9.5 3.000001 9.5 c 2.723858 9.5 2.500001 9.276142 2.500001 9 c 2.500001 8.723858 2.723858 8.5 3.000001 8.5 c h 3.000001 11.5 m 3.276143 11.5 3.500001 11.72386 3.500001 12 c 3.500001 12.27614 3.276143 12.5 3.000001 12.5 c 2.723858 12.5 2.500001 12.27614 2.500001 12 c 2.500001 11.72386 2.723858 11.5 3.000001 11.5 c h 3.000001 14.5 m 3.276143 14.5 3.500001 14.72386 3.500001 15 c 3.500001 15.27614 3.276143 15.5 3.000001 15.5 c 2.723858 15.5 2.500001 15.27614 2.500001 15 c 2.500001 14.72386 2.723858 14.5 3.000001 14.5 c h 3.000001 17.5 m 3.276143 17.5 3.500001 17.72386 3.500001 18 c 3.500001 18.27614 3.276143 18.5 3.000001 18.5 c 2.723858 18.5 2.500001 18.27614 2.500001 18 c 2.500001 17.72386 2.723858 17.5 3.000001 17.5 c h 6 -0.5 m 6.276142 -0.5 6.5 -0.2761424 6.5 0 c 6.5 0.2761424 6.276142 0.5 6 0.5 c 5.723857 0.5 5.5 0.2761424 5.5 0 c 5.5 -0.2761424 5.723857 -0.5 6 -0.5 c h 6 2.500001 m 6.276142 2.500001 6.5 2.723858 6.5 3.000001 c 6.5 3.276143 6.276142 3.500001 6 3.500001 c 5.723857 3.500001 5.5 3.276143 5.5 3.000001 c 5.5 2.723858 5.723857 2.500001 6 2.500001 c h 6 5.5 m 6.276142 5.5 6.5 5.723857 6.5 6 c 6.5 6.276142 6.276142 6.5 6 6.5 c 5.723857 6.5 5.5 6.276142 5.5 6 c 5.5 5.723857 5.723857 5.5 6 5.5 c h 6 8.5 m 6.276142 8.5 6.5 8.723858 6.5 9 c 6.5 9.276142 6.276142 9.5 6 9.5 c 5.723857 9.5 5.5 9.276142 5.5 9 c 5.5 8.723858 5.723857 8.5 6 8.5 c h 6 11.5 m 6.276142 11.5 6.5 11.72386 6.5 12 c 6.5 12.27614 6.276142 12.5 6 12.5 c 5.723857 12.5 5.5 12.27614 5.5 12 c 5.5 11.72386 5.723857 11.5 6 11.5 c h 6 14.5 m 6.276142 14.5 6.5 14.72386 6.5 15 c 6.5 15.27614 6.276142 15.5 6 15.5 c 5.723857 15.5 5.5 15.27614 5.5 15 c 5.5 14.72386 5.723857 14.5 6 14.5 c h 6 17.5 m 6.276142 17.5 6.5 17.72386 6.5 18 c 6.5 18.27614 6.276142 18.5 6 18.5 c 5.723857 18.5 5.5 18.27614 5.5 18 c 5.5 17.72386 5.723857 17.5 6 17.5 c h 9 -0.5 m 9.276142 -0.5 9.5 -0.2761424 9.5 0 c 9.5 0.2761424 9.276142 0.5 9 0.5 c 8.723858 0.5 8.5 0.2761424 8.5 0 c 8.5 -0.2761424 8.723858 -0.5 9 -0.5 c h 9 2.500001 m 9.276142 2.500001 9.5 2.723858 9.5 3.000001 c 9.5 3.276143 9.276142 3.500001 9 3.500001 c 8.723858 3.500001 8.5 3.276143 8.5 3.000001 c 8.5 2.723858 8.723858 2.500001 9 2.500001 c h 9 5.5 m 9.276142 5.5 9.5 5.723857 9.5 6 c 9.5 6.276142 9.276142 6.5 9 6.5 c 8.723858 6.5 8.5 6.276142 8.5 6 c 8.5 5.723857 8.723858 5.5 9 5.5 c h 9 8.5 m 9.276142 8.5 9.5 8.723858 9.5 9 c 9.5 9.276142 9.276142 9.5 9 9.5 c 8.723858 9.5 8.5 9.276142 8.5 9 c 8.5 8.723858 8.723858 8.5 9 8.5 c h 9 11.5 m 9.276142 11.5 9.5 11.72386 9.5 12 c 9.5 12.27614 9.276142 12.5 9 12.5 c 8.723858 12.5 8.5 12.27614 8.5 12 c 8.5 11.72386 8.723858 11.5 9 11.5 c h 9 14.5 m 9.276142 14.5 9.5 14.72386 9.5 15 c 9.5 15.27614 9.276142 15.5 9 15.5 c 8.723858 15.5 8.5 15.27614 8.5 15 c 8.5 14.72386 8.723858 14.5 9 14.5 c h 9 17.5 m 9.276142 17.5 9.5 17.72386 9.5 18 c 9.5 18.27614 9.276142 18.5 9 18.5 c 8.723858 18.5 8.5 18.27614 8.5 18 c 8.5 17.72386 8.723858 17.5 9 17.5 c h 12 -0.5 m 12.27614 -0.5 12.5 -0.2761424 12.5 0 c 12.5 0.2761424 12.27614 0.5 12 0.5 c 11.72386 0.5 11.5 0.2761424 11.5 0 c 11.5 -0.2761424 11.72386 -0.5 12 -0.5 c h 12 2.500001 m 12.27614 2.500001 12.5 2.723858 12.5 3.000001 c 12.5 3.276143 12.27614 3.500001 12 3.500001 c 11.72386 3.500001 11.5 3.276143 11.5 3.000001 c 11.5 2.723858 11.72386 2.500001 12 2.500001 c h 12 5.5 m 12.27614 5.5 12.5 5.723857 12.5 6 c 12.5 6.276142 12.27614 6.5 12 6.5 c 11.72386 6.5 11.5 6.276142 11.5 6 c 11.5 5.723857 11.72386 5.5 12 5.5 c h 12 8.5 m 12.27614 8.5 12.5 8.723858 12.5 9 c 12.5 9.276142 12.27614 9.5 12 9.5 c 11.72386 9.5 11.5 9.276142 11.5 9 c 11.5 8.723858 11.72386 8.5 12 8.5 c h 12 11.5 m 12.27614 11.5 12.5 11.72386 12.5 12 c 12.5 12.27614 12.27614 12.5 12 12.5 c 11.72386 12.5 11.5 12.27614 11.5 12 c 11.5 11.72386 11.72386 11.5 12 11.5 c h 12 14.5 m 12.27614 14.5 12.5 14.72386 12.5 15 c 12.5 15.27614 12.27614 15.5 12 15.5 c 11.72386 15.5 11.5 15.27614 11.5 15 c 11.5 14.72386 11.72386 14.5 12 14.5 c h 12 17.5 m 12.27614 17.5 12.5 17.72386 12.5 18 c 12.5 18.27614 12.27614 18.5 12 18.5 c 11.72386 18.5 11.5 18.27614 11.5 18 c 11.5 17.72386 11.72386 17.5 12 17.5 c h 15 -0.5 m 15.27614 -0.5 15.5 -0.2761424 15.5 0 c 15.5 0.2761424 15.27614 0.5 15 0.5 c 14.72386 0.5 14.5 0.2761424 14.5 0 c 14.5 -0.2761424 14.72386 -0.5 15 -0.5 c h 15 2.500001 m 15.27614 2.500001 15.5 2.723858 15.5 3.000001 c 15.5 3.276143 15.27614 3.500001 15 3.500001 c 14.72386 3.500001 14.5 3.276143 14.5 3.000001 c 14.5 2.723858 14.72386 2.500001 15 2.500001 c h 15 5.5 m 15.27614 5.5 15.5 5.723857 15.5 6 c 15.5 6.276142 15.27614 6.5 15 6.5 c 14.72386 6.5 14.5 6.276142 14.5 6 c 14.5 5.723857 14.72386 5.5 15 5.5 c h 15 8.5 m 15.27614 8.5 15.5 8.723858 15.5 9 c 15.5 9.276142 15.27614 9.5 15 9.5 c 14.72386 9.5 14.5 9.276142 14.5 9 c 14.5 8.723858 14.72386 8.5 15 8.5 c h 15 11.5 m 15.27614 11.5 15.5 11.72386 15.5 12 c 15.5 12.27614 15.27614 12.5 15 12.5 c 14.72386 12.5 14.5 12.27614 14.5 12 c 14.5 11.72386 14.72386 11.5 15 11.5 c h 15 14.5 m 15.27614 14.5 15.5 14.72386 15.5 15 c 15.5 15.27614 15.27614 15.5 15 15.5 c 14.72386 15.5 14.5 15.27614 14.5 15 c 14.5 14.72386 14.72386 14.5 15 14.5 c h 15 17.5 m 15.27614 17.5 15.5 17.72386 15.5 18 c 15.5 18.27614 15.27614 18.5 15 18.5 c 14.72386 18.5 14.5 18.27614 14.5 18 c 14.5 17.72386 14.72386 17.5 15 17.5 c h 18 -0.5 m 18.27614 -0.5 18.5 -0.2761424 18.5 0 c 18.5 0.2761424 18.27614 0.5 18 0.5 c 17.72386 0.5 17.5 0.2761424 17.5 0 c 17.5 -0.2761424 17.72386 -0.5 18 -0.5 c h 18 2.500001 m 18.27614 2.500001 18.5 2.723858 18.5 3.000001 c 18.5 3.276143 18.27614 3.500001 18 3.500001 c 17.72386 3.500001 17.5 3.276143 17.5 3.000001 c 17.5 2.723858 17.72386 2.500001 18 2.500001 c h 18 5.5 m 18.27614 5.5 18.5 5.723857 18.5 6 c 18.5 6.276142 18.27614 6.5 18 6.5 c 17.72386 6.5 17.5 6.276142 17.5 6 c 17.5 5.723857 17.72386 5.5 18 5.5 c h 18 8.5 m 18.27614 8.5 18.5 8.723858 18.5 9 c 18.5 9.276142 18.27614 9.5 18 9.5 c 17.72386 9.5 17.5 9.276142 17.5 9 c 17.5 8.723858 17.72386 8.5 18 8.5 c h 18 11.5 m 18.27614 11.5 18.5 11.72386 18.5 12 c 18.5 12.27614 18.27614 12.5 18 12.5 c 17.72386 12.5 17.5 12.27614 17.5 12 c 17.5 11.72386 17.72386 11.5 18 11.5 c h 18 14.5 m 18.27614 14.5 18.5 14.72386 18.5 15 c 18.5 15.27614 18.27614 15.5 18 15.5 c 17.72386 15.5 17.5 15.27614 17.5 15 c 17.5 14.72386 17.72386 14.5 18 14.5 c h 18 17.5 m 18.27614 17.5 18.5 17.72386 18.5 18 c 18.5 18.27614 18.27614 18.5 18 18.5 c 17.72386 18.5 17.5 18.27614 17.5 18 c 17.5 17.72386 17.72386 17.5 18 17.5 c h 1.5 1 m 1.776143 1 2 1.223858 2 1.5 c 2 1.776143 1.776143 2 1.5 2 c 1.223858 2 1 1.776143 1 1.5 c 1 1.223858 1.223858 1 1.5 1 c h 1.5 4 m 1.776143 4 2 4.223857 2 4.5 c 2 4.776143 1.776143 5 1.5 5 c 1.223858 5 1 4.776143 1 4.5 c 1 4.223857 1.223858 4 1.5 4 c h 1.5 7 m 1.776143 7 2 7.223858 2 7.5 c 2 7.776143 1.776143 8 1.5 8 c 1.223858 8 1 7.776143 1 7.5 c 1 7.223858 1.223858 7 1.5 7 c h 1.5 10 m 1.776143 10 2 10.22386 2 10.5 c 2 10.77614 1.776143 11 1.5 11 c 1.223858 11 1 10.77614 1 10.5 c 1 10.22386 1.223858 10 1.5 10 c h 1.5 13 m 1.776143 13 2 13.22386 2 13.5 c 2 13.77614 1.776143 14 1.5 14 c 1.223858 14 1 13.77614 1 13.5 c 1 13.22386 1.223858 13 1.5 13 c h 1.5 16 m 1.776143 16 2 16.22386 2 16.5 c 2 16.77614 1.776143 17 1.5 17 c 1.223858 17 1 16.77614 1 16.5 c 1 16.22386 1.223858 16 1.5 16 c h 4.5 1 m 4.776143 1 5 1.223858 5 1.5 c 5 1.776143 4.776143 2 4.5 2 c 4.223857 2 4 1.776143 4 1.5 c 4 1.223858 4.223857 1 4.5 1 c h 4.5 4 m 4.776143 4 5 4.223857 5 4.5 c 5 4.776143 4.776143 5 4.5 5 c 4.223857 5 4 4.776143 4 4.5 c 4 4.223857 4.223857 4 4.5 4 c h 4.5 7 m 4.776143 7 5 7.223858 5 7.5 c 5 7.776143 4.776143 8 4.5 8 c 4.223857 8 4 7.776143 4 7.5 c 4 7.223858 4.223857 7 4.5 7 c h 4.5 10 m 4.776143 10 5 10.22386 5 10.5 c 5 10.77614 4.776143 11 4.5 11 c 4.223857 11 4 10.77614 4 10.5 c 4 10.22386 4.223857 10 4.5 10 c h 4.5 13 m 4.776143 13 5 13.22386 5 13.5 c 5 13.77614 4.776143 14 4.5 14 c 4.223857 14 4 13.77614 4 13.5 c 4 13.22386 4.223857 13 4.5 13 c h 4.5 16 m 4.776143 16 5 16.22386 5 16.5 c 5 16.77614 4.776143 17 4.5 17 c 4.223857 17 4 16.77614 4 16.5 c 4 16.22386 4.223857 16 4.5 16 c h 7.5 1 m 7.776143 1 8 1.223858 8 1.5 c 8 1.776143 7.776143 2 7.5 2 c 7.223858 2 7 1.776143 7 1.5 c 7 1.223858 7.223858 1 7.5 1 c h 7.5 4 m 7.776143 4 8 4.223857 8 4.5 c 8 4.776143 7.776143 5 7.5 5 c 7.223858 5 7 4.776143 7 4.5 c 7 4.223857 7.223858 4 7.5 4 c h 7.5 7 m 7.776143 7 8 7.223858 8 7.5 c 8 7.776143 7.776143 8 7.5 8 c 7.223858 8 7 7.776143 7 7.5 c 7 7.223858 7.223858 7 7.5 7 c h 7.5 10 m 7.776143 10 8 10.22386 8 10.5 c 8 10.77614 7.776143 11 7.5 11 c 7.223858 11 7 10.77614 7 10.5 c 7 10.22386 7.223858 10 7.5 10 c h 7.5 13 m 7.776143 13 8 13.22386 8 13.5 c 8 13.77614 7.776143 14 7.5 14 c 7.223858 14 7 13.77614 7 13.5 c 7 13.22386 7.223858 13 7.5 13 c h 7.5 16 m 7.776143 16 8 16.22386 8 16.5 c 8 16.77614 7.776143 17 7.5 17 c 7.223858 17 7 16.77614 7 16.5 c 7 16.22386 7.223858 16 7.5 16 c h 10.5 1 m 10.77614 1 11 1.223858 11 1.5 c 11 1.776143 10.77614 2 10.5 2 c 10.22386 2 10 1.776143 10 1.5 c 10 1.223858 10.22386 1 10.5 1 c h 10.5 4 m 10.77614 4 11 4.223857 11 4.5 c 11 4.776143 10.77614 5 10.5 5 c 10.22386 5 10 4.776143 10 4.5 c 10 4.223857 10.22386 4 10.5 4 c h 10.5 7 m 10.77614 7 11 7.223858 11 7.5 c 11 7.776143 10.77614 8 10.5 8 c 10.22386 8 10 7.776143 10 7.5 c 10 7.223858 10.22386 7 10.5 7 c h 10.5 10 m 10.77614 10 11 10.22386 11 10.5 c 11 10.77614 10.77614 11 10.5 11 c 10.22386 11 10 10.77614 10 10.5 c 10 10.22386 10.22386 10 10.5 10 c h 10.5 13 m 10.77614 13 11 13.22386 11 13.5 c 11 13.77614 10.77614 14 10.5 14 c 10.22386 14 10 13.77614 10 13.5 c 10 13.22386 10.22386 13 10.5 13 c h 10.5 16 m 10.77614 16 11 16.22386 11 16.5 c 11 16.77614 10.77614 17 10.5 17 c 10.22386 17 10 16.77614 10 16.5 c 10 16.22386 10.22386 16 10.5 16 c h 13.5 1 m 13.77614 1 14 1.223858 14 1.5 c 14 1.776143 13.77614 2 13.5 2 c 13.22386 2 13 1.776143 13 1.5 c 13 1.223858 13.22386 1 13.5 1 c h 13.5 4 m 13.77614 4 14 4.223857 14 4.5 c 14 4.776143 13.77614 5 13.5 5 c 13.22386 5 13 4.776143 13 4.5 c 13 4.223857 13.22386 4 13.5 4 c h 13.5 7 m 13.77614 7 14 7.223858 14 7.5 c 14 7.776143 13.77614 8 13.5 8 c 13.22386 8 13 7.776143 13 7.5 c 13 7.223858 13.22386 7 13.5 7 c h 13.5 10 m 13.77614 10 14 10.22386 14 10.5 c 14 10.77614 13.77614 11 13.5 11 c 13.22386 11 13 10.77614 13 10.5 c 13 10.22386 13.22386 10 13.5 10 c h 13.5 13 m 13.77614 13 14 13.22386 14 13.5 c 14 13.77614 13.77614 14 13.5 14 c 13.22386 14 13 13.77614 13 13.5 c 13 13.22386 13.22386 13 13.5 13 c h 13.5 16 m 13.77614 16 14 16.22386 14 16.5 c 14 16.77614 13.77614 17 13.5 17 c 13.22386 17 13 16.77614 13 16.5 c 13 16.22386 13.22386 16 13.5 16 c h 16.5 1 m 16.77614 1 17 1.223858 17 1.5 c 17 1.776143 16.77614 2 16.5 2 c 16.22386 2 16 1.776143 16 1.5 c 16 1.223858 16.22386 1 16.5 1 c h 16.5 4 m 16.77614 4 17 4.223857 17 4.5 c 17 4.776143 16.77614 5 16.5 5 c 16.22386 5 16 4.776143 16 4.5 c 16 4.223857 16.22386 4 16.5 4 c h 16.5 7 m 16.77614 7 17 7.223858 17 7.5 c 17 7.776143 16.77614 8 16.5 8 c 16.22386 8 16 7.776143 16 7.5 c 16 7.223858 16.22386 7 16.5 7 c h 16.5 10 m 16.77614 10 17 10.22386 17 10.5 c 17 10.77614 16.77614 11 16.5 11 c 16.22386 11 16 10.77614 16 10.5 c 16 10.22386 16.22386 10 16.5 10 c h 16.5 13 m 16.77614 13 17 13.22386 17 13.5 c 17 13.77614 16.77614 14 16.5 14 c 16.22386 14 16 13.77614 16 13.5 c 16 13.22386 16.22386 13 16.5 13 c h 16.5 16 m 16.77614 16 17 16.22386 17 16.5 c 17 16.77614 16.77614 17 16.5 17 c 16.22386 17 16 16.77614 16 16.5 c 16 16.22386 16.22386 16 16.5 16 c h - {strokecolor} rg f - endstream''' -, -'30Dots':'''<> - /Matrix[1 0 0 1 0 0]/BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n - {fillcolor} rg 0 0 18 18 re f {strokecolor} RG - 1 w 0 -0.5 m 0.2761424 -0.5 0.5 -0.2761424 0.5 0 c 0.5 0.2761424 0.2761424 0.5 0 0.5 c -0.2761424 0.5 -0.5 0.2761424 -0.5 0 c -0.5 -0.2761424 -0.2761424 -0.5 0 -0.5 c h 0 2.071428 m 0.2761424 2.071428 0.5 2.295285 0.5 2.571428 c 0.5 2.84757 0.2761424 3.071428 0 3.071428 c -0.2761424 3.071428 -0.5 2.84757 -0.5 2.571428 c -0.5 2.295285 -0.2761424 2.071428 0 2.071428 c h 0 4.642858 m 0.2761424 4.642858 0.5 4.866715 0.5 5.142858 c 0.5 5.419 0.2761424 5.642858 0 5.642858 c -0.2761424 5.642858 -0.5 5.419 -0.5 5.142858 c -0.5 4.866715 -0.2761424 4.642858 0 4.642858 c h 0 7.214287 m 0.2761424 7.214287 0.5 7.438144 0.5 7.714287 c 0.5 7.990429 0.2761424 8.214287 0 8.214287 c -0.2761424 8.214287 -0.5 7.990429 -0.5 7.714287 c -0.5 7.438144 -0.2761424 7.214287 0 7.214287 c h 0 9.785715 m 0.2761424 9.785715 0.5 10.00957 0.5 10.28572 c 0.5 10.56186 0.2761424 10.78572 0 10.78572 c -0.2761424 10.78572 -0.5 10.56186 -0.5 10.28572 c -0.5 10.00957 -0.2761424 9.785715 0 9.785715 c h 0 12.35714 m 0.2761424 12.35714 0.5 12.581 0.5 12.85714 c 0.5 13.13328 0.2761424 13.35714 0 13.35714 c -0.2761424 13.35714 -0.5 13.13328 -0.5 12.85714 c -0.5 12.581 -0.2761424 12.35714 0 12.35714 c h 0 14.92857 m 0.2761424 14.92857 0.5 15.15243 0.5 15.42857 c 0.5 15.70471 0.2761424 15.92857 0 15.92857 c -0.2761424 15.92857 -0.5 15.70471 -0.5 15.42857 c -0.5 15.15243 -0.2761424 14.92857 0 14.92857 c h 0 17.5 m 0.2761424 17.5 0.5 17.72386 0.5 18 c 0.5 18.27614 0.2761424 18.5 0 18.5 c -0.2761424 18.5 -0.5 18.27614 -0.5 18 c -0.5 17.72386 -0.2761424 17.5 0 17.5 c h 2.571428 -0.5 m 2.84757 -0.5 3.071428 -0.2761424 3.071428 0 c 3.071428 0.2761424 2.84757 0.5 2.571428 0.5 c 2.295285 0.5 2.071428 0.2761424 2.071428 0 c 2.071428 -0.2761424 2.295285 -0.5 2.571428 -0.5 c h 2.571428 2.071428 m 2.84757 2.071428 3.071428 2.295285 3.071428 2.571428 c 3.071428 2.84757 2.84757 3.071428 2.571428 3.071428 c 2.295285 3.071428 2.071428 2.84757 2.071428 2.571428 c 2.071428 2.295285 2.295285 2.071428 2.571428 2.071428 c h 2.571428 4.642858 m 2.84757 4.642858 3.071428 4.866715 3.071428 5.142858 c 3.071428 5.419 2.84757 5.642858 2.571428 5.642858 c 2.295285 5.642858 2.071428 5.419 2.071428 5.142858 c 2.071428 4.866715 2.295285 4.642858 2.571428 4.642858 c h 2.571428 7.214287 m 2.84757 7.214287 3.071428 7.438144 3.071428 7.714287 c 3.071428 7.990429 2.84757 8.214287 2.571428 8.214287 c 2.295285 8.214287 2.071428 7.990429 2.071428 7.714287 c 2.071428 7.438144 2.295285 7.214287 2.571428 7.214287 c h 2.571428 9.785715 m 2.84757 9.785715 3.071428 10.00957 3.071428 10.28572 c 3.071428 10.56186 2.84757 10.78572 2.571428 10.78572 c 2.295285 10.78572 2.071428 10.56186 2.071428 10.28572 c 2.071428 10.00957 2.295285 9.785715 2.571428 9.785715 c h 2.571428 12.35714 m 2.84757 12.35714 3.071428 12.581 3.071428 12.85714 c 3.071428 13.13328 2.84757 13.35714 2.571428 13.35714 c 2.295285 13.35714 2.071428 13.13328 2.071428 12.85714 c 2.071428 12.581 2.295285 12.35714 2.571428 12.35714 c h 2.571428 14.92857 m 2.84757 14.92857 3.071428 15.15243 3.071428 15.42857 c 3.071428 15.70471 2.84757 15.92857 2.571428 15.92857 c 2.295285 15.92857 2.071428 15.70471 2.071428 15.42857 c 2.071428 15.15243 2.295285 14.92857 2.571428 14.92857 c h 2.571428 17.5 m 2.84757 17.5 3.071428 17.72386 3.071428 18 c 3.071428 18.27614 2.84757 18.5 2.571428 18.5 c 2.295285 18.5 2.071428 18.27614 2.071428 18 c 2.071428 17.72386 2.295285 17.5 2.571428 17.5 c h 5.142858 -0.5 m 5.419 -0.5 5.642858 -0.2761424 5.642858 0 c 5.642858 0.2761424 5.419 0.5 5.142858 0.5 c 4.866715 0.5 4.642858 0.2761424 4.642858 0 c 4.642858 -0.2761424 4.866715 -0.5 5.142858 -0.5 c h 5.142858 2.071428 m 5.419 2.071428 5.642858 2.295285 5.642858 2.571428 c 5.642858 2.84757 5.419 3.071428 5.142858 3.071428 c 4.866715 3.071428 4.642858 2.84757 4.642858 2.571428 c 4.642858 2.295285 4.866715 2.071428 5.142858 2.071428 c h 5.142858 4.642858 m 5.419 4.642858 5.642858 4.866715 5.642858 5.142858 c 5.642858 5.419 5.419 5.642858 5.142858 5.642858 c 4.866715 5.642858 4.642858 5.419 4.642858 5.142858 c 4.642858 4.866715 4.866715 4.642858 5.142858 4.642858 c h 5.142858 7.214287 m 5.419 7.214287 5.642858 7.438144 5.642858 7.714287 c 5.642858 7.990429 5.419 8.214287 5.142858 8.214287 c 4.866715 8.214287 4.642858 7.990429 4.642858 7.714287 c 4.642858 7.438144 4.866715 7.214287 5.142858 7.214287 c h 5.142858 9.785715 m 5.419 9.785715 5.642858 10.00957 5.642858 10.28572 c 5.642858 10.56186 5.419 10.78572 5.142858 10.78572 c 4.866715 10.78572 4.642858 10.56186 4.642858 10.28572 c 4.642858 10.00957 4.866715 9.785715 5.142858 9.785715 c h 5.142858 12.35714 m 5.419 12.35714 5.642858 12.581 5.642858 12.85714 c 5.642858 13.13328 5.419 13.35714 5.142858 13.35714 c 4.866715 13.35714 4.642858 13.13328 4.642858 12.85714 c 4.642858 12.581 4.866715 12.35714 5.142858 12.35714 c h 5.142858 14.92857 m 5.419 14.92857 5.642858 15.15243 5.642858 15.42857 c 5.642858 15.70471 5.419 15.92857 5.142858 15.92857 c 4.866715 15.92857 4.642858 15.70471 4.642858 15.42857 c 4.642858 15.15243 4.866715 14.92857 5.142858 14.92857 c h 5.142858 17.5 m 5.419 17.5 5.642858 17.72386 5.642858 18 c 5.642858 18.27614 5.419 18.5 5.142858 18.5 c 4.866715 18.5 4.642858 18.27614 4.642858 18 c 4.642858 17.72386 4.866715 17.5 5.142858 17.5 c h 7.714287 -0.5 m 7.990429 -0.5 8.214287 -0.2761424 8.214287 0 c 8.214287 0.2761424 7.990429 0.5 7.714287 0.5 c 7.438144 0.5 7.214287 0.2761424 7.214287 0 c 7.214287 -0.2761424 7.438144 -0.5 7.714287 -0.5 c h 7.714287 2.071428 m 7.990429 2.071428 8.214287 2.295285 8.214287 2.571428 c 8.214287 2.84757 7.990429 3.071428 7.714287 3.071428 c 7.438144 3.071428 7.214287 2.84757 7.214287 2.571428 c 7.214287 2.295285 7.438144 2.071428 7.714287 2.071428 c h 7.714287 4.642858 m 7.990429 4.642858 8.214287 4.866715 8.214287 5.142858 c 8.214287 5.419 7.990429 5.642858 7.714287 5.642858 c 7.438144 5.642858 7.214287 5.419 7.214287 5.142858 c 7.214287 4.866715 7.438144 4.642858 7.714287 4.642858 c h 7.714287 7.214287 m 7.990429 7.214287 8.214287 7.438144 8.214287 7.714287 c 8.214287 7.990429 7.990429 8.214287 7.714287 8.214287 c 7.438144 8.214287 7.214287 7.990429 7.214287 7.714287 c 7.214287 7.438144 7.438144 7.214287 7.714287 7.214287 c h 7.714287 9.785715 m 7.990429 9.785715 8.214287 10.00957 8.214287 10.28572 c 8.214287 10.56186 7.990429 10.78572 7.714287 10.78572 c 7.438144 10.78572 7.214287 10.56186 7.214287 10.28572 c 7.214287 10.00957 7.438144 9.785715 7.714287 9.785715 c h 7.714287 12.35714 m 7.990429 12.35714 8.214287 12.581 8.214287 12.85714 c 8.214287 13.13328 7.990429 13.35714 7.714287 13.35714 c 7.438144 13.35714 7.214287 13.13328 7.214287 12.85714 c 7.214287 12.581 7.438144 12.35714 7.714287 12.35714 c h 7.714287 14.92857 m 7.990429 14.92857 8.214287 15.15243 8.214287 15.42857 c 8.214287 15.70471 7.990429 15.92857 7.714287 15.92857 c 7.438144 15.92857 7.214287 15.70471 7.214287 15.42857 c 7.214287 15.15243 7.438144 14.92857 7.714287 14.92857 c h 7.714287 17.5 m 7.990429 17.5 8.214287 17.72386 8.214287 18 c 8.214287 18.27614 7.990429 18.5 7.714287 18.5 c 7.438144 18.5 7.214287 18.27614 7.214287 18 c 7.214287 17.72386 7.438144 17.5 7.714287 17.5 c h 10.28572 -0.5 m 10.56186 -0.5 10.78572 -0.2761424 10.78572 0 c 10.78572 0.2761424 10.56186 0.5 10.28572 0.5 c 10.00957 0.5 9.785715 0.2761424 9.785715 0 c 9.785715 -0.2761424 10.00957 -0.5 10.28572 -0.5 c h 10.28572 2.071428 m 10.56186 2.071428 10.78572 2.295285 10.78572 2.571428 c 10.78572 2.84757 10.56186 3.071428 10.28572 3.071428 c 10.00957 3.071428 9.785715 2.84757 9.785715 2.571428 c 9.785715 2.295285 10.00957 2.071428 10.28572 2.071428 c h 10.28572 4.642858 m 10.56186 4.642858 10.78572 4.866715 10.78572 5.142858 c 10.78572 5.419 10.56186 5.642858 10.28572 5.642858 c 10.00957 5.642858 9.785715 5.419 9.785715 5.142858 c 9.785715 4.866715 10.00957 4.642858 10.28572 4.642858 c h 10.28572 7.214287 m 10.56186 7.214287 10.78572 7.438144 10.78572 7.714287 c 10.78572 7.990429 10.56186 8.214287 10.28572 8.214287 c 10.00957 8.214287 9.785715 7.990429 9.785715 7.714287 c 9.785715 7.438144 10.00957 7.214287 10.28572 7.214287 c h 10.28572 9.785715 m 10.56186 9.785715 10.78572 10.00957 10.78572 10.28572 c 10.78572 10.56186 10.56186 10.78572 10.28572 10.78572 c 10.00957 10.78572 9.785715 10.56186 9.785715 10.28572 c 9.785715 10.00957 10.00957 9.785715 10.28572 9.785715 c h 10.28572 12.35714 m 10.56186 12.35714 10.78572 12.581 10.78572 12.85714 c 10.78572 13.13328 10.56186 13.35714 10.28572 13.35714 c 10.00957 13.35714 9.785715 13.13328 9.785715 12.85714 c 9.785715 12.581 10.00957 12.35714 10.28572 12.35714 c h 10.28572 14.92857 m 10.56186 14.92857 10.78572 15.15243 10.78572 15.42857 c 10.78572 15.70471 10.56186 15.92857 10.28572 15.92857 c 10.00957 15.92857 9.785715 15.70471 9.785715 15.42857 c 9.785715 15.15243 10.00957 14.92857 10.28572 14.92857 c h 10.28572 17.5 m 10.56186 17.5 10.78572 17.72386 10.78572 18 c 10.78572 18.27614 10.56186 18.5 10.28572 18.5 c 10.00957 18.5 9.785715 18.27614 9.785715 18 c 9.785715 17.72386 10.00957 17.5 10.28572 17.5 c h 12.85714 -0.5 m 13.13328 -0.5 13.35714 -0.2761424 13.35714 0 c 13.35714 0.2761424 13.13328 0.5 12.85714 0.5 c 12.581 0.5 12.35714 0.2761424 12.35714 0 c 12.35714 -0.2761424 12.581 -0.5 12.85714 -0.5 c h 12.85714 2.071428 m 13.13328 2.071428 13.35714 2.295285 13.35714 2.571428 c 13.35714 2.84757 13.13328 3.071428 12.85714 3.071428 c 12.581 3.071428 12.35714 2.84757 12.35714 2.571428 c 12.35714 2.295285 12.581 2.071428 12.85714 2.071428 c h 12.85714 4.642858 m 13.13328 4.642858 13.35714 4.866715 13.35714 5.142858 c 13.35714 5.419 13.13328 5.642858 12.85714 5.642858 c 12.581 5.642858 12.35714 5.419 12.35714 5.142858 c 12.35714 4.866715 12.581 4.642858 12.85714 4.642858 c h 12.85714 7.214287 m 13.13328 7.214287 13.35714 7.438144 13.35714 7.714287 c 13.35714 7.990429 13.13328 8.214287 12.85714 8.214287 c 12.581 8.214287 12.35714 7.990429 12.35714 7.714287 c 12.35714 7.438144 12.581 7.214287 12.85714 7.214287 c h 12.85714 9.785715 m 13.13328 9.785715 13.35714 10.00957 13.35714 10.28572 c 13.35714 10.56186 13.13328 10.78572 12.85714 10.78572 c 12.581 10.78572 12.35714 10.56186 12.35714 10.28572 c 12.35714 10.00957 12.581 9.785715 12.85714 9.785715 c h 12.85714 12.35714 m 13.13328 12.35714 13.35714 12.581 13.35714 12.85714 c 13.35714 13.13328 13.13328 13.35714 12.85714 13.35714 c 12.581 13.35714 12.35714 13.13328 12.35714 12.85714 c 12.35714 12.581 12.581 12.35714 12.85714 12.35714 c h 12.85714 14.92857 m 13.13328 14.92857 13.35714 15.15243 13.35714 15.42857 c 13.35714 15.70471 13.13328 15.92857 12.85714 15.92857 c 12.581 15.92857 12.35714 15.70471 12.35714 15.42857 c 12.35714 15.15243 12.581 14.92857 12.85714 14.92857 c h 12.85714 17.5 m 13.13328 17.5 13.35714 17.72386 13.35714 18 c 13.35714 18.27614 13.13328 18.5 12.85714 18.5 c 12.581 18.5 12.35714 18.27614 12.35714 18 c 12.35714 17.72386 12.581 17.5 12.85714 17.5 c h 15.42857 -0.5 m 15.70471 -0.5 15.92857 -0.2761424 15.92857 0 c 15.92857 0.2761424 15.70471 0.5 15.42857 0.5 c 15.15243 0.5 14.92857 0.2761424 14.92857 0 c 14.92857 -0.2761424 15.15243 -0.5 15.42857 -0.5 c h 15.42857 2.071428 m 15.70471 2.071428 15.92857 2.295285 15.92857 2.571428 c 15.92857 2.84757 15.70471 3.071428 15.42857 3.071428 c 15.15243 3.071428 14.92857 2.84757 14.92857 2.571428 c 14.92857 2.295285 15.15243 2.071428 15.42857 2.071428 c h 15.42857 4.642858 m 15.70471 4.642858 15.92857 4.866715 15.92857 5.142858 c 15.92857 5.419 15.70471 5.642858 15.42857 5.642858 c 15.15243 5.642858 14.92857 5.419 14.92857 5.142858 c 14.92857 4.866715 15.15243 4.642858 15.42857 4.642858 c h 15.42857 7.214287 m 15.70471 7.214287 15.92857 7.438144 15.92857 7.714287 c 15.92857 7.990429 15.70471 8.214287 15.42857 8.214287 c 15.15243 8.214287 14.92857 7.990429 14.92857 7.714287 c 14.92857 7.438144 15.15243 7.214287 15.42857 7.214287 c h 15.42857 9.785715 m 15.70471 9.785715 15.92857 10.00957 15.92857 10.28572 c 15.92857 10.56186 15.70471 10.78572 15.42857 10.78572 c 15.15243 10.78572 14.92857 10.56186 14.92857 10.28572 c 14.92857 10.00957 15.15243 9.785715 15.42857 9.785715 c h 15.42857 12.35714 m 15.70471 12.35714 15.92857 12.581 15.92857 12.85714 c 15.92857 13.13328 15.70471 13.35714 15.42857 13.35714 c 15.15243 13.35714 14.92857 13.13328 14.92857 12.85714 c 14.92857 12.581 15.15243 12.35714 15.42857 12.35714 c h 15.42857 14.92857 m 15.70471 14.92857 15.92857 15.15243 15.92857 15.42857 c 15.92857 15.70471 15.70471 15.92857 15.42857 15.92857 c 15.15243 15.92857 14.92857 15.70471 14.92857 15.42857 c 14.92857 15.15243 15.15243 14.92857 15.42857 14.92857 c h 15.42857 17.5 m 15.70471 17.5 15.92857 17.72386 15.92857 18 c 15.92857 18.27614 15.70471 18.5 15.42857 18.5 c 15.15243 18.5 14.92857 18.27614 14.92857 18 c 14.92857 17.72386 15.15243 17.5 15.42857 17.5 c h 18 -0.5 m 18.27614 -0.5 18.5 -0.2761424 18.5 0 c 18.5 0.2761424 18.27614 0.5 18 0.5 c 17.72386 0.5 17.5 0.2761424 17.5 0 c 17.5 -0.2761424 17.72386 -0.5 18 -0.5 c h 18 2.071428 m 18.27614 2.071428 18.5 2.295285 18.5 2.571428 c 18.5 2.84757 18.27614 3.071428 18 3.071428 c 17.72386 3.071428 17.5 2.84757 17.5 2.571428 c 17.5 2.295285 17.72386 2.071428 18 2.071428 c h 18 4.642858 m 18.27614 4.642858 18.5 4.866715 18.5 5.142858 c 18.5 5.419 18.27614 5.642858 18 5.642858 c 17.72386 5.642858 17.5 5.419 17.5 5.142858 c 17.5 4.866715 17.72386 4.642858 18 4.642858 c h 18 7.214287 m 18.27614 7.214287 18.5 7.438144 18.5 7.714287 c 18.5 7.990429 18.27614 8.214287 18 8.214287 c 17.72386 8.214287 17.5 7.990429 17.5 7.714287 c 17.5 7.438144 17.72386 7.214287 18 7.214287 c h 18 9.785715 m 18.27614 9.785715 18.5 10.00957 18.5 10.28572 c 18.5 10.56186 18.27614 10.78572 18 10.78572 c 17.72386 10.78572 17.5 10.56186 17.5 10.28572 c 17.5 10.00957 17.72386 9.785715 18 9.785715 c h 18 12.35714 m 18.27614 12.35714 18.5 12.581 18.5 12.85714 c 18.5 13.13328 18.27614 13.35714 18 13.35714 c 17.72386 13.35714 17.5 13.13328 17.5 12.85714 c 17.5 12.581 17.72386 12.35714 18 12.35714 c h 18 14.92857 m 18.27614 14.92857 18.5 15.15243 18.5 15.42857 c 18.5 15.70471 18.27614 15.92857 18 15.92857 c 17.72386 15.92857 17.5 15.70471 17.5 15.42857 c 17.5 15.15243 17.72386 14.92857 18 14.92857 c h 18 17.5 m 18.27614 17.5 18.5 17.72386 18.5 18 c 18.5 18.27614 18.27614 18.5 18 18.5 c 17.72386 18.5 17.5 18.27614 17.5 18 c 17.5 17.72386 17.72386 17.5 18 17.5 c h 1.285714 0.7857141 m 1.561857 0.7857141 1.785714 1.009572 1.785714 1.285714 c 1.785714 1.561857 1.561857 1.785714 1.285714 1.785714 c 1.009572 1.785714 0.7857141 1.561857 0.7857141 1.285714 c 0.7857141 1.009572 1.009572 0.7857141 1.285714 0.7857141 c h 1.285714 3.357143 m 1.561857 3.357143 1.785714 3.581 1.785714 3.857143 c 1.785714 4.133285 1.561857 4.357142 1.285714 4.357142 c 1.009572 4.357142 0.7857141 4.133285 0.7857141 3.857143 c 0.7857141 3.581 1.009572 3.357143 1.285714 3.357143 c h 1.285714 5.928572 m 1.561857 5.928572 1.785714 6.15243 1.785714 6.428572 c 1.785714 6.704715 1.561857 6.928572 1.285714 6.928572 c 1.009572 6.928572 0.7857141 6.704715 0.7857141 6.428572 c 0.7857141 6.15243 1.009572 5.928572 1.285714 5.928572 c h 1.285714 8.5 m 1.561857 8.5 1.785714 8.723858 1.785714 9 c 1.785714 9.276142 1.561857 9.5 1.285714 9.5 c 1.009572 9.5 0.7857141 9.276142 0.7857141 9 c 0.7857141 8.723858 1.009572 8.5 1.285714 8.5 c h 1.285714 11.07143 m 1.561857 11.07143 1.785714 11.29529 1.785714 11.57143 c 1.785714 11.84757 1.561857 12.07143 1.285714 12.07143 c 1.009572 12.07143 0.7857141 11.84757 0.7857141 11.57143 c 0.7857141 11.29529 1.009572 11.07143 1.285714 11.07143 c h 1.285714 13.64286 m 1.561857 13.64286 1.785714 13.86672 1.785714 14.14286 c 1.785714 14.419 1.561857 14.64286 1.285714 14.64286 c 1.009572 14.64286 0.7857141 14.419 0.7857141 14.14286 c 0.7857141 13.86672 1.009572 13.64286 1.285714 13.64286 c h 1.285714 16.21429 m 1.561857 16.21429 1.785714 16.43814 1.785714 16.71429 c 1.785714 16.99043 1.561857 17.21429 1.285714 17.21429 c 1.009572 17.21429 0.7857141 16.99043 0.7857141 16.71429 c 0.7857141 16.43814 1.009572 16.21429 1.285714 16.21429 c h 3.857143 0.7857141 m 4.133285 0.7857141 4.357142 1.009572 4.357142 1.285714 c 4.357142 1.561857 4.133285 1.785714 3.857143 1.785714 c 3.581 1.785714 3.357143 1.561857 3.357143 1.285714 c 3.357143 1.009572 3.581 0.7857141 3.857143 0.7857141 c h 3.857143 3.357143 m 4.133285 3.357143 4.357142 3.581 4.357142 3.857143 c 4.357142 4.133285 4.133285 4.357142 3.857143 4.357142 c 3.581 4.357142 3.357143 4.133285 3.357143 3.857143 c 3.357143 3.581 3.581 3.357143 3.857143 3.357143 c h 3.857143 5.928572 m 4.133285 5.928572 4.357142 6.15243 4.357142 6.428572 c 4.357142 6.704715 4.133285 6.928572 3.857143 6.928572 c 3.581 6.928572 3.357143 6.704715 3.357143 6.428572 c 3.357143 6.15243 3.581 5.928572 3.857143 5.928572 c h 3.857143 8.5 m 4.133285 8.5 4.357142 8.723858 4.357142 9 c 4.357142 9.276142 4.133285 9.5 3.857143 9.5 c 3.581 9.5 3.357143 9.276142 3.357143 9 c 3.357143 8.723858 3.581 8.5 3.857143 8.5 c h 3.857143 11.07143 m 4.133285 11.07143 4.357142 11.29529 4.357142 11.57143 c 4.357142 11.84757 4.133285 12.07143 3.857143 12.07143 c 3.581 12.07143 3.357143 11.84757 3.357143 11.57143 c 3.357143 11.29529 3.581 11.07143 3.857143 11.07143 c h 3.857143 13.64286 m 4.133285 13.64286 4.357142 13.86672 4.357142 14.14286 c 4.357142 14.419 4.133285 14.64286 3.857143 14.64286 c 3.581 14.64286 3.357143 14.419 3.357143 14.14286 c 3.357143 13.86672 3.581 13.64286 3.857143 13.64286 c h 3.857143 16.21429 m 4.133285 16.21429 4.357142 16.43814 4.357142 16.71429 c 4.357142 16.99043 4.133285 17.21429 3.857143 17.21429 c 3.581 17.21429 3.357143 16.99043 3.357143 16.71429 c 3.357143 16.43814 3.581 16.21429 3.857143 16.21429 c h 6.428572 0.7857141 m 6.704715 0.7857141 6.928572 1.009572 6.928572 1.285714 c 6.928572 1.561857 6.704715 1.785714 6.428572 1.785714 c 6.15243 1.785714 5.928572 1.561857 5.928572 1.285714 c 5.928572 1.009572 6.15243 0.7857141 6.428572 0.7857141 c h 6.428572 3.357143 m 6.704715 3.357143 6.928572 3.581 6.928572 3.857143 c 6.928572 4.133285 6.704715 4.357142 6.428572 4.357142 c 6.15243 4.357142 5.928572 4.133285 5.928572 3.857143 c 5.928572 3.581 6.15243 3.357143 6.428572 3.357143 c h 6.428572 5.928572 m 6.704715 5.928572 6.928572 6.15243 6.928572 6.428572 c 6.928572 6.704715 6.704715 6.928572 6.428572 6.928572 c 6.15243 6.928572 5.928572 6.704715 5.928572 6.428572 c 5.928572 6.15243 6.15243 5.928572 6.428572 5.928572 c h 6.428572 8.5 m 6.704715 8.5 6.928572 8.723858 6.928572 9 c 6.928572 9.276142 6.704715 9.5 6.428572 9.5 c 6.15243 9.5 5.928572 9.276142 5.928572 9 c 5.928572 8.723858 6.15243 8.5 6.428572 8.5 c h 6.428572 11.07143 m 6.704715 11.07143 6.928572 11.29529 6.928572 11.57143 c 6.928572 11.84757 6.704715 12.07143 6.428572 12.07143 c 6.15243 12.07143 5.928572 11.84757 5.928572 11.57143 c 5.928572 11.29529 6.15243 11.07143 6.428572 11.07143 c h 6.428572 13.64286 m 6.704715 13.64286 6.928572 13.86672 6.928572 14.14286 c 6.928572 14.419 6.704715 14.64286 6.428572 14.64286 c 6.15243 14.64286 5.928572 14.419 5.928572 14.14286 c 5.928572 13.86672 6.15243 13.64286 6.428572 13.64286 c h 6.428572 16.21429 m 6.704715 16.21429 6.928572 16.43814 6.928572 16.71429 c 6.928572 16.99043 6.704715 17.21429 6.428572 17.21429 c 6.15243 17.21429 5.928572 16.99043 5.928572 16.71429 c 5.928572 16.43814 6.15243 16.21429 6.428572 16.21429 c h 9 0.7857141 m 9.276142 0.7857141 9.5 1.009572 9.5 1.285714 c 9.5 1.561857 9.276142 1.785714 9 1.785714 c 8.723858 1.785714 8.5 1.561857 8.5 1.285714 c 8.5 1.009572 8.723858 0.7857141 9 0.7857141 c h 9 3.357143 m 9.276142 3.357143 9.5 3.581 9.5 3.857143 c 9.5 4.133285 9.276142 4.357142 9 4.357142 c 8.723858 4.357142 8.5 4.133285 8.5 3.857143 c 8.5 3.581 8.723858 3.357143 9 3.357143 c h 9 5.928572 m 9.276142 5.928572 9.5 6.15243 9.5 6.428572 c 9.5 6.704715 9.276142 6.928572 9 6.928572 c 8.723858 6.928572 8.5 6.704715 8.5 6.428572 c 8.5 6.15243 8.723858 5.928572 9 5.928572 c h 9 8.5 m 9.276142 8.5 9.5 8.723858 9.5 9 c 9.5 9.276142 9.276142 9.5 9 9.5 c 8.723858 9.5 8.5 9.276142 8.5 9 c 8.5 8.723858 8.723858 8.5 9 8.5 c h 9 11.07143 m 9.276142 11.07143 9.5 - 11.29529 9.5 11.57143 c 9.5 11.84757 9.276142 12.07143 9 12.07143 c 8.723858 12.07143 8.5 11.84757 8.5 11.57143 c 8.5 11.29529 8.723858 11.07143 9 11.07143 c h 9 13.64286 m 9.276142 13.64286 9.5 13.86672 9.5 14.14286 c 9.5 14.419 9.276142 14.64286 9 14.64286 c 8.723858 14.64286 8.5 14.419 8.5 14.14286 c 8.5 13.86672 8.723858 13.64286 9 13.64286 c h 9 16.21429 m 9.276142 16.21429 9.5 16.43814 9.5 16.71429 c 9.5 16.99043 9.276142 17.21429 9 17.21429 c 8.723858 17.21429 8.5 16.99043 8.5 16.71429 c 8.5 16.43814 8.723858 16.21429 9 16.21429 c h 11.57143 0.7857141 m 11.84757 0.7857141 12.07143 1.009572 12.07143 1.285714 c 12.07143 1.561857 11.84757 1.785714 11.57143 1.785714 c 11.29529 1.785714 11.07143 1.561857 11.07143 1.285714 c 11.07143 1.009572 11.29529 0.7857141 11.57143 0.7857141 c h 11.57143 3.357143 m 11.84757 3.357143 12.07143 3.581 12.07143 3.857143 c 12.07143 4.133285 11.84757 4.357142 11.57143 4.357142 c 11.29529 4.357142 11.07143 4.133285 11.07143 3.857143 c 11.07143 3.581 11.29529 3.357143 11.57143 3.357143 c h 11.57143 5.928572 m 11.84757 5.928572 12.07143 6.15243 12.07143 6.428572 c 12.07143 6.704715 11.84757 6.928572 11.57143 6.928572 c 11.29529 6.928572 11.07143 6.704715 11.07143 6.428572 c 11.07143 6.15243 11.29529 5.928572 11.57143 5.928572 c h 11.57143 8.5 m 11.84757 8.5 12.07143 8.723858 12.07143 9 c 12.07143 9.276142 11.84757 9.5 11.57143 9.5 c 11.29529 9.5 11.07143 9.276142 11.07143 9 c 11.07143 8.723858 11.29529 8.5 11.57143 8.5 c h 11.57143 11.07143 m 11.84757 11.07143 12.07143 11.29529 12.07143 11.57143 c 12.07143 11.84757 11.84757 12.07143 11.57143 12.07143 c 11.29529 12.07143 11.07143 11.84757 11.07143 11.57143 c 11.07143 11.29529 11.29529 11.07143 11.57143 11.07143 c h 11.57143 13.64286 m 11.84757 13.64286 12.07143 13.86672 12.07143 14.14286 c 12.07143 14.419 11.84757 14.64286 11.57143 14.64286 c 11.29529 14.64286 11.07143 14.419 11.07143 14.14286 c 11.07143 13.86672 11.29529 13.64286 11.57143 13.64286 c h 11.57143 16.21429 m 11.84757 16.21429 12.07143 16.43814 12.07143 16.71429 c 12.07143 16.99043 11.84757 17.21429 11.57143 17.21429 c 11.29529 17.21429 11.07143 16.99043 11.07143 16.71429 c 11.07143 16.43814 11.29529 16.21429 11.57143 16.21429 c h 14.14286 0.7857141 m 14.419 0.7857141 14.64286 1.009572 14.64286 1.285714 c 14.64286 1.561857 14.419 1.785714 14.14286 1.785714 c 13.86672 1.785714 13.64286 1.561857 13.64286 1.285714 c 13.64286 1.009572 13.86672 0.7857141 14.14286 0.7857141 c h 14.14286 3.357143 m 14.419 3.357143 14.64286 3.581 14.64286 3.857143 c 14.64286 4.133285 14.419 4.357142 14.14286 4.357142 c 13.86672 4.357142 13.64286 4.133285 13.64286 3.857143 c 13.64286 3.581 13.86672 3.357143 14.14286 3.357143 c h 14.14286 5.928572 m 14.419 5.928572 14.64286 6.15243 14.64286 6.428572 c 14.64286 6.704715 14.419 6.928572 14.14286 6.928572 c 13.86672 6.928572 13.64286 6.704715 13.64286 6.428572 c 13.64286 6.15243 13.86672 5.928572 14.14286 5.928572 c h 14.14286 8.5 m 14.419 8.5 14.64286 8.723858 14.64286 9 c 14.64286 9.276142 14.419 9.5 14.14286 9.5 c 13.86672 9.5 13.64286 9.276142 13.64286 9 c 13.64286 8.723858 13.86672 8.5 14.14286 8.5 c h 14.14286 11.07143 m 14.419 11.07143 14.64286 11.29529 14.64286 11.57143 c 14.64286 11.84757 14.419 12.07143 14.14286 12.07143 c 13.86672 12.07143 13.64286 11.84757 13.64286 11.57143 c 13.64286 11.29529 13.86672 11.07143 14.14286 11.07143 c h 14.14286 13.64286 m 14.419 13.64286 14.64286 13.86672 14.64286 14.14286 c 14.64286 14.419 14.419 14.64286 14.14286 14.64286 c 13.86672 14.64286 13.64286 14.419 13.64286 14.14286 c 13.64286 13.86672 13.86672 13.64286 14.14286 13.64286 c h 14.14286 16.21429 m 14.419 16.21429 14.64286 16.43814 14.64286 16.71429 c 14.64286 16.99043 14.419 17.21429 14.14286 17.21429 c 13.86672 17.21429 13.64286 16.99043 13.64286 16.71429 c 13.64286 16.43814 13.86672 16.21429 14.14286 16.21429 c h 16.71429 0.7857141 m 16.99043 0.7857141 17.21429 1.009572 17.21429 1.285714 c 17.21429 1.561857 16.99043 1.785714 16.71429 1.785714 c 16.43814 1.785714 16.21429 1.561857 16.21429 1.285714 c 16.21429 1.009572 16.43814 0.7857141 16.71429 0.7857141 c h 16.71429 3.357143 m 16.99043 3.357143 17.21429 3.581 17.21429 3.857143 c 17.21429 4.133285 16.99043 4.357142 16.71429 4.357142 c 16.43814 4.357142 16.21429 4.133285 16.21429 3.857143 c 16.21429 3.581 16.43814 3.357143 16.71429 3.357143 c h 16.71429 5.928572 m 16.99043 5.928572 17.21429 6.15243 17.21429 6.428572 c 17.21429 6.704715 16.99043 6.928572 16.71429 6.928572 c 16.43814 6.928572 16.21429 6.704715 16.21429 6.428572 c 16.21429 6.15243 16.43814 5.928572 16.71429 5.928572 c h 16.71429 8.5 m 16.99043 8.5 17.21429 8.723858 17.21429 9 c 17.21429 9.276142 16.99043 9.5 16.71429 9.5 c 16.43814 9.5 16.21429 9.276142 16.21429 9 c 16.21429 8.723858 16.43814 8.5 16.71429 8.5 c h 16.71429 11.07143 m 16.99043 11.07143 17.21429 11.29529 17.21429 11.57143 c 17.21429 11.84757 16.99043 12.07143 16.71429 12.07143 c 16.43814 12.07143 16.21429 11.84757 16.21429 11.57143 c 16.21429 11.29529 16.43814 11.07143 16.71429 11.07143 c h 16.71429 13.64286 m 16.99043 13.64286 17.21429 13.86672 17.21429 14.14286 c 17.21429 14.419 16.99043 14.64286 16.71429 14.64286 c 16.43814 14.64286 16.21429 14.419 16.21429 14.14286 c 16.21429 13.86672 16.43814 13.64286 16.71429 13.64286 c h 16.71429 16.21429 m 16.99043 16.21429 17.21429 16.43814 17.21429 16.71429 c 17.21429 16.99043 16.99043 17.21429 16.71429 17.21429 c 16.43814 17.21429 16.21429 16.99043 16.21429 16.71429 c 16.21429 16.43814 16.43814 16.21429 16.71429 16.21429 c h - {strokecolor} rg f - endstream''' -} - -HatchStyleTemplates={ - 'Brick' :'/PatternName(Brick)', #BBObjPtr - 'DiagonalBrick':'/PatternName(Diagonal Brick)', - 'Horizontal':'/PatternName(Horizontal)', - 'Vertical':'/PatternName(Vertical)', - 'DiagonalDown':'/PatternName(Diagonal Down)', - 'DiagonalUp':'/PatternName(Diagonal Up)', - 'Grid':'/PatternName(Grid)', - 'Weave':'/PatternName(Weave)', - '10Dots':'/PatternName(10% Dots)', - '20Dots':'/PatternName(20% Dots)', - '30Dots':'/PatternName(30% Dots)' -} - -def calculate_bounding_rect(vertices): - xs = [pt[0] for pt in vertices] - ys = [pt[1] for pt in vertices] - min_x = min(xs) - max_x = max(xs) - min_y = min(ys) - max_y = max(ys) - return [min_x, min_y, max_x, max_y] -def generate_annotation_xml_block(vertices, area_text, author, custom_data: dict, column_order: list, index: int, - type_internal: str = 'Bluebeam.PDF.Annotations.AnnotationMeasureArea', - subject: str = 'Area Measurement', - label: str = '',opacity:str='', - color:str='', linestyle:str='', - hatchstyle:str='',hatchLinescolor:str='', - bb_objptrMeas:str=''): - now = datetime.datetime.utcnow() - mod_date = now.strftime("D:%Y%m%d%H%M%S+00'00'") - creation_date = now.isoformat() + 'Z' - id_str = "fitz-" + uuid.uuid4().hex[:4].upper() - - vert_str = ' '.join([f'{x:.4f}' for point in vertices for x in point]) - ordered_column_values = [f'({custom_data.get(col, "")})' for col in column_order] - bsi_column_data = ''.join(ordered_column_values) - meastype='' - if subject.startswith('Area'): - meastype='129' - polygonpolylineDimension='/PolygonDimension' - polygonpolyline='/Polygon' - elif subject.startswith('Perimeter'): - meastype='130' - polygonpolylineDimension='/PolyLineDimension' - polygonpolyline='/PolyLine' - rectvertices=calculate_bounding_rect(vertices) - - raw_text = f'''<< -/DS(font: Helvetica 12pt; text-align:center; line-height:13.8pt; color:#FF0000) -/Cap false -/AlignOnSegment true -/MeasurementTypes {meastype} -/SlopeType 1 -/PitchRun 12 -/IT -{polygonpolylineDimension} -/Vertices[{vert_str}] -/IC[{color}] -/Pattern/{hatchstyle}/PatternColor[{hatchLinescolor}] -/FillOpacity {opacity} -/T({author}) -/CA {opacity} -/RC(
{area_text}
) -/Label({label}) -/Subj({subject}) -/Measure/BBObjPtr_{bb_objptrMeas} -/BSIColumnData[{bsi_column_data}] -/NM({id_str}) -/Subtype/{polygonpolyline} -/Rect[{rectvertices[0]} {rectvertices[1]} {rectvertices[2]} {rectvertices[3]}] -/Contents({area_text}) -/F 4 -/C[{color}] -/BS{linestyle} -/M({mod_date}) ->>'''.encode('utf-8') - - compressed = zlib.compress(raw_text) - base64_raw = base64.b16encode(compressed).lower().decode() - - annotation = Element('Annotation') - SubElement(annotation, 'Page').text = '1' - SubElement(annotation, 'Contents').text = area_text - SubElement(annotation, 'ModDate').text = creation_date - SubElement(annotation, 'Color').text = '#B7B7E8' - SubElement(annotation, 'Type').text = 'Polygon' - SubElement(annotation, 'ID').text = id_str - SubElement(annotation, 'TypeInternal').text = type_internal - SubElement(annotation, 'Raw').text = base64_raw - SubElement(annotation, 'Index').text = str(index) - - custom = SubElement(annotation, 'Custom') - for key, value in custom_data.items(): - SubElement(custom, key).text = value - - SubElement(annotation, 'Subject').text = subject - SubElement(annotation, 'CreationDate').text = creation_date - SubElement(annotation, 'Author').text = author - SubElement(annotation, 'Label').text = label - - return annotation - -def generate_bb_objptr(): - return ''.join(random.choices(string.ascii_uppercase, k=16)) - -def compresslikeBBRaw(textToCompress): - decompressedX = textToCompress.encode('utf-8') - print(decompressedX) - recompressedX = zlib.compress(decompressedX) - print(recompressedX.hex()) - return recompressedX.hex() - -def setBrickHatch(fillcolor,strokecolor): - # resourceid='789cf30b0877f2f40cf30f758ff48e0a0df3040029f004fd' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['Brick'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Brick'],compressedRaw, resourceid - -def setDiagonalBrickHatch(fillcolor,strokecolor): - # resourceid='789c0b0d8cf47274f60d0df28a740ef4f4f3020029ab04da' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['DiagonalBrick'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['DiagonalBrick'],compressedRaw,resourceid - -def setHorizontalHatch(fillcolor,strokecolor): - # resourceid='789cf3720b76f6f072f173f58cf071f209f00000273604a3' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['Horizontal'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Horizontal'],compressedRaw,resourceid - -def setVerticalHatch(fillcolor,strokecolor): - # resourceid='789cf30d080ef4f609088b74740ff0890a7607002a1904f0' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['Vertical'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Vertical'],compressedRaw,resourceid - -def setDiagonalDownHatch(fillcolor,strokecolor): - # resourceid='789cf3f28b74f477f7770b0c7675f68f74f60300288f04c3' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['DiagonalDown'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['DiagonalDown'],compressedRaw,resourceid - -def setDiagonalUpHatch(fillcolor,strokecolor): - # resourceid='789c0b8a70f30df4f70b09f40cf6f108757606002a2304dc' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['DiagonalUp'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['DiagonalUp'],compressedRaw,resourceid - -def setGridHatch(fillcolor,strokecolor): - # resourceid='789c730b71738e0a760cf3758972f370740a0300286b04ba' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['Grid'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Grid'],compressedRaw,resourceid - -def setWeaveHatch(fillcolor,strokecolor): - # resourceid='789cf30af775f2f1f776720d8972740c8af40500285c04c6' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['Weave'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Weave'],compressedRaw,resourceid - -def set10DotsHatch(fillcolor,strokecolor): - # resourceid='789cf3740f71f6770d0e8c0a0f76f50e0df00600291c04e4' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['10Dots'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['10Dots'],compressedRaw,resourceid - -def set20DotsHatch(fillcolor,strokecolor): - # resourceid='789c738f0cf70bf5f0f0770a0df471760df7000029b004d5' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['20Dots'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['20Dots'],compressedRaw,resourceid - -def set30DotsHatch(fillcolor,strokecolor): - # resourceid='789cf38c747789f4f68a8c0cf2f6f2f676f2070029b104dc' - randombb_objptr=generate_bb_objptr() - resourceid=compresslikeBBRaw(randombb_objptr) - compressedRaw=compresslikeBBRaw(AllhatchesCodes['30Dots'].format(fillcolor=fillcolor, strokecolor=strokecolor)) - return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['30Dots'],compressedRaw,resourceid - -def save_multiple_annotations_bax(annotations, output_path, column_order,pdfWidth,pdfHeight): - """ - annotations: list of dicts, each with: - - vertices: list of [x, y] - - text: str (label/tooltip) - - author: str - - custom_data: dict of custom field values - - type_internal: str (e.g., Bluebeam.PDF.Annotations.AnnotationMeasurePerimeter) - - subject: str (e.g., Perimeter Measurement) - """ - globalhatches=[] - scales=[] - doc = Element('Document', Version='1') - ########## Subelement1 - page ################ - page = SubElement(doc, 'Page', Index='0') - SubElement(page, 'Label').text = '1' - SubElement(page, 'Width').text = str(pdfWidth) - SubElement(page, 'Height').text = str(pdfHeight) - - for i, ann in enumerate(annotations): - - bb_objptrMeas=generate_bb_objptr() - resourceidComp=compresslikeBBRaw(bb_objptrMeas) - scales.append(resourceidComp) - - hatchstyle_key = ann.get('hatchstyle') # e.g., 'Brick' - if hatchstyle_key not in globalhatches and hatchstyle_key: - globalhatches.append([hatchstyle_key[2],hatchstyle_key[1]]) # id, raw - hatchstyle=hatchstyle_key[0] - else: - hatchstyle='none' - - annotation_xml = generate_annotation_xml_block( - vertices=ann['vertices'], - area_text=ann['text'], - author=ann['author'], - custom_data=ann['custom_data'], - column_order=column_order, - index=i, - bb_objptrMeas=bb_objptrMeas, - type_internal=ann.get('type_internal', 'Bluebeam.PDF.Annotations.AnnotationMeasureArea'), - subject=ann.get('subject', 'Area Measurement'), - label=ann.get('label', 'label1'), - opacity=ann.get('opacity', ''), - color=ann.get('color', ''), - linestyle=ann.get('linestyle', ''), - hatchstyle=hatchstyle, - hatchLinescolor=ann.get('hatchLinescolor',''), - ) - page.append(annotation_xml) - ################# Subelement 2 - Global resources############ - GlobalResources = SubElement(doc, 'GlobalResources') - for hatch in globalhatches: - Resource = SubElement(GlobalResources, 'Resource') - SubElement(Resource, 'ID').text = hatch[0] - SubElement(Resource, 'Raw').text = hatch[1] - for scale in scales: - Resource = SubElement(GlobalResources, 'Resource') - SubElement(Resource, 'ID').text = scale - SubElement(Resource, 'Raw').text = '789c85d04f0b82401005f0af3247bd34a35176b085503c55847f22a80e2a4b7858ad7537e8dba7951ea2a5e330efc783e7fb983eae1c373c6fb5e498e842f577bcc6d872a014b00407848d87e310dd6a5170193552e40a33abfb0540139ace5ccff316188243844962d98c9d3134b297eba2f4255626d1dee06d3e200a4149cd47989ae0c99dd32fb8ebe0a8f7265dea3fb5b9bc7095d5950a9aface655b3575bf070d8b30f604438f6873' - bax_xml= tostring(doc, encoding="unicode", method="xml") #tostring(doc, encoding="utf-8", method="xml").decode("utf-8") - - # print(f" Saved {len(annotations)} annotations to {output_path}") - return bax_xml - -"""PDF to image""" - -def pdftoimg(datadoc,pdf_content=0): - if pdf_content: - doc = fitz.open(stream=pdf_content, filetype="pdf") - else: - doc =fitz.open('pdf',datadoc) - page=doc[0] - pix = page.get_pixmap() # render page to an image - pl=Image.frombytes('RGB', [pix.width,pix.height],pix.samples) - img=np.array(pl) - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - print("IMAGE") -# cv2_imshow(img) - return img,pix - - -# Standard ISO paper sizes in inches -ISO_SIZES_INCHES = { - "A0": (33.11, 46.81), - "A1": (23.39, 33.11), - "A2": (16.54, 23.39), - "A3": (11.69, 16.54), - "A4": (8.27, 11.69), - "A5": (5.83, 8.27), - "A6": (4.13, 5.83), - "A7": (2.91, 4.13), - "A8": (2.05, 2.91), - "A9": (1.46, 2.05), - "A10": (1.02, 1.46) -} - -def get_paper_size_in_inches(width, height): - """Find the closest matching paper size in inches.""" - for size, (w, h) in ISO_SIZES_INCHES.items(): - if (abs(w - width) < 0.1 and abs(h - height) < 0.1) or (abs(w - height) < 0.1 and abs(h - width) < 0.1): - return size - return "Unknown Size" - -def analyze_pdf(datadoc,pdf_content=0): - # Open the PDF file - if pdf_content: - pdf_document = fitz.open(stream=pdf_content, filetype="pdf") - else: - pdf_document = fitz.open('pdf',datadoc) - - # Iterate through pages and print their sizes - for page_number in range(len(pdf_document)): - page = pdf_document[page_number] - rect = page.rect - width_points, height_points = rect.width, rect.height - - # Convert points to inches - width_inches, height_inches = width_points / 72, height_points / 72 - - paper_size = get_paper_size_in_inches(width_inches, height_inches) - - print(f"Page {page_number + 1}: {width_inches:.2f} x {height_inches:.2f} inches ({paper_size})") - - pdf_document.close() - return width_inches , height_inches , paper_size - - -def get_dxfSize(dxfpath): - - doc = ezdxf.readfile(dxfpath) - msp = doc.modelspace() - # Create a cache for bounding box calculations - # Get the overall bounding box for all entities in the modelspace - cache = bbox.Cache() - overall_bbox = bbox.extents(msp, cache=cache) - print("Overall Bounding Box:", overall_bbox) - print(overall_bbox.extmin[0]+overall_bbox.extmax[0], overall_bbox.extmin[1]+overall_bbox.extmax[1]) - - return overall_bbox.extmin[0]+overall_bbox.extmax[0], overall_bbox.extmin[1]+overall_bbox.extmax[1] - - - -def switch_case(argument): - switcher = { - "A0": 1.27, - "A1": 2.54, - "A2": 5.08, - "A3": 10.16, - "A4": 20.32, - "A5": 40.64, - "A6": 81.28, - "A7": 162.56, - "A8": 325.12, - "A9": 650.24, - "A10": 1300.48 - } - # Get the value from the dictionary; if not found, return a default value - print("Final Ratio=",switcher.get(argument, 1)) - return switcher.get(argument, 1) - - - - -def RetriveRatio(datadoc,dxfpath,pdf_content=0): - if pdf_content: - width,height,paper_size = analyze_pdf (datadoc,pdf_content) - else: - width,height,paper_size = analyze_pdf (datadoc) - if(width > height ): - bigger=width - else: - bigger=height - - width_dxf,height_dxf = get_dxfSize(dxfpath) - - if(width_dxf > height_dxf ): - bigger_dxf=width_dxf - else: - bigger_dxf=height_dxf - - if(0.2 < bigger_dxf/bigger < 1.2): - print("bigger_dxf/bigger",bigger/bigger_dxf) - argument = paper_size - FinalRatio=switch_case(argument) - else: - FinalRatio=1 - return FinalRatio,width_dxf - - -"""Flips image -DXF origin is at the bottom left while img origin is top left -""" - -def flip(img): - height, width = img.shape[:2] - - # Define the rotation angle (clockwise) - angle = 180 - - # Calculate the rotation matrix - rotation_matrix = cv2.getRotationMatrix2D((width/2, height/2), angle, 1) - - # Rotate the image - rotated_image = cv2.warpAffine(img, rotation_matrix, (width, height)) - flipped_horizontal = cv2.flip(rotated_image, 1) - return flipped_horizontal - - - -def aci_to_rgb(aci): - aci_rgb_map = { - 0: (0, 0, 0), - 1: (255, 0, 0), - 2: (255, 255, 0), - 3: (0, 255, 0), - 4: (0, 255, 255), - 5: (0, 0, 255), - 6: (255, 0, 255), - 7: (255, 255, 255), - 8: (65, 65, 65), - 9: (128, 128, 128), - 10: (255, 0, 0), - 11: (255, 170, 170), - 12: (189, 0, 0), - 13: (189, 126, 126), - 14: (129, 0, 0), - 15: (129, 86, 86), - 16: (104, 0, 0), - 17: (104, 69, 69), - 18: (79, 0, 0), - 19: (79, 53, 53), - 20: (255, 63, 0), - 21: (255, 191, 170), - 22: (189, 46, 0), - 23: (189, 141, 126), - 24: (129, 31, 0), - 25: (129, 96, 86), - 26: (104, 25, 0), - 27: (104, 78, 69), - 28: (79, 19, 0), - 29: (79, 59, 53), - 30: (255, 127, 0), - 31: (255, 212, 170), - 32: (189, 94, 0), - 33: (189, 157, 126), - 34: (129, 64, 0), - 35: (129, 107, 86), - 36: (104, 52, 0), - 37: (104, 86, 69), - 38: (79, 39, 0), - 39: (79, 66, 53), - 40: (255, 191, 0), - 41: (255, 234, 170), - 42: (189, 141, 0), - 43: (189, 173, 126), - 44: (129, 96, 0), - 45: (129, 118, 86), - 46: (104, 78, 0), - 47: (104, 95, 69), - 48: (79, 59, 0), - 49: (79, 73, 53), - 50: (255, 255, 0), - 51: (255, 255, 170), - 52: (189, 189, 0), - 53: (189, 189, 126), - 54: (129, 129, 0), - 55: (129, 129, 86), - 56: (104, 104, 0), - 57: (104, 104, 69), - 58: (79, 79, 0), - 59: (79, 79, 53), - 60: (191, 255, 0), - 61: (234, 255, 170), - 62: (141, 189, 0), - 63: (173, 189, 126), - 64: (96, 129, 0), - 65: (118, 129, 86), - 66: (78, 104, 0), - 67: (95, 104, 69), - 68: (59, 79, 0), - 69: (73, 79, 53), - 70: (127, 255, 0), - 71: (212, 255, 170), - 72: (94, 189, 0), - 73: (157, 189, 126), - 74: (64, 129, 0), - 75: (107, 129, 86), - 76: (52, 104, 0), - 77: (86, 104, 69), - 78: (39, 79, 0), - 79: (66, 79, 53), - 80: (63, 255, 0), - 81: (191, 255, 170), - 82: (46, 189, 0), - 83: (141, 189, 126), - 84: (31, 129, 0), - 85: (96, 129, 86), - 86: (25, 104, 0), - 87: (78, 104, 69), - 88: (19, 79, 0), - 89: (59, 79, 53), - 90: (0, 255, 0), - 91: (170, 255, 170), - 92: (0, 189, 0), - 93: (126, 189, 126), - 94: (0, 129, 0), - 95: (86, 129, 86), - 96: (0, 104, 0), - 97: (69, 104, 69), - 98: (0, 79, 0), - 99: (53, 79, 53), - 100: (0, 255, 63), - 101: (170, 255, 191), - 102: (0, 189, 46), - 103: (126, 189, 141), - 104: (0, 129, 31), - 105: (86, 129, 96), - 106: (0, 104, 25), - 107: (69, 104, 78), - 108: (0, 79, 19), - 109: (53, 79, 59), - 110: (0, 255, 127), - 111: (170, 255, 212), - 112: (0, 189, 94), - 113: (126, 189, 157), - 114: (0, 129, 64), - 115: (86, 129, 107), - 116: (0, 104, 52), - 117: (69, 104, 86), - 118: (0, 79, 39), - 119: (53, 79, 66), - 120: (0, 255, 191), - 121: (170, 255, 234), - 122: (0, 189, 141), - 123: (126, 189, 173), - 124: (0, 129, 96), - 125: (86, 129, 118), - 126: (0, 104, 78), - 127: (69, 104, 95), - 128: (0, 79, 59), - 129: (53, 79, 73), - 130: (0, 255, 255), - 131: (170, 255, 255), - 132: (0, 189, 189), - 133: (126, 189, 189), - 134: (0, 129, 129), - 135: (86, 129, 129), - 136: (0, 104, 104), - 137: (69, 104, 104), - 138: (0, 79, 79), - 139: (53, 79, 79), - 140: (0, 191, 255), - 141: (170, 234, 255), - 142: (0, 141, 189), - 143: (126, 173, 189), - 144: (0, 96, 129), - 145: (86, 118, 129), - 146: (0, 78, 104), - 147: (69, 95, 104), - 148: (0, 59, 79), - 149: (53, 73, 79), - 150: (0, 127, 255), - 151: (170, 212, 255), - 152: (0, 94, 189), - 153: (126, 157, 189), - 154: (0, 64, 129), - 155: (86, 107, 129), - 156: (0, 52, 104), - 157: (69, 86, 104), - 158: (0, 39, 79), - 159: (53, 66, 79), - 160: (0, 63, 255), - 161: (170, 191, 255), - 162: (0, 46, 189), - 163: (126, 141, 189), - 164: (0, 31, 129), - 165: (86, 96, 129), - 166: (0, 25, 104), - 167: (69, 78, 104), - 168: (0, 19, 79), - 169: (53, 59, 79), - 170: (0, 0, 255), - 171: (170, 170, 255), - 172: (0, 0, 189), - 173: (126, 126, 189), - 174: (0, 0, 129), - 175: (86, 86, 129), - 176: (0, 0, 104), - 177: (69, 69, 104), - 178: (0, 0, 79), - 179: (53, 53, 79), - 180: (63, 0, 255), - 181: (191, 170, 255), - 182: (46, 0, 189), - 183: (141, 126, 189), - 184: (31, 0, 129), - 185: (96, 86, 129), - 186: (25, 0, 104), - 187: (78, 69, 104), - 188: (19, 0, 79), - 189: (59, 53, 79), - 190: (127, 0, 255), - 191: (212, 170, 255), - 192: (94, 0, 189), - 193: (157, 126, 189), - 194: (64, 0, 129), - 195: (107, 86, 129), - 196: (52, 0, 104), - 197: (86, 69, 104), - 198: (39, 0, 79), - 199: (66, 53, 79), - 200: (191, 0, 255), - 201: (234, 170, 255), - 202: (141, 0, 189), - 203: (173, 126, 189), - 204: (96, 0, 129), - 205: (118, 86, 129), - 206: (78, 0, 104), - 207: (95, 69, 104), - 208: (59, 0, 79), - 209: (73, 53, 79), - 210: (255, 0, 255), - 211: (255, 170, 255), - 212: (189, 0, 189), - 213: (189, 126, 189), - 214: (129, 0, 129), - 215: (129, 86, 129), - 216: (104, 0, 104), - 217: (104, 69, 104), - 218: (79, 0, 79), - 219: (79, 53, 79), - 220: (255, 0, 191), - 221: (255, 170, 234), - 222: (189, 0, 141), - 223: (189, 126, 173), - 224: (129, 0, 96), - 225: (129, 86, 118), - 226: (104, 0, 78), - 227: (104, 69, 95), - 228: (79, 0, 59), - 229: (79, 53, 73), - 230: (255, 0, 127), - 231: (255, 170, 212), - 232: (189, 0, 94), - 233: (189, 126, 157), - 234: (129, 0, 64), - 235: (129, 86, 107), - 236: (104, 0, 52), - 237: (104, 69, 86), - 238: (79, 0, 39), - 239: (79, 53, 66), - 240: (255, 0, 63), - 241: (255, 170, 191), - 242: (189, 0, 46), - 243: (189, 126, 141), - 244: (129, 0, 31), - 245: (129, 86, 96), - 246: (104, 0, 25), - 247: (104, 69, 78), - 248: (79, 0, 19), - 249: (79, 53, 59), - 250: (51, 51, 51), - 251: (80, 80, 80), - 252: (105, 105, 105), - 253: (130, 130, 130), - 254: (190, 190, 190), - 255: (255, 255, 255) - } - - # Default to white if index is invalid or not found - return aci_rgb_map.get(aci, (255, 255, 255)) - - -def int_to_rgb(color_int): - """Convert an integer to an (R, G, B) tuple.""" - r = (color_int >> 16) & 255 - g = (color_int >> 8) & 255 - b = color_int & 255 - return (r, g, b) - - -def get_hatch_color(entity): - """Extract hatch color with detailed debugging.""" - if not entity: - # print("No entity provided for color extraction.") - return (255, 255, 255) - - # Check for true color - if entity.dxf.hasattr('true_color'): - true_color = entity.dxf.true_color - rgb_color = int_to_rgb(true_color) # Convert integer to (R, G, B) - # print(f"True color detected (RGB): {rgb_color}") - return rgb_color - - # Check for color index - color_index = entity.dxf.color - # print(f"Entity color index: {color_index}") - if 1 <= color_index <= 255: - rgb_color = aci_to_rgb(color_index) # Convert ACI to RGB - # print(f"Converted ACI to RGB: {rgb_color}") - return rgb_color - - # Handle ByLayer or ByBlock - if color_index == 0: # ByLayer - layer_name = entity.dxf.layer - layer = entity.doc.layers.get(layer_name) - # print(f"ByLayer detected for layer '{layer_name}'.") - if layer: - layer_color_index = layer.dxf.color - # print(layer_color_index) - rgb_color = aci_to_rgb(layer_color_index) - # print(f"Layer '{layer_name}' color index {layer_color_index} converted to RGB: {rgb_color}") - return rgb_color - else: - # print(f"Layer '{layer_name}' not found. Defaulting to white.") - return (255, 255, 255) - - # Default - # print("Unhandled color case. Defaulting to white.") - return (255, 255, 255) - - - -def point_in_rectangle(point, rect_coords): - x, y = point - (x1, y1), (x2, y2) = rect_coords - return x1 <= x <= x2 and y1 <= y <= y2 - -from math import sqrt - -def euclidean_distance(point1, point2): - x1, y1 = point1 - x2, y2 = point2 - return sqrt((x2 - x1)**2 + (y2 - y1)**2) - -def compute_hatch_centroid(hatch): - x_coords = [] - y_coords = [] - for path in hatch.paths: - if path.PATH_TYPE == "PolylinePath": - for vertex in path.vertices: - x_coords.append(vertex[0]) - y_coords.append(vertex[1]) - elif path.PATH_TYPE == "EdgePath": - for edge in path.edges: - if hasattr(edge, "start"): - x_coords.append(edge.start[0]) - y_coords.append(edge.start[1]) - if hasattr(edge, "end"): - x_coords.append(edge.end[0]) - y_coords.append(edge.end[1]) - if x_coords and y_coords: - return (sum(x_coords) / len(x_coords), sum(y_coords) / len(y_coords)) - return None - -"""### Hatched areas""" -def get_hatched_areas(datadoc,filename,FinalRatio,rotationangle,SearchArray): - - print("SearchArray = ",SearchArray) - - doc = ezdxf.readfile(filename) - doc.header['$MEASUREMENT'] = 1 - msp = doc.modelspace() - trial=0 - hatched_areas = [] - threshold=0.001 - TextFound = 0 - j=0 - unique_shapes = [] - - - text_with_positions = [] - text_color_mapping = {} - color_palette = [ - (255, 0, 0), (0, 0, 255), (0, 255, 255), (0, 64, 0), (255, 204, 0), - (255, 128, 64), (255, 0, 128), (255, 128, 192), (128, 128, 255), - (128, 64, 0), (0, 255, 0), (0, 200, 0), (255, 128, 255), (128, 0, 255), - (0, 128, 192), (128, 0, 128), (128, 0, 0), (0, 128, 255), (149, 1, 70), - (255, 182, 128), (222, 48, 71), (240, 0, 112), (255, 0, 255), - (192, 46, 65), (0, 0, 128), (0, 128, 64), (255, 255, 0), (128, 0, 80), - (255, 255, 128), (90, 255, 140), (255, 200, 20), (91, 16, 51), - (90, 105, 138), (114, 10, 138), (36, 82, 78), (225, 105, 190), - (108, 150, 170), (11, 35, 75), (42, 176, 170), (255, 176, 170), - (209, 151, 15), (81, 27, 85), (226, 106, 122), (67, 119, 149), - (159, 179, 140), (159, 179, 30), (255, 85, 198), (255, 27, 85), - (188, 158, 8), (140, 188, 120), (59, 61, 52), (65, 81, 21), - (212, 255, 174), (15, 164, 90), (41, 217, 245), (213, 23, 182), - (11, 85, 169), (78, 153, 239), (0, 66, 141), (64, 98, 232), - (140, 112, 255), (57, 33, 154), (194, 117, 252), (116, 92, 135), - (74, 43, 98), (188, 13, 123), (129, 58, 91), (255, 128, 100), - (171, 122, 145), (255, 98, 98), (222, 48, 77) - ] - import re - - text_with_positions = [] - # SearchArray=[["","Wall Type","",""],["","","",""]] - - # print("SearchArray=",len(SearchArray)) - # print("SearchArray=",len(SearchArray[0])) - # print("SearchArray=",SearchArray[0][0]) - - if(SearchArray): - for i in range(len(SearchArray)): - - if (SearchArray[i][0] and SearchArray[i][1] and SearchArray[i][2]): - for text_entity in doc.modelspace().query('TEXT MTEXT'): - text = text_entity.text.strip() if hasattr(text_entity, 'text') else "" - # if (text.startswith("P") and len(text) == 3) or (text.startswith("I") and len(text) == 3): # Filter for "Wall" - if(text.startswith(SearchArray[i][0]) and len(text)==int(SearchArray[i][2])): - position = text_entity.dxf.insert # Extract text position - x, y = position.x, position.y - - for text_entity in doc.modelspace().query('TEXT MTEXT'): - NBS = text_entity.text.strip() if hasattr(text_entity, 'text') else "" - if (NBS.startswith(SearchArray[i][1])): - positionNBS = text_entity.dxf.insert # Extract text position - xNBS, yNBS = positionNBS.x, positionNBS.y - - if(x == xNBS or y == yNBS): - textNBS=NBS - break - - else: - textNBS = None - - - - nearest_hatch = None - min_distance = float('inf') # Initialize with a very large value - detected_color = (255, 255, 255) # Default to white - - # Search for the nearest hatch - for hatch in doc.modelspace().query('HATCH'): # Query only hatches - if hatch.paths: - for path in hatch.paths: - if path.type == 1: # PolylinePath - vertices = [v[:2] for v in path.vertices] - # Calculate the centroid of the hatch - centroid_x = sum(v[0] for v in vertices) / len(vertices) - centroid_y = sum(v[1] for v in vertices) / len(vertices) - centroid = (centroid_x, centroid_y) - - # Calculate the distance between the text and the hatch centroid - distance = calculate_distance((x, y), centroid) - - # Update the nearest hatch if a closer one is found - if distance < min_distance: - min_distance = distance - nearest_hatch = hatch - - # Get the color of this hatch - current_color = get_hatch_color(hatch) - if current_color != (255, 255, 255): # Valid color found - detected_color = current_color - break # Stop checking further paths for this hatch - - - # Append the detected result only once - text_with_positions.append([text, textNBS, (x, y), detected_color]) - print("text_with_positions=",text_with_positions) - - elif (SearchArray[i][0] and SearchArray[i][2]): - for text_entity in doc.modelspace().query('TEXT MTEXT'): - text = text_entity.text.strip() if hasattr(text_entity, 'text') else "" - # if (text.startswith("P") and len(text) == 3) or (text.startswith("I") and len(text) == 3): # Filter for "Wall" - if(text.startswith(SearchArray[i][0]) and len(text)==int(SearchArray[i][2])): - position = text_entity.dxf.insert # Extract text position - x, y = position.x, position.y - textNBS = None - nearest_hatch = None - min_distance = float('inf') # Initialize with a very large value - detected_color = (255, 255, 255) # Default to white - - # Search for the nearest hatch - for hatch in doc.modelspace().query('HATCH'): # Query only hatches - if hatch.paths: - for path in hatch.paths: - if path.type == 1: # PolylinePath - vertices = [v[:2] for v in path.vertices] - # Calculate the centroid of the hatch - centroid_x = sum(v[0] for v in vertices) / len(vertices) - centroid_y = sum(v[1] for v in vertices) / len(vertices) - centroid = (centroid_x, centroid_y) - - # Calculate the distance between the text and the hatch centroid - distance = calculate_distance((x, y), centroid) - - # Update the nearest hatch if a closer one is found - if distance < min_distance: - min_distance = distance - nearest_hatch = hatch - - # Get the color of this hatch - current_color = get_hatch_color(hatch) - if current_color != (255, 255, 255): # Valid color found - detected_color = current_color - break # Stop checking further paths for this hatch - - - # Append the detected result only once - text_with_positions.append([text, textNBS, (x, y), detected_color]) - print("text_with_positions=",text_with_positions) - - elif(SearchArray[i][0]): - for text_entity in doc.modelspace().query('TEXT MTEXT'): - text = text_entity.text.strip() if hasattr(text_entity, 'text') else "" - # if (text.startswith("P") and len(text) == 3) or (text.startswith("I") and len(text) == 3): # Filter for "Wall" - if(text.startswith(SearchArray[i][0])): - position = text_entity.dxf.insert # Extract text position - x, y = position.x, position.y - textNBS = None - nearest_hatch = None - min_distance = float('inf') # Initialize with a very large value - detected_color = (255, 255, 255) # Default to white - - # Search for the nearest hatch - for hatch in doc.modelspace().query('HATCH'): # Query only hatches - if hatch.paths: - for path in hatch.paths: - if path.type == 1: # PolylinePath - vertices = [v[:2] for v in path.vertices] - # Calculate the centroid of the hatch - centroid_x = sum(v[0] for v in vertices) / len(vertices) - centroid_y = sum(v[1] for v in vertices) / len(vertices) - centroid = (centroid_x, centroid_y) - - # Calculate the distance between the text and the hatch centroid - distance = calculate_distance((x, y), centroid) - - # Update the nearest hatch if a closer one is found - if distance < min_distance: - min_distance = distance - nearest_hatch = hatch - - # Get the color of this hatch - current_color = get_hatch_color(hatch) - if current_color != (255, 255, 255): # Valid color found - detected_color = current_color - break # Stop checking further paths for this hatch - - - # Append the detected result only once - text_with_positions.append([text, textNBS, (x, y), detected_color]) - print("text_with_positions=",text_with_positions) - - - - - - - - - grouped = {} - for entry in text_with_positions: - key = entry[0] - grouped.setdefault(key, []).append(entry) - - # Filter the groups: if any entry in a group has a non-None Text Nbs, keep only one of those - filtered_results = [] - for key, entries in grouped.items(): - # Find the first entry with a valid textNBS (non-None) - complete = next((entry for entry in entries if entry[1] is not None), None) - if complete: - filtered_results.append(complete) - else: - # If none are complete, you can choose to keep just one entry - filtered_results.append(entries[0]) - - text_with_positions=filtered_results - - for entity in msp: - if entity.dxftype() == 'HATCH': - - cntPoints=[] - for path in entity.paths: - - # path_type = path.type - - # # Resolve the path type to its name - # path_type_name = BoundaryPathType(path_type).name - # print(f"Encountered path type: {path_type_name}") - - vertices = [] # Reset vertices for each path - - # print(str(path.type)) - - if str(path.type) == 'BoundaryPathType.POLYLINE' or path.type == 1: - # if path.type == 2: # Polyline path - # Handle POLYLINE type HATCH - vertices = [(vertex[0] * FinalRatio, vertex[1] * FinalRatio) for vertex in path.vertices] - # print("Hatch Vertices = ",vertices) - - if len(vertices) > 3: - poly = ShapelyPolygon(vertices) - minx, miny, maxx, maxy = poly.bounds - width = maxx - minx - height = maxy - miny - - - - - if (poly.area > 0 and (height > 0.2 or width > 0.2)): - - length = height - if(width > length): - length = width - - area1 = round(poly.area, 3) - perimeter = round(poly.length, 3) - # print("Vertices = ",vertices) - normalized_vertices = normalize_vertices(vertices) - - rgb_color = get_hatch_color(entity) - # print("rgb_color = ",rgb_color) - - # if(rgb_color == (255, 255, 255)): - # if(len(text_with_positions)>0): - - # for text, position, color in text_with_positions: - # text_position = Point(position[0], position[1]) - - # if poly.contains(text_position): - # rgb_color = color - # break - - duplicate_found = False - for existing_vertices, existing_area in unique_shapes: - if normalized_vertices == existing_vertices and areas_are_similar(area1, existing_area): - duplicate_found = True - break - - if not duplicate_found: - # rgb_color = get_hatch_color(entity) # Assuming this function exists - unique_shapes.append((normalized_vertices, area1)) - - if length > 0.6: - hatched_areas.append([vertices, area1, length, rgb_color]) - - elif str(path.type) == 'BoundaryPathType.EDGE' or path.type == 2: - # elif path.type == 2: # Edge path - # Handle EDGE type HATCH - vert = [] - for edge in path.edges: - x, y = edge.start - x1, y1 = edge.end - vert.append((x * FinalRatio, y * FinalRatio)) - vert.append((x1 * FinalRatio, y1 * FinalRatio)) - - poly = ShapelyPolygon(vert) - minx, miny, maxx, maxy = poly.bounds - width = maxx - minx - height = maxy - miny - - if (poly.area > 0 and (height > 0.2 or width > 0.2)): - - length = height - if(width > length): - length = width - - area1 = round(poly.area, 3) - perimeter = round(poly.length, 3) - normalized_vertices = normalize_vertices(vert) - rgb_color = get_hatch_color(entity) - # print("rgb_color = ",rgb_color) - - # if(rgb_color == (255, 255, 255)): - # if(len(text_with_positions)>0): - # for text, position, color in text_with_positions: - # text_position = Point(position[0], position[1]) - - # if poly.contains(text_position): - # rgb_color = color - # break - - - duplicate_found = False - for existing_vertices, existing_area in unique_shapes: - if normalized_vertices == existing_vertices and areas_are_similar(area1, existing_area): - duplicate_found = True - break - - if not duplicate_found: - # rgb_color = get_hatch_color(entity) # Assuming this function exists - unique_shapes.append((normalized_vertices, area1)) - - if length > 0.6: - hatched_areas.append([vert, area1, length, rgb_color]) - - else: - print(f"Encountered path type: {path.type}") - - elif entity.dxftype() == 'SOLID': - - - - vertices = [entity.dxf.vtx0 * (FinalRatio), entity.dxf.vtx1* (FinalRatio), entity.dxf.vtx2* (FinalRatio), entity.dxf.vtx3* (FinalRatio)] - poly = ShapelyPolygon(vertices) - minx, miny, maxx, maxy = poly.bounds - - # Calculate the width and height of the bounding box - width = maxx - minx - height = maxy - miny - - if (poly.area > 0 and (height > 0 and width > 0)): - area1 = round(poly.area, 3) - perimeter = round(poly.length, 3) - normalized_vertices = normalize_vertices(vertices) - - duplicate_found = False - for existing_vertices, existing_area in unique_shapes: - if normalized_vertices == existing_vertices or areas_are_similar(area1, existing_area): - duplicate_found = True - break - - if not duplicate_found: - rgb_color = get_hatch_color(entity) # Assuming this function exists - unique_shapes.append((normalized_vertices, area1)) - hatched_areas.append([vertices, area1, perimeter, rgb_color]) - - - - elif entity.dxftype() == 'LWPOLYLINE': - - vertices = [] - lwpolyline = entity - points = lwpolyline.get_points() - flag = 0 - - # Collect vertices and apply the FinalRatio - for i in range(len(points)): - vertices.append([points[i][0] * FinalRatio, points[i][1] * FinalRatio]) - - # # Ensure there are more than 3 vertices - if len(vertices) > 3: - # Check if the polyline is closed - if vertices[0][0] == vertices[-1][0] or vertices[0][1] == vertices[-1][1]: - poly = ShapelyPolygon(vertices) - minx, miny, maxx, maxy = poly.bounds - - # Calculate width and height of the bounding box - width = maxx - minx - height = maxy - miny - - # Check area and size constraints - if (poly.area > 0 and (height > 0 and width > 0)): - area1 = round(poly.area, 3) - perimeter = round(poly.length, 3) - normalized_vertices = normalize_vertices(vertices) - - duplicate_found = False - for existing_vertices, existing_area in unique_shapes: - if normalized_vertices == existing_vertices or areas_are_similar(area1, existing_area): - duplicate_found = True - break - - if not duplicate_found: - rgb_color = get_hatch_color(entity) # Assuming this function exists - unique_shapes.append((normalized_vertices, area1)) - hatched_areas.append([vertices, area1, perimeter, rgb_color]) - - - - elif entity.dxftype() == 'POLYLINE': - - flag=0 - vertices = [(v.dxf.location.x * (FinalRatio), v.dxf.location.y * (FinalRatio)) for v in entity.vertices] - # print('Vertices:', vertices) - - if(len(vertices)>3): - - if(vertices[0][0] == vertices[len(vertices)-1][0] or vertices[0][1] == vertices[len(vertices)-1][1]): - - poly=ShapelyPolygon(vertices) - minx, miny, maxx, maxy = poly.bounds - - # Calculate the width and height of the bounding box - width = maxx - minx - height = maxy - miny - - if (poly.area > 0 and (height > 0 and width > 0)): - area1 = round(poly.area,3) - perimeter = round (poly.length,3) - normalized_vertices = normalize_vertices(vertices) - - duplicate_found = False - for existing_vertices, existing_area in unique_shapes: - if normalized_vertices == existing_vertices or areas_are_similar(area1, existing_area): - duplicate_found = True - break - - if not duplicate_found: - rgb_color = get_hatch_color(entity) # Assuming this function exists - unique_shapes.append((normalized_vertices, area1)) - hatched_areas.append([vertices, area1, perimeter, rgb_color]) - - - elif entity.dxftype() == 'SPLINE': - - spline_entity = entity - vertices = [] - control_points = spline_entity.control_points - if(len(control_points)>3): - for i in range(len(control_points)): - vertices.append([control_points[i][0]* (FinalRatio),control_points[i][1]* (FinalRatio)]) - poly=ShapelyPolygon(vertices) - - minx, miny, maxx, maxy = poly.bounds - - # Calculate the width and height of the bounding box - width = maxx - minx - height = maxy - miny - - - if (poly.area > 0 and (height > 0 and width > 0)): - area1 = round(poly.area,3) - perimeter = round (poly.length,3) - normalized_vertices = normalize_vertices(vertices) - - duplicate_found = False - for existing_vertices, existing_area in unique_shapes: - if normalized_vertices == existing_vertices or areas_are_similar(area1, existing_area): - duplicate_found = True - break - - if not duplicate_found: - rgb_color = get_hatch_color(entity) # Assuming this function exists - unique_shapes.append((normalized_vertices, area1)) - hatched_areas.append([vertices, area1, perimeter, rgb_color]) - - - - sorted_data = sorted(hatched_areas, key=lambda x: x[1]) - return sorted_data,text_with_positions - - -"""### Rotate polygon""" - - - -def rotate_point(point, angle,pdfrotation,width,height, center_point=(0, 0)): - """Rotates a point around center_point(origin by default) - Angle is in degrees. - Rotation is counter-clockwise - """ - angle_rad = radians(angle % 360) - # Shift the point so that center_point becomes the origin - new_point = (point[0] - center_point[0], point[1] - center_point[1]) - new_point = (new_point[0] * cos(angle_rad) - new_point[1] * sin(angle_rad), - new_point[0] * sin(angle_rad) + new_point[1] * cos(angle_rad)) - # Reverse the shifting we have done - if pdfrotation!=0: - - new_point = (new_point[0]+width + center_point[0], new_point[1] + center_point[1]) #pdfsize[2] is the same as +width - else: - - new_point = (new_point[0] + center_point[0], new_point[1]+ height + center_point[1]) # pdfsize[3] is the same as +height - # new_point = (new_point[0] + center_point[0], new_point[1] + center_point[1]) - return new_point - - -def rotate_polygon(polygon, angle, pdfrotation,width,height,center_point=(0, 0)): - """Rotates the given polygon which consists of corners represented as (x,y) - around center_point (origin by default) - Rotation is counter-clockwise - Angle is in degrees - """ - rotated_polygon = [] - for corner in polygon: - rotated_corner = rotate_point(corner, angle,pdfrotation,width,height, center_point) - rotated_polygon.append(rotated_corner) - return rotated_polygon - -#create a dataframe containing color , count(how many times is this object found in the plan), area of 1 of these shapes, total area -#perimeter, totat perimeter, length, total length -#import pandas as pd -#SimilarAreaDictionary= pd.DataFrame(columns=['Guess','Color','Occurences','Area','Total Area','Perimeter','Total Perimeter','Length','Total Length','R','G','B']) -#loop 3la hatched areas and count the occurences of each shape w create a table bl hagat di - - - -def Create_DF(dxfpath,datadoc,hatched_areas,pdf_content=0): - - if pdf_content: - FinalRatio,width_dxf= RetriveRatio(datadoc,dxfpath,pdf_content) - else: - FinalRatio,width_dxf= RetriveRatio(datadoc,dxfpath) - # hatched_areas = get_hatched_areas(datadoc,dxfpath,FinalRatio) - - # hatched_areas=remove_duplicate_shapes(new_hatched_areas) - - # SimilarAreaDictionary= pd.DataFrame(columns=['Area', 'Total Area', 'Perimeter', 'Total Perimeter', 'Occurences', 'Color']) - SimilarAreaDictionary= pd.DataFrame(columns=['Guess','Color','Occurences','Area','Total Area','Perimeter','Total Perimeter','Length','Total Length','Texts','Comments']) - - # colorRanges2=generate_color_array(30000) - # colorRanges = [[255, 0, 0], [0, 0, 255], [0, 255, 255], [0, 64, 0], [255, 204, 0], [255, 128, 64], [255, 0, 128], [255, 128, 192], [128, 128, 255], [128, 64, 0],[0, 255, 0],[0, 200, 0],[255, 128, 255], [128, 0, 255], [0, 128, 192], [128, 0, 128],[128, 0, 0], [0, 128, 255], [149, 1, 70], [255, 182, 128], [222, 48, 71], [240, 0, 112], [255, 0, 255], [192, 46, 65], [0, 0, 128],[0, 128, 64],[255, 255, 0], [128, 0, 80], [255, 255, 128], [90, 255, 140],[255, 200, 20],[91, 16, 51], [90, 105, 138], [114, 10, 138], [36, 82, 78], [225, 105, 190], [108, 150, 170], [11, 35, 75], [42, 176, 170], [255, 176, 170], [209, 151, 15],[81, 27, 85], [226, 106, 122], [67, 119, 149], [159, 179, 140], [159, 179, 30],[255, 85, 198], [255, 27, 85], [188, 158, 8],[140, 188, 120], [59, 61, 52], [65, 81, 21], [212, 255, 174], [15, 164, 90],[41, 217, 245], [213, 23, 182], [11, 85, 169], [78, 153, 239], [0, 66, 141],[64, 98, 232], [140, 112, 255], [57, 33, 154], [194, 117, 252], [116, 92, 135], [74, 43, 98], [188, 13, 123], [129, 58, 91], [255, 128, 100], [171, 122, 145], [255, 98, 98], [222, 48, 77]] - # colorUsed=[] - TotalArea=0 - TotalPerimeter=0 - for shape in hatched_areas: - area = shape[1] # area - perimeter = shape[2] # perimeter - # if(i < len(colorRanges)): - # color = colorRanges[i] - # colorUsed.append(color) - # else: - # color = colorRanges2[i] - # colorUsed.append(color) - TotalArea = area - TotalPerimeter = perimeter - tol=0 - condition1 = (SimilarAreaDictionary['Area'] >= area - tol) & (SimilarAreaDictionary['Area'] <= area +tol) - condition2 = (SimilarAreaDictionary['Perimeter'] >= perimeter -tol) & (SimilarAreaDictionary['Perimeter'] <= perimeter +tol) - combined_condition = condition1 & condition2 - - if any(combined_condition): - index = np.where(combined_condition)[0][0] - SimilarAreaDictionary.at[index, 'Occurences'] += 1 - SimilarAreaDictionary.at[index, 'Total Area'] = SimilarAreaDictionary.at[index, 'Area'] * SimilarAreaDictionary.at[index, 'Occurences'] - SimilarAreaDictionary.at[index, 'Total Perimeter'] = SimilarAreaDictionary.at[index, 'Perimeter'] * SimilarAreaDictionary.at[index, 'Occurences'] - else: - TotalArea=area - TotalPerimeter=perimeter - # print("Shape[3]",shape[3]) - new_data = {'Area': area, 'Total Area': TotalArea ,'Perimeter': perimeter, 'Total Perimeter': TotalPerimeter, 'Occurences': 1, 'Color':shape[3],'Comments':''} #add color here and read color to insert in - SimilarAreaDictionary = pd.concat([SimilarAreaDictionary, pd.DataFrame([new_data])], ignore_index=True) - - # print(SimilarAreaDictionary) - return SimilarAreaDictionary -"""### Draw on Image and PDF""" - -# from sklearn.cluster import KMeans - -def color_distance(color1, color2): - print("color1 = ",color1) - print("color2 = ",color2) - print("abs(color1[0] - color2[0]) = ",abs(color1[0] - color2[0])) - print("abs(color1[1] - color2[1]) = ",abs(color1[1] - color2[1])) - print("abs(color1[2] - color2[2]) = ",abs(color1[2] - color2[2])) - if(abs(color1[0] - color2[0]) < 20 and - abs(color1[1] - color2[1]) < 20 and - abs(color1[2] - color2[2]) < 20): - return 1 - else: - return 100 - # return np.sqrt(sum((a - b) ** 2 for a, b in zip(color1, color2))) - -# Unify colors within a distance threshold -def unify_colors(df, threshold=20): - # Convert colors to tuple if they are not already in tuple format - df['Color'] = df['Color'].apply(lambda x: tuple(x) if isinstance(x, list) else x) - - # Iterate through the DataFrame and compare each color with the next one - for i in range(len(df) - 1): # We don't need to compare the last color with anything - current_color = df.at[i, 'Color'] - next_color = df.at[i + 1, 'Color'] - - # If the distance between current color and the next color is smaller than the threshold - if color_distance(current_color, next_color) <= threshold: - # Make both the same color (unify them to the current color) - df.at[i + 1, 'Color'] = current_color # Change the next color to the current color - - return df - -def normalize_color(color): - """Convert PDF color (range 0-1) to RGB (range 0-255).""" - return tuple(min(max(round(c * 255), 0), 255) for c in color) - - -def color_close_enough(c1, c2, threshold=10): - return all(abs(a - b) <= threshold for a, b in zip(c1, c2)) - -def adjustannotations(OutputPdfStage1,text_with_positions): - input_pdf_path = OutputPdfStage1 - output_pdf_path = "Final-WallsAdjusted.pdf" - annotations_data = [] - - # Load the input PDF - pdf_bytes_io = BytesIO(OutputPdfStage1) - - reader = PdfReader(pdf_bytes_io) - writer = PdfWriter() - - # Append all pages to the writer - writer.append_pages_from_reader(reader) - - # Add metadata (optional) - metadata = reader.metadata - writer.add_metadata(metadata) - - for page_index, page in enumerate(writer.pages): - if "/Annots" not in page: - continue - - for annot in page["/Annots"]: - obj = annot.get_object() - subtype = obj.get("/Subtype") - - # Group vertices for metadata - if subtype == "/Line": - raw_vertices = obj.get("/L", []) - else: - raw_vertices = obj.get("/Vertices", []) - vertices = group_vertices(raw_vertices) - - # Normalize color - raw_color = obj.get("/C") - try: - annot_color = normalize_color(raw_color) - except: - annot_color = raw_color - - # Extract measurement from annotation content - measurement = extract_measurement(obj) - # Assign to area or perimeter based on subtype - area = measurement if subtype == "/Polygon" else None - perimeter = measurement if subtype in ["/Line", "/PolyLine"] else None - - # Match text and NBS - matched_text = None - matched_nbs = None - if subtype in ["/Line", "/PolyLine", "/Polygon"] and raw_color: - matched_entry = next( - ((t, n) for t, n, _, c in text_with_positions if color_close_enough(annot_color, c)), - (None, None) - ) - matched_text, matched_nbs = matched_entry - combined = "" - if matched_text and matched_nbs: - combined = f"{matched_text} - {matched_nbs}" - elif matched_text: - combined = matched_text - elif matched_nbs: - combined = matched_nbs - if combined: - obj.update({NameObject("/T"): TextStringObject(combined)}) - - # Update annotation dictionaries for measurement type - if subtype == "/Line" and obj.get("/Subj", "") == "Perimeter Measurement": - obj.update({ - NameObject("/Measure"): DictionaryObject({ - NameObject("/Type"): NameObject("/Measure"), - NameObject("/L"): DictionaryObject({ - NameObject("/G"): FloatObject(1), - NameObject("/U"): TextStringObject("m"), - }), - }), - NameObject("/IT"): NameObject("/LineDimension"), - NameObject("/Subj"): TextStringObject("Length Measurement"), - }) - if subtype == "/Polygon" and obj.get("/Subj", "") == "Area Measurement": - obj.update({ - NameObject("/Measure"): DictionaryObject({ - NameObject("/Type"): NameObject("/Measure"), - NameObject("/Area"): DictionaryObject({ - NameObject("/G"): FloatObject(1), - NameObject("/U"): TextStringObject("sq m"), - }), - }), - NameObject("/IT"): NameObject("/Area_Annotation"), - NameObject("/Subj"): TextStringObject("Area Measurement"), - }) - - # Append metadata - annotations_data.append([ - vertices, - area, - perimeter, - annot_color, - matched_text, - matched_nbs, - ]) - - - - output_pdf_io = BytesIO() - writer.write(output_pdf_io) - output_pdf_io.seek(0) - - print(f"Annotations updated and saved to {output_pdf_path}") - return output_pdf_io.read() , annotations_data - -def distance(rect1, rect2): - """Calculate the Euclidean distance between two annotation centers.""" - x1, y1 = (float(rect1[0]) + float(rect1[2])) / 2, (float(rect1[1]) + float(rect1[3])) / 2 - x2, y2 = (float(rect2[0]) + float(rect2[2])) / 2, (float(rect2[1]) + float(rect2[3])) / 2 - return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) - -def group_vertices(raw): - """Convert flat list [x1,y1,x2,y2,...] into [[x1,y1],[x2,y2],...]""" - if not raw or len(raw) < 2: - return [] - return [[float(raw[i]), float(raw[i+1])] for i in range(0, len(raw), 2)] - -def group_rect(verts): - """Convert list of [x,y] vertices into a bounding rect [x_min, y_min, x_max, y_max].""" - xs = [v[0] for v in verts] - ys = [v[1] for v in verts] - return [min(xs), min(ys), max(xs), max(ys)] if verts else None - -def extract_measurement(obj): - """Extract first numeric measurement from an annotation's /Contents.""" - contents = obj.get("/Contents") - if not contents: - return None - match = re.search(r"([0-9]*\.?[0-9]+)", str(contents)) - return float(match.group(1)) if match else None - -def remove_duplicate_annotations(pdf_path, threshold): - """Remove one of the duplicate annotations if they are close and have the same color.""" - - input_pdf_path = pdf_path - output_pdf_path = "Filtered-Walls.pdf" - - # Load the input PDF - pdf_bytes_io = BytesIO(pdf_path) - - reader = PdfReader(pdf_bytes_io) - writer = PdfWriter() - - # Append all pages to the writer - # writer.append_pages_from_reader(reader) - - # Add metadata (optional) - metadata = reader.metadata - writer.add_metadata(metadata) - - for page_index in range(len(reader.pages)): - page = reader.pages[page_index] - - if "/Annots" in page: - annotations = page["/Annots"] - annots_data = [] - to_delete = set() - - # Extract annotation positions and colors - # for annot_index, annot_ref in enumerate(annotations): - # annot = annot_ref.get_object() - - # if "/Rect" in annot and "/C" in annot: - # rect = annot["/Rect"] - # if isinstance(rect, ArrayObject): # Ensure rect is a list - # rect = list(rect) - - # color = normalize_color(annot["/C"]) - # annots_data.append((annot_index, rect, color)) - - for i, annot_ref in enumerate(annotations): - annot = annot_ref.get_object() - rect = annot.get("/Rect") - color = annot.get("/C") - - if rect and color and isinstance(rect, ArrayObject) and len(rect) == 4: - norm_color = normalize_color(color) - annots_data.append((i, list(rect), norm_color)) - - - for i, (idx1, rect1, color1) in enumerate(annots_data): - if idx1 in to_delete: - continue - for j in range(i + 1, len(annots_data)): - idx2, rect2, color2 = annots_data[j] - if idx2 in to_delete: - continue - if color_close_enough(color1, color2) and distance(rect1, rect2) < threshold: - to_delete.add(idx2) - - # Keep only non-duplicates - new_annots = [annotations[i] for i in range(len(annotations)) if i not in to_delete] - page[NameObject("/Annots")] = ArrayObject(new_annots) - # Compare distances and mark duplicates - # for i, (idx1, rect1, color1) in enumerate(annots_data): - # if idx1 in to_delete: - # continue - # for j, (idx2, rect2, color2) in enumerate(annots_data[i+1:], start=i+1): - # if idx2 in to_delete: - # continue - # if color1 == color2 and distance(rect1, rect2) < threshold: - # to_delete.add(idx2) # Mark second annotation for deletion - - # # Remove duplicates - # new_annotations = [annotations[i] for i in range(len(annotations)) if i not in to_delete] - # page[NameObject("/Annots")] = ArrayObject(new_annotations) - - writer.add_page(page) - - output_pdf_io = BytesIO() - writer.write(output_pdf_io) - output_pdf_io.seek(0) - - return output_pdf_io.read() - - -def rect_distance(r1, r2): - """Euclidean distance between rect centers.""" - if not r1 or not r2: - return float('inf') - cx1, cy1 = (r1[0]+r1[2])/2, (r1[1]+r1[3])/2 - cx2, cy2 = (r2[0]+r2[2])/2, (r2[1]+r2[3])/2 - return math.hypot(cx2-cx1, cy2-cy1) - -def group_rect(verts): - """Turn [[x,y],…] into (x_min, y_min, x_max, y_max).""" - xs = [x for x,_ in verts] - ys = [y for _,y in verts] - return (min(xs), min(ys), max(xs), max(ys)) if verts else None - -def clean_annotations(annotations_data, threshold): - """ - Remove “nearby” duplicates from annotations_data, - where each entry is EITHER a dict with a 'vertices' key - OR a list/tuple whose first element *is* the vertices list. - """ - # 1) Extract a parallel list of bounding rects - rects = [] - for item in annotations_data: - if isinstance(item, dict): - verts = item.get('vertices', []) - elif isinstance(item, (list, tuple)) and item: - # heuristically assume the first element is vertices - verts = item[0] if isinstance(item[0], list) else [] - else: - verts = [] - rects.append(group_rect(verts)) - - # 2) Mark duplicates - to_delete = set() - for i, r1 in enumerate(rects): - if i in to_delete: - continue - for j in range(i+1, len(rects)): - if j in to_delete: - continue - if rect_distance(r1, rects[j]) < threshold: - to_delete.add(j) - - # 3) Build cleaned list - cleaned = [] - for idx, item in enumerate(annotations_data): - if idx not in to_delete: - cleaned.append(item) - - return cleaned - - -def calculate_distance(p1, p2): - return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) - -def ROI_boundingBoxCoor(img): - # Threshold (invert: walls are white) - imgGray= cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - _, thresh = cv2.threshold(imgGray, 250, 255, cv2.THRESH_BINARY_INV) - - # Morphological cleanup - kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)) - walls = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) - - # --- Connected components --- - num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(walls, connectivity=8) - - best_idx = None - best_score = -1 - h, w = walls.shape - cx_img, cy_img = w//2, h//2 # image center - - for i in range(1, num_labels): # ignore background (0) - x, y, bw, bh, area = stats[i] - cx, cy = centroids[i] - - # Score = area minus distance penalty - #prefer large area AND closeness to image center - dist = np.hypot(cx - cx_img, cy - cy_img) - score = area - 0.5 * dist #tune 0.5 - - if score > best_score: - best_score = score - best_idx = i - - # --- Get bounding box of best region --- - bbox = None - if best_idx is not None: - x, y, bw, bh, _ = stats[best_idx] - margin = 50 - x = int(max(x - margin, 0)) - y = int(max(y - margin, 0)) - bw = int(min(bw + 2*margin, w-x)) - bh = int(min(bh + 2*margin, h-y)) - - # Define bounding box as (x_min, y_min, x_max, y_max) - bbox = (x, y, x+bw, y+bh) - - # Draw ROI - imgcopy = img.copy() - cv2.rectangle(imgcopy, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0,255,0), 2) - - mask = np.zeros_like(img) - cv2.rectangle(mask, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (255,255,255), -1) - main_zone = cv2.bitwise_and(img, mask) - - return imgcopy, main_zone, bbox - - return img, img, bbox - -def draw_bb_onPDF(doc,bbox): - page = doc[0] - x1, y1 = bbox[0],bbox[1] - x2, y2 = bbox[2],bbox[3] - - p1 = fitz.Point(x1,y1) - p2 = fitz.Point(x2,y2) - - p1=p1*page.derotation_matrix - p2=p2*page.derotation_matrix - - rect = fitz.Rect(p1, p2).normalize() - x0, y0, x1, y1 = rect.x0, rect.y0, rect.x1, rect.y1 - pdf_bbox=[x0, y0, x1, y1] - - page.draw_rect(rect) #for visualization only - doc.save('kk.pdf') #ffor visualization only - return pdf_bbox - -def mainFunctionDrawImgPdf(datadoc,dxfpath, dxfratio,SearchArray,CorrectionRatio,points_Of_drawing_Canvas,Thickness,pdfpath=0,pdfname=0,pdf_content=0): - print("points_Of_drawing_Canvas in 2.7 = ",points_Of_drawing_Canvas) - OutputPdfStage1='BB Trial.pdf' - if pdf_content: - FinalRatio,width_dxf= RetriveRatio(datadoc,dxfpath,pdf_content) - else: - FinalRatio,width_dxf= RetriveRatio(datadoc,dxfpath) - - # hatched_areas = get_hatched_areas(datadoc,dxfpath,FinalRatio) - # hatched_areas=remove_duplicate_shapes(new_hatched_areas) - if pdf_content: - img,pix2=pdftoimg(datadoc,pdf_content) - else: - img,pix2=pdftoimg(datadoc) - flipped_horizontal=flip(img) - allcnts = [] - imgg = flipped_horizontal - # imgtransparent1=imgg.copy() - if pdf_content: - doc = fitz.open(stream=pdf_content, filetype="pdf") - else: - doc = fitz.open('pdf',datadoc) - page2 = doc[0] - rotationOld=page2.rotation - derotationMatrix=page2.derotation_matrix - # print("Derotation Matrix = ",derotationMatrix) - pix=page2.get_pixmap() - width=abs(page2.mediabox[2])+abs(page2.mediabox[0]) - height=abs(page2.mediabox[3])+abs(page2.mediabox[1]) - print('mediabox', width , height) - - Correction = CorrectionRatio / width_dxf - print("Correction Factor = ",round((Correction/FinalRatio),1)) - dxfratio = dxfratio * round((Correction/FinalRatio),1) - print("new omar dxfRatio = ",dxfratio) - - imgcopy, main_zone, bbox = ROI_boundingBoxCoor(img) #send here bgr img not gray - pdf_bbox=draw_bb_onPDF(doc,bbox) - bxmin, bymin, bxmax, bymax = pdf_bbox - - -# print('olddxfratio',dxfratio) -# correction_factor= detect_scale_from_page(dxfpath,width,dxfratio/1000) - -# factor=1 -# print('corr_factor',correction_factor) -# if correction_factor <0.26: #if less than 0.25 then the dxf ratio is correeect, if greater then *2 -# factor=1 -# print('Ratio working: keep as it is') -# else: -# factor =2 -# print('Ratio was adjusted to be ur input ratio x2') - -# dxfratio=dxfratio*factor -# print('new dxfratio', dxfratio) - - if page2.rotation!=0: - - rotationangle = page2.rotation - page2.set_rotation(0) - ratio = pix.width/ img.shape[0] - else: - ratio = pix.width/ img.shape[1] - rotationangle = 270 - - hatched_areas,text_with_positions = get_hatched_areas(datadoc,dxfpath,FinalRatio,rotationangle,SearchArray) - allshapes=[] - # Iterate through each polygon in metric units - NewColors = [] - if pdf_content: - SimilarAreaDictionary=Create_DF(dxfpath,datadoc,hatched_areas,pdf_content) - else: - SimilarAreaDictionary=Create_DF(dxfpath,datadoc,hatched_areas) - i=0 - flagcolor = 0 - ColorCounter = 0 - ColorCheck=[] - deleterows = [] - - - # def color_distance(color1, color2): - # return np.sqrt(sum((a - b) ** 2 for a, b in zip(color1, color2))) - - color_margin = 2 # Define margin threshold - - for polygon in hatched_areas: - cntPoints = [] - cntPoints1 = [] - shapeePerimeter = [] - shapeeArea = [] - Text_Detected = 0 - - blackImgShapes = np.zeros(imgg.shape[:2], dtype="uint8") - blackImgShapes= cv2.cvtColor(blackImgShapes, cv2.COLOR_GRAY2BGR) - - # Convert each vertex from metric to pixel coordinates - for vertex in polygon[0]: - x = (vertex[0]) *dxfratio - y = (vertex[1]) *dxfratio - if rotationangle==0: - if y<0: - y=y*-1 - cntPoints.append([int(x), int(y)]) - cntPoints1.append([x, y]) - - cv2.drawContours(blackImgShapes, [np.array(cntPoints)], -1, ([255,255,255]), thickness=-1) - x, y, w, h = cv2.boundingRect(np.array(cntPoints)) - firstpoint = 0 - for poi in np.array(cntPoints1): - if firstpoint == 0: - x2, y2 = poi - p2 = fitz.Point(x2,y2) - # p1 = fitz.Point(x1,y1) - p2=p2*derotationMatrix - shapeePerimeter.append([p2[0],p2[1]]) - firstpoint = 1 - else: - x1, y1 = poi - p1 = fitz.Point(x1,y1) - # p1 = fitz.Point(x1,y1) - p1=p1*derotationMatrix - # print("P1 = ",p1) - shapeePerimeter.append([p1[0],p1[1]]) - - shapeePerimeter.append([p2[0],p2[1]]) - shapeePerimeter=np.flip(shapeePerimeter,1) - shapeePerimeter=rotate_polygon(shapeePerimeter,rotationangle,rotationOld,width,height) - - for poi in np.array(cntPoints1): - x1, y1 = poi - p1 = fitz.Point(x1,y1) - # p1 = fitz.Point(x1,y1) - p1=p1*derotationMatrix - # print("P1 = ",p1) - shapeeArea.append([p1[0],p1[1]]) - - shapeeArea.append([p2[0],p2[1]]) - shapeeArea=np.flip(shapeeArea,1) - shapeeArea=rotate_polygon(shapeeArea,rotationangle,rotationOld,width,height) - - tol=0 - condition1 = (SimilarAreaDictionary['Area'] >= polygon[1] - tol) & (SimilarAreaDictionary['Area'] <= polygon[1] +tol) - condition2 = (SimilarAreaDictionary['Perimeter'] >= polygon[2] -tol) & (SimilarAreaDictionary['Perimeter'] <= polygon[2] +tol) - combined_condition = condition1 & condition2 - # print("combined_condition = ",combined_condition) - - if any(combined_condition): - - flagcolor = 1 - index = np.where(combined_condition)[0][0] - # print(SimilarAreaDictionary.at[index, 'Color']) - NewColors=SimilarAreaDictionary.at[index, 'Color'] - - else: - flagcolor = 2 - NewColors=SimilarAreaDictionary.at[i, 'Color'] - # flagcolor = 2 - - # cv2.drawContours(imgg, [np.array(cntPoints)], -1, (NewColors), thickness=2) - # print("new color = ",NewColors) - # print("New Colors = ",NewColors) - # if img is not None or img.shape[0] != 0 or img.shape[1] != 0: - if(int(NewColors[0])==255 and int(NewColors[1])==255 and int(NewColors[2])==255): - - WhiteImgFinal = cv2.bitwise_and(blackImgShapes,imgg) - # print("length = ",WhiteImgFinal.shape[0]) - # print("width = ",WhiteImgFinal.shape[1]) - flipped=flip(WhiteImgFinal) - # print("Flipped") - # cv2_imshow(flipped) - - imgslice = WhiteImgFinal[y:y+h, x:x+w] - # print("length slice = ",imgslice.shape[0]) - # print("width slice = ",imgslice.shape[1]) - if(imgslice.shape[0] != 0 and imgslice.shape[1] != 0): - flippedSlice=flip(imgslice) - # print("Sliced & Flipped") - # cv2_imshow(flippedSlice) - - # Convert flippedSlice to PIL for color extraction - flippedSlice_pil = Image.fromarray(flippedSlice) - - # Define patch size for color sampling (e.g., 10x10 pixels) - patch_size = 100 - patch_colors = [] - - # Loop through patches in the image - for i in range(0, flippedSlice_pil.width, patch_size): - for j in range(0, flippedSlice_pil.height, patch_size): - # Crop a patch from the original image - patch = flippedSlice_pil.crop((i, j, i + patch_size, j + patch_size)) - patch_colors += patch.getcolors(patch_size * patch_size) - - # Calculate the dominant color from all patches - max_count = 0 - dominant_color = None - tolerance = 5 - black_threshold = 30 # Max RGB value for a color to be considered "black" - white_threshold = 225 # Min RGB value for a color to be considered "white" - - for count, color in patch_colors: - # Exclude colors within the black and white ranges - if not (all(c <= black_threshold for c in color) or all(c >= white_threshold for c in color)): - # Update if the current color has a higher count than previous max - if count > max_count: - max_count = count - dominant_color = color - - # print("Dominant Color =", dominant_color) - - # Append dominant color to ColorCheck and update NewColors - if dominant_color is not None: - ColorCheck.append(dominant_color) - - NewColors = None # Initialize NewColors - - for color in ColorCheck: - # Check if the current color is within the tolerance - # print("color = ",color) - # print("dominant_color = ",dominant_color) - if (abs(color[0] - dominant_color[0]) < 20 and - abs(color[1] - dominant_color[1]) < 20 and - abs(color[2] - dominant_color[2]) < 20): - NewColors = (color[2], color[1], color[0]) # Set the new color - break - else: - # If no color in ColorCheck meets the tolerance, use the dominant color - NewColors = (dominant_color[2], dominant_color[1], dominant_color[0]) - # break - - # Avoid appending `dominant_color` again unnecessarily - if NewColors not in ColorCheck: - ColorCheck.append(NewColors) - - if flagcolor == 1: - SimilarAreaDictionary.at[index, 'Color'] = NewColors - # # print(f"Updated Color at index {index} with {NewColors}.") - elif flagcolor == 2: - SimilarAreaDictionary.at[i, 'Color'] = NewColors - # print("New Colors = ",NewColors) - cv2.drawContours(imgg, [np.array(cntPoints)], -1, ([NewColors[2],NewColors[1],NewColors[0]]), thickness=3) - - - - - start_point1 = shapeePerimeter[0] - end_point1 = shapeePerimeter[1] - start_point2 = shapeePerimeter[0] - end_point2 = shapeePerimeter[-2] - - distance1 = calculate_distance(start_point1, end_point1) - distance2 = calculate_distance(start_point2, end_point2) - - - - # Divide the shapePerimeter into two halves - half_index = len(shapeePerimeter) // 2 - half1 = shapeePerimeter[1:half_index+1] - half2 = shapeePerimeter[half_index:] - # half1 = shapeePerimeter[1:half_index] - # half2 = shapeePerimeter[half_index:-1] - - - - # Calculate distances for the halves - if len(half1) >= 2: - half1_distance = sum(calculate_distance(half1[i], half1[i + 1]) for i in range(len(half1) - 1)) - else: - half1_distance = 0 - - if len(half2) >= 2: - half2_distance = sum(calculate_distance(half2[i], half2[i + 1]) for i in range(len(half2) - 1)) - else: - half2_distance = 0 - - max_distance = max(distance1, distance2, half1_distance) - - if max_distance == distance1: - # Draw the line annotation for distance1 - chosen_start = start_point1 - chosen_end = end_point1 - # annot12 = page2.add_line_annot(chosen_start, chosen_end) - points=[] - points.append(chosen_start) - points.append(chosen_end) - discard = False - # if(points_Of_drawing_Canvas): - print("Canva points = ",points_Of_drawing_Canvas) - if(points_Of_drawing_Canvas): - Boundingpolygon = np.array( - [(p['x'], p['y']) for p in points_Of_drawing_Canvas[0]['coordinates']], - dtype=np.float32 - ) - - for x, y in points: - # Check if the point is outside the polygon - result = cv2.pointPolygonTest(Boundingpolygon, (x, y), False) - if result < 0: # < 0 means point is outside - discard = True - break - else: - for point in points: - if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): - discard = True - break - # for point in points: - # if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): - # discard = True - # break - if not discard: - annot12 = page2.add_polyline_annot(points) - - elif max_distance == distance2: - # Draw the line annotation for distance2 - chosen_start = start_point2 - chosen_end = end_point2 - # annot12 = page2.add_line_annot(chosen_start, chosen_end) - points=[] - points.append(chosen_start) - points.append(chosen_end) - # annot12 = page2.add_polyline_annot(points) - points=[] - points.append(chosen_start) - points.append(chosen_end) - discard = False - print("Canva points = ",points_Of_drawing_Canvas) - if(points_Of_drawing_Canvas): - Boundingpolygon = np.array( - [(p['x'], p['y']) for p in points_Of_drawing_Canvas[0]['coordinates']], - dtype=np.float32 - ) - - for x, y in points: - # Check if the point is outside the polygon - result = cv2.pointPolygonTest(Boundingpolygon, (x, y), False) - if result < 0: # < 0 means point is outside - discard = True - break - else: - for point in points: - if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): - discard = True - break - - if not discard: - annot12 = page2.add_polyline_annot(points) - - elif max_distance == half1_distance: - # annot12 = page2.add_polyline_annot(half1) - max_pair_distance = 0.0 - max_pair_start = None - max_pair_end = None - - # 2. Loop through each consecutive pair in half1 - for i in range(len(half1) - 1): - p_current = half1[i] - p_next = half1[i + 1] - - # 3. Compute distance between these two points - dist = calculate_distance(p_current, p_next) - - # 4. Update max if this distance is greater - if dist > max_pair_distance: - max_pair_distance = dist - max_pair_start = p_current - max_pair_end = p_next - - # 5. After the loop, max_pair_start and max_pair_end represent - # the two consecutive points with the greatest separation. - if max_pair_start is not None and max_pair_end is not None: - # 6. Draw the line annotation using these two points - # annot12 = page2.add_line_annot(max_pair_start, max_pair_end) - points=[] - points.append(max_pair_start) - points.append(max_pair_end) - discard = False - print("Canva points = ",points_Of_drawing_Canvas) - if(points_Of_drawing_Canvas): - Boundingpolygon = np.array( - [(p['x'], p['y']) for p in points_Of_drawing_Canvas[0]['coordinates']], - dtype=np.float32 - ) - - for x, y in points: - # Check if the point is outside the polygon - result = cv2.pointPolygonTest(Boundingpolygon, (x, y), False) - if result < 0: # < 0 means point is outside - discard = True - break - else: - for point in points: - if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): - discard = True - break - - if not discard: - annot12 = page2.add_polyline_annot(points) - # print(f"Drew line annotation between {max_pair_start} and {max_pair_end}") - else: - # This case only occurs if half1 has fewer than 2 points - print("Not enough points in half1 to compute a line.") - - - discard = False - print("Canva points = ",points_Of_drawing_Canvas) - if(points_Of_drawing_Canvas): - Boundingpolygon = np.array( - [(p['x'], p['y']) for p in points_Of_drawing_Canvas[0]['coordinates']], - dtype=np.float32 - ) - - for x, y in points: - # Check if the point is outside the polygon - result = cv2.pointPolygonTest(Boundingpolygon, (x, y), False) - if result < 0: # < 0 means point is outside - discard = True - break - else: - for point in points: - if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): - discard = True - break - - if not discard: - annot12.set_border(width=0.8) - annot12.set_colors(stroke=(int(NewColors[0])/255,int(NewColors[1])/255,int(NewColors[2])/255)) - # annot12.set_info(content=str(polygon[2])+' m',subject='Perimeter Measurement', title="ADR Team") - annot12.set_info(subject='Perimeter Measurement',content=str(polygon[2])+' m') - annot12.set_opacity(0.8) - annot12.update() - - - i += 1 - alpha = 0.8 # Transparency factor. - - page2.set_rotation(rotationOld) - Correct_img=flip(imgg) - - image_new1 = cv2.addWeighted(Correct_img, alpha, img, 1 - alpha, 0) - SimilarAreaDictionary = SimilarAreaDictionary.fillna(' ') - - # Define white color to filter out - white_color = (255, 255, 255) - - # Delete rows where 'Guess' equals white_color - SimilarAreaDictionary = SimilarAreaDictionary[SimilarAreaDictionary['Color'] != white_color] - - # Reset the index to update row numbering - SimilarAreaDictionary.reset_index(drop=True, inplace=True) - - - grouped_df = SimilarAreaDictionary.groupby('Color').agg({ - 'Guess': 'first', - 'Occurences': 'sum', # Sum of occurrences for each color - 'Area':'first', - 'Total Area': 'sum', # Sum of areas for each color - 'Perimeter':'first', - 'Total Perimeter': 'sum', # Sum of perimeters for each color - 'Length':'first', - 'Total Length': 'sum', # Sum of lengths for each color - 'Texts': 'first', # Keep the first occurrence of 'Texts' - 'Comments': 'first' # Keep the first occurrence of 'Comments' - - }).reset_index() - -# doc.save(OutputPdfStage1) -# OutputPdfStage2=adjustannotations(OutputPdfStage1,text_with_positions) - modified_pdf_data = doc.tobytes() - OutputPdfStage2 , annotations_data=adjustannotations(modified_pdf_data,text_with_positions) - - if (Thickness): - - threshold = math.ceil(float(Thickness) * float(dxfratio) ) - cleaned_list = clean_annotations(annotations_data, threshold) - - else: - cleaned_list = clean_annotations(annotations_data, threshold=10) - - allvertices = cleaned_list - # PerimeterVertices = XMLPerimeter - #Example Color : this is in RGB normalized format as for e.g.: 200/255 for r g b - - - hatchcolorR= '0' - hatchcolorG= '1' - hatchcolorB= '1' - # Define templates with placeholder {w} instead of hardcoded LINEWIDTH - LinestyleTemplates = { - 'Solid': '<>', - 'Dashed1':'<>', - 'Dashed2': '<>', - 'Dashed3': '<>', - 'Dashed4': '<>', - 'Dashed5': '<>', - 'Dashed6': '<>' - } - #/BS<> - - HatchFunctions = { - 'None':'', - 'Brick': setBrickHatch, - 'DiagonalBrick':setDiagonalBrickHatch, - 'Horizontal':setHorizontalHatch, - 'Vertical':setVerticalHatch, - 'DiagonalDown':setDiagonalDownHatch, - 'DiagonalUp':setDiagonalUpHatch, - 'Grid': setGridHatch, - 'Weave':setWeaveHatch, - '10Dots':set10DotsHatch, - '20Dots':set20DotsHatch, - '30Dots':set30DotsHatch - } - - #Area and perimeter numbers example - area = 20 - perimeter = 30 - - import colorsys - - annotations=[] - for shapeinvertices in allvertices: - - rn=shapeinvertices[3][0]/255 - gn=shapeinvertices[3][1]/255 - bn=shapeinvertices[3][2]/255 - - h, s, v = colorsys.rgb_to_hsv(rn, gn, bn) - - # snap to full saturation, 50% brightness - s2, v2 = 0.6, 0.9 - - # back to RGB - r2, g2, b2 = colorsys.hsv_to_rgb(h, s2, v2) - - R=str(r2) - G=str(g2) - B=str(b2) - - annotations.append( - { - - 'vertices': shapeinvertices[0], # [[x,y],[x1,y1],[....]] position of ur markup - 'text': str(shapeinvertices[2])+' m', - 'author': 'ADR', - 'custom_data': {'Specification':shapeinvertices[5]},#identify custom colums here as( Column name: Text to add ) - 'type_internal': 'Bluebeam.PDF.Annotations.AnnotationMeasurePerimeter', - 'subject': 'Perimeter Measurement', - 'label':shapeinvertices[4], - 'opacity': '0.7',#opacity of ur shape fill - 'color': R+ ' '+G + ' '+B,# normalized (RGB --> R/255 G/255 B/255) - 'linestyle': LinestyleTemplates['Dashed6'].format(w=2) # LineStyles as in BB ,this w is the linewidth - - } - ) - - - column_order = ['Specification'] #specify here the custom columns in order - # print(bax_annotations) - - #replace with ur pdf width and height variables - pdfWidth=1684 - pdfHeight=2384 - - - # save_multiple_annotations_bax( - # bax_annotations, 'Area_Perimeter_OMAR_output.bax', column_order, pdfWidth, pdfHeight - # ) - bax_xml=save_multiple_annotations_bax( - annotations, '2552 Page 1.bax', column_order, pdfWidth, pdfHeight - ) - - from xml.etree.ElementTree import Element, SubElement, tostring - - def generate_bluebeam_columns_raw(column_names): - """ - Generate BluebeamUserDefinedColumns XML as raw string, without headers or extra fields. - """ - root = Element("BluebeamUserDefinedColumns") - - for idx, name in enumerate(column_names): - item = SubElement(root, "BSIColumnItem", Index=str(idx), Subtype="Text") - SubElement(item, "Name").text = name - SubElement(item, "DisplayOrder").text = str(idx) - SubElement(item, "Deleted").text = "False" - SubElement(item, "Multiline").text = "False" - - # Convert to string and decode raw bytes - return tostring(root, encoding="unicode", method="xml") - - - column_xml = generate_bluebeam_columns_raw(column_order) - -# with open("2552 Page 1.xml", "w", encoding="utf-8") as f: -# f.write(column_xml) - -# print(column_xml) - - - if pdf_content: - latestimg,pix=pdftoimg(OutputPdfStage2,pdf_content) - else: - latestimg,pix=pdftoimg(OutputPdfStage2) - doc2 =fitz.open('pdf',OutputPdfStage2) - if pdf_content: - gc,spreadsheet_service,spreadsheetId, spreadsheet_url , namepathArr=google_sheet_Legend.legendGoogleSheets(grouped_df , pdfname,pdfpath,pdf_content) - else: - gc,spreadsheet_service,spreadsheetId, spreadsheet_url , namepathArr=google_sheet_Legend.legendGoogleSheets(grouped_df , pdfname,pdfpath) - list1=pd.DataFrame(columns=['content', 'id', 'subject','color']) - - # for page in doc: - for page in doc2: - # Iterate through annotations on the page - for annot in page.annots(): - # Get the color of the annotation - annot_color = annot.colors - if annot_color is not None: - # annot_color is a dictionary with 'stroke' and 'fill' keys - stroke_color = annot_color.get('stroke') # Border color - fill_color = annot_color.get('fill') # Fill color - if fill_color: - v='fill' - # print('fill') - if stroke_color: - v='stroke' - x,y,z=int(annot_color.get(v)[0]*255),int(annot_color.get(v)[1]*255),int(annot_color.get(v)[2]*255) - list1.loc[len(list1)] =[annot.info['content'],annot.info['id'],annot.info['subject'],[x,y,z]] - print('LISTTT',list1) - return doc2,latestimg, SimilarAreaDictionary ,spreadsheetId, spreadsheet_url , namepathArr , list1,hatched_areas, bax_xml, column_xml - +# -*- coding: utf-8 -*- +"""2.7 Code to be deployed 21.02.2025 + +Automatically generated by Colab. + +Original file is located at + https://colab.research.google.com/drive/1RWSQn0GW_KXoHkJLcbYzLAGGyc0tiDWl +""" + +"""## Imports""" +import sys +import math +import random +import string +import zlib +import base64 +import datetime +import uuid +import re +from io import BytesIO +from ctypes import sizeof +from collections import Counter +from typing import NewType +import xml.etree.ElementTree as ET +from xml.etree.ElementTree import Element, SubElement, tostring, ElementTree +from xml.dom.minidom import parseString + +import numpy as np +import cv2 +from matplotlib import pyplot as plt +from matplotlib.patches import Polygon +from shapely.geometry import Point, Polygon as ShapelyPolygon +from shapely.ops import unary_union +from PIL import Image, ImageDraw, ImageFont, ImageColor + +import fitz +import ezdxf +from ezdxf import units, bbox +from ezdxf.colors import aci2rgb +from ezdxf.math import OCS, Matrix44, Vec3, Vec2 + +import pandas as pd +import google_sheet_Legend +import tsadropboxretrieval + +from PyPDF2 import PdfReader, PdfWriter +from PyPDF2.generic import ( + NameObject, + TextStringObject, + DictionaryObject, + ArrayObject, + FloatObject, + NumberObject, +) + +from math import sin, cos, radians, isclose + + + +def normalize_vertices(vertices): + """Sort vertices to ensure consistent order.""" + return tuple(sorted(tuple(v) for v in vertices)) + +def areas_are_similar(area1, area2, tolerance=0.2): + """Check if two areas are within a given tolerance.""" + return abs(area1 - area2) <= tolerance + + +# -*- coding: utf-8 -*-wj +"""Version to be deployed of 3.2 Calculating area/perimeter + +Automatically generated by Colab. + +Original file is located at + https://colab.research.google.com/drive/1XPeCoTBgWSNBYZ3aMKBteP4YG3w4bORs +""" +import ezdxf +from ezdxf.bbox import extents + +def detect_scale_from_page(dxf_path, page_pixel_width, m_per_pixel_from_pdf): + """ + Detects mm/px scale factor using the bounding box of the entire DXF content. + """ + doc = ezdxf.readfile(dxf_path) + + #getting the bounding box from modelspace + msp = doc.modelspace() + bbox_msp = extents(msp, fast=True) + + if bbox_msp.has_data: #not empty + min_x, min_y, max_x, max_y = bbox_msp.extmin.x, bbox_msp.extmin.y, bbox_msp.extmax.x, bbox_msp.extmax.y + else: + # Try paperspace as a fallback + psp = doc.layout("Layout1") + bbox_psp = extents(psp, fast=True) + if not bbox_psp.has_data: + raise ValueError("No bounding box data found in modelspace or paperspace.") + min_x, min_y, max_x, max_y = bbox_psp.extmin.x, bbox_psp.extmin.y, bbox_psp.extmax.x, bbox_psp.extmax.y + + # DXF width + dxf_width = max_x - min_x + + # PDF width in m + pdf_metric_width = page_pixel_width * m_per_pixel_from_pdf + + # Correction factor + correction_factor = dxf_width / pdf_metric_width + # final_scale = mm_per_pixel_from_pdf * correction_factor + + return correction_factor + + +"""## Notes""" + +#new approach to get width and height of dxf plan +''' +This portion is used to convert vertices read from dxf to pixels in order to accurately locate shapes in the image and pdf + ratio : + MeasuredMetric* PixelValue/ DxfMetric = MeasuredPixel + PixelValue: get from pixel conversion code , second number in the bracker represents the perimeter + DxfMetric: measured perimeter from foxit + + divide pixelvalue by dxfmetric, will give u a ratio , this is ur dxfratio + + +''' + +AllhatchesCodes= { + 'Brick':'<>/Matrix[1 0 0 1 0 0]/BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n{fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 18 m 19.00001 18 l 9 18 m 9 9 l -1 9 m 19.00001 9 l 0 9 m 0 0 l -1 0 m 19.00001 0 l 18 0 m 18 9 l S \nendstream' +, +'DiagonalBrick': '''<> + /Matrix [1 0 0 1 0 0] + /BBox [0 0 18 18] + /XStep 18/YStep 18>>stream + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 -1 m 19.00001 19.00001 l 9 9 m 0 18 l -1 17 m 1 19.00001 l 17 -1 m 19.00001 1 l S + endstream''' +, +'Horizontal':'''<> + /Matrix[1 0 0 1 0 0] + /BBox[0 0 18 18] + /XStep 18/YStep 18>>\nstream\n + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 13.5 m 19.00001 13.5 l -1 4.5 m 19.00001 4.5 l S + endstream''' +, +'Vertical':'''<> + /Matrix[1 0 0 1 0 0] + /BBox[0 0 18 18] + /XStep 18/YStep 18>>\nstream\n + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w 4.5 19.00001 m 4.5 -1 l 13.5 19.00001 m 13.5 -1 l S + endstream''' +, +'DiagonalDown':'''<>/Matrix[1 0 0 1 0 0] + /BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 19.00001 m 19.00001 -1 l -1 1 m 1 -1 l 17 19.00001 m 19.00001 17 l S + endstream''' +, +'DiagonalUp':'''<>/Matrix[1 0 0 1 0 0] + /BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 17 m 1 19.00001 l -1 -1 m 19.00001 19.00001 l 17 -1 m 19.00001 1 l S + endstream''' +, +'Grid':'''<>/Matrix[1 0 0 1 0 0] + /BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w 4.5 19.00001 m 4.5 -1 l 13.5 19.00001 m 13.5 -1 l -1 13.5 m 19.00001 13.5 l -1 4.5 m 19.00001 4.5 l S + endstream''' +, +'Weave':'''<>/Matrix[1 0 0 1 0 0] + /BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w -1 19.00001 m 4.5 13.5 l -1 7.999999 m 10 19.00001 l 7.999999 19.00001 m 19.00001 7.999999 l 17 19.00001 m 19.00001 17 l -1 -1 m 13.5 13.5 l 4.5 4.5 m 10 -1 l 9 9 m 19.00001 -1 l 18 9 m 13.5 4.5 l S + endstream''' +, +'10Dots':'''<> + /Matrix[1 0 0 1 0 0]/BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w 0 -0.5 m 0.2761424 -0.5 0.5 -0.2761424 0.5 0 c 0.5 0.2761424 0.2761424 0.5 0 0.5 c -0.2761424 0.5 -0.5 0.2761424 -0.5 0 c -0.5 -0.2761424 -0.2761424 -0.5 0 -0.5 c h 0 4 m 0.2761424 4 0.5 4.223857 0.5 4.5 c 0.5 4.776143 0.2761424 5 0 5 c -0.2761424 5 -0.5 4.776143 -0.5 4.5 c -0.5 4.223857 -0.2761424 4 0 4 c h 0 8.5 m 0.2761424 8.5 0.5 8.723858 0.5 9 c 0.5 9.276142 0.2761424 9.5 0 9.5 c -0.2761424 9.5 -0.5 9.276142 -0.5 9 c -0.5 8.723858 -0.2761424 8.5 0 8.5 c h 0 13 m 0.2761424 13 0.5 13.22386 0.5 13.5 c 0.5 13.77614 0.2761424 14 0 14 c -0.2761424 14 -0.5 13.77614 -0.5 13.5 c -0.5 13.22386 -0.2761424 13 0 13 c h 0 17.5 m 0.2761424 17.5 0.5 17.72386 0.5 18 c 0.5 18.27614 0.2761424 18.5 0 18.5 c -0.2761424 18.5 -0.5 18.27614 -0.5 18 c -0.5 17.72386 -0.2761424 17.5 0 17.5 c h 4.5 -0.5 m 4.776143 -0.5 5 -0.2761424 5 0 c 5 0.2761424 4.776143 0.5 4.5 0.5 c 4.223857 0.5 4 0.2761424 4 0 c 4 -0.2761424 4.223857 -0.5 4.5 -0.5 c h 4.5 4 m 4.776143 4 5 4.223857 5 4.5 c 5 4.776143 4.776143 5 4.5 5 c 4.223857 5 4 4.776143 4 4.5 c 4 4.223857 4.223857 4 4.5 4 c h 4.5 8.5 m 4.776143 8.5 5 8.723858 5 9 c 5 9.276142 4.776143 9.5 4.5 9.5 c 4.223857 9.5 4 9.276142 4 9 c 4 8.723858 4.223857 8.5 4.5 8.5 c h 4.5 13 m 4.776143 13 5 13.22386 5 13.5 c 5 13.77614 4.776143 14 4.5 14 c 4.223857 14 4 13.77614 4 13.5 c 4 13.22386 4.223857 13 4.5 13 c h 4.5 17.5 m 4.776143 17.5 5 17.72386 5 18 c 5 18.27614 4.776143 18.5 4.5 18.5 c 4.223857 18.5 4 18.27614 4 18 c 4 17.72386 4.223857 17.5 4.5 17.5 c h 9 -0.5 m 9.276142 -0.5 9.5 -0.2761424 9.5 0 c 9.5 0.2761424 9.276142 0.5 9 0.5 c 8.723858 0.5 8.5 0.2761424 8.5 0 c 8.5 -0.2761424 8.723858 -0.5 9 -0.5 c h 9 4 m 9.276142 4 9.5 4.223857 9.5 4.5 c 9.5 4.776143 9.276142 5 9 5 c 8.723858 5 8.5 4.776143 8.5 4.5 c 8.5 4.223857 8.723858 4 9 4 c h 9 8.5 m 9.276142 8.5 9.5 8.723858 9.5 9 c 9.5 9.276142 9.276142 9.5 9 9.5 c 8.723858 9.5 8.5 9.276142 8.5 9 c 8.5 8.723858 8.723858 8.5 9 8.5 c h 9 13 m 9.276142 13 9.5 13.22386 9.5 13.5 c 9.5 13.77614 9.276142 14 9 14 c 8.723858 14 8.5 13.77614 8.5 13.5 c 8.5 13.22386 8.723858 13 9 13 c h 9 17.5 m 9.276142 17.5 9.5 17.72386 9.5 18 c 9.5 18.27614 9.276142 18.5 9 18.5 c 8.723858 18.5 8.5 18.27614 8.5 18 c 8.5 17.72386 8.723858 17.5 9 17.5 c h 13.5 -0.5 m 13.77614 -0.5 14 -0.2761424 14 0 c 14 0.2761424 13.77614 0.5 13.5 0.5 c 13.22386 0.5 13 0.2761424 13 0 c 13 -0.2761424 13.22386 -0.5 13.5 -0.5 c h 13.5 4 m 13.77614 4 14 4.223857 14 4.5 c 14 4.776143 13.77614 5 13.5 5 c 13.22386 5 13 4.776143 13 4.5 c 13 4.223857 13.22386 4 13.5 4 c h 13.5 8.5 m 13.77614 8.5 14 8.723858 14 9 c 14 9.276142 13.77614 9.5 13.5 9.5 c 13.22386 9.5 13 9.276142 13 9 c 13 8.723858 13.22386 8.5 13.5 8.5 c h 13.5 13 m 13.77614 13 14 13.22386 14 13.5 c 14 13.77614 13.77614 14 13.5 14 c 13.22386 14 13 13.77614 13 13.5 c 13 13.22386 13.22386 13 13.5 13 c h 13.5 17.5 m 13.77614 17.5 14 17.72386 14 18 c 14 18.27614 13.77614 18.5 13.5 18.5 c 13.22386 18.5 13 18.27614 13 18 c 13 17.72386 13.22386 17.5 13.5 17.5 c h 18 -0.5 m 18.27614 -0.5 18.5 -0.2761424 18.5 0 c 18.5 0.2761424 18.27614 0.5 18 0.5 c 17.72386 0.5 17.5 0.2761424 17.5 0 c 17.5 -0.2761424 17.72386 -0.5 18 -0.5 c h 18 4 m 18.27614 4 18.5 4.223857 18.5 4.5 c 18.5 4.776143 18.27614 5 18 5 c 17.72386 5 17.5 4.776143 17.5 4.5 c 17.5 4.223857 17.72386 4 18 4 c h 18 8.5 m 18.27614 8.5 18.5 8.723858 18.5 9 c 18.5 9.276142 18.27614 9.5 18 9.5 c 17.72386 9.5 17.5 9.276142 17.5 9 c 17.5 8.723858 17.72386 8.5 18 8.5 c h 18 13 m 18.27614 13 18.5 13.22386 18.5 13.5 c 18.5 13.77614 18.27614 14 18 14 c 17.72386 14 17.5 13.77614 17.5 13.5 c 17.5 13.22386 17.72386 13 18 13 c h 18 17.5 m 18.27614 17.5 18.5 17.72386 18.5 18 c 18.5 18.27614 18.27614 18.5 18 18.5 c 17.72386 18.5 17.5 18.27614 17.5 18 c 17.5 17.72386 17.72386 17.5 18 17.5 c h 2.25 1.75 m 2.526142 1.75 2.75 1.973858 2.75 2.25 c 2.75 2.526142 2.526142 2.75 2.25 2.75 c 1.973858 2.75 1.75 2.526142 1.75 2.25 c 1.75 1.973858 1.973858 1.75 2.25 1.75 c h 2.25 6.25 m 2.526142 6.25 2.75 6.473857 2.75 6.75 c 2.75 7.026143 2.526142 7.25 2.25 7.25 c 1.973858 7.25 1.75 7.026143 1.75 6.75 c 1.75 6.473857 1.973858 6.25 2.25 6.25 c h 2.25 10.75 m 2.526142 10.75 2.75 10.97386 2.75 11.25 c 2.75 11.52614 2.526142 11.75 2.25 11.75 c 1.973858 11.75 1.75 11.52614 1.75 11.25 c 1.75 10.97386 1.973858 10.75 2.25 10.75 c h 2.25 15.25 m 2.526142 15.25 2.75 15.47386 2.75 15.75 c 2.75 16.02614 2.526142 16.25 2.25 16.25 c 1.973858 16.25 1.75 16.02614 1.75 15.75 c 1.75 15.47386 1.973858 15.25 2.25 15.25 c h 6.75 1.75 m 7.026143 1.75 7.25 1.973858 7.25 2.25 c 7.25 2.526142 7.026143 2.75 6.75 2.75 c 6.473857 2.75 6.25 2.526142 6.25 2.25 c 6.25 1.973858 6.473857 1.75 6.75 1.75 c h 6.75 6.25 m 7.026143 6.25 7.25 6.473857 7.25 6.75 c 7.25 7.026143 7.026143 7.25 6.75 7.25 c 6.473857 7.25 6.25 7.026143 6.25 6.75 c 6.25 6.473857 6.473857 6.25 6.75 6.25 c h 6.75 10.75 m 7.026143 10.75 7.25 10.97386 7.25 11.25 c 7.25 11.52614 7.026143 11.75 6.75 11.75 c 6.473857 11.75 6.25 11.52614 6.25 11.25 c 6.25 10.97386 6.473857 10.75 6.75 10.75 c h 6.75 15.25 m 7.026143 15.25 7.25 15.47386 7.25 15.75 c 7.25 16.02614 7.026143 16.25 6.75 16.25 c 6.473857 16.25 6.25 16.02614 6.25 15.75 c 6.25 15.47386 6.473857 15.25 6.75 15.25 c h 11.25 1.75 m 11.52614 1.75 11.75 1.973858 11.75 2.25 c 11.75 2.526142 11.52614 2.75 11.25 2.75 c 10.97386 2.75 10.75 2.526142 10.75 2.25 c 10.75 1.973858 10.97386 1.75 11.25 1.75 c h 11.25 6.25 m 11.52614 6.25 11.75 6.473857 11.75 6.75 c 11.75 7.026143 11.52614 7.25 11.25 7.25 c 10.97386 7.25 10.75 7.026143 10.75 6.75 c 10.75 6.473857 10.97386 6.25 11.25 6.25 c h 11.25 10.75 m 11.52614 10.75 11.75 10.97386 11.75 11.25 c 11.75 11.52614 11.52614 11.75 11.25 11.75 c 10.97386 11.75 10.75 11.52614 10.75 11.25 c 10.75 10.97386 10.97386 10.75 11.25 10.75 c h 11.25 15.25 m 11.52614 15.25 11.75 15.47386 11.75 15.75 c 11.75 16.02614 11.52614 16.25 11.25 16.25 c 10.97386 16.25 10.75 16.02614 10.75 15.75 c 10.75 15.47386 10.97386 15.25 11.25 15.25 c h 15.75 1.75 m 16.02614 1.75 16.25 1.973858 16.25 2.25 c 16.25 2.526142 16.02614 2.75 15.75 2.75 c 15.47386 2.75 15.25 2.526142 15.25 2.25 c 15.25 1.973858 15.47386 1.75 15.75 1.75 c h 15.75 6.25 m 16.02614 6.25 16.25 6.473857 16.25 6.75 c 16.25 7.026143 16.02614 7.25 15.75 7.25 c 15.47386 7.25 15.25 7.026143 15.25 6.75 c 15.25 6.473857 15.47386 6.25 15.75 6.25 c h 15.75 10.75 m 16.02614 10.75 16.25 10.97386 16.25 11.25 c 16.25 11.52614 16.02614 11.75 15.75 11.75 c 15.47386 11.75 15.25 11.52614 15.25 11.25 c 15.25 10.97386 15.47386 10.75 15.75 10.75 c h 15.75 15.25 m 16.02614 15.25 16.25 15.47386 16.25 15.75 c 16.25 16.02614 16.02614 16.25 15.75 16.25 c 15.47386 16.25 15.25 16.02614 15.25 15.75 c 15.25 15.47386 15.47386 15.25 15.75 15.25 c h + {strokecolor} rg f + endstream''' +, +'20Dots':'''<> + /Matrix[1 0 0 1 0 0]/BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG 1 w 0 -0.5 m 0.2761424 -0.5 0.5 -0.2761424 0.5 0 c 0.5 0.2761424 0.2761424 0.5 0 0.5 c -0.2761424 0.5 -0.5 0.2761424 -0.5 0 c -0.5 -0.2761424 -0.2761424 -0.5 0 -0.5 c h 0 2.500001 m 0.2761424 2.500001 0.5 2.723858 0.5 3.000001 c 0.5 3.276143 0.2761424 3.500001 0 3.500001 c -0.2761424 3.500001 -0.5 3.276143 -0.5 3.000001 c -0.5 2.723858 -0.2761424 2.500001 0 2.500001 c h 0 5.5 m 0.2761424 5.5 0.5 5.723857 0.5 6 c 0.5 6.276142 0.2761424 6.5 0 6.5 c -0.2761424 6.5 -0.5 6.276142 -0.5 6 c -0.5 5.723857 -0.2761424 5.5 0 5.5 c h 0 8.5 m 0.2761424 8.5 0.5 8.723858 0.5 9 c 0.5 9.276142 0.2761424 9.5 0 9.5 c -0.2761424 9.5 -0.5 9.276142 -0.5 9 c -0.5 8.723858 -0.2761424 8.5 0 8.5 c h 0 11.5 m 0.2761424 11.5 0.5 11.72386 0.5 12 c 0.5 12.27614 0.2761424 12.5 0 12.5 c -0.2761424 12.5 -0.5 12.27614 -0.5 12 c -0.5 11.72386 -0.2761424 11.5 0 11.5 c h 0 14.5 m 0.2761424 14.5 0.5 14.72386 0.5 15 c 0.5 15.27614 0.2761424 15.5 0 15.5 c -0.2761424 15.5 -0.5 15.27614 -0.5 15 c -0.5 14.72386 -0.2761424 14.5 0 14.5 c h 0 17.5 m 0.2761424 17.5 0.5 17.72386 0.5 18 c 0.5 18.27614 0.2761424 18.5 0 18.5 c -0.2761424 18.5 -0.5 18.27614 -0.5 18 c -0.5 17.72386 -0.2761424 17.5 0 17.5 c h 3.000001 -0.5 m 3.276143 -0.5 3.500001 -0.2761424 3.500001 0 c 3.500001 0.2761424 3.276143 0.5 3.000001 0.5 c 2.723858 0.5 2.500001 0.2761424 2.500001 0 c 2.500001 -0.2761424 2.723858 -0.5 3.000001 -0.5 c h 3.000001 2.500001 m 3.276143 2.500001 3.500001 2.723858 3.500001 3.000001 c 3.500001 3.276143 3.276143 3.500001 3.000001 3.500001 c 2.723858 3.500001 2.500001 3.276143 2.500001 3.000001 c 2.500001 2.723858 2.723858 2.500001 3.000001 2.500001 c h 3.000001 5.5 m 3.276143 5.5 3.500001 5.723857 3.500001 6 c 3.500001 6.276142 3.276143 6.5 3.000001 6.5 c 2.723858 6.5 2.500001 6.276142 2.500001 6 c 2.500001 5.723857 2.723858 5.5 3.000001 5.5 c h 3.000001 8.5 m 3.276143 8.5 3.500001 8.723858 3.500001 9 c 3.500001 9.276142 3.276143 9.5 3.000001 9.5 c 2.723858 9.5 2.500001 9.276142 2.500001 9 c 2.500001 8.723858 2.723858 8.5 3.000001 8.5 c h 3.000001 11.5 m 3.276143 11.5 3.500001 11.72386 3.500001 12 c 3.500001 12.27614 3.276143 12.5 3.000001 12.5 c 2.723858 12.5 2.500001 12.27614 2.500001 12 c 2.500001 11.72386 2.723858 11.5 3.000001 11.5 c h 3.000001 14.5 m 3.276143 14.5 3.500001 14.72386 3.500001 15 c 3.500001 15.27614 3.276143 15.5 3.000001 15.5 c 2.723858 15.5 2.500001 15.27614 2.500001 15 c 2.500001 14.72386 2.723858 14.5 3.000001 14.5 c h 3.000001 17.5 m 3.276143 17.5 3.500001 17.72386 3.500001 18 c 3.500001 18.27614 3.276143 18.5 3.000001 18.5 c 2.723858 18.5 2.500001 18.27614 2.500001 18 c 2.500001 17.72386 2.723858 17.5 3.000001 17.5 c h 6 -0.5 m 6.276142 -0.5 6.5 -0.2761424 6.5 0 c 6.5 0.2761424 6.276142 0.5 6 0.5 c 5.723857 0.5 5.5 0.2761424 5.5 0 c 5.5 -0.2761424 5.723857 -0.5 6 -0.5 c h 6 2.500001 m 6.276142 2.500001 6.5 2.723858 6.5 3.000001 c 6.5 3.276143 6.276142 3.500001 6 3.500001 c 5.723857 3.500001 5.5 3.276143 5.5 3.000001 c 5.5 2.723858 5.723857 2.500001 6 2.500001 c h 6 5.5 m 6.276142 5.5 6.5 5.723857 6.5 6 c 6.5 6.276142 6.276142 6.5 6 6.5 c 5.723857 6.5 5.5 6.276142 5.5 6 c 5.5 5.723857 5.723857 5.5 6 5.5 c h 6 8.5 m 6.276142 8.5 6.5 8.723858 6.5 9 c 6.5 9.276142 6.276142 9.5 6 9.5 c 5.723857 9.5 5.5 9.276142 5.5 9 c 5.5 8.723858 5.723857 8.5 6 8.5 c h 6 11.5 m 6.276142 11.5 6.5 11.72386 6.5 12 c 6.5 12.27614 6.276142 12.5 6 12.5 c 5.723857 12.5 5.5 12.27614 5.5 12 c 5.5 11.72386 5.723857 11.5 6 11.5 c h 6 14.5 m 6.276142 14.5 6.5 14.72386 6.5 15 c 6.5 15.27614 6.276142 15.5 6 15.5 c 5.723857 15.5 5.5 15.27614 5.5 15 c 5.5 14.72386 5.723857 14.5 6 14.5 c h 6 17.5 m 6.276142 17.5 6.5 17.72386 6.5 18 c 6.5 18.27614 6.276142 18.5 6 18.5 c 5.723857 18.5 5.5 18.27614 5.5 18 c 5.5 17.72386 5.723857 17.5 6 17.5 c h 9 -0.5 m 9.276142 -0.5 9.5 -0.2761424 9.5 0 c 9.5 0.2761424 9.276142 0.5 9 0.5 c 8.723858 0.5 8.5 0.2761424 8.5 0 c 8.5 -0.2761424 8.723858 -0.5 9 -0.5 c h 9 2.500001 m 9.276142 2.500001 9.5 2.723858 9.5 3.000001 c 9.5 3.276143 9.276142 3.500001 9 3.500001 c 8.723858 3.500001 8.5 3.276143 8.5 3.000001 c 8.5 2.723858 8.723858 2.500001 9 2.500001 c h 9 5.5 m 9.276142 5.5 9.5 5.723857 9.5 6 c 9.5 6.276142 9.276142 6.5 9 6.5 c 8.723858 6.5 8.5 6.276142 8.5 6 c 8.5 5.723857 8.723858 5.5 9 5.5 c h 9 8.5 m 9.276142 8.5 9.5 8.723858 9.5 9 c 9.5 9.276142 9.276142 9.5 9 9.5 c 8.723858 9.5 8.5 9.276142 8.5 9 c 8.5 8.723858 8.723858 8.5 9 8.5 c h 9 11.5 m 9.276142 11.5 9.5 11.72386 9.5 12 c 9.5 12.27614 9.276142 12.5 9 12.5 c 8.723858 12.5 8.5 12.27614 8.5 12 c 8.5 11.72386 8.723858 11.5 9 11.5 c h 9 14.5 m 9.276142 14.5 9.5 14.72386 9.5 15 c 9.5 15.27614 9.276142 15.5 9 15.5 c 8.723858 15.5 8.5 15.27614 8.5 15 c 8.5 14.72386 8.723858 14.5 9 14.5 c h 9 17.5 m 9.276142 17.5 9.5 17.72386 9.5 18 c 9.5 18.27614 9.276142 18.5 9 18.5 c 8.723858 18.5 8.5 18.27614 8.5 18 c 8.5 17.72386 8.723858 17.5 9 17.5 c h 12 -0.5 m 12.27614 -0.5 12.5 -0.2761424 12.5 0 c 12.5 0.2761424 12.27614 0.5 12 0.5 c 11.72386 0.5 11.5 0.2761424 11.5 0 c 11.5 -0.2761424 11.72386 -0.5 12 -0.5 c h 12 2.500001 m 12.27614 2.500001 12.5 2.723858 12.5 3.000001 c 12.5 3.276143 12.27614 3.500001 12 3.500001 c 11.72386 3.500001 11.5 3.276143 11.5 3.000001 c 11.5 2.723858 11.72386 2.500001 12 2.500001 c h 12 5.5 m 12.27614 5.5 12.5 5.723857 12.5 6 c 12.5 6.276142 12.27614 6.5 12 6.5 c 11.72386 6.5 11.5 6.276142 11.5 6 c 11.5 5.723857 11.72386 5.5 12 5.5 c h 12 8.5 m 12.27614 8.5 12.5 8.723858 12.5 9 c 12.5 9.276142 12.27614 9.5 12 9.5 c 11.72386 9.5 11.5 9.276142 11.5 9 c 11.5 8.723858 11.72386 8.5 12 8.5 c h 12 11.5 m 12.27614 11.5 12.5 11.72386 12.5 12 c 12.5 12.27614 12.27614 12.5 12 12.5 c 11.72386 12.5 11.5 12.27614 11.5 12 c 11.5 11.72386 11.72386 11.5 12 11.5 c h 12 14.5 m 12.27614 14.5 12.5 14.72386 12.5 15 c 12.5 15.27614 12.27614 15.5 12 15.5 c 11.72386 15.5 11.5 15.27614 11.5 15 c 11.5 14.72386 11.72386 14.5 12 14.5 c h 12 17.5 m 12.27614 17.5 12.5 17.72386 12.5 18 c 12.5 18.27614 12.27614 18.5 12 18.5 c 11.72386 18.5 11.5 18.27614 11.5 18 c 11.5 17.72386 11.72386 17.5 12 17.5 c h 15 -0.5 m 15.27614 -0.5 15.5 -0.2761424 15.5 0 c 15.5 0.2761424 15.27614 0.5 15 0.5 c 14.72386 0.5 14.5 0.2761424 14.5 0 c 14.5 -0.2761424 14.72386 -0.5 15 -0.5 c h 15 2.500001 m 15.27614 2.500001 15.5 2.723858 15.5 3.000001 c 15.5 3.276143 15.27614 3.500001 15 3.500001 c 14.72386 3.500001 14.5 3.276143 14.5 3.000001 c 14.5 2.723858 14.72386 2.500001 15 2.500001 c h 15 5.5 m 15.27614 5.5 15.5 5.723857 15.5 6 c 15.5 6.276142 15.27614 6.5 15 6.5 c 14.72386 6.5 14.5 6.276142 14.5 6 c 14.5 5.723857 14.72386 5.5 15 5.5 c h 15 8.5 m 15.27614 8.5 15.5 8.723858 15.5 9 c 15.5 9.276142 15.27614 9.5 15 9.5 c 14.72386 9.5 14.5 9.276142 14.5 9 c 14.5 8.723858 14.72386 8.5 15 8.5 c h 15 11.5 m 15.27614 11.5 15.5 11.72386 15.5 12 c 15.5 12.27614 15.27614 12.5 15 12.5 c 14.72386 12.5 14.5 12.27614 14.5 12 c 14.5 11.72386 14.72386 11.5 15 11.5 c h 15 14.5 m 15.27614 14.5 15.5 14.72386 15.5 15 c 15.5 15.27614 15.27614 15.5 15 15.5 c 14.72386 15.5 14.5 15.27614 14.5 15 c 14.5 14.72386 14.72386 14.5 15 14.5 c h 15 17.5 m 15.27614 17.5 15.5 17.72386 15.5 18 c 15.5 18.27614 15.27614 18.5 15 18.5 c 14.72386 18.5 14.5 18.27614 14.5 18 c 14.5 17.72386 14.72386 17.5 15 17.5 c h 18 -0.5 m 18.27614 -0.5 18.5 -0.2761424 18.5 0 c 18.5 0.2761424 18.27614 0.5 18 0.5 c 17.72386 0.5 17.5 0.2761424 17.5 0 c 17.5 -0.2761424 17.72386 -0.5 18 -0.5 c h 18 2.500001 m 18.27614 2.500001 18.5 2.723858 18.5 3.000001 c 18.5 3.276143 18.27614 3.500001 18 3.500001 c 17.72386 3.500001 17.5 3.276143 17.5 3.000001 c 17.5 2.723858 17.72386 2.500001 18 2.500001 c h 18 5.5 m 18.27614 5.5 18.5 5.723857 18.5 6 c 18.5 6.276142 18.27614 6.5 18 6.5 c 17.72386 6.5 17.5 6.276142 17.5 6 c 17.5 5.723857 17.72386 5.5 18 5.5 c h 18 8.5 m 18.27614 8.5 18.5 8.723858 18.5 9 c 18.5 9.276142 18.27614 9.5 18 9.5 c 17.72386 9.5 17.5 9.276142 17.5 9 c 17.5 8.723858 17.72386 8.5 18 8.5 c h 18 11.5 m 18.27614 11.5 18.5 11.72386 18.5 12 c 18.5 12.27614 18.27614 12.5 18 12.5 c 17.72386 12.5 17.5 12.27614 17.5 12 c 17.5 11.72386 17.72386 11.5 18 11.5 c h 18 14.5 m 18.27614 14.5 18.5 14.72386 18.5 15 c 18.5 15.27614 18.27614 15.5 18 15.5 c 17.72386 15.5 17.5 15.27614 17.5 15 c 17.5 14.72386 17.72386 14.5 18 14.5 c h 18 17.5 m 18.27614 17.5 18.5 17.72386 18.5 18 c 18.5 18.27614 18.27614 18.5 18 18.5 c 17.72386 18.5 17.5 18.27614 17.5 18 c 17.5 17.72386 17.72386 17.5 18 17.5 c h 1.5 1 m 1.776143 1 2 1.223858 2 1.5 c 2 1.776143 1.776143 2 1.5 2 c 1.223858 2 1 1.776143 1 1.5 c 1 1.223858 1.223858 1 1.5 1 c h 1.5 4 m 1.776143 4 2 4.223857 2 4.5 c 2 4.776143 1.776143 5 1.5 5 c 1.223858 5 1 4.776143 1 4.5 c 1 4.223857 1.223858 4 1.5 4 c h 1.5 7 m 1.776143 7 2 7.223858 2 7.5 c 2 7.776143 1.776143 8 1.5 8 c 1.223858 8 1 7.776143 1 7.5 c 1 7.223858 1.223858 7 1.5 7 c h 1.5 10 m 1.776143 10 2 10.22386 2 10.5 c 2 10.77614 1.776143 11 1.5 11 c 1.223858 11 1 10.77614 1 10.5 c 1 10.22386 1.223858 10 1.5 10 c h 1.5 13 m 1.776143 13 2 13.22386 2 13.5 c 2 13.77614 1.776143 14 1.5 14 c 1.223858 14 1 13.77614 1 13.5 c 1 13.22386 1.223858 13 1.5 13 c h 1.5 16 m 1.776143 16 2 16.22386 2 16.5 c 2 16.77614 1.776143 17 1.5 17 c 1.223858 17 1 16.77614 1 16.5 c 1 16.22386 1.223858 16 1.5 16 c h 4.5 1 m 4.776143 1 5 1.223858 5 1.5 c 5 1.776143 4.776143 2 4.5 2 c 4.223857 2 4 1.776143 4 1.5 c 4 1.223858 4.223857 1 4.5 1 c h 4.5 4 m 4.776143 4 5 4.223857 5 4.5 c 5 4.776143 4.776143 5 4.5 5 c 4.223857 5 4 4.776143 4 4.5 c 4 4.223857 4.223857 4 4.5 4 c h 4.5 7 m 4.776143 7 5 7.223858 5 7.5 c 5 7.776143 4.776143 8 4.5 8 c 4.223857 8 4 7.776143 4 7.5 c 4 7.223858 4.223857 7 4.5 7 c h 4.5 10 m 4.776143 10 5 10.22386 5 10.5 c 5 10.77614 4.776143 11 4.5 11 c 4.223857 11 4 10.77614 4 10.5 c 4 10.22386 4.223857 10 4.5 10 c h 4.5 13 m 4.776143 13 5 13.22386 5 13.5 c 5 13.77614 4.776143 14 4.5 14 c 4.223857 14 4 13.77614 4 13.5 c 4 13.22386 4.223857 13 4.5 13 c h 4.5 16 m 4.776143 16 5 16.22386 5 16.5 c 5 16.77614 4.776143 17 4.5 17 c 4.223857 17 4 16.77614 4 16.5 c 4 16.22386 4.223857 16 4.5 16 c h 7.5 1 m 7.776143 1 8 1.223858 8 1.5 c 8 1.776143 7.776143 2 7.5 2 c 7.223858 2 7 1.776143 7 1.5 c 7 1.223858 7.223858 1 7.5 1 c h 7.5 4 m 7.776143 4 8 4.223857 8 4.5 c 8 4.776143 7.776143 5 7.5 5 c 7.223858 5 7 4.776143 7 4.5 c 7 4.223857 7.223858 4 7.5 4 c h 7.5 7 m 7.776143 7 8 7.223858 8 7.5 c 8 7.776143 7.776143 8 7.5 8 c 7.223858 8 7 7.776143 7 7.5 c 7 7.223858 7.223858 7 7.5 7 c h 7.5 10 m 7.776143 10 8 10.22386 8 10.5 c 8 10.77614 7.776143 11 7.5 11 c 7.223858 11 7 10.77614 7 10.5 c 7 10.22386 7.223858 10 7.5 10 c h 7.5 13 m 7.776143 13 8 13.22386 8 13.5 c 8 13.77614 7.776143 14 7.5 14 c 7.223858 14 7 13.77614 7 13.5 c 7 13.22386 7.223858 13 7.5 13 c h 7.5 16 m 7.776143 16 8 16.22386 8 16.5 c 8 16.77614 7.776143 17 7.5 17 c 7.223858 17 7 16.77614 7 16.5 c 7 16.22386 7.223858 16 7.5 16 c h 10.5 1 m 10.77614 1 11 1.223858 11 1.5 c 11 1.776143 10.77614 2 10.5 2 c 10.22386 2 10 1.776143 10 1.5 c 10 1.223858 10.22386 1 10.5 1 c h 10.5 4 m 10.77614 4 11 4.223857 11 4.5 c 11 4.776143 10.77614 5 10.5 5 c 10.22386 5 10 4.776143 10 4.5 c 10 4.223857 10.22386 4 10.5 4 c h 10.5 7 m 10.77614 7 11 7.223858 11 7.5 c 11 7.776143 10.77614 8 10.5 8 c 10.22386 8 10 7.776143 10 7.5 c 10 7.223858 10.22386 7 10.5 7 c h 10.5 10 m 10.77614 10 11 10.22386 11 10.5 c 11 10.77614 10.77614 11 10.5 11 c 10.22386 11 10 10.77614 10 10.5 c 10 10.22386 10.22386 10 10.5 10 c h 10.5 13 m 10.77614 13 11 13.22386 11 13.5 c 11 13.77614 10.77614 14 10.5 14 c 10.22386 14 10 13.77614 10 13.5 c 10 13.22386 10.22386 13 10.5 13 c h 10.5 16 m 10.77614 16 11 16.22386 11 16.5 c 11 16.77614 10.77614 17 10.5 17 c 10.22386 17 10 16.77614 10 16.5 c 10 16.22386 10.22386 16 10.5 16 c h 13.5 1 m 13.77614 1 14 1.223858 14 1.5 c 14 1.776143 13.77614 2 13.5 2 c 13.22386 2 13 1.776143 13 1.5 c 13 1.223858 13.22386 1 13.5 1 c h 13.5 4 m 13.77614 4 14 4.223857 14 4.5 c 14 4.776143 13.77614 5 13.5 5 c 13.22386 5 13 4.776143 13 4.5 c 13 4.223857 13.22386 4 13.5 4 c h 13.5 7 m 13.77614 7 14 7.223858 14 7.5 c 14 7.776143 13.77614 8 13.5 8 c 13.22386 8 13 7.776143 13 7.5 c 13 7.223858 13.22386 7 13.5 7 c h 13.5 10 m 13.77614 10 14 10.22386 14 10.5 c 14 10.77614 13.77614 11 13.5 11 c 13.22386 11 13 10.77614 13 10.5 c 13 10.22386 13.22386 10 13.5 10 c h 13.5 13 m 13.77614 13 14 13.22386 14 13.5 c 14 13.77614 13.77614 14 13.5 14 c 13.22386 14 13 13.77614 13 13.5 c 13 13.22386 13.22386 13 13.5 13 c h 13.5 16 m 13.77614 16 14 16.22386 14 16.5 c 14 16.77614 13.77614 17 13.5 17 c 13.22386 17 13 16.77614 13 16.5 c 13 16.22386 13.22386 16 13.5 16 c h 16.5 1 m 16.77614 1 17 1.223858 17 1.5 c 17 1.776143 16.77614 2 16.5 2 c 16.22386 2 16 1.776143 16 1.5 c 16 1.223858 16.22386 1 16.5 1 c h 16.5 4 m 16.77614 4 17 4.223857 17 4.5 c 17 4.776143 16.77614 5 16.5 5 c 16.22386 5 16 4.776143 16 4.5 c 16 4.223857 16.22386 4 16.5 4 c h 16.5 7 m 16.77614 7 17 7.223858 17 7.5 c 17 7.776143 16.77614 8 16.5 8 c 16.22386 8 16 7.776143 16 7.5 c 16 7.223858 16.22386 7 16.5 7 c h 16.5 10 m 16.77614 10 17 10.22386 17 10.5 c 17 10.77614 16.77614 11 16.5 11 c 16.22386 11 16 10.77614 16 10.5 c 16 10.22386 16.22386 10 16.5 10 c h 16.5 13 m 16.77614 13 17 13.22386 17 13.5 c 17 13.77614 16.77614 14 16.5 14 c 16.22386 14 16 13.77614 16 13.5 c 16 13.22386 16.22386 13 16.5 13 c h 16.5 16 m 16.77614 16 17 16.22386 17 16.5 c 17 16.77614 16.77614 17 16.5 17 c 16.22386 17 16 16.77614 16 16.5 c 16 16.22386 16.22386 16 16.5 16 c h + {strokecolor} rg f + endstream''' +, +'30Dots':'''<> + /Matrix[1 0 0 1 0 0]/BBox[0 0 18 18]/XStep 18/YStep 18>>\nstream\n + {fillcolor} rg 0 0 18 18 re f {strokecolor} RG + 1 w 0 -0.5 m 0.2761424 -0.5 0.5 -0.2761424 0.5 0 c 0.5 0.2761424 0.2761424 0.5 0 0.5 c -0.2761424 0.5 -0.5 0.2761424 -0.5 0 c -0.5 -0.2761424 -0.2761424 -0.5 0 -0.5 c h 0 2.071428 m 0.2761424 2.071428 0.5 2.295285 0.5 2.571428 c 0.5 2.84757 0.2761424 3.071428 0 3.071428 c -0.2761424 3.071428 -0.5 2.84757 -0.5 2.571428 c -0.5 2.295285 -0.2761424 2.071428 0 2.071428 c h 0 4.642858 m 0.2761424 4.642858 0.5 4.866715 0.5 5.142858 c 0.5 5.419 0.2761424 5.642858 0 5.642858 c -0.2761424 5.642858 -0.5 5.419 -0.5 5.142858 c -0.5 4.866715 -0.2761424 4.642858 0 4.642858 c h 0 7.214287 m 0.2761424 7.214287 0.5 7.438144 0.5 7.714287 c 0.5 7.990429 0.2761424 8.214287 0 8.214287 c -0.2761424 8.214287 -0.5 7.990429 -0.5 7.714287 c -0.5 7.438144 -0.2761424 7.214287 0 7.214287 c h 0 9.785715 m 0.2761424 9.785715 0.5 10.00957 0.5 10.28572 c 0.5 10.56186 0.2761424 10.78572 0 10.78572 c -0.2761424 10.78572 -0.5 10.56186 -0.5 10.28572 c -0.5 10.00957 -0.2761424 9.785715 0 9.785715 c h 0 12.35714 m 0.2761424 12.35714 0.5 12.581 0.5 12.85714 c 0.5 13.13328 0.2761424 13.35714 0 13.35714 c -0.2761424 13.35714 -0.5 13.13328 -0.5 12.85714 c -0.5 12.581 -0.2761424 12.35714 0 12.35714 c h 0 14.92857 m 0.2761424 14.92857 0.5 15.15243 0.5 15.42857 c 0.5 15.70471 0.2761424 15.92857 0 15.92857 c -0.2761424 15.92857 -0.5 15.70471 -0.5 15.42857 c -0.5 15.15243 -0.2761424 14.92857 0 14.92857 c h 0 17.5 m 0.2761424 17.5 0.5 17.72386 0.5 18 c 0.5 18.27614 0.2761424 18.5 0 18.5 c -0.2761424 18.5 -0.5 18.27614 -0.5 18 c -0.5 17.72386 -0.2761424 17.5 0 17.5 c h 2.571428 -0.5 m 2.84757 -0.5 3.071428 -0.2761424 3.071428 0 c 3.071428 0.2761424 2.84757 0.5 2.571428 0.5 c 2.295285 0.5 2.071428 0.2761424 2.071428 0 c 2.071428 -0.2761424 2.295285 -0.5 2.571428 -0.5 c h 2.571428 2.071428 m 2.84757 2.071428 3.071428 2.295285 3.071428 2.571428 c 3.071428 2.84757 2.84757 3.071428 2.571428 3.071428 c 2.295285 3.071428 2.071428 2.84757 2.071428 2.571428 c 2.071428 2.295285 2.295285 2.071428 2.571428 2.071428 c h 2.571428 4.642858 m 2.84757 4.642858 3.071428 4.866715 3.071428 5.142858 c 3.071428 5.419 2.84757 5.642858 2.571428 5.642858 c 2.295285 5.642858 2.071428 5.419 2.071428 5.142858 c 2.071428 4.866715 2.295285 4.642858 2.571428 4.642858 c h 2.571428 7.214287 m 2.84757 7.214287 3.071428 7.438144 3.071428 7.714287 c 3.071428 7.990429 2.84757 8.214287 2.571428 8.214287 c 2.295285 8.214287 2.071428 7.990429 2.071428 7.714287 c 2.071428 7.438144 2.295285 7.214287 2.571428 7.214287 c h 2.571428 9.785715 m 2.84757 9.785715 3.071428 10.00957 3.071428 10.28572 c 3.071428 10.56186 2.84757 10.78572 2.571428 10.78572 c 2.295285 10.78572 2.071428 10.56186 2.071428 10.28572 c 2.071428 10.00957 2.295285 9.785715 2.571428 9.785715 c h 2.571428 12.35714 m 2.84757 12.35714 3.071428 12.581 3.071428 12.85714 c 3.071428 13.13328 2.84757 13.35714 2.571428 13.35714 c 2.295285 13.35714 2.071428 13.13328 2.071428 12.85714 c 2.071428 12.581 2.295285 12.35714 2.571428 12.35714 c h 2.571428 14.92857 m 2.84757 14.92857 3.071428 15.15243 3.071428 15.42857 c 3.071428 15.70471 2.84757 15.92857 2.571428 15.92857 c 2.295285 15.92857 2.071428 15.70471 2.071428 15.42857 c 2.071428 15.15243 2.295285 14.92857 2.571428 14.92857 c h 2.571428 17.5 m 2.84757 17.5 3.071428 17.72386 3.071428 18 c 3.071428 18.27614 2.84757 18.5 2.571428 18.5 c 2.295285 18.5 2.071428 18.27614 2.071428 18 c 2.071428 17.72386 2.295285 17.5 2.571428 17.5 c h 5.142858 -0.5 m 5.419 -0.5 5.642858 -0.2761424 5.642858 0 c 5.642858 0.2761424 5.419 0.5 5.142858 0.5 c 4.866715 0.5 4.642858 0.2761424 4.642858 0 c 4.642858 -0.2761424 4.866715 -0.5 5.142858 -0.5 c h 5.142858 2.071428 m 5.419 2.071428 5.642858 2.295285 5.642858 2.571428 c 5.642858 2.84757 5.419 3.071428 5.142858 3.071428 c 4.866715 3.071428 4.642858 2.84757 4.642858 2.571428 c 4.642858 2.295285 4.866715 2.071428 5.142858 2.071428 c h 5.142858 4.642858 m 5.419 4.642858 5.642858 4.866715 5.642858 5.142858 c 5.642858 5.419 5.419 5.642858 5.142858 5.642858 c 4.866715 5.642858 4.642858 5.419 4.642858 5.142858 c 4.642858 4.866715 4.866715 4.642858 5.142858 4.642858 c h 5.142858 7.214287 m 5.419 7.214287 5.642858 7.438144 5.642858 7.714287 c 5.642858 7.990429 5.419 8.214287 5.142858 8.214287 c 4.866715 8.214287 4.642858 7.990429 4.642858 7.714287 c 4.642858 7.438144 4.866715 7.214287 5.142858 7.214287 c h 5.142858 9.785715 m 5.419 9.785715 5.642858 10.00957 5.642858 10.28572 c 5.642858 10.56186 5.419 10.78572 5.142858 10.78572 c 4.866715 10.78572 4.642858 10.56186 4.642858 10.28572 c 4.642858 10.00957 4.866715 9.785715 5.142858 9.785715 c h 5.142858 12.35714 m 5.419 12.35714 5.642858 12.581 5.642858 12.85714 c 5.642858 13.13328 5.419 13.35714 5.142858 13.35714 c 4.866715 13.35714 4.642858 13.13328 4.642858 12.85714 c 4.642858 12.581 4.866715 12.35714 5.142858 12.35714 c h 5.142858 14.92857 m 5.419 14.92857 5.642858 15.15243 5.642858 15.42857 c 5.642858 15.70471 5.419 15.92857 5.142858 15.92857 c 4.866715 15.92857 4.642858 15.70471 4.642858 15.42857 c 4.642858 15.15243 4.866715 14.92857 5.142858 14.92857 c h 5.142858 17.5 m 5.419 17.5 5.642858 17.72386 5.642858 18 c 5.642858 18.27614 5.419 18.5 5.142858 18.5 c 4.866715 18.5 4.642858 18.27614 4.642858 18 c 4.642858 17.72386 4.866715 17.5 5.142858 17.5 c h 7.714287 -0.5 m 7.990429 -0.5 8.214287 -0.2761424 8.214287 0 c 8.214287 0.2761424 7.990429 0.5 7.714287 0.5 c 7.438144 0.5 7.214287 0.2761424 7.214287 0 c 7.214287 -0.2761424 7.438144 -0.5 7.714287 -0.5 c h 7.714287 2.071428 m 7.990429 2.071428 8.214287 2.295285 8.214287 2.571428 c 8.214287 2.84757 7.990429 3.071428 7.714287 3.071428 c 7.438144 3.071428 7.214287 2.84757 7.214287 2.571428 c 7.214287 2.295285 7.438144 2.071428 7.714287 2.071428 c h 7.714287 4.642858 m 7.990429 4.642858 8.214287 4.866715 8.214287 5.142858 c 8.214287 5.419 7.990429 5.642858 7.714287 5.642858 c 7.438144 5.642858 7.214287 5.419 7.214287 5.142858 c 7.214287 4.866715 7.438144 4.642858 7.714287 4.642858 c h 7.714287 7.214287 m 7.990429 7.214287 8.214287 7.438144 8.214287 7.714287 c 8.214287 7.990429 7.990429 8.214287 7.714287 8.214287 c 7.438144 8.214287 7.214287 7.990429 7.214287 7.714287 c 7.214287 7.438144 7.438144 7.214287 7.714287 7.214287 c h 7.714287 9.785715 m 7.990429 9.785715 8.214287 10.00957 8.214287 10.28572 c 8.214287 10.56186 7.990429 10.78572 7.714287 10.78572 c 7.438144 10.78572 7.214287 10.56186 7.214287 10.28572 c 7.214287 10.00957 7.438144 9.785715 7.714287 9.785715 c h 7.714287 12.35714 m 7.990429 12.35714 8.214287 12.581 8.214287 12.85714 c 8.214287 13.13328 7.990429 13.35714 7.714287 13.35714 c 7.438144 13.35714 7.214287 13.13328 7.214287 12.85714 c 7.214287 12.581 7.438144 12.35714 7.714287 12.35714 c h 7.714287 14.92857 m 7.990429 14.92857 8.214287 15.15243 8.214287 15.42857 c 8.214287 15.70471 7.990429 15.92857 7.714287 15.92857 c 7.438144 15.92857 7.214287 15.70471 7.214287 15.42857 c 7.214287 15.15243 7.438144 14.92857 7.714287 14.92857 c h 7.714287 17.5 m 7.990429 17.5 8.214287 17.72386 8.214287 18 c 8.214287 18.27614 7.990429 18.5 7.714287 18.5 c 7.438144 18.5 7.214287 18.27614 7.214287 18 c 7.214287 17.72386 7.438144 17.5 7.714287 17.5 c h 10.28572 -0.5 m 10.56186 -0.5 10.78572 -0.2761424 10.78572 0 c 10.78572 0.2761424 10.56186 0.5 10.28572 0.5 c 10.00957 0.5 9.785715 0.2761424 9.785715 0 c 9.785715 -0.2761424 10.00957 -0.5 10.28572 -0.5 c h 10.28572 2.071428 m 10.56186 2.071428 10.78572 2.295285 10.78572 2.571428 c 10.78572 2.84757 10.56186 3.071428 10.28572 3.071428 c 10.00957 3.071428 9.785715 2.84757 9.785715 2.571428 c 9.785715 2.295285 10.00957 2.071428 10.28572 2.071428 c h 10.28572 4.642858 m 10.56186 4.642858 10.78572 4.866715 10.78572 5.142858 c 10.78572 5.419 10.56186 5.642858 10.28572 5.642858 c 10.00957 5.642858 9.785715 5.419 9.785715 5.142858 c 9.785715 4.866715 10.00957 4.642858 10.28572 4.642858 c h 10.28572 7.214287 m 10.56186 7.214287 10.78572 7.438144 10.78572 7.714287 c 10.78572 7.990429 10.56186 8.214287 10.28572 8.214287 c 10.00957 8.214287 9.785715 7.990429 9.785715 7.714287 c 9.785715 7.438144 10.00957 7.214287 10.28572 7.214287 c h 10.28572 9.785715 m 10.56186 9.785715 10.78572 10.00957 10.78572 10.28572 c 10.78572 10.56186 10.56186 10.78572 10.28572 10.78572 c 10.00957 10.78572 9.785715 10.56186 9.785715 10.28572 c 9.785715 10.00957 10.00957 9.785715 10.28572 9.785715 c h 10.28572 12.35714 m 10.56186 12.35714 10.78572 12.581 10.78572 12.85714 c 10.78572 13.13328 10.56186 13.35714 10.28572 13.35714 c 10.00957 13.35714 9.785715 13.13328 9.785715 12.85714 c 9.785715 12.581 10.00957 12.35714 10.28572 12.35714 c h 10.28572 14.92857 m 10.56186 14.92857 10.78572 15.15243 10.78572 15.42857 c 10.78572 15.70471 10.56186 15.92857 10.28572 15.92857 c 10.00957 15.92857 9.785715 15.70471 9.785715 15.42857 c 9.785715 15.15243 10.00957 14.92857 10.28572 14.92857 c h 10.28572 17.5 m 10.56186 17.5 10.78572 17.72386 10.78572 18 c 10.78572 18.27614 10.56186 18.5 10.28572 18.5 c 10.00957 18.5 9.785715 18.27614 9.785715 18 c 9.785715 17.72386 10.00957 17.5 10.28572 17.5 c h 12.85714 -0.5 m 13.13328 -0.5 13.35714 -0.2761424 13.35714 0 c 13.35714 0.2761424 13.13328 0.5 12.85714 0.5 c 12.581 0.5 12.35714 0.2761424 12.35714 0 c 12.35714 -0.2761424 12.581 -0.5 12.85714 -0.5 c h 12.85714 2.071428 m 13.13328 2.071428 13.35714 2.295285 13.35714 2.571428 c 13.35714 2.84757 13.13328 3.071428 12.85714 3.071428 c 12.581 3.071428 12.35714 2.84757 12.35714 2.571428 c 12.35714 2.295285 12.581 2.071428 12.85714 2.071428 c h 12.85714 4.642858 m 13.13328 4.642858 13.35714 4.866715 13.35714 5.142858 c 13.35714 5.419 13.13328 5.642858 12.85714 5.642858 c 12.581 5.642858 12.35714 5.419 12.35714 5.142858 c 12.35714 4.866715 12.581 4.642858 12.85714 4.642858 c h 12.85714 7.214287 m 13.13328 7.214287 13.35714 7.438144 13.35714 7.714287 c 13.35714 7.990429 13.13328 8.214287 12.85714 8.214287 c 12.581 8.214287 12.35714 7.990429 12.35714 7.714287 c 12.35714 7.438144 12.581 7.214287 12.85714 7.214287 c h 12.85714 9.785715 m 13.13328 9.785715 13.35714 10.00957 13.35714 10.28572 c 13.35714 10.56186 13.13328 10.78572 12.85714 10.78572 c 12.581 10.78572 12.35714 10.56186 12.35714 10.28572 c 12.35714 10.00957 12.581 9.785715 12.85714 9.785715 c h 12.85714 12.35714 m 13.13328 12.35714 13.35714 12.581 13.35714 12.85714 c 13.35714 13.13328 13.13328 13.35714 12.85714 13.35714 c 12.581 13.35714 12.35714 13.13328 12.35714 12.85714 c 12.35714 12.581 12.581 12.35714 12.85714 12.35714 c h 12.85714 14.92857 m 13.13328 14.92857 13.35714 15.15243 13.35714 15.42857 c 13.35714 15.70471 13.13328 15.92857 12.85714 15.92857 c 12.581 15.92857 12.35714 15.70471 12.35714 15.42857 c 12.35714 15.15243 12.581 14.92857 12.85714 14.92857 c h 12.85714 17.5 m 13.13328 17.5 13.35714 17.72386 13.35714 18 c 13.35714 18.27614 13.13328 18.5 12.85714 18.5 c 12.581 18.5 12.35714 18.27614 12.35714 18 c 12.35714 17.72386 12.581 17.5 12.85714 17.5 c h 15.42857 -0.5 m 15.70471 -0.5 15.92857 -0.2761424 15.92857 0 c 15.92857 0.2761424 15.70471 0.5 15.42857 0.5 c 15.15243 0.5 14.92857 0.2761424 14.92857 0 c 14.92857 -0.2761424 15.15243 -0.5 15.42857 -0.5 c h 15.42857 2.071428 m 15.70471 2.071428 15.92857 2.295285 15.92857 2.571428 c 15.92857 2.84757 15.70471 3.071428 15.42857 3.071428 c 15.15243 3.071428 14.92857 2.84757 14.92857 2.571428 c 14.92857 2.295285 15.15243 2.071428 15.42857 2.071428 c h 15.42857 4.642858 m 15.70471 4.642858 15.92857 4.866715 15.92857 5.142858 c 15.92857 5.419 15.70471 5.642858 15.42857 5.642858 c 15.15243 5.642858 14.92857 5.419 14.92857 5.142858 c 14.92857 4.866715 15.15243 4.642858 15.42857 4.642858 c h 15.42857 7.214287 m 15.70471 7.214287 15.92857 7.438144 15.92857 7.714287 c 15.92857 7.990429 15.70471 8.214287 15.42857 8.214287 c 15.15243 8.214287 14.92857 7.990429 14.92857 7.714287 c 14.92857 7.438144 15.15243 7.214287 15.42857 7.214287 c h 15.42857 9.785715 m 15.70471 9.785715 15.92857 10.00957 15.92857 10.28572 c 15.92857 10.56186 15.70471 10.78572 15.42857 10.78572 c 15.15243 10.78572 14.92857 10.56186 14.92857 10.28572 c 14.92857 10.00957 15.15243 9.785715 15.42857 9.785715 c h 15.42857 12.35714 m 15.70471 12.35714 15.92857 12.581 15.92857 12.85714 c 15.92857 13.13328 15.70471 13.35714 15.42857 13.35714 c 15.15243 13.35714 14.92857 13.13328 14.92857 12.85714 c 14.92857 12.581 15.15243 12.35714 15.42857 12.35714 c h 15.42857 14.92857 m 15.70471 14.92857 15.92857 15.15243 15.92857 15.42857 c 15.92857 15.70471 15.70471 15.92857 15.42857 15.92857 c 15.15243 15.92857 14.92857 15.70471 14.92857 15.42857 c 14.92857 15.15243 15.15243 14.92857 15.42857 14.92857 c h 15.42857 17.5 m 15.70471 17.5 15.92857 17.72386 15.92857 18 c 15.92857 18.27614 15.70471 18.5 15.42857 18.5 c 15.15243 18.5 14.92857 18.27614 14.92857 18 c 14.92857 17.72386 15.15243 17.5 15.42857 17.5 c h 18 -0.5 m 18.27614 -0.5 18.5 -0.2761424 18.5 0 c 18.5 0.2761424 18.27614 0.5 18 0.5 c 17.72386 0.5 17.5 0.2761424 17.5 0 c 17.5 -0.2761424 17.72386 -0.5 18 -0.5 c h 18 2.071428 m 18.27614 2.071428 18.5 2.295285 18.5 2.571428 c 18.5 2.84757 18.27614 3.071428 18 3.071428 c 17.72386 3.071428 17.5 2.84757 17.5 2.571428 c 17.5 2.295285 17.72386 2.071428 18 2.071428 c h 18 4.642858 m 18.27614 4.642858 18.5 4.866715 18.5 5.142858 c 18.5 5.419 18.27614 5.642858 18 5.642858 c 17.72386 5.642858 17.5 5.419 17.5 5.142858 c 17.5 4.866715 17.72386 4.642858 18 4.642858 c h 18 7.214287 m 18.27614 7.214287 18.5 7.438144 18.5 7.714287 c 18.5 7.990429 18.27614 8.214287 18 8.214287 c 17.72386 8.214287 17.5 7.990429 17.5 7.714287 c 17.5 7.438144 17.72386 7.214287 18 7.214287 c h 18 9.785715 m 18.27614 9.785715 18.5 10.00957 18.5 10.28572 c 18.5 10.56186 18.27614 10.78572 18 10.78572 c 17.72386 10.78572 17.5 10.56186 17.5 10.28572 c 17.5 10.00957 17.72386 9.785715 18 9.785715 c h 18 12.35714 m 18.27614 12.35714 18.5 12.581 18.5 12.85714 c 18.5 13.13328 18.27614 13.35714 18 13.35714 c 17.72386 13.35714 17.5 13.13328 17.5 12.85714 c 17.5 12.581 17.72386 12.35714 18 12.35714 c h 18 14.92857 m 18.27614 14.92857 18.5 15.15243 18.5 15.42857 c 18.5 15.70471 18.27614 15.92857 18 15.92857 c 17.72386 15.92857 17.5 15.70471 17.5 15.42857 c 17.5 15.15243 17.72386 14.92857 18 14.92857 c h 18 17.5 m 18.27614 17.5 18.5 17.72386 18.5 18 c 18.5 18.27614 18.27614 18.5 18 18.5 c 17.72386 18.5 17.5 18.27614 17.5 18 c 17.5 17.72386 17.72386 17.5 18 17.5 c h 1.285714 0.7857141 m 1.561857 0.7857141 1.785714 1.009572 1.785714 1.285714 c 1.785714 1.561857 1.561857 1.785714 1.285714 1.785714 c 1.009572 1.785714 0.7857141 1.561857 0.7857141 1.285714 c 0.7857141 1.009572 1.009572 0.7857141 1.285714 0.7857141 c h 1.285714 3.357143 m 1.561857 3.357143 1.785714 3.581 1.785714 3.857143 c 1.785714 4.133285 1.561857 4.357142 1.285714 4.357142 c 1.009572 4.357142 0.7857141 4.133285 0.7857141 3.857143 c 0.7857141 3.581 1.009572 3.357143 1.285714 3.357143 c h 1.285714 5.928572 m 1.561857 5.928572 1.785714 6.15243 1.785714 6.428572 c 1.785714 6.704715 1.561857 6.928572 1.285714 6.928572 c 1.009572 6.928572 0.7857141 6.704715 0.7857141 6.428572 c 0.7857141 6.15243 1.009572 5.928572 1.285714 5.928572 c h 1.285714 8.5 m 1.561857 8.5 1.785714 8.723858 1.785714 9 c 1.785714 9.276142 1.561857 9.5 1.285714 9.5 c 1.009572 9.5 0.7857141 9.276142 0.7857141 9 c 0.7857141 8.723858 1.009572 8.5 1.285714 8.5 c h 1.285714 11.07143 m 1.561857 11.07143 1.785714 11.29529 1.785714 11.57143 c 1.785714 11.84757 1.561857 12.07143 1.285714 12.07143 c 1.009572 12.07143 0.7857141 11.84757 0.7857141 11.57143 c 0.7857141 11.29529 1.009572 11.07143 1.285714 11.07143 c h 1.285714 13.64286 m 1.561857 13.64286 1.785714 13.86672 1.785714 14.14286 c 1.785714 14.419 1.561857 14.64286 1.285714 14.64286 c 1.009572 14.64286 0.7857141 14.419 0.7857141 14.14286 c 0.7857141 13.86672 1.009572 13.64286 1.285714 13.64286 c h 1.285714 16.21429 m 1.561857 16.21429 1.785714 16.43814 1.785714 16.71429 c 1.785714 16.99043 1.561857 17.21429 1.285714 17.21429 c 1.009572 17.21429 0.7857141 16.99043 0.7857141 16.71429 c 0.7857141 16.43814 1.009572 16.21429 1.285714 16.21429 c h 3.857143 0.7857141 m 4.133285 0.7857141 4.357142 1.009572 4.357142 1.285714 c 4.357142 1.561857 4.133285 1.785714 3.857143 1.785714 c 3.581 1.785714 3.357143 1.561857 3.357143 1.285714 c 3.357143 1.009572 3.581 0.7857141 3.857143 0.7857141 c h 3.857143 3.357143 m 4.133285 3.357143 4.357142 3.581 4.357142 3.857143 c 4.357142 4.133285 4.133285 4.357142 3.857143 4.357142 c 3.581 4.357142 3.357143 4.133285 3.357143 3.857143 c 3.357143 3.581 3.581 3.357143 3.857143 3.357143 c h 3.857143 5.928572 m 4.133285 5.928572 4.357142 6.15243 4.357142 6.428572 c 4.357142 6.704715 4.133285 6.928572 3.857143 6.928572 c 3.581 6.928572 3.357143 6.704715 3.357143 6.428572 c 3.357143 6.15243 3.581 5.928572 3.857143 5.928572 c h 3.857143 8.5 m 4.133285 8.5 4.357142 8.723858 4.357142 9 c 4.357142 9.276142 4.133285 9.5 3.857143 9.5 c 3.581 9.5 3.357143 9.276142 3.357143 9 c 3.357143 8.723858 3.581 8.5 3.857143 8.5 c h 3.857143 11.07143 m 4.133285 11.07143 4.357142 11.29529 4.357142 11.57143 c 4.357142 11.84757 4.133285 12.07143 3.857143 12.07143 c 3.581 12.07143 3.357143 11.84757 3.357143 11.57143 c 3.357143 11.29529 3.581 11.07143 3.857143 11.07143 c h 3.857143 13.64286 m 4.133285 13.64286 4.357142 13.86672 4.357142 14.14286 c 4.357142 14.419 4.133285 14.64286 3.857143 14.64286 c 3.581 14.64286 3.357143 14.419 3.357143 14.14286 c 3.357143 13.86672 3.581 13.64286 3.857143 13.64286 c h 3.857143 16.21429 m 4.133285 16.21429 4.357142 16.43814 4.357142 16.71429 c 4.357142 16.99043 4.133285 17.21429 3.857143 17.21429 c 3.581 17.21429 3.357143 16.99043 3.357143 16.71429 c 3.357143 16.43814 3.581 16.21429 3.857143 16.21429 c h 6.428572 0.7857141 m 6.704715 0.7857141 6.928572 1.009572 6.928572 1.285714 c 6.928572 1.561857 6.704715 1.785714 6.428572 1.785714 c 6.15243 1.785714 5.928572 1.561857 5.928572 1.285714 c 5.928572 1.009572 6.15243 0.7857141 6.428572 0.7857141 c h 6.428572 3.357143 m 6.704715 3.357143 6.928572 3.581 6.928572 3.857143 c 6.928572 4.133285 6.704715 4.357142 6.428572 4.357142 c 6.15243 4.357142 5.928572 4.133285 5.928572 3.857143 c 5.928572 3.581 6.15243 3.357143 6.428572 3.357143 c h 6.428572 5.928572 m 6.704715 5.928572 6.928572 6.15243 6.928572 6.428572 c 6.928572 6.704715 6.704715 6.928572 6.428572 6.928572 c 6.15243 6.928572 5.928572 6.704715 5.928572 6.428572 c 5.928572 6.15243 6.15243 5.928572 6.428572 5.928572 c h 6.428572 8.5 m 6.704715 8.5 6.928572 8.723858 6.928572 9 c 6.928572 9.276142 6.704715 9.5 6.428572 9.5 c 6.15243 9.5 5.928572 9.276142 5.928572 9 c 5.928572 8.723858 6.15243 8.5 6.428572 8.5 c h 6.428572 11.07143 m 6.704715 11.07143 6.928572 11.29529 6.928572 11.57143 c 6.928572 11.84757 6.704715 12.07143 6.428572 12.07143 c 6.15243 12.07143 5.928572 11.84757 5.928572 11.57143 c 5.928572 11.29529 6.15243 11.07143 6.428572 11.07143 c h 6.428572 13.64286 m 6.704715 13.64286 6.928572 13.86672 6.928572 14.14286 c 6.928572 14.419 6.704715 14.64286 6.428572 14.64286 c 6.15243 14.64286 5.928572 14.419 5.928572 14.14286 c 5.928572 13.86672 6.15243 13.64286 6.428572 13.64286 c h 6.428572 16.21429 m 6.704715 16.21429 6.928572 16.43814 6.928572 16.71429 c 6.928572 16.99043 6.704715 17.21429 6.428572 17.21429 c 6.15243 17.21429 5.928572 16.99043 5.928572 16.71429 c 5.928572 16.43814 6.15243 16.21429 6.428572 16.21429 c h 9 0.7857141 m 9.276142 0.7857141 9.5 1.009572 9.5 1.285714 c 9.5 1.561857 9.276142 1.785714 9 1.785714 c 8.723858 1.785714 8.5 1.561857 8.5 1.285714 c 8.5 1.009572 8.723858 0.7857141 9 0.7857141 c h 9 3.357143 m 9.276142 3.357143 9.5 3.581 9.5 3.857143 c 9.5 4.133285 9.276142 4.357142 9 4.357142 c 8.723858 4.357142 8.5 4.133285 8.5 3.857143 c 8.5 3.581 8.723858 3.357143 9 3.357143 c h 9 5.928572 m 9.276142 5.928572 9.5 6.15243 9.5 6.428572 c 9.5 6.704715 9.276142 6.928572 9 6.928572 c 8.723858 6.928572 8.5 6.704715 8.5 6.428572 c 8.5 6.15243 8.723858 5.928572 9 5.928572 c h 9 8.5 m 9.276142 8.5 9.5 8.723858 9.5 9 c 9.5 9.276142 9.276142 9.5 9 9.5 c 8.723858 9.5 8.5 9.276142 8.5 9 c 8.5 8.723858 8.723858 8.5 9 8.5 c h 9 11.07143 m 9.276142 11.07143 9.5 + 11.29529 9.5 11.57143 c 9.5 11.84757 9.276142 12.07143 9 12.07143 c 8.723858 12.07143 8.5 11.84757 8.5 11.57143 c 8.5 11.29529 8.723858 11.07143 9 11.07143 c h 9 13.64286 m 9.276142 13.64286 9.5 13.86672 9.5 14.14286 c 9.5 14.419 9.276142 14.64286 9 14.64286 c 8.723858 14.64286 8.5 14.419 8.5 14.14286 c 8.5 13.86672 8.723858 13.64286 9 13.64286 c h 9 16.21429 m 9.276142 16.21429 9.5 16.43814 9.5 16.71429 c 9.5 16.99043 9.276142 17.21429 9 17.21429 c 8.723858 17.21429 8.5 16.99043 8.5 16.71429 c 8.5 16.43814 8.723858 16.21429 9 16.21429 c h 11.57143 0.7857141 m 11.84757 0.7857141 12.07143 1.009572 12.07143 1.285714 c 12.07143 1.561857 11.84757 1.785714 11.57143 1.785714 c 11.29529 1.785714 11.07143 1.561857 11.07143 1.285714 c 11.07143 1.009572 11.29529 0.7857141 11.57143 0.7857141 c h 11.57143 3.357143 m 11.84757 3.357143 12.07143 3.581 12.07143 3.857143 c 12.07143 4.133285 11.84757 4.357142 11.57143 4.357142 c 11.29529 4.357142 11.07143 4.133285 11.07143 3.857143 c 11.07143 3.581 11.29529 3.357143 11.57143 3.357143 c h 11.57143 5.928572 m 11.84757 5.928572 12.07143 6.15243 12.07143 6.428572 c 12.07143 6.704715 11.84757 6.928572 11.57143 6.928572 c 11.29529 6.928572 11.07143 6.704715 11.07143 6.428572 c 11.07143 6.15243 11.29529 5.928572 11.57143 5.928572 c h 11.57143 8.5 m 11.84757 8.5 12.07143 8.723858 12.07143 9 c 12.07143 9.276142 11.84757 9.5 11.57143 9.5 c 11.29529 9.5 11.07143 9.276142 11.07143 9 c 11.07143 8.723858 11.29529 8.5 11.57143 8.5 c h 11.57143 11.07143 m 11.84757 11.07143 12.07143 11.29529 12.07143 11.57143 c 12.07143 11.84757 11.84757 12.07143 11.57143 12.07143 c 11.29529 12.07143 11.07143 11.84757 11.07143 11.57143 c 11.07143 11.29529 11.29529 11.07143 11.57143 11.07143 c h 11.57143 13.64286 m 11.84757 13.64286 12.07143 13.86672 12.07143 14.14286 c 12.07143 14.419 11.84757 14.64286 11.57143 14.64286 c 11.29529 14.64286 11.07143 14.419 11.07143 14.14286 c 11.07143 13.86672 11.29529 13.64286 11.57143 13.64286 c h 11.57143 16.21429 m 11.84757 16.21429 12.07143 16.43814 12.07143 16.71429 c 12.07143 16.99043 11.84757 17.21429 11.57143 17.21429 c 11.29529 17.21429 11.07143 16.99043 11.07143 16.71429 c 11.07143 16.43814 11.29529 16.21429 11.57143 16.21429 c h 14.14286 0.7857141 m 14.419 0.7857141 14.64286 1.009572 14.64286 1.285714 c 14.64286 1.561857 14.419 1.785714 14.14286 1.785714 c 13.86672 1.785714 13.64286 1.561857 13.64286 1.285714 c 13.64286 1.009572 13.86672 0.7857141 14.14286 0.7857141 c h 14.14286 3.357143 m 14.419 3.357143 14.64286 3.581 14.64286 3.857143 c 14.64286 4.133285 14.419 4.357142 14.14286 4.357142 c 13.86672 4.357142 13.64286 4.133285 13.64286 3.857143 c 13.64286 3.581 13.86672 3.357143 14.14286 3.357143 c h 14.14286 5.928572 m 14.419 5.928572 14.64286 6.15243 14.64286 6.428572 c 14.64286 6.704715 14.419 6.928572 14.14286 6.928572 c 13.86672 6.928572 13.64286 6.704715 13.64286 6.428572 c 13.64286 6.15243 13.86672 5.928572 14.14286 5.928572 c h 14.14286 8.5 m 14.419 8.5 14.64286 8.723858 14.64286 9 c 14.64286 9.276142 14.419 9.5 14.14286 9.5 c 13.86672 9.5 13.64286 9.276142 13.64286 9 c 13.64286 8.723858 13.86672 8.5 14.14286 8.5 c h 14.14286 11.07143 m 14.419 11.07143 14.64286 11.29529 14.64286 11.57143 c 14.64286 11.84757 14.419 12.07143 14.14286 12.07143 c 13.86672 12.07143 13.64286 11.84757 13.64286 11.57143 c 13.64286 11.29529 13.86672 11.07143 14.14286 11.07143 c h 14.14286 13.64286 m 14.419 13.64286 14.64286 13.86672 14.64286 14.14286 c 14.64286 14.419 14.419 14.64286 14.14286 14.64286 c 13.86672 14.64286 13.64286 14.419 13.64286 14.14286 c 13.64286 13.86672 13.86672 13.64286 14.14286 13.64286 c h 14.14286 16.21429 m 14.419 16.21429 14.64286 16.43814 14.64286 16.71429 c 14.64286 16.99043 14.419 17.21429 14.14286 17.21429 c 13.86672 17.21429 13.64286 16.99043 13.64286 16.71429 c 13.64286 16.43814 13.86672 16.21429 14.14286 16.21429 c h 16.71429 0.7857141 m 16.99043 0.7857141 17.21429 1.009572 17.21429 1.285714 c 17.21429 1.561857 16.99043 1.785714 16.71429 1.785714 c 16.43814 1.785714 16.21429 1.561857 16.21429 1.285714 c 16.21429 1.009572 16.43814 0.7857141 16.71429 0.7857141 c h 16.71429 3.357143 m 16.99043 3.357143 17.21429 3.581 17.21429 3.857143 c 17.21429 4.133285 16.99043 4.357142 16.71429 4.357142 c 16.43814 4.357142 16.21429 4.133285 16.21429 3.857143 c 16.21429 3.581 16.43814 3.357143 16.71429 3.357143 c h 16.71429 5.928572 m 16.99043 5.928572 17.21429 6.15243 17.21429 6.428572 c 17.21429 6.704715 16.99043 6.928572 16.71429 6.928572 c 16.43814 6.928572 16.21429 6.704715 16.21429 6.428572 c 16.21429 6.15243 16.43814 5.928572 16.71429 5.928572 c h 16.71429 8.5 m 16.99043 8.5 17.21429 8.723858 17.21429 9 c 17.21429 9.276142 16.99043 9.5 16.71429 9.5 c 16.43814 9.5 16.21429 9.276142 16.21429 9 c 16.21429 8.723858 16.43814 8.5 16.71429 8.5 c h 16.71429 11.07143 m 16.99043 11.07143 17.21429 11.29529 17.21429 11.57143 c 17.21429 11.84757 16.99043 12.07143 16.71429 12.07143 c 16.43814 12.07143 16.21429 11.84757 16.21429 11.57143 c 16.21429 11.29529 16.43814 11.07143 16.71429 11.07143 c h 16.71429 13.64286 m 16.99043 13.64286 17.21429 13.86672 17.21429 14.14286 c 17.21429 14.419 16.99043 14.64286 16.71429 14.64286 c 16.43814 14.64286 16.21429 14.419 16.21429 14.14286 c 16.21429 13.86672 16.43814 13.64286 16.71429 13.64286 c h 16.71429 16.21429 m 16.99043 16.21429 17.21429 16.43814 17.21429 16.71429 c 17.21429 16.99043 16.99043 17.21429 16.71429 17.21429 c 16.43814 17.21429 16.21429 16.99043 16.21429 16.71429 c 16.21429 16.43814 16.43814 16.21429 16.71429 16.21429 c h + {strokecolor} rg f + endstream''' +} + +HatchStyleTemplates={ + 'Brick' :'/PatternName(Brick)', #BBObjPtr + 'DiagonalBrick':'/PatternName(Diagonal Brick)', + 'Horizontal':'/PatternName(Horizontal)', + 'Vertical':'/PatternName(Vertical)', + 'DiagonalDown':'/PatternName(Diagonal Down)', + 'DiagonalUp':'/PatternName(Diagonal Up)', + 'Grid':'/PatternName(Grid)', + 'Weave':'/PatternName(Weave)', + '10Dots':'/PatternName(10% Dots)', + '20Dots':'/PatternName(20% Dots)', + '30Dots':'/PatternName(30% Dots)' +} + +def calculate_bounding_rect(vertices): + xs = [pt[0] for pt in vertices] + ys = [pt[1] for pt in vertices] + min_x = min(xs) + max_x = max(xs) + min_y = min(ys) + max_y = max(ys) + return [min_x, min_y, max_x, max_y] +def generate_annotation_xml_block(vertices, area_text, author, custom_data: dict, column_order: list, index: int, + type_internal: str = 'Bluebeam.PDF.Annotations.AnnotationMeasureArea', + subject: str = 'Area Measurement', + label: str = '',opacity:str='', + color:str='', linestyle:str='', + hatchstyle:str='',hatchLinescolor:str='', + bb_objptrMeas:str=''): + now = datetime.datetime.utcnow() + mod_date = now.strftime("D:%Y%m%d%H%M%S+00'00'") + creation_date = now.isoformat() + 'Z' + id_str = "fitz-" + uuid.uuid4().hex[:4].upper() + + vert_str = ' '.join([f'{x:.4f}' for point in vertices for x in point]) + ordered_column_values = [f'({custom_data.get(col, "")})' for col in column_order] + bsi_column_data = ''.join(ordered_column_values) + meastype='' + if subject.startswith('Area'): + meastype='129' + polygonpolylineDimension='/PolygonDimension' + polygonpolyline='/Polygon' + elif subject.startswith('Perimeter'): + meastype='130' + polygonpolylineDimension='/PolyLineDimension' + polygonpolyline='/PolyLine' + rectvertices=calculate_bounding_rect(vertices) + + raw_text = f'''<< +/DS(font: Helvetica 12pt; text-align:center; line-height:13.8pt; color:#FF0000) +/Cap false +/AlignOnSegment true +/MeasurementTypes {meastype} +/SlopeType 1 +/PitchRun 12 +/IT +{polygonpolylineDimension} +/Vertices[{vert_str}] +/IC[{color}] +/Pattern/{hatchstyle}/PatternColor[{hatchLinescolor}] +/FillOpacity {opacity} +/T({author}) +/CA {opacity} +/RC({area_text}
) +/Label({label}) +/Subj({subject}) +/Measure/BBObjPtr_{bb_objptrMeas} +/BSIColumnData[{bsi_column_data}] +/NM({id_str}) +/Subtype/{polygonpolyline} +/Rect[{rectvertices[0]} {rectvertices[1]} {rectvertices[2]} {rectvertices[3]}] +/Contents({area_text}) +/F 4 +/C[{color}] +/BS{linestyle} +/M({mod_date}) +>>'''.encode('utf-8') + + compressed = zlib.compress(raw_text) + base64_raw = base64.b16encode(compressed).lower().decode() + + annotation = Element('Annotation') + SubElement(annotation, 'Page').text = '1' + SubElement(annotation, 'Contents').text = area_text + SubElement(annotation, 'ModDate').text = creation_date + SubElement(annotation, 'Color').text = '#B7B7E8' + SubElement(annotation, 'Type').text = 'Polygon' + SubElement(annotation, 'ID').text = id_str + SubElement(annotation, 'TypeInternal').text = type_internal + SubElement(annotation, 'Raw').text = base64_raw + SubElement(annotation, 'Index').text = str(index) + + custom = SubElement(annotation, 'Custom') + for key, value in custom_data.items(): + SubElement(custom, key).text = value + + SubElement(annotation, 'Subject').text = subject + SubElement(annotation, 'CreationDate').text = creation_date + SubElement(annotation, 'Author').text = author + SubElement(annotation, 'Label').text = label + + return annotation + +def generate_bb_objptr(): + return ''.join(random.choices(string.ascii_uppercase, k=16)) + +def compresslikeBBRaw(textToCompress): + decompressedX = textToCompress.encode('utf-8') + print(decompressedX) + recompressedX = zlib.compress(decompressedX) + print(recompressedX.hex()) + return recompressedX.hex() + +def setBrickHatch(fillcolor,strokecolor): + # resourceid='789cf30b0877f2f40cf30f758ff48e0a0df3040029f004fd' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['Brick'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Brick'],compressedRaw, resourceid + +def setDiagonalBrickHatch(fillcolor,strokecolor): + # resourceid='789c0b0d8cf47274f60d0df28a740ef4f4f3020029ab04da' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['DiagonalBrick'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['DiagonalBrick'],compressedRaw,resourceid + +def setHorizontalHatch(fillcolor,strokecolor): + # resourceid='789cf3720b76f6f072f173f58cf071f209f00000273604a3' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['Horizontal'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Horizontal'],compressedRaw,resourceid + +def setVerticalHatch(fillcolor,strokecolor): + # resourceid='789cf30d080ef4f609088b74740ff0890a7607002a1904f0' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['Vertical'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Vertical'],compressedRaw,resourceid + +def setDiagonalDownHatch(fillcolor,strokecolor): + # resourceid='789cf3f28b74f477f7770b0c7675f68f74f60300288f04c3' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['DiagonalDown'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['DiagonalDown'],compressedRaw,resourceid + +def setDiagonalUpHatch(fillcolor,strokecolor): + # resourceid='789c0b8a70f30df4f70b09f40cf6f108757606002a2304dc' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['DiagonalUp'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['DiagonalUp'],compressedRaw,resourceid + +def setGridHatch(fillcolor,strokecolor): + # resourceid='789c730b71738e0a760cf3758972f370740a0300286b04ba' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['Grid'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Grid'],compressedRaw,resourceid + +def setWeaveHatch(fillcolor,strokecolor): + # resourceid='789cf30af775f2f1f776720d8972740c8af40500285c04c6' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['Weave'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['Weave'],compressedRaw,resourceid + +def set10DotsHatch(fillcolor,strokecolor): + # resourceid='789cf3740f71f6770d0e8c0a0f76f50e0df00600291c04e4' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['10Dots'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['10Dots'],compressedRaw,resourceid + +def set20DotsHatch(fillcolor,strokecolor): + # resourceid='789c738f0cf70bf5f0f0770a0df471760df7000029b004d5' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['20Dots'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['20Dots'],compressedRaw,resourceid + +def set30DotsHatch(fillcolor,strokecolor): + # resourceid='789cf38c747789f4f68a8c0cf2f6f2f676f2070029b104dc' + randombb_objptr=generate_bb_objptr() + resourceid=compresslikeBBRaw(randombb_objptr) + compressedRaw=compresslikeBBRaw(AllhatchesCodes['30Dots'].format(fillcolor=fillcolor, strokecolor=strokecolor)) + return 'BBObjPtr_'+randombb_objptr+HatchStyleTemplates['30Dots'],compressedRaw,resourceid + +def save_multiple_annotations_bax(annotations, output_path, column_order,pdfWidth,pdfHeight): + """ + annotations: list of dicts, each with: + - vertices: list of [x, y] + - text: str (label/tooltip) + - author: str + - custom_data: dict of custom field values + - type_internal: str (e.g., Bluebeam.PDF.Annotations.AnnotationMeasurePerimeter) + - subject: str (e.g., Perimeter Measurement) + """ + globalhatches=[] + scales=[] + doc = Element('Document', Version='1') + ########## Subelement1 - page ################ + page = SubElement(doc, 'Page', Index='0') + SubElement(page, 'Label').text = '1' + SubElement(page, 'Width').text = str(pdfWidth) + SubElement(page, 'Height').text = str(pdfHeight) + + for i, ann in enumerate(annotations): + + bb_objptrMeas=generate_bb_objptr() + resourceidComp=compresslikeBBRaw(bb_objptrMeas) + scales.append(resourceidComp) + + hatchstyle_key = ann.get('hatchstyle') # e.g., 'Brick' + if hatchstyle_key not in globalhatches and hatchstyle_key: + globalhatches.append([hatchstyle_key[2],hatchstyle_key[1]]) # id, raw + hatchstyle=hatchstyle_key[0] + else: + hatchstyle='none' + + annotation_xml = generate_annotation_xml_block( + vertices=ann['vertices'], + area_text=ann['text'], + author=ann['author'], + custom_data=ann['custom_data'], + column_order=column_order, + index=i, + bb_objptrMeas=bb_objptrMeas, + type_internal=ann.get('type_internal', 'Bluebeam.PDF.Annotations.AnnotationMeasureArea'), + subject=ann.get('subject', 'Area Measurement'), + label=ann.get('label', 'label1'), + opacity=ann.get('opacity', ''), + color=ann.get('color', ''), + linestyle=ann.get('linestyle', ''), + hatchstyle=hatchstyle, + hatchLinescolor=ann.get('hatchLinescolor',''), + ) + page.append(annotation_xml) + ################# Subelement 2 - Global resources############ + GlobalResources = SubElement(doc, 'GlobalResources') + for hatch in globalhatches: + Resource = SubElement(GlobalResources, 'Resource') + SubElement(Resource, 'ID').text = hatch[0] + SubElement(Resource, 'Raw').text = hatch[1] + for scale in scales: + Resource = SubElement(GlobalResources, 'Resource') + SubElement(Resource, 'ID').text = scale + SubElement(Resource, 'Raw').text = '789c85d04f0b82401005f0af3247bd34a35176b085503c55847f22a80e2a4b7858ad7537e8dba7951ea2a5e330efc783e7fb983eae1c373c6fb5e498e842f577bcc6d872a014b00407848d87e310dd6a5170193552e40a33abfb0540139ace5ccff316188243844962d98c9d3134b297eba2f4255626d1dee06d3e200a4149cd47989ae0c99dd32fb8ebe0a8f7265dea3fb5b9bc7095d5950a9aface655b3575bf070d8b30f604438f6873' + bax_xml= tostring(doc, encoding="unicode", method="xml") #tostring(doc, encoding="utf-8", method="xml").decode("utf-8") + + # print(f" Saved {len(annotations)} annotations to {output_path}") + return bax_xml + +"""PDF to image""" + +def pdftoimg(datadoc,pdf_content=0): + if pdf_content: + doc = fitz.open(stream=pdf_content, filetype="pdf") + else: + doc =fitz.open('pdf',datadoc) + page=doc[0] + pix = page.get_pixmap() # render page to an image + pl=Image.frombytes('RGB', [pix.width,pix.height],pix.samples) + img=np.array(pl) + img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + print("IMAGE") +# cv2_imshow(img) + return img,pix + + +# Standard ISO paper sizes in inches +ISO_SIZES_INCHES = { + "A0": (33.11, 46.81), + "A1": (23.39, 33.11), + "A2": (16.54, 23.39), + "A3": (11.69, 16.54), + "A4": (8.27, 11.69), + "A5": (5.83, 8.27), + "A6": (4.13, 5.83), + "A7": (2.91, 4.13), + "A8": (2.05, 2.91), + "A9": (1.46, 2.05), + "A10": (1.02, 1.46) +} + +def get_paper_size_in_inches(width, height): + """Find the closest matching paper size in inches.""" + for size, (w, h) in ISO_SIZES_INCHES.items(): + if (abs(w - width) < 0.1 and abs(h - height) < 0.1) or (abs(w - height) < 0.1 and abs(h - width) < 0.1): + return size + return "Unknown Size" + +def analyze_pdf(datadoc,pdf_content=0): + # Open the PDF file + if pdf_content: + pdf_document = fitz.open(stream=pdf_content, filetype="pdf") + else: + pdf_document = fitz.open('pdf',datadoc) + + # Iterate through pages and print their sizes + for page_number in range(len(pdf_document)): + page = pdf_document[page_number] + rect = page.rect + width_points, height_points = rect.width, rect.height + + # Convert points to inches + width_inches, height_inches = width_points / 72, height_points / 72 + + paper_size = get_paper_size_in_inches(width_inches, height_inches) + + print(f"Page {page_number + 1}: {width_inches:.2f} x {height_inches:.2f} inches ({paper_size})") + + pdf_document.close() + return width_inches , height_inches , paper_size + + +def get_dxfSize(dxfpath): + + doc = ezdxf.readfile(dxfpath) + msp = doc.modelspace() + # Create a cache for bounding box calculations + # Get the overall bounding box for all entities in the modelspace + cache = bbox.Cache() + overall_bbox = bbox.extents(msp, cache=cache) + print("Overall Bounding Box:", overall_bbox) + print(overall_bbox.extmin[0]+overall_bbox.extmax[0], overall_bbox.extmin[1]+overall_bbox.extmax[1]) + + return overall_bbox.extmin[0]+overall_bbox.extmax[0], overall_bbox.extmin[1]+overall_bbox.extmax[1] + + + +def switch_case(argument): + switcher = { + "A0": 1.27, + "A1": 2.54, + "A2": 5.08, + "A3": 10.16, + "A4": 20.32, + "A5": 40.64, + "A6": 81.28, + "A7": 162.56, + "A8": 325.12, + "A9": 650.24, + "A10": 1300.48 + } + # Get the value from the dictionary; if not found, return a default value + print("Final Ratio=",switcher.get(argument, 1)) + return switcher.get(argument, 1) + + + + +def RetriveRatio(datadoc,dxfpath,pdf_content=0): + if pdf_content: + width,height,paper_size = analyze_pdf (datadoc,pdf_content) + else: + width,height,paper_size = analyze_pdf (datadoc) + if(width > height ): + bigger=width + else: + bigger=height + + width_dxf,height_dxf = get_dxfSize(dxfpath) + + if(width_dxf > height_dxf ): + bigger_dxf=width_dxf + else: + bigger_dxf=height_dxf + + if(0.2 < bigger_dxf/bigger < 1.2): + print("bigger_dxf/bigger",bigger/bigger_dxf) + argument = paper_size + FinalRatio=switch_case(argument) + else: + FinalRatio=1 + return FinalRatio,width_dxf + + +"""Flips image +DXF origin is at the bottom left while img origin is top left +""" + +def flip(img): + height, width = img.shape[:2] + + # Define the rotation angle (clockwise) + angle = 180 + + # Calculate the rotation matrix + rotation_matrix = cv2.getRotationMatrix2D((width/2, height/2), angle, 1) + + # Rotate the image + rotated_image = cv2.warpAffine(img, rotation_matrix, (width, height)) + flipped_horizontal = cv2.flip(rotated_image, 1) + return flipped_horizontal + + + +def aci_to_rgb(aci): + aci_rgb_map = { + 0: (0, 0, 0), + 1: (255, 0, 0), + 2: (255, 255, 0), + 3: (0, 255, 0), + 4: (0, 255, 255), + 5: (0, 0, 255), + 6: (255, 0, 255), + 7: (255, 255, 255), + 8: (65, 65, 65), + 9: (128, 128, 128), + 10: (255, 0, 0), + 11: (255, 170, 170), + 12: (189, 0, 0), + 13: (189, 126, 126), + 14: (129, 0, 0), + 15: (129, 86, 86), + 16: (104, 0, 0), + 17: (104, 69, 69), + 18: (79, 0, 0), + 19: (79, 53, 53), + 20: (255, 63, 0), + 21: (255, 191, 170), + 22: (189, 46, 0), + 23: (189, 141, 126), + 24: (129, 31, 0), + 25: (129, 96, 86), + 26: (104, 25, 0), + 27: (104, 78, 69), + 28: (79, 19, 0), + 29: (79, 59, 53), + 30: (255, 127, 0), + 31: (255, 212, 170), + 32: (189, 94, 0), + 33: (189, 157, 126), + 34: (129, 64, 0), + 35: (129, 107, 86), + 36: (104, 52, 0), + 37: (104, 86, 69), + 38: (79, 39, 0), + 39: (79, 66, 53), + 40: (255, 191, 0), + 41: (255, 234, 170), + 42: (189, 141, 0), + 43: (189, 173, 126), + 44: (129, 96, 0), + 45: (129, 118, 86), + 46: (104, 78, 0), + 47: (104, 95, 69), + 48: (79, 59, 0), + 49: (79, 73, 53), + 50: (255, 255, 0), + 51: (255, 255, 170), + 52: (189, 189, 0), + 53: (189, 189, 126), + 54: (129, 129, 0), + 55: (129, 129, 86), + 56: (104, 104, 0), + 57: (104, 104, 69), + 58: (79, 79, 0), + 59: (79, 79, 53), + 60: (191, 255, 0), + 61: (234, 255, 170), + 62: (141, 189, 0), + 63: (173, 189, 126), + 64: (96, 129, 0), + 65: (118, 129, 86), + 66: (78, 104, 0), + 67: (95, 104, 69), + 68: (59, 79, 0), + 69: (73, 79, 53), + 70: (127, 255, 0), + 71: (212, 255, 170), + 72: (94, 189, 0), + 73: (157, 189, 126), + 74: (64, 129, 0), + 75: (107, 129, 86), + 76: (52, 104, 0), + 77: (86, 104, 69), + 78: (39, 79, 0), + 79: (66, 79, 53), + 80: (63, 255, 0), + 81: (191, 255, 170), + 82: (46, 189, 0), + 83: (141, 189, 126), + 84: (31, 129, 0), + 85: (96, 129, 86), + 86: (25, 104, 0), + 87: (78, 104, 69), + 88: (19, 79, 0), + 89: (59, 79, 53), + 90: (0, 255, 0), + 91: (170, 255, 170), + 92: (0, 189, 0), + 93: (126, 189, 126), + 94: (0, 129, 0), + 95: (86, 129, 86), + 96: (0, 104, 0), + 97: (69, 104, 69), + 98: (0, 79, 0), + 99: (53, 79, 53), + 100: (0, 255, 63), + 101: (170, 255, 191), + 102: (0, 189, 46), + 103: (126, 189, 141), + 104: (0, 129, 31), + 105: (86, 129, 96), + 106: (0, 104, 25), + 107: (69, 104, 78), + 108: (0, 79, 19), + 109: (53, 79, 59), + 110: (0, 255, 127), + 111: (170, 255, 212), + 112: (0, 189, 94), + 113: (126, 189, 157), + 114: (0, 129, 64), + 115: (86, 129, 107), + 116: (0, 104, 52), + 117: (69, 104, 86), + 118: (0, 79, 39), + 119: (53, 79, 66), + 120: (0, 255, 191), + 121: (170, 255, 234), + 122: (0, 189, 141), + 123: (126, 189, 173), + 124: (0, 129, 96), + 125: (86, 129, 118), + 126: (0, 104, 78), + 127: (69, 104, 95), + 128: (0, 79, 59), + 129: (53, 79, 73), + 130: (0, 255, 255), + 131: (170, 255, 255), + 132: (0, 189, 189), + 133: (126, 189, 189), + 134: (0, 129, 129), + 135: (86, 129, 129), + 136: (0, 104, 104), + 137: (69, 104, 104), + 138: (0, 79, 79), + 139: (53, 79, 79), + 140: (0, 191, 255), + 141: (170, 234, 255), + 142: (0, 141, 189), + 143: (126, 173, 189), + 144: (0, 96, 129), + 145: (86, 118, 129), + 146: (0, 78, 104), + 147: (69, 95, 104), + 148: (0, 59, 79), + 149: (53, 73, 79), + 150: (0, 127, 255), + 151: (170, 212, 255), + 152: (0, 94, 189), + 153: (126, 157, 189), + 154: (0, 64, 129), + 155: (86, 107, 129), + 156: (0, 52, 104), + 157: (69, 86, 104), + 158: (0, 39, 79), + 159: (53, 66, 79), + 160: (0, 63, 255), + 161: (170, 191, 255), + 162: (0, 46, 189), + 163: (126, 141, 189), + 164: (0, 31, 129), + 165: (86, 96, 129), + 166: (0, 25, 104), + 167: (69, 78, 104), + 168: (0, 19, 79), + 169: (53, 59, 79), + 170: (0, 0, 255), + 171: (170, 170, 255), + 172: (0, 0, 189), + 173: (126, 126, 189), + 174: (0, 0, 129), + 175: (86, 86, 129), + 176: (0, 0, 104), + 177: (69, 69, 104), + 178: (0, 0, 79), + 179: (53, 53, 79), + 180: (63, 0, 255), + 181: (191, 170, 255), + 182: (46, 0, 189), + 183: (141, 126, 189), + 184: (31, 0, 129), + 185: (96, 86, 129), + 186: (25, 0, 104), + 187: (78, 69, 104), + 188: (19, 0, 79), + 189: (59, 53, 79), + 190: (127, 0, 255), + 191: (212, 170, 255), + 192: (94, 0, 189), + 193: (157, 126, 189), + 194: (64, 0, 129), + 195: (107, 86, 129), + 196: (52, 0, 104), + 197: (86, 69, 104), + 198: (39, 0, 79), + 199: (66, 53, 79), + 200: (191, 0, 255), + 201: (234, 170, 255), + 202: (141, 0, 189), + 203: (173, 126, 189), + 204: (96, 0, 129), + 205: (118, 86, 129), + 206: (78, 0, 104), + 207: (95, 69, 104), + 208: (59, 0, 79), + 209: (73, 53, 79), + 210: (255, 0, 255), + 211: (255, 170, 255), + 212: (189, 0, 189), + 213: (189, 126, 189), + 214: (129, 0, 129), + 215: (129, 86, 129), + 216: (104, 0, 104), + 217: (104, 69, 104), + 218: (79, 0, 79), + 219: (79, 53, 79), + 220: (255, 0, 191), + 221: (255, 170, 234), + 222: (189, 0, 141), + 223: (189, 126, 173), + 224: (129, 0, 96), + 225: (129, 86, 118), + 226: (104, 0, 78), + 227: (104, 69, 95), + 228: (79, 0, 59), + 229: (79, 53, 73), + 230: (255, 0, 127), + 231: (255, 170, 212), + 232: (189, 0, 94), + 233: (189, 126, 157), + 234: (129, 0, 64), + 235: (129, 86, 107), + 236: (104, 0, 52), + 237: (104, 69, 86), + 238: (79, 0, 39), + 239: (79, 53, 66), + 240: (255, 0, 63), + 241: (255, 170, 191), + 242: (189, 0, 46), + 243: (189, 126, 141), + 244: (129, 0, 31), + 245: (129, 86, 96), + 246: (104, 0, 25), + 247: (104, 69, 78), + 248: (79, 0, 19), + 249: (79, 53, 59), + 250: (51, 51, 51), + 251: (80, 80, 80), + 252: (105, 105, 105), + 253: (130, 130, 130), + 254: (190, 190, 190), + 255: (255, 255, 255) + } + + # Default to white if index is invalid or not found + return aci_rgb_map.get(aci, (255, 255, 255)) + + +def int_to_rgb(color_int): + """Convert an integer to an (R, G, B) tuple.""" + r = (color_int >> 16) & 255 + g = (color_int >> 8) & 255 + b = color_int & 255 + return (r, g, b) + + +def get_hatch_color(entity): + """Extract hatch color with detailed debugging.""" + if not entity: + # print("No entity provided for color extraction.") + return (255, 255, 255) + + # Check for true color + if entity.dxf.hasattr('true_color'): + true_color = entity.dxf.true_color + rgb_color = int_to_rgb(true_color) # Convert integer to (R, G, B) + # print(f"True color detected (RGB): {rgb_color}") + return rgb_color + + # Check for color index + color_index = entity.dxf.color + # print(f"Entity color index: {color_index}") + if 1 <= color_index <= 255: + rgb_color = aci_to_rgb(color_index) # Convert ACI to RGB + # print(f"Converted ACI to RGB: {rgb_color}") + return rgb_color + + # Handle ByLayer or ByBlock + if color_index == 0: # ByLayer + layer_name = entity.dxf.layer + layer = entity.doc.layers.get(layer_name) + # print(f"ByLayer detected for layer '{layer_name}'.") + if layer: + layer_color_index = layer.dxf.color + # print(layer_color_index) + rgb_color = aci_to_rgb(layer_color_index) + # print(f"Layer '{layer_name}' color index {layer_color_index} converted to RGB: {rgb_color}") + return rgb_color + else: + # print(f"Layer '{layer_name}' not found. Defaulting to white.") + return (255, 255, 255) + + # Default + # print("Unhandled color case. Defaulting to white.") + return (255, 255, 255) + + + +def point_in_rectangle(point, rect_coords): + x, y = point + (x1, y1), (x2, y2) = rect_coords + return x1 <= x <= x2 and y1 <= y <= y2 + +from math import sqrt + +def euclidean_distance(point1, point2): + x1, y1 = point1 + x2, y2 = point2 + return sqrt((x2 - x1)**2 + (y2 - y1)**2) + +def compute_hatch_centroid(hatch): + x_coords = [] + y_coords = [] + for path in hatch.paths: + if path.PATH_TYPE == "PolylinePath": + for vertex in path.vertices: + x_coords.append(vertex[0]) + y_coords.append(vertex[1]) + elif path.PATH_TYPE == "EdgePath": + for edge in path.edges: + if hasattr(edge, "start"): + x_coords.append(edge.start[0]) + y_coords.append(edge.start[1]) + if hasattr(edge, "end"): + x_coords.append(edge.end[0]) + y_coords.append(edge.end[1]) + if x_coords and y_coords: + return (sum(x_coords) / len(x_coords), sum(y_coords) / len(y_coords)) + return None + +"""### Hatched areas""" +def get_hatched_areas(datadoc,filename,FinalRatio,rotationangle,SearchArray,CollectedColors): + + coloredarray = [tuple(x) for x in CollectedColors] + print("CollectedColors = ",CollectedColors) + print("coloredarray = ",coloredarray) + + print("SearchArray = ",SearchArray) + + doc = ezdxf.readfile(filename) + doc.header['$MEASUREMENT'] = 1 + msp = doc.modelspace() + trial=0 + hatched_areas = [] + threshold=0.001 + TextFound = 0 + j=0 + unique_shapes = [] + + + text_with_positions = [] + text_color_mapping = {} + color_palette = [ + (255, 0, 0), (0, 0, 255), (0, 255, 255), (0, 64, 0), (255, 204, 0), + (255, 128, 64), (255, 0, 128), (255, 128, 192), (128, 128, 255), + (128, 64, 0), (0, 255, 0), (0, 200, 0), (255, 128, 255), (128, 0, 255), + (0, 128, 192), (128, 0, 128), (128, 0, 0), (0, 128, 255), (149, 1, 70), + (255, 182, 128), (222, 48, 71), (240, 0, 112), (255, 0, 255), + (192, 46, 65), (0, 0, 128), (0, 128, 64), (255, 255, 0), (128, 0, 80), + (255, 255, 128), (90, 255, 140), (255, 200, 20), (91, 16, 51), + (90, 105, 138), (114, 10, 138), (36, 82, 78), (225, 105, 190), + (108, 150, 170), (11, 35, 75), (42, 176, 170), (255, 176, 170), + (209, 151, 15), (81, 27, 85), (226, 106, 122), (67, 119, 149), + (159, 179, 140), (159, 179, 30), (255, 85, 198), (255, 27, 85), + (188, 158, 8), (140, 188, 120), (59, 61, 52), (65, 81, 21), + (212, 255, 174), (15, 164, 90), (41, 217, 245), (213, 23, 182), + (11, 85, 169), (78, 153, 239), (0, 66, 141), (64, 98, 232), + (140, 112, 255), (57, 33, 154), (194, 117, 252), (116, 92, 135), + (74, 43, 98), (188, 13, 123), (129, 58, 91), (255, 128, 100), + (171, 122, 145), (255, 98, 98), (222, 48, 77) + ] + import re + + text_with_positions = [] + # SearchArray=[["","Wall Type","",""],["","","",""]] + + # print("SearchArray=",len(SearchArray)) + # print("SearchArray=",len(SearchArray[0])) + # print("SearchArray=",SearchArray[0][0]) + + if(SearchArray): + for i in range(len(SearchArray)): + + if (SearchArray[i][0] and SearchArray[i][1] and SearchArray[i][2]): + for text_entity in doc.modelspace().query('TEXT MTEXT'): + text = text_entity.text.strip() if hasattr(text_entity, 'text') else "" + # if (text.startswith("P") and len(text) == 3) or (text.startswith("I") and len(text) == 3): # Filter for "Wall" + if(text.startswith(SearchArray[i][0]) and len(text)==int(SearchArray[i][2])): + position = text_entity.dxf.insert # Extract text position + x, y = position.x, position.y + + for text_entity in doc.modelspace().query('TEXT MTEXT'): + NBS = text_entity.text.strip() if hasattr(text_entity, 'text') else "" + if (NBS.startswith(SearchArray[i][1])): + positionNBS = text_entity.dxf.insert # Extract text position + xNBS, yNBS = positionNBS.x, positionNBS.y + + if(x == xNBS or y == yNBS): + textNBS=NBS + break + + else: + textNBS = None + + + + nearest_hatch = None + min_distance = float('inf') # Initialize with a very large value + detected_color = (255, 255, 255) # Default to white + + # Search for the nearest hatch + for hatch in doc.modelspace().query('HATCH'): # Query only hatches + if hatch.paths: + for path in hatch.paths: + if path.type == 1: # PolylinePath + vertices = [v[:2] for v in path.vertices] + # Calculate the centroid of the hatch + centroid_x = sum(v[0] for v in vertices) / len(vertices) + centroid_y = sum(v[1] for v in vertices) / len(vertices) + centroid = (centroid_x, centroid_y) + + # Calculate the distance between the text and the hatch centroid + distance = calculate_distance((x, y), centroid) + + # Update the nearest hatch if a closer one is found + if distance < min_distance: + min_distance = distance + nearest_hatch = hatch + + # Get the color of this hatch + current_color = get_hatch_color(hatch) + if current_color != (255, 255, 255): # Valid color found + detected_color = current_color + break # Stop checking further paths for this hatch + + + # Append the detected result only once + text_with_positions.append([text, textNBS, (x, y), detected_color]) + print("text_with_positions=",text_with_positions) + + elif (SearchArray[i][0] and SearchArray[i][2]): + for text_entity in doc.modelspace().query('TEXT MTEXT'): + text = text_entity.text.strip() if hasattr(text_entity, 'text') else "" + # if (text.startswith("P") and len(text) == 3) or (text.startswith("I") and len(text) == 3): # Filter for "Wall" + if(text.startswith(SearchArray[i][0]) and len(text)==int(SearchArray[i][2])): + position = text_entity.dxf.insert # Extract text position + x, y = position.x, position.y + textNBS = None + nearest_hatch = None + min_distance = float('inf') # Initialize with a very large value + detected_color = (255, 255, 255) # Default to white + + # Search for the nearest hatch + for hatch in doc.modelspace().query('HATCH'): # Query only hatches + if hatch.paths: + for path in hatch.paths: + if path.type == 1: # PolylinePath + vertices = [v[:2] for v in path.vertices] + # Calculate the centroid of the hatch + centroid_x = sum(v[0] for v in vertices) / len(vertices) + centroid_y = sum(v[1] for v in vertices) / len(vertices) + centroid = (centroid_x, centroid_y) + + # Calculate the distance between the text and the hatch centroid + distance = calculate_distance((x, y), centroid) + + # Update the nearest hatch if a closer one is found + if distance < min_distance: + min_distance = distance + nearest_hatch = hatch + + # Get the color of this hatch + current_color = get_hatch_color(hatch) + if current_color != (255, 255, 255): # Valid color found + detected_color = current_color + break # Stop checking further paths for this hatch + + + # Append the detected result only once + text_with_positions.append([text, textNBS, (x, y), detected_color]) + print("text_with_positions=",text_with_positions) + + elif(SearchArray[i][0]): + for text_entity in doc.modelspace().query('TEXT MTEXT'): + text = text_entity.text.strip() if hasattr(text_entity, 'text') else "" + # if (text.startswith("P") and len(text) == 3) or (text.startswith("I") and len(text) == 3): # Filter for "Wall" + if(text.startswith(SearchArray[i][0])): + position = text_entity.dxf.insert # Extract text position + x, y = position.x, position.y + textNBS = None + nearest_hatch = None + min_distance = float('inf') # Initialize with a very large value + detected_color = (255, 255, 255) # Default to white + + # Search for the nearest hatch + for hatch in doc.modelspace().query('HATCH'): # Query only hatches + if hatch.paths: + for path in hatch.paths: + if path.type == 1: # PolylinePath + vertices = [v[:2] for v in path.vertices] + # Calculate the centroid of the hatch + centroid_x = sum(v[0] for v in vertices) / len(vertices) + centroid_y = sum(v[1] for v in vertices) / len(vertices) + centroid = (centroid_x, centroid_y) + + # Calculate the distance between the text and the hatch centroid + distance = calculate_distance((x, y), centroid) + + # Update the nearest hatch if a closer one is found + if distance < min_distance: + min_distance = distance + nearest_hatch = hatch + + # Get the color of this hatch + current_color = get_hatch_color(hatch) + if current_color != (255, 255, 255): # Valid color found + detected_color = current_color + break # Stop checking further paths for this hatch + + + # Append the detected result only once + text_with_positions.append([text, textNBS, (x, y), detected_color]) + print("text_with_positions=",text_with_positions) + + + + + + + + + grouped = {} + for entry in text_with_positions: + key = entry[0] + grouped.setdefault(key, []).append(entry) + + # Filter the groups: if any entry in a group has a non-None Text Nbs, keep only one of those + filtered_results = [] + for key, entries in grouped.items(): + # Find the first entry with a valid textNBS (non-None) + complete = next((entry for entry in entries if entry[1] is not None), None) + if complete: + filtered_results.append(complete) + else: + # If none are complete, you can choose to keep just one entry + filtered_results.append(entries[0]) + + text_with_positions=filtered_results + + for entity in msp: + if entity.dxftype() == 'HATCH': + + cntPoints=[] + for path in entity.paths: + + # path_type = path.type + + # # Resolve the path type to its name + # path_type_name = BoundaryPathType(path_type).name + # print(f"Encountered path type: {path_type_name}") + + vertices = [] # Reset vertices for each path + + # print(str(path.type)) + + if str(path.type) == 'BoundaryPathType.POLYLINE' or path.type == 1: + # if path.type == 2: # Polyline path + # Handle POLYLINE type HATCH + vertices = [(vertex[0] * FinalRatio, vertex[1] * FinalRatio) for vertex in path.vertices] + # print("Hatch Vertices = ",vertices) + + if len(vertices) > 3: + poly = ShapelyPolygon(vertices) + minx, miny, maxx, maxy = poly.bounds + width = maxx - minx + height = maxy - miny + + + + + if (poly.area > 0 and (height > 0.2 or width > 0.2)): + + length = height + if(width > length): + length = width + + area1 = round(poly.area, 3) + perimeter = round(poly.length, 3) + # print("Vertices = ",vertices) + normalized_vertices = normalize_vertices(vertices) + + rgb_color = get_hatch_color(entity) + # print("rgb_color = ",rgb_color) + + # if(rgb_color == (255, 255, 255)): + # if(len(text_with_positions)>0): + + # for text, position, color in text_with_positions: + # text_position = Point(position[0], position[1]) + + # if poly.contains(text_position): + # rgb_color = color + # break + + duplicate_found = False + for existing_vertices, existing_area in unique_shapes: + if normalized_vertices == existing_vertices and areas_are_similar(area1, existing_area): + duplicate_found = True + break + + if not duplicate_found: + # rgb_color = get_hatch_color(entity) # Assuming this function exists + unique_shapes.append((normalized_vertices, area1)) + + if length > 0.6: + print(rgb_color) + print(coloredarray) + if ( len(coloredarray) > 0 and (rgb_color in coloredarray)): + hatched_areas.append([vertices, area1, length, rgb_color]) + elif (len(coloredarray) == 0): + hatched_areas.append([vertices, area1, length, rgb_color]) + + + elif str(path.type) == 'BoundaryPathType.EDGE' or path.type == 2: + # elif path.type == 2: # Edge path + # Handle EDGE type HATCH + vert = [] + for edge in path.edges: + x, y = edge.start + x1, y1 = edge.end + vert.append((x * FinalRatio, y * FinalRatio)) + vert.append((x1 * FinalRatio, y1 * FinalRatio)) + + poly = ShapelyPolygon(vert) + minx, miny, maxx, maxy = poly.bounds + width = maxx - minx + height = maxy - miny + + if (poly.area > 0 and (height > 0.2 or width > 0.2)): + + length = height + if(width > length): + length = width + + area1 = round(poly.area, 3) + perimeter = round(poly.length, 3) + normalized_vertices = normalize_vertices(vert) + rgb_color = get_hatch_color(entity) + # print("rgb_color = ",rgb_color) + + # if(rgb_color == (255, 255, 255)): + # if(len(text_with_positions)>0): + # for text, position, color in text_with_positions: + # text_position = Point(position[0], position[1]) + + # if poly.contains(text_position): + # rgb_color = color + # break + + + duplicate_found = False + for existing_vertices, existing_area in unique_shapes: + if normalized_vertices == existing_vertices and areas_are_similar(area1, existing_area): + duplicate_found = True + break + + if not duplicate_found: + # rgb_color = get_hatch_color(entity) # Assuming this function exists + unique_shapes.append((normalized_vertices, area1)) + + if length > 0.6: + if ( len(coloredarray) > 0 and (rgb_color in coloredarray)): + hatched_areas.append([vert, area1, length, rgb_color]) + elif (len(coloredarray) == 0): + hatched_areas.append([vert, area1, length, rgb_color]) + else: + print(f"Encountered path type: {path.type}") + + elif entity.dxftype() == 'SOLID': + + + + vertices = [entity.dxf.vtx0 * (FinalRatio), entity.dxf.vtx1* (FinalRatio), entity.dxf.vtx2* (FinalRatio), entity.dxf.vtx3* (FinalRatio)] + poly = ShapelyPolygon(vertices) + minx, miny, maxx, maxy = poly.bounds + + # Calculate the width and height of the bounding box + width = maxx - minx + height = maxy - miny + + if (poly.area > 0 and (height > 0 and width > 0)): + area1 = round(poly.area, 3) + perimeter = round(poly.length, 3) + normalized_vertices = normalize_vertices(vertices) + + duplicate_found = False + for existing_vertices, existing_area in unique_shapes: + if normalized_vertices == existing_vertices or areas_are_similar(area1, existing_area): + duplicate_found = True + break + + if not duplicate_found: + rgb_color = get_hatch_color(entity) # Assuming this function exists + unique_shapes.append((normalized_vertices, area1)) + if ( len(coloredarray) > 0 and (rgb_color in coloredarray)): + hatched_areas.append([vertices, area1, perimeter, rgb_color]) + elif (len(coloredarray) == 0): + hatched_areas.append([vertices, area1, perimeter, rgb_color]) + + + + + elif entity.dxftype() == 'LWPOLYLINE': + + vertices = [] + lwpolyline = entity + points = lwpolyline.get_points() + flag = 0 + + # Collect vertices and apply the FinalRatio + for i in range(len(points)): + vertices.append([points[i][0] * FinalRatio, points[i][1] * FinalRatio]) + + # # Ensure there are more than 3 vertices + if len(vertices) > 3: + # Check if the polyline is closed + if vertices[0][0] == vertices[-1][0] or vertices[0][1] == vertices[-1][1]: + poly = ShapelyPolygon(vertices) + minx, miny, maxx, maxy = poly.bounds + + # Calculate width and height of the bounding box + width = maxx - minx + height = maxy - miny + + # Check area and size constraints + if (poly.area > 0 and (height > 0 and width > 0)): + area1 = round(poly.area, 3) + perimeter = round(poly.length, 3) + normalized_vertices = normalize_vertices(vertices) + + duplicate_found = False + for existing_vertices, existing_area in unique_shapes: + if normalized_vertices == existing_vertices or areas_are_similar(area1, existing_area): + duplicate_found = True + break + + if not duplicate_found: + rgb_color = get_hatch_color(entity) # Assuming this function exists + unique_shapes.append((normalized_vertices, area1)) + hatched_areas.append([vertices, area1, perimeter, rgb_color]) + + + + elif entity.dxftype() == 'POLYLINE': + + flag=0 + vertices = [(v.dxf.location.x * (FinalRatio), v.dxf.location.y * (FinalRatio)) for v in entity.vertices] + # print('Vertices:', vertices) + + if(len(vertices)>3): + + if(vertices[0][0] == vertices[len(vertices)-1][0] or vertices[0][1] == vertices[len(vertices)-1][1]): + + poly=ShapelyPolygon(vertices) + minx, miny, maxx, maxy = poly.bounds + + # Calculate the width and height of the bounding box + width = maxx - minx + height = maxy - miny + + if (poly.area > 0 and (height > 0 and width > 0)): + area1 = round(poly.area,3) + perimeter = round (poly.length,3) + normalized_vertices = normalize_vertices(vertices) + + duplicate_found = False + for existing_vertices, existing_area in unique_shapes: + if normalized_vertices == existing_vertices or areas_are_similar(area1, existing_area): + duplicate_found = True + break + + if not duplicate_found: + rgb_color = get_hatch_color(entity) # Assuming this function exists + unique_shapes.append((normalized_vertices, area1)) + hatched_areas.append([vertices, area1, perimeter, rgb_color]) + + + elif entity.dxftype() == 'SPLINE': + + spline_entity = entity + vertices = [] + control_points = spline_entity.control_points + if(len(control_points)>3): + for i in range(len(control_points)): + vertices.append([control_points[i][0]* (FinalRatio),control_points[i][1]* (FinalRatio)]) + poly=ShapelyPolygon(vertices) + + minx, miny, maxx, maxy = poly.bounds + + # Calculate the width and height of the bounding box + width = maxx - minx + height = maxy - miny + + + if (poly.area > 0 and (height > 0 and width > 0)): + area1 = round(poly.area,3) + perimeter = round (poly.length,3) + normalized_vertices = normalize_vertices(vertices) + + duplicate_found = False + for existing_vertices, existing_area in unique_shapes: + if normalized_vertices == existing_vertices or areas_are_similar(area1, existing_area): + duplicate_found = True + break + + if not duplicate_found: + rgb_color = get_hatch_color(entity) # Assuming this function exists + unique_shapes.append((normalized_vertices, area1)) + hatched_areas.append([vertices, area1, perimeter, rgb_color]) + + + + sorted_data = sorted(hatched_areas, key=lambda x: x[1]) + return sorted_data,text_with_positions + + +"""### Rotate polygon""" + + + +def rotate_point(point, angle,pdfrotation,width,height, center_point=(0, 0)): + """Rotates a point around center_point(origin by default) + Angle is in degrees. + Rotation is counter-clockwise + """ + angle_rad = radians(angle % 360) + # Shift the point so that center_point becomes the origin + new_point = (point[0] - center_point[0], point[1] - center_point[1]) + new_point = (new_point[0] * cos(angle_rad) - new_point[1] * sin(angle_rad), + new_point[0] * sin(angle_rad) + new_point[1] * cos(angle_rad)) + # Reverse the shifting we have done + if pdfrotation!=0: + + new_point = (new_point[0]+width + center_point[0], new_point[1] + center_point[1]) #pdfsize[2] is the same as +width + else: + + new_point = (new_point[0] + center_point[0], new_point[1]+ height + center_point[1]) # pdfsize[3] is the same as +height + # new_point = (new_point[0] + center_point[0], new_point[1] + center_point[1]) + return new_point + + +def rotate_polygon(polygon, angle, pdfrotation,width,height,center_point=(0, 0)): + """Rotates the given polygon which consists of corners represented as (x,y) + around center_point (origin by default) + Rotation is counter-clockwise + Angle is in degrees + """ + rotated_polygon = [] + for corner in polygon: + rotated_corner = rotate_point(corner, angle,pdfrotation,width,height, center_point) + rotated_polygon.append(rotated_corner) + return rotated_polygon + +#create a dataframe containing color , count(how many times is this object found in the plan), area of 1 of these shapes, total area +#perimeter, totat perimeter, length, total length +#import pandas as pd +#SimilarAreaDictionary= pd.DataFrame(columns=['Guess','Color','Occurences','Area','Total Area','Perimeter','Total Perimeter','Length','Total Length','R','G','B']) +#loop 3la hatched areas and count the occurences of each shape w create a table bl hagat di + + + +def Create_DF(dxfpath,datadoc,hatched_areas,pdf_content=0): + + if pdf_content: + FinalRatio,width_dxf= RetriveRatio(datadoc,dxfpath,pdf_content) + else: + FinalRatio,width_dxf= RetriveRatio(datadoc,dxfpath) + # hatched_areas = get_hatched_areas(datadoc,dxfpath,FinalRatio) + + # hatched_areas=remove_duplicate_shapes(new_hatched_areas) + + # SimilarAreaDictionary= pd.DataFrame(columns=['Area', 'Total Area', 'Perimeter', 'Total Perimeter', 'Occurences', 'Color']) + SimilarAreaDictionary= pd.DataFrame(columns=['Guess','Color','Occurences','Area','Total Area','Perimeter','Total Perimeter','Length','Total Length','Texts','Comments']) + + # colorRanges2=generate_color_array(30000) + # colorRanges = [[255, 0, 0], [0, 0, 255], [0, 255, 255], [0, 64, 0], [255, 204, 0], [255, 128, 64], [255, 0, 128], [255, 128, 192], [128, 128, 255], [128, 64, 0],[0, 255, 0],[0, 200, 0],[255, 128, 255], [128, 0, 255], [0, 128, 192], [128, 0, 128],[128, 0, 0], [0, 128, 255], [149, 1, 70], [255, 182, 128], [222, 48, 71], [240, 0, 112], [255, 0, 255], [192, 46, 65], [0, 0, 128],[0, 128, 64],[255, 255, 0], [128, 0, 80], [255, 255, 128], [90, 255, 140],[255, 200, 20],[91, 16, 51], [90, 105, 138], [114, 10, 138], [36, 82, 78], [225, 105, 190], [108, 150, 170], [11, 35, 75], [42, 176, 170], [255, 176, 170], [209, 151, 15],[81, 27, 85], [226, 106, 122], [67, 119, 149], [159, 179, 140], [159, 179, 30],[255, 85, 198], [255, 27, 85], [188, 158, 8],[140, 188, 120], [59, 61, 52], [65, 81, 21], [212, 255, 174], [15, 164, 90],[41, 217, 245], [213, 23, 182], [11, 85, 169], [78, 153, 239], [0, 66, 141],[64, 98, 232], [140, 112, 255], [57, 33, 154], [194, 117, 252], [116, 92, 135], [74, 43, 98], [188, 13, 123], [129, 58, 91], [255, 128, 100], [171, 122, 145], [255, 98, 98], [222, 48, 77]] + # colorUsed=[] + TotalArea=0 + TotalPerimeter=0 + for shape in hatched_areas: + area = shape[1] # area + perimeter = shape[2] # perimeter + # if(i < len(colorRanges)): + # color = colorRanges[i] + # colorUsed.append(color) + # else: + # color = colorRanges2[i] + # colorUsed.append(color) + TotalArea = area + TotalPerimeter = perimeter + tol=0 + condition1 = (SimilarAreaDictionary['Area'] >= area - tol) & (SimilarAreaDictionary['Area'] <= area +tol) + condition2 = (SimilarAreaDictionary['Perimeter'] >= perimeter -tol) & (SimilarAreaDictionary['Perimeter'] <= perimeter +tol) + combined_condition = condition1 & condition2 + + if any(combined_condition): + index = np.where(combined_condition)[0][0] + SimilarAreaDictionary.at[index, 'Occurences'] += 1 + SimilarAreaDictionary.at[index, 'Total Area'] = SimilarAreaDictionary.at[index, 'Area'] * SimilarAreaDictionary.at[index, 'Occurences'] + SimilarAreaDictionary.at[index, 'Total Perimeter'] = SimilarAreaDictionary.at[index, 'Perimeter'] * SimilarAreaDictionary.at[index, 'Occurences'] + else: + TotalArea=area + TotalPerimeter=perimeter + # print("Shape[3]",shape[3]) + new_data = {'Area': area, 'Total Area': TotalArea ,'Perimeter': perimeter, 'Total Perimeter': TotalPerimeter, 'Occurences': 1, 'Color':shape[3],'Comments':''} #add color here and read color to insert in + SimilarAreaDictionary = pd.concat([SimilarAreaDictionary, pd.DataFrame([new_data])], ignore_index=True) + + # print(SimilarAreaDictionary) + return SimilarAreaDictionary +"""### Draw on Image and PDF""" + +# from sklearn.cluster import KMeans + +def color_distance(color1, color2): + print("color1 = ",color1) + print("color2 = ",color2) + print("abs(color1[0] - color2[0]) = ",abs(color1[0] - color2[0])) + print("abs(color1[1] - color2[1]) = ",abs(color1[1] - color2[1])) + print("abs(color1[2] - color2[2]) = ",abs(color1[2] - color2[2])) + if(abs(color1[0] - color2[0]) < 20 and + abs(color1[1] - color2[1]) < 20 and + abs(color1[2] - color2[2]) < 20): + return 1 + else: + return 100 + # return np.sqrt(sum((a - b) ** 2 for a, b in zip(color1, color2))) + +# Unify colors within a distance threshold +def unify_colors(df, threshold=20): + # Convert colors to tuple if they are not already in tuple format + df['Color'] = df['Color'].apply(lambda x: tuple(x) if isinstance(x, list) else x) + + # Iterate through the DataFrame and compare each color with the next one + for i in range(len(df) - 1): # We don't need to compare the last color with anything + current_color = df.at[i, 'Color'] + next_color = df.at[i + 1, 'Color'] + + # If the distance between current color and the next color is smaller than the threshold + if color_distance(current_color, next_color) <= threshold: + # Make both the same color (unify them to the current color) + df.at[i + 1, 'Color'] = current_color # Change the next color to the current color + + return df + +def normalize_color(color): + """Convert PDF color (range 0-1) to RGB (range 0-255).""" + return tuple(min(max(round(c * 255), 0), 255) for c in color) + + +def color_close_enough(c1, c2, threshold=10): + return all(abs(a - b) <= threshold for a, b in zip(c1, c2)) + +def adjustannotations(OutputPdfStage1,text_with_positions): + input_pdf_path = OutputPdfStage1 + output_pdf_path = "Final-WallsAdjusted.pdf" + annotations_data = [] + + # Load the input PDF + pdf_bytes_io = BytesIO(OutputPdfStage1) + + reader = PdfReader(pdf_bytes_io) + writer = PdfWriter() + + # Append all pages to the writer + writer.append_pages_from_reader(reader) + + # Add metadata (optional) + metadata = reader.metadata + writer.add_metadata(metadata) + + for page_index, page in enumerate(writer.pages): + if "/Annots" not in page: + continue + + for annot in page["/Annots"]: + obj = annot.get_object() + subtype = obj.get("/Subtype") + + # Group vertices for metadata + if subtype == "/Line": + raw_vertices = obj.get("/L", []) + else: + raw_vertices = obj.get("/Vertices", []) + vertices = group_vertices(raw_vertices) + + # Normalize color + raw_color = obj.get("/C") + try: + annot_color = normalize_color(raw_color) + except: + annot_color = raw_color + + # Extract measurement from annotation content + measurement = extract_measurement(obj) + # Assign to area or perimeter based on subtype + area = measurement if subtype == "/Polygon" else None + perimeter = measurement if subtype in ["/Line", "/PolyLine"] else None + + # Match text and NBS + matched_text = None + matched_nbs = None + if subtype in ["/Line", "/PolyLine", "/Polygon"] and raw_color: + matched_entry = next( + ((t, n) for t, n, _, c in text_with_positions if color_close_enough(annot_color, c)), + (None, None) + ) + matched_text, matched_nbs = matched_entry + combined = "" + if matched_text and matched_nbs: + combined = f"{matched_text} - {matched_nbs}" + elif matched_text: + combined = matched_text + elif matched_nbs: + combined = matched_nbs + if combined: + obj.update({NameObject("/T"): TextStringObject(combined)}) + + # Update annotation dictionaries for measurement type + if subtype == "/Line" and obj.get("/Subj", "") == "Perimeter Measurement": + obj.update({ + NameObject("/Measure"): DictionaryObject({ + NameObject("/Type"): NameObject("/Measure"), + NameObject("/L"): DictionaryObject({ + NameObject("/G"): FloatObject(1), + NameObject("/U"): TextStringObject("m"), + }), + }), + NameObject("/IT"): NameObject("/LineDimension"), + NameObject("/Subj"): TextStringObject("Length Measurement"), + }) + if subtype == "/Polygon" and obj.get("/Subj", "") == "Area Measurement": + obj.update({ + NameObject("/Measure"): DictionaryObject({ + NameObject("/Type"): NameObject("/Measure"), + NameObject("/Area"): DictionaryObject({ + NameObject("/G"): FloatObject(1), + NameObject("/U"): TextStringObject("sq m"), + }), + }), + NameObject("/IT"): NameObject("/Area_Annotation"), + NameObject("/Subj"): TextStringObject("Area Measurement"), + }) + + # Append metadata + annotations_data.append([ + vertices, + area, + perimeter, + annot_color, + matched_text, + matched_nbs, + ]) + + + + output_pdf_io = BytesIO() + writer.write(output_pdf_io) + output_pdf_io.seek(0) + + print(f"Annotations updated and saved to {output_pdf_path}") + return output_pdf_io.read() , annotations_data + +def distance(rect1, rect2): + """Calculate the Euclidean distance between two annotation centers.""" + x1, y1 = (float(rect1[0]) + float(rect1[2])) / 2, (float(rect1[1]) + float(rect1[3])) / 2 + x2, y2 = (float(rect2[0]) + float(rect2[2])) / 2, (float(rect2[1]) + float(rect2[3])) / 2 + return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) + +def group_vertices(raw): + """Convert flat list [x1,y1,x2,y2,...] into [[x1,y1],[x2,y2],...]""" + if not raw or len(raw) < 2: + return [] + return [[float(raw[i]), float(raw[i+1])] for i in range(0, len(raw), 2)] + +def group_rect(verts): + """Convert list of [x,y] vertices into a bounding rect [x_min, y_min, x_max, y_max].""" + xs = [v[0] for v in verts] + ys = [v[1] for v in verts] + return [min(xs), min(ys), max(xs), max(ys)] if verts else None + +def extract_measurement(obj): + """Extract first numeric measurement from an annotation's /Contents.""" + contents = obj.get("/Contents") + if not contents: + return None + match = re.search(r"([0-9]*\.?[0-9]+)", str(contents)) + return float(match.group(1)) if match else None + +def remove_duplicate_annotations(pdf_path, threshold): + """Remove one of the duplicate annotations if they are close and have the same color.""" + + input_pdf_path = pdf_path + output_pdf_path = "Filtered-Walls.pdf" + + # Load the input PDF + pdf_bytes_io = BytesIO(pdf_path) + + reader = PdfReader(pdf_bytes_io) + writer = PdfWriter() + + # Append all pages to the writer + # writer.append_pages_from_reader(reader) + + # Add metadata (optional) + metadata = reader.metadata + writer.add_metadata(metadata) + + for page_index in range(len(reader.pages)): + page = reader.pages[page_index] + + if "/Annots" in page: + annotations = page["/Annots"] + annots_data = [] + to_delete = set() + + # Extract annotation positions and colors + # for annot_index, annot_ref in enumerate(annotations): + # annot = annot_ref.get_object() + + # if "/Rect" in annot and "/C" in annot: + # rect = annot["/Rect"] + # if isinstance(rect, ArrayObject): # Ensure rect is a list + # rect = list(rect) + + # color = normalize_color(annot["/C"]) + # annots_data.append((annot_index, rect, color)) + + for i, annot_ref in enumerate(annotations): + annot = annot_ref.get_object() + rect = annot.get("/Rect") + color = annot.get("/C") + + if rect and color and isinstance(rect, ArrayObject) and len(rect) == 4: + norm_color = normalize_color(color) + annots_data.append((i, list(rect), norm_color)) + + + for i, (idx1, rect1, color1) in enumerate(annots_data): + if idx1 in to_delete: + continue + for j in range(i + 1, len(annots_data)): + idx2, rect2, color2 = annots_data[j] + if idx2 in to_delete: + continue + if color_close_enough(color1, color2) and distance(rect1, rect2) < threshold: + to_delete.add(idx2) + + # Keep only non-duplicates + new_annots = [annotations[i] for i in range(len(annotations)) if i not in to_delete] + page[NameObject("/Annots")] = ArrayObject(new_annots) + # Compare distances and mark duplicates + # for i, (idx1, rect1, color1) in enumerate(annots_data): + # if idx1 in to_delete: + # continue + # for j, (idx2, rect2, color2) in enumerate(annots_data[i+1:], start=i+1): + # if idx2 in to_delete: + # continue + # if color1 == color2 and distance(rect1, rect2) < threshold: + # to_delete.add(idx2) # Mark second annotation for deletion + + # # Remove duplicates + # new_annotations = [annotations[i] for i in range(len(annotations)) if i not in to_delete] + # page[NameObject("/Annots")] = ArrayObject(new_annotations) + + writer.add_page(page) + + output_pdf_io = BytesIO() + writer.write(output_pdf_io) + output_pdf_io.seek(0) + + return output_pdf_io.read() + + +def rect_distance(r1, r2): + """Euclidean distance between rect centers.""" + if not r1 or not r2: + return float('inf') + cx1, cy1 = (r1[0]+r1[2])/2, (r1[1]+r1[3])/2 + cx2, cy2 = (r2[0]+r2[2])/2, (r2[1]+r2[3])/2 + return math.hypot(cx2-cx1, cy2-cy1) + +def group_rect(verts): + """Turn [[x,y],…] into (x_min, y_min, x_max, y_max).""" + xs = [x for x,_ in verts] + ys = [y for _,y in verts] + return (min(xs), min(ys), max(xs), max(ys)) if verts else None + +def clean_annotations(annotations_data, threshold): + """ + Remove “nearby” duplicates from annotations_data, + where each entry is EITHER a dict with a 'vertices' key + OR a list/tuple whose first element *is* the vertices list. + """ + # 1) Extract a parallel list of bounding rects + rects = [] + for item in annotations_data: + if isinstance(item, dict): + verts = item.get('vertices', []) + elif isinstance(item, (list, tuple)) and item: + # heuristically assume the first element is vertices + verts = item[0] if isinstance(item[0], list) else [] + else: + verts = [] + rects.append(group_rect(verts)) + + # 2) Mark duplicates + to_delete = set() + for i, r1 in enumerate(rects): + if i in to_delete: + continue + for j in range(i+1, len(rects)): + if j in to_delete: + continue + if rect_distance(r1, rects[j]) < threshold: + to_delete.add(j) + + # 3) Build cleaned list + cleaned = [] + for idx, item in enumerate(annotations_data): + if idx not in to_delete: + cleaned.append(item) + + return cleaned + + +def calculate_distance(p1, p2): + return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) + +def ROI_boundingBoxCoor(img): + # Threshold (invert: walls are white) + imgGray= cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + _, thresh = cv2.threshold(imgGray, 250, 255, cv2.THRESH_BINARY_INV) + + # Morphological cleanup + kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)) + walls = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) + + # --- Connected components --- + num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(walls, connectivity=8) + + best_idx = None + best_score = -1 + h, w = walls.shape + cx_img, cy_img = w//2, h//2 # image center + + for i in range(1, num_labels): # ignore background (0) + x, y, bw, bh, area = stats[i] + cx, cy = centroids[i] + + # Score = area minus distance penalty + #prefer large area AND closeness to image center + dist = np.hypot(cx - cx_img, cy - cy_img) + score = area - 0.5 * dist #tune 0.5 + + if score > best_score: + best_score = score + best_idx = i + + # --- Get bounding box of best region --- + bbox = None + if best_idx is not None: + x, y, bw, bh, _ = stats[best_idx] + margin = 50 + x = int(max(x - margin, 0)) + y = int(max(y - margin, 0)) + bw = int(min(bw + 2*margin, w-x)) + bh = int(min(bh + 2*margin, h-y)) + + # Define bounding box as (x_min, y_min, x_max, y_max) + bbox = (x, y, x+bw, y+bh) + + # Draw ROI + imgcopy = img.copy() + cv2.rectangle(imgcopy, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0,255,0), 2) + + mask = np.zeros_like(img) + cv2.rectangle(mask, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (255,255,255), -1) + main_zone = cv2.bitwise_and(img, mask) + + return imgcopy, main_zone, bbox + + return img, img, bbox + +def draw_bb_onPDF(doc,bbox): + page = doc[0] + x1, y1 = bbox[0],bbox[1] + x2, y2 = bbox[2],bbox[3] + + p1 = fitz.Point(x1,y1) + p2 = fitz.Point(x2,y2) + + p1=p1*page.derotation_matrix + p2=p2*page.derotation_matrix + + rect = fitz.Rect(p1, p2).normalize() + x0, y0, x1, y1 = rect.x0, rect.y0, rect.x1, rect.y1 + pdf_bbox=[x0, y0, x1, y1] + + page.draw_rect(rect) #for visualization only + doc.save('kk.pdf') #ffor visualization only + return pdf_bbox + +def mainFunctionDrawImgPdf(datadoc,dxfpath, dxfratio,SearchArray,CorrectionRatio,CollectedColors,points_Of_drawing_Canvas,Thickness,pdfpath=0,pdfname=0,pdf_content=0): + print("points_Of_drawing_Canvas in 2.7 = ",points_Of_drawing_Canvas) + print("CollectedColors = ",CollectedColors) + OutputPdfStage1='BB Trial.pdf' + if pdf_content: + FinalRatio,width_dxf= RetriveRatio(datadoc,dxfpath,pdf_content) + else: + FinalRatio,width_dxf= RetriveRatio(datadoc,dxfpath) + + # hatched_areas = get_hatched_areas(datadoc,dxfpath,FinalRatio) + # hatched_areas=remove_duplicate_shapes(new_hatched_areas) + if pdf_content: + img,pix2=pdftoimg(datadoc,pdf_content) + else: + img,pix2=pdftoimg(datadoc) + flipped_horizontal=flip(img) + allcnts = [] + imgg = flipped_horizontal + # imgtransparent1=imgg.copy() + if pdf_content: + doc = fitz.open(stream=pdf_content, filetype="pdf") + else: + doc = fitz.open('pdf',datadoc) + page2 = doc[0] + rotationOld=page2.rotation + derotationMatrix=page2.derotation_matrix + # print("Derotation Matrix = ",derotationMatrix) + pix=page2.get_pixmap() + width=abs(page2.mediabox[2])+abs(page2.mediabox[0]) + height=abs(page2.mediabox[3])+abs(page2.mediabox[1]) + print('mediabox', width , height) + + Correction = CorrectionRatio / width_dxf + print("Correction Factor = ",round((Correction/FinalRatio),1)) + dxfratio = dxfratio * round((Correction/FinalRatio),1) + print("new omar dxfRatio = ",dxfratio) + + imgcopy, main_zone, bbox = ROI_boundingBoxCoor(img) #send here bgr img not gray + pdf_bbox=draw_bb_onPDF(doc,bbox) + bxmin, bymin, bxmax, bymax = pdf_bbox + + +# print('olddxfratio',dxfratio) +# correction_factor= detect_scale_from_page(dxfpath,width,dxfratio/1000) + +# factor=1 +# print('corr_factor',correction_factor) +# if correction_factor <0.26: #if less than 0.25 then the dxf ratio is correeect, if greater then *2 +# factor=1 +# print('Ratio working: keep as it is') +# else: +# factor =2 +# print('Ratio was adjusted to be ur input ratio x2') + +# dxfratio=dxfratio*factor +# print('new dxfratio', dxfratio) + + if page2.rotation!=0: + + rotationangle = page2.rotation + page2.set_rotation(0) + ratio = pix.width/ img.shape[0] + else: + ratio = pix.width/ img.shape[1] + rotationangle = 270 + + hatched_areas,text_with_positions = get_hatched_areas(datadoc,dxfpath,FinalRatio,rotationangle,SearchArray,CollectedColors) + allshapes=[] + # Iterate through each polygon in metric units + NewColors = [] + if pdf_content: + SimilarAreaDictionary=Create_DF(dxfpath,datadoc,hatched_areas,pdf_content) + else: + SimilarAreaDictionary=Create_DF(dxfpath,datadoc,hatched_areas) + i=0 + flagcolor = 0 + ColorCounter = 0 + ColorCheck=[] + deleterows = [] + + + # def color_distance(color1, color2): + # return np.sqrt(sum((a - b) ** 2 for a, b in zip(color1, color2))) + + color_margin = 2 # Define margin threshold + + for polygon in hatched_areas: + cntPoints = [] + cntPoints1 = [] + shapeePerimeter = [] + shapeeArea = [] + Text_Detected = 0 + + blackImgShapes = np.zeros(imgg.shape[:2], dtype="uint8") + blackImgShapes= cv2.cvtColor(blackImgShapes, cv2.COLOR_GRAY2BGR) + + # Convert each vertex from metric to pixel coordinates + for vertex in polygon[0]: + x = (vertex[0]) *dxfratio + y = (vertex[1]) *dxfratio + if rotationangle==0: + if y<0: + y=y*-1 + cntPoints.append([int(x), int(y)]) + cntPoints1.append([x, y]) + + cv2.drawContours(blackImgShapes, [np.array(cntPoints)], -1, ([255,255,255]), thickness=-1) + x, y, w, h = cv2.boundingRect(np.array(cntPoints)) + firstpoint = 0 + for poi in np.array(cntPoints1): + if firstpoint == 0: + x2, y2 = poi + p2 = fitz.Point(x2,y2) + # p1 = fitz.Point(x1,y1) + p2=p2*derotationMatrix + shapeePerimeter.append([p2[0],p2[1]]) + firstpoint = 1 + else: + x1, y1 = poi + p1 = fitz.Point(x1,y1) + # p1 = fitz.Point(x1,y1) + p1=p1*derotationMatrix + # print("P1 = ",p1) + shapeePerimeter.append([p1[0],p1[1]]) + + shapeePerimeter.append([p2[0],p2[1]]) + shapeePerimeter=np.flip(shapeePerimeter,1) + shapeePerimeter=rotate_polygon(shapeePerimeter,rotationangle,rotationOld,width,height) + + for poi in np.array(cntPoints1): + x1, y1 = poi + p1 = fitz.Point(x1,y1) + # p1 = fitz.Point(x1,y1) + p1=p1*derotationMatrix + # print("P1 = ",p1) + shapeeArea.append([p1[0],p1[1]]) + + shapeeArea.append([p2[0],p2[1]]) + shapeeArea=np.flip(shapeeArea,1) + shapeeArea=rotate_polygon(shapeeArea,rotationangle,rotationOld,width,height) + + tol=0 + condition1 = (SimilarAreaDictionary['Area'] >= polygon[1] - tol) & (SimilarAreaDictionary['Area'] <= polygon[1] +tol) + condition2 = (SimilarAreaDictionary['Perimeter'] >= polygon[2] -tol) & (SimilarAreaDictionary['Perimeter'] <= polygon[2] +tol) + combined_condition = condition1 & condition2 + # print("combined_condition = ",combined_condition) + + if any(combined_condition): + + flagcolor = 1 + index = np.where(combined_condition)[0][0] + # print(SimilarAreaDictionary.at[index, 'Color']) + NewColors=SimilarAreaDictionary.at[index, 'Color'] + + else: + flagcolor = 2 + NewColors=SimilarAreaDictionary.at[i, 'Color'] + # flagcolor = 2 + + # cv2.drawContours(imgg, [np.array(cntPoints)], -1, (NewColors), thickness=2) + # print("new color = ",NewColors) + # print("New Colors = ",NewColors) + # if img is not None or img.shape[0] != 0 or img.shape[1] != 0: + if(int(NewColors[0])==255 and int(NewColors[1])==255 and int(NewColors[2])==255): + + WhiteImgFinal = cv2.bitwise_and(blackImgShapes,imgg) + # print("length = ",WhiteImgFinal.shape[0]) + # print("width = ",WhiteImgFinal.shape[1]) + flipped=flip(WhiteImgFinal) + # print("Flipped") + # cv2_imshow(flipped) + + imgslice = WhiteImgFinal[y:y+h, x:x+w] + # print("length slice = ",imgslice.shape[0]) + # print("width slice = ",imgslice.shape[1]) + if(imgslice.shape[0] != 0 and imgslice.shape[1] != 0): + flippedSlice=flip(imgslice) + # print("Sliced & Flipped") + # cv2_imshow(flippedSlice) + + # Convert flippedSlice to PIL for color extraction + flippedSlice_pil = Image.fromarray(flippedSlice) + + # Define patch size for color sampling (e.g., 10x10 pixels) + patch_size = 100 + patch_colors = [] + + # Loop through patches in the image + for i in range(0, flippedSlice_pil.width, patch_size): + for j in range(0, flippedSlice_pil.height, patch_size): + # Crop a patch from the original image + patch = flippedSlice_pil.crop((i, j, i + patch_size, j + patch_size)) + patch_colors += patch.getcolors(patch_size * patch_size) + + # Calculate the dominant color from all patches + max_count = 0 + dominant_color = None + tolerance = 5 + black_threshold = 30 # Max RGB value for a color to be considered "black" + white_threshold = 225 # Min RGB value for a color to be considered "white" + + for count, color in patch_colors: + # Exclude colors within the black and white ranges + if not (all(c <= black_threshold for c in color) or all(c >= white_threshold for c in color)): + # Update if the current color has a higher count than previous max + if count > max_count: + max_count = count + dominant_color = color + + # print("Dominant Color =", dominant_color) + + # Append dominant color to ColorCheck and update NewColors + if dominant_color is not None: + ColorCheck.append(dominant_color) + + NewColors = None # Initialize NewColors + + for color in ColorCheck: + # Check if the current color is within the tolerance + # print("color = ",color) + # print("dominant_color = ",dominant_color) + if (abs(color[0] - dominant_color[0]) < 20 and + abs(color[1] - dominant_color[1]) < 20 and + abs(color[2] - dominant_color[2]) < 20): + NewColors = (color[2], color[1], color[0]) # Set the new color + break + else: + # If no color in ColorCheck meets the tolerance, use the dominant color + NewColors = (dominant_color[2], dominant_color[1], dominant_color[0]) + # break + + # Avoid appending `dominant_color` again unnecessarily + if NewColors not in ColorCheck: + ColorCheck.append(NewColors) + + if flagcolor == 1: + SimilarAreaDictionary.at[index, 'Color'] = NewColors + # # print(f"Updated Color at index {index} with {NewColors}.") + elif flagcolor == 2: + SimilarAreaDictionary.at[i, 'Color'] = NewColors + # print("New Colors = ",NewColors) + cv2.drawContours(imgg, [np.array(cntPoints)], -1, ([NewColors[2],NewColors[1],NewColors[0]]), thickness=3) + + + + + start_point1 = shapeePerimeter[0] + end_point1 = shapeePerimeter[1] + start_point2 = shapeePerimeter[0] + end_point2 = shapeePerimeter[-2] + + distance1 = calculate_distance(start_point1, end_point1) + distance2 = calculate_distance(start_point2, end_point2) + + + + # Divide the shapePerimeter into two halves + half_index = len(shapeePerimeter) // 2 + half1 = shapeePerimeter[1:half_index+1] + half2 = shapeePerimeter[half_index:] + # half1 = shapeePerimeter[1:half_index] + # half2 = shapeePerimeter[half_index:-1] + + + + # Calculate distances for the halves + if len(half1) >= 2: + half1_distance = sum(calculate_distance(half1[i], half1[i + 1]) for i in range(len(half1) - 1)) + else: + half1_distance = 0 + + if len(half2) >= 2: + half2_distance = sum(calculate_distance(half2[i], half2[i + 1]) for i in range(len(half2) - 1)) + else: + half2_distance = 0 + + max_distance = max(distance1, distance2, half1_distance) + + if max_distance == distance1: + # Draw the line annotation for distance1 + chosen_start = start_point1 + chosen_end = end_point1 + # annot12 = page2.add_line_annot(chosen_start, chosen_end) + points=[] + points.append(chosen_start) + points.append(chosen_end) + discard = False + # if(points_Of_drawing_Canvas): + print("Canva points = ",points_Of_drawing_Canvas) + if(points_Of_drawing_Canvas): + Boundingpolygon = np.array( + [(p['x'], p['y']) for p in points_Of_drawing_Canvas[0]['coordinates']], + dtype=np.float32 + ) + + for x, y in points: + # Check if the point is outside the polygon + result = cv2.pointPolygonTest(Boundingpolygon, (x, y), False) + if result < 0: # < 0 means point is outside + discard = True + break + else: + for point in points: + if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): + discard = True + break + # for point in points: + # if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): + # discard = True + # break + if not discard: + annot12 = page2.add_polyline_annot(points) + + elif max_distance == distance2: + # Draw the line annotation for distance2 + chosen_start = start_point2 + chosen_end = end_point2 + # annot12 = page2.add_line_annot(chosen_start, chosen_end) + points=[] + points.append(chosen_start) + points.append(chosen_end) + # annot12 = page2.add_polyline_annot(points) + points=[] + points.append(chosen_start) + points.append(chosen_end) + discard = False + print("Canva points = ",points_Of_drawing_Canvas) + if(points_Of_drawing_Canvas): + Boundingpolygon = np.array( + [(p['x'], p['y']) for p in points_Of_drawing_Canvas[0]['coordinates']], + dtype=np.float32 + ) + + for x, y in points: + # Check if the point is outside the polygon + result = cv2.pointPolygonTest(Boundingpolygon, (x, y), False) + if result < 0: # < 0 means point is outside + discard = True + break + else: + for point in points: + if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): + discard = True + break + + if not discard: + annot12 = page2.add_polyline_annot(points) + + elif max_distance == half1_distance: + # annot12 = page2.add_polyline_annot(half1) + max_pair_distance = 0.0 + max_pair_start = None + max_pair_end = None + + # 2. Loop through each consecutive pair in half1 + for i in range(len(half1) - 1): + p_current = half1[i] + p_next = half1[i + 1] + + # 3. Compute distance between these two points + dist = calculate_distance(p_current, p_next) + + # 4. Update max if this distance is greater + if dist > max_pair_distance: + max_pair_distance = dist + max_pair_start = p_current + max_pair_end = p_next + + # 5. After the loop, max_pair_start and max_pair_end represent + # the two consecutive points with the greatest separation. + if max_pair_start is not None and max_pair_end is not None: + # 6. Draw the line annotation using these two points + # annot12 = page2.add_line_annot(max_pair_start, max_pair_end) + points=[] + points.append(max_pair_start) + points.append(max_pair_end) + discard = False + print("Canva points = ",points_Of_drawing_Canvas) + if(points_Of_drawing_Canvas): + Boundingpolygon = np.array( + [(p['x'], p['y']) for p in points_Of_drawing_Canvas[0]['coordinates']], + dtype=np.float32 + ) + + for x, y in points: + # Check if the point is outside the polygon + result = cv2.pointPolygonTest(Boundingpolygon, (x, y), False) + if result < 0: # < 0 means point is outside + discard = True + break + else: + for point in points: + if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): + discard = True + break + + if not discard: + annot12 = page2.add_polyline_annot(points) + # print(f"Drew line annotation between {max_pair_start} and {max_pair_end}") + else: + # This case only occurs if half1 has fewer than 2 points + print("Not enough points in half1 to compute a line.") + + + discard = False + print("Canva points = ",points_Of_drawing_Canvas) + if(points_Of_drawing_Canvas): + Boundingpolygon = np.array( + [(p['x'], p['y']) for p in points_Of_drawing_Canvas[0]['coordinates']], + dtype=np.float32 + ) + + for x, y in points: + # Check if the point is outside the polygon + result = cv2.pointPolygonTest(Boundingpolygon, (x, y), False) + if result < 0: # < 0 means point is outside + discard = True + break + else: + for point in points: + if not (bxmin <= point[0] <= bxmax and bymin <= point[1] <= bymax): + discard = True + break + + if not discard: + annot12.set_border(width=0.8) + annot12.set_colors(stroke=(int(NewColors[0])/255,int(NewColors[1])/255,int(NewColors[2])/255)) + # annot12.set_info(content=str(polygon[2])+' m',subject='Perimeter Measurement', title="ADR Team") + annot12.set_info(subject='Perimeter Measurement',content=str(polygon[2])+' m') + annot12.set_opacity(0.8) + annot12.update() + + + i += 1 + alpha = 0.8 # Transparency factor. + + page2.set_rotation(rotationOld) + Correct_img=flip(imgg) + + image_new1 = cv2.addWeighted(Correct_img, alpha, img, 1 - alpha, 0) + SimilarAreaDictionary = SimilarAreaDictionary.fillna(' ') + + # Define white color to filter out + white_color = (255, 255, 255) + + # Delete rows where 'Guess' equals white_color + SimilarAreaDictionary = SimilarAreaDictionary[SimilarAreaDictionary['Color'] != white_color] + + # Reset the index to update row numbering + SimilarAreaDictionary.reset_index(drop=True, inplace=True) + + + grouped_df = SimilarAreaDictionary.groupby('Color').agg({ + 'Guess': 'first', + 'Occurences': 'sum', # Sum of occurrences for each color + 'Area':'first', + 'Total Area': 'sum', # Sum of areas for each color + 'Perimeter':'first', + 'Total Perimeter': 'sum', # Sum of perimeters for each color + 'Length':'first', + 'Total Length': 'sum', # Sum of lengths for each color + 'Texts': 'first', # Keep the first occurrence of 'Texts' + 'Comments': 'first' # Keep the first occurrence of 'Comments' + + }).reset_index() + +# doc.save(OutputPdfStage1) +# OutputPdfStage2=adjustannotations(OutputPdfStage1,text_with_positions) + modified_pdf_data = doc.tobytes() + OutputPdfStage2 , annotations_data=adjustannotations(modified_pdf_data,text_with_positions) + + if (Thickness): + + threshold = round((float(Thickness) * float(dxfratio) ),1) + cleaned_list = clean_annotations(annotations_data, threshold) + + else: + cleaned_list = clean_annotations(annotations_data, threshold=10) + + allvertices = cleaned_list + # PerimeterVertices = XMLPerimeter + #Example Color : this is in RGB normalized format as for e.g.: 200/255 for r g b + + + hatchcolorR= '0' + hatchcolorG= '1' + hatchcolorB= '1' + # Define templates with placeholder {w} instead of hardcoded LINEWIDTH + LinestyleTemplates = { + 'Solid': '<>', + 'Dashed1':'<>', + 'Dashed2': '<>', + 'Dashed3': '<>', + 'Dashed4': '<>', + 'Dashed5': '<>', + 'Dashed6': '<>' + } + #/BS<> + + HatchFunctions = { + 'None':'', + 'Brick': setBrickHatch, + 'DiagonalBrick':setDiagonalBrickHatch, + 'Horizontal':setHorizontalHatch, + 'Vertical':setVerticalHatch, + 'DiagonalDown':setDiagonalDownHatch, + 'DiagonalUp':setDiagonalUpHatch, + 'Grid': setGridHatch, + 'Weave':setWeaveHatch, + '10Dots':set10DotsHatch, + '20Dots':set20DotsHatch, + '30Dots':set30DotsHatch + } + + #Area and perimeter numbers example + area = 20 + perimeter = 30 + + import colorsys + + annotations=[] + for shapeinvertices in allvertices: + + rn=shapeinvertices[3][0]/255 + gn=shapeinvertices[3][1]/255 + bn=shapeinvertices[3][2]/255 + + h, s, v = colorsys.rgb_to_hsv(rn, gn, bn) + + # snap to full saturation, 50% brightness + s2, v2 = 0.6, 0.9 + + # back to RGB + r2, g2, b2 = colorsys.hsv_to_rgb(h, s2, v2) + + R=str(r2) + G=str(g2) + B=str(b2) + + annotations.append( + { + + 'vertices': shapeinvertices[0], # [[x,y],[x1,y1],[....]] position of ur markup + 'text': str(shapeinvertices[2])+' m', + 'author': 'ADR', + 'custom_data': {'Specification':shapeinvertices[5]},#identify custom colums here as( Column name: Text to add ) + 'type_internal': 'Bluebeam.PDF.Annotations.AnnotationMeasurePerimeter', + 'subject': 'Perimeter Measurement', + 'label':shapeinvertices[4], + 'opacity': '0.7',#opacity of ur shape fill + 'color': R+ ' '+G + ' '+B,# normalized (RGB --> R/255 G/255 B/255) + 'linestyle': LinestyleTemplates['Dashed6'].format(w=2) # LineStyles as in BB ,this w is the linewidth + + } + ) + + + column_order = ['Specification'] #specify here the custom columns in order + # print(bax_annotations) + + #replace with ur pdf width and height variables + pdfWidth=1684 + pdfHeight=2384 + + + # save_multiple_annotations_bax( + # bax_annotations, 'Area_Perimeter_OMAR_output.bax', column_order, pdfWidth, pdfHeight + # ) + bax_xml=save_multiple_annotations_bax( + annotations, '2552 Page 1.bax', column_order, pdfWidth, pdfHeight + ) + + from xml.etree.ElementTree import Element, SubElement, tostring + + def generate_bluebeam_columns_raw(column_names): + """ + Generate BluebeamUserDefinedColumns XML as raw string, without headers or extra fields. + """ + root = Element("BluebeamUserDefinedColumns") + + for idx, name in enumerate(column_names): + item = SubElement(root, "BSIColumnItem", Index=str(idx), Subtype="Text") + SubElement(item, "Name").text = name + SubElement(item, "DisplayOrder").text = str(idx) + SubElement(item, "Deleted").text = "False" + SubElement(item, "Multiline").text = "False" + + # Convert to string and decode raw bytes + return tostring(root, encoding="unicode", method="xml") + + + column_xml = generate_bluebeam_columns_raw(column_order) + +# with open("2552 Page 1.xml", "w", encoding="utf-8") as f: +# f.write(column_xml) + +# print(column_xml) + + + if pdf_content: + latestimg,pix=pdftoimg(OutputPdfStage2,pdf_content) + else: + latestimg,pix=pdftoimg(OutputPdfStage2) + doc2 =fitz.open('pdf',OutputPdfStage2) + if pdf_content: + gc,spreadsheet_service,spreadsheetId, spreadsheet_url , namepathArr=google_sheet_Legend.legendGoogleSheets(grouped_df , pdfname,pdfpath,pdf_content) + else: + gc,spreadsheet_service,spreadsheetId, spreadsheet_url , namepathArr=google_sheet_Legend.legendGoogleSheets(grouped_df , pdfname,pdfpath) + list1=pd.DataFrame(columns=['content', 'id', 'subject','color']) + + # for page in doc: + for page in doc2: + # Iterate through annotations on the page + for annot in page.annots(): + # Get the color of the annotation + annot_color = annot.colors + if annot_color is not None: + # annot_color is a dictionary with 'stroke' and 'fill' keys + stroke_color = annot_color.get('stroke') # Border color + fill_color = annot_color.get('fill') # Fill color + if fill_color: + v='fill' + # print('fill') + if stroke_color: + v='stroke' + x,y,z=int(annot_color.get(v)[0]*255),int(annot_color.get(v)[1]*255),int(annot_color.get(v)[2]*255) + list1.loc[len(list1)] =[annot.info['content'],annot.info['id'],annot.info['subject'],[x,y,z]] + print('LISTTT',list1) + return doc2,latestimg, SimilarAreaDictionary ,spreadsheetId, spreadsheet_url , namepathArr , list1,hatched_areas, bax_xml, column_xml + \ No newline at end of file