Skip to content

Commit d705037

Browse files
committed
wip
1 parent e5ca295 commit d705037

6 files changed

Lines changed: 166 additions & 28 deletions

File tree

app.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from dxf import SurveyDXFManager
66
from models.plan import PlanProps
7-
from utils import polygon_orientation, line_normals, line_direction
7+
from utils import polygon_orientation, line_normals, line_direction, html_to_mtext
88

99
app = Flask(__name__)
1010
app.config["SECRET_KEY"] = "secret"
@@ -22,7 +22,7 @@ def generate_cadastral_plan():
2222
extent = plan.get_extent()
2323
beacon_size = extent * 0.02
2424

25-
drawer = SurveyDXFManager(plan_name=plan.name, scale=plan.scale)
25+
drawer = SurveyDXFManager(plan_name=plan.name, scale=plan.get_drawing_scale())
2626
drawer.setup_beacon_style(type_=plan.beacon_type, size=beacon_size or 1.0)
2727
drawer.setup_font(plan.font)
2828

@@ -81,7 +81,6 @@ def generate_cadastral_plan():
8181

8282
drawer.add_text(f"{leg.distance:.2f} m", mid_x, mid_y, angle=text_angle, height=label_height)
8383
ld = line_direction(angle_deg)
84-
print(leg.from_.id, leg.to.id, ld)
8584
if ld == "left → right":
8685
drawer.add_text(f"{leg.bearing.degrees}°", first_x, first_y, angle=text_angle, height=label_height)
8786
drawer.add_text(f"{leg.bearing.minutes}'", last_x, last_y, angle=text_angle, height=label_height)
@@ -96,21 +95,31 @@ def generate_cadastral_plan():
9695
height = max_y - min_y
9796

9897
# Draw frame
99-
margin_x = max(width, height) * 0.4
100-
margin_y = max(height, width) * 0.75
101-
drawer.draw_frame(min_x - margin_x, min_y - margin_y, max_x + margin_x, max_y + margin_y)
98+
margin_x = max(width, height) * 0.35
99+
margin_y = max(height, width) * 0.7
100+
frame_left = min_x - margin_x
101+
frame_bottom = min_y - margin_y
102+
frame_right = max_x + margin_x
103+
frame_top = max_y + margin_y
104+
drawer.draw_frame(frame_left, frame_bottom, frame_right,frame_top)
102105

103106
# offset frame
104-
offset_x = max(width, height) * 0.43
105-
offset_y = max(height, width) * 0.78
107+
offset_x = max(width, height) * 0.38
108+
offset_y = max(height, width) * 0.73
106109
drawer.draw_frame(min_x - offset_x, min_y - offset_y, max_x + offset_x, max_y + offset_y)
107110

108111
# add title block
109-
box_width = (max_x + margin_x) - (min_x - margin_x) * 0.6
110-
title_x = ((min_x - margin_x) + (max_x + margin_x)) / 2
111-
title_y = (max_y + margin_y) - (margin_y * 0.15)
112-
text_height = plan.font_size or ((max_y + margin_y) - (min_y - margin_y)) * 0.02
113-
drawer.add_title(plan.title.upper(), title_x, title_y, width=box_width, height=text_height)
112+
# Calculate center position for title
113+
frame_width = frame_right - frame_left
114+
frame_center_x = frame_left + (frame_width / 2)
115+
116+
# Title positioning
117+
title_y = frame_top - (margin_y * 0.2) # 5 units below the top of frame
118+
title_height = plan.font_size # Height of title text
119+
title_width = frame_width * 0.6 # Use 60% of frame width for title box
120+
121+
drawer.add_title(html_to_mtext(plan.build_title()), frame_center_x, title_y, title_width, title_height)
122+
114123

115124
drawer.save_dxf()
116125
return jsonify({"message": "Cadastral plan generated", "filename": plan.name}), 200

dxf.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import matplotlib.pyplot as plt
77
from ezdxf.addons.drawing import Frontend, RenderContext
88
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
9+
from ezdxf.tools.text import MTextEditor
910

1011
class SurveyDXFManager:
1112
def __init__(self, plan_name: str = "Survey Plan", scale: float = 1.0):
@@ -140,23 +141,23 @@ def add_text(self, text: str, x: float, y: float, angle: float = 0.0, height: fl
140141
align=TextEntityAlignment.MIDDLE_CENTER
141142
)
142143

143-
def add_title(self, text: str, x: float, y: float, width: float, height: float = 1.0):
144-
print(width)
145-
"""Add title text at given coordinates with optional rotation"""
146-
mtext = self.msp.add_mtext(text, dxfattribs={'layer': 'TITLE_BLOCK', 'style': 'SURVEY_TEXT'})
147-
mtext.set_location((x * self.scale, y * self.scale))
148-
mtext.dxf.char_height = height * self.scale
149-
mtext.dxf.width = 60
150-
mtext.dxf.attachment_point = 2 # top center
151-
# mtext.dxf.paragraphs = MTextParagraphAlignment.JUSTIFIED
144+
def add_title(self, text: str, x: float, y: float, width: float, title_height: float = 1.0):
145+
title_mtext = self.msp.add_mtext(
146+
text=f"{MTextEditor.UNDERLINE_START}{text}{MTextEditor.UNDERLINE_STOP}",
147+
dxfattribs={'layer': 'TITLE_BLOCK', 'style': 'SURVEY_TEXT'},
148+
)
149+
title_mtext.set_location((x * self.scale, y * self.scale))
150+
title_mtext.dxf.attachment_point = ezdxf.enums.MTextEntityAlignment.TOP_CENTER
151+
title_mtext.dxf.char_height = title_height * self.scale
152+
title_mtext.dxf.width = width * self.scale
152153

153154
def draw_frame(self, min_x, min_y, max_x, max_y):
154155
"""Draw a rectangle given min and max coordinates"""
155156
self.msp.add_lwpolyline([
156-
(min_x * self.scale, min_y* self.scale),
157-
(max_x* self.scale, min_y* self.scale),
158-
(max_x* self.scale, max_y* self.scale),
159-
(min_x* self.scale, max_y* self.scale)
157+
(min_x * self.scale, min_y * self.scale),
158+
(max_x * self.scale, min_y * self.scale),
159+
(max_x * self.scale, max_y * self.scale),
160+
(min_x * self.scale, max_y * self.scale)
160161
], close=True, dxfattribs={
161162
'layer': 'FRAME',
162163
})

models/plan.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import List, Optional, Union
33
from datetime import datetime
44
from pydantic import BaseModel, Field
5+
from bs4 import BeautifulSoup
56

67
# ---------- Enums ----------
78
class PlanType(str, Enum):
@@ -78,13 +79,18 @@ class PlanProps(BaseModel):
7879
state: str = ""
7980
plan_number: str = ""
8081
origin: PlanOrigin = PlanOrigin.UTM_ZONE_31
81-
scale: float = 1
82+
scale: float = 1000
8283
beacon_type: BeaconType = BeaconType.NONE
8384
personel_name: str = ""
8485
surveyor_name: str = ""
8586
forward_computation_data: Optional[ForwardComputationData] = None
8687
traverse_computation_data: Optional[TraverseComputationData] = None
8788

89+
def get_drawing_scale(self):
90+
if not self.scale:
91+
return 1.0
92+
return 1000 / self.scale
93+
8894
def get_extent(self) -> float:
8995
# get bounding box
9096
min_x, min_y, max_x, max_y = self.get_bounding_box()
@@ -107,3 +113,29 @@ def get_bounding_box(self) -> Optional[tuple]:
107113
min_y, max_y = min(ys), max(ys)
108114

109115
return min_x, min_y, max_x, max_y
116+
117+
def build_title(self) -> str:
118+
soup = BeautifulSoup(self.title.upper(), "html.parser")
119+
120+
if self.address:
121+
p1 = soup.new_tag("p")
122+
p1.string = self.address.upper()
123+
soup.append(p1)
124+
125+
if self.local_govt:
126+
p2 = soup.new_tag("p")
127+
p2.string = self.local_govt.upper()
128+
soup.append(p2)
129+
130+
if self.state:
131+
p3 = soup.new_tag("p")
132+
p3.string = f"{self.state.upper()} STATE"
133+
soup.append(p3)
134+
135+
if self.scale:
136+
p4 = soup.new_tag("p")
137+
p4.string = f"SCALE :- 1 : {int(self.scale)}"
138+
soup.append(p4)
139+
140+
return soup.prettify()
141+

pl.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from bs4 import BeautifulSoup, Tag
2+
from ezdxf.tools.text import MTextEditor
3+
4+
5+
def html_to_mtext(html_text: str):
6+
if not html_text:
7+
return ""
8+
9+
soup = BeautifulSoup(html_text, "html.parser")
10+
editor = MTextEditor()
11+
12+
def parse_tag(tag):
13+
for child in tag.children:
14+
if isinstance(child, str): # Plain text
15+
editor.append(child)
16+
else:
17+
# Apply formatting recursively
18+
if child.name == "b" or child.name == "strong":
19+
editor.font("Arial", bold=True)
20+
parse_tag(child)
21+
# elif child.name == "i":
22+
# editor.font(child.get_text(), bold=False, italic=True)
23+
elif child.name == "u":
24+
editor.append(MTextEditor.UNDERLINE_START)
25+
parse_tag(child)
26+
editor.append(MTextEditor.UNDERLINE_STOP)
27+
elif child.name == "br":
28+
editor.append(MTextEditor.NEW_LINE)
29+
elif child.name == "p":
30+
editor.append(MTextEditor.NEW_LINE)
31+
parse_tag(child)
32+
editor.append(MTextEditor.NEW_LINE)
33+
else:
34+
parse_tag(child)
35+
36+
parse_tag(soup)
37+
return str(editor)
38+
39+
40+
if __name__ == "__main__":
41+
print(html_to_mtext("<p>PLAN SHEWING PROPERTY</p><p>SAID TO BELONG TO</p><p><strong>MR. BABAWALE OLATUNBO OKUNOLA</strong></p><p><strong>&amp;</strong></p><p><strong>MRS. AZEEZAT OLUSEYI OKUNOLA</strong></p><p>Hello Sir</p>"))

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
annotated-types==0.7.0
2+
beautifulsoup4==4.13.5
23
blinker==1.9.0
4+
bs4==0.0.2
35
click==8.2.1
46
colorama==0.4.6
57
comtypes==1.4.12
@@ -22,6 +24,7 @@ pydantic_core==2.33.2
2224
pyparsing==3.2.4
2325
python-dateutil==2.9.0.post0
2426
six==1.17.0
27+
soupsieve==2.8
2528
typing-inspection==0.4.1
2629
typing_extensions==4.15.0
2730
Werkzeug==3.1.3

utils.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from bs4 import BeautifulSoup
2+
from ezdxf.tools.text import MTextEditor
3+
14
def polygon_orientation(coords):
25
# coords = [(x1,y1), (x2,y2), ...]
36
area = 0
@@ -22,4 +25,53 @@ def line_direction(angle) -> str:
2225
if -90 <= angle <= 90:
2326
return "left → right"
2427
else:
25-
return "right → left"
28+
return "right → left"
29+
30+
def html_to_mtext(html_text: str):
31+
if not html_text:
32+
return ""
33+
34+
html_text = html_text.replace('\n', '')
35+
print(html_text)
36+
soup = BeautifulSoup(html_text, "html.parser")
37+
editor = MTextEditor()
38+
39+
def parse_tag(tag):
40+
for child in tag.children:
41+
if isinstance(child, str): # Plain text
42+
editor.append(child.strip())
43+
else:
44+
# Apply formatting recursively
45+
if child.name == "b" or child.name == "strong":
46+
editor.append("\\B")
47+
parse_tag(child)
48+
editor.append("\\b")
49+
elif child.name == "i":
50+
editor.append("\\I")
51+
parse_tag(child)
52+
editor.append("\\i")
53+
elif child.name == "u":
54+
editor.append(MTextEditor.UNDERLINE_START)
55+
parse_tag(child)
56+
editor.append(MTextEditor.UNDERLINE_STOP)
57+
elif child.name == "br":
58+
editor.append(MTextEditor.NEW_LINE)
59+
elif child.name == "p":
60+
prev = child.previous_sibling
61+
while prev and str(prev).strip() == "":
62+
prev = prev.previous_sibling
63+
if prev is not None:
64+
editor.append(MTextEditor.NEW_LINE)
65+
parse_tag(child)
66+
# editor.append(MTextEditor.NEW_LINE)
67+
else:
68+
parse_tag(child)
69+
70+
parse_tag(soup)
71+
result = str(editor)
72+
result = result.replace('\n', '')
73+
return result
74+
75+
76+
if __name__ == '__main__':
77+
print(repr(html_to_mtext()))

0 commit comments

Comments
 (0)