Fix #3209. Handle loading of custom scales, fix incomplete NTS implementation, and use Lark for more reliable imperial scale parsing.

This commit is contained in:
Dion Moult
2023-05-30 12:52:49 +10:00
parent c5de64de51
commit 1d5891fde7
7 changed files with 111 additions and 89 deletions
@@ -9,6 +9,7 @@ DATA;
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); #2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); #3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); #4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); #5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); #6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); #7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
@@ -63,12 +63,7 @@ class Annotator:
font_size = 1.6 / 1000 font_size = 1.6 / 1000
font_size *= float(text_obj.data.BIMTextProperties.font_size) font_size *= float(text_obj.data.BIMTextProperties.font_size)
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM": font_size /= tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
numerator, denominator = fraction.split("/")
font_size /= float(numerator) / float(denominator)
text_obj.data.size = font_size text_obj.data.size = font_size
@@ -171,14 +171,6 @@ class BaseDecorator:
def is_landscape(render): def is_landscape(render):
return render.resolution_x > render.resolution_y return render.resolution_x > render.resolution_y
def get_scale(camera):
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
numerator, denominator = fraction.split("/")
return float(numerator) / float(denominator)
camera = bpy.context.scene.camera camera = bpy.context.scene.camera
render = bpy.context.scene.render render = bpy.context.scene.render
if is_landscape(render): if is_landscape(render):
@@ -186,7 +178,8 @@ class BaseDecorator:
else: else:
camera_width_model = camera.data.ortho_scale / render.resolution_y * render.resolution_x camera_width_model = camera.data.ortho_scale / render.resolution_y * render.resolution_x
camera_width_mm = get_scale(camera) * camera_width_model scale = tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
camera_width_mm = scale * camera_width_model
return camera_width_mm return camera_width_mm
def camera_zoom_to_factor(self, zoom): def camera_zoom_to_factor(self, zoom):
@@ -947,53 +947,13 @@ class CreateDrawing(bpy.types.Operator):
return svg_path return svg_path
def get_scale(self, context): def get_scale(self, context):
camera_props = self.camera.data.BIMCameraProperties diagram_scale = tool.Drawing.get_diagram_scale(self.camera)
if camera_props.diagram_scale == "CUSTOM": self.human_scale = diagram_scale["HumanScale"]
if context.scene.unit_settings.system == "IMPERIAL": self.scale = tool.Drawing.get_scale_ratio(diagram_scale["Scale"])
if (
camera_props.custom_diagram_scale_input1[-1] == "'"
or camera_props.custom_diagram_scale_input1[-1] == '"'
):
camera_props.custom_diagram_scale_input1 = camera_props.custom_diagram_scale_input1[:-1]
if camera_props.custom_diagram_scale_input2[-1] == "'":
camera_props.custom_diagram_scale_input2 = camera_props.custom_diagram_scale_input2[:-1]
if (
camera_props.custom_diagram_scale_input2[1:] == "'-0\""
or camera_props.custom_diagram_scale_input2[1:] == "' 0\""
):
camera_props.custom_diagram_scale_input2 = camera_props.custom_diagram_scale_input2[0]
if "/" in camera_props.custom_diagram_scale_input1:
if " " in camera_props.custom_diagram_scale_input1:
n1, n2 = camera_props.custom_diagram_scale_input1.split(" ")
n2_1, n2_2 = n2.split("/")
numerator = int(n1) + (int(n2_1) / int(n2_2))
elif "-" in camera_props.custom_diagram_scale_input1:
n1, n2 = camera_props.custom_diagram_scale_input1.split("-")
n2_1, n2_2 = n2.split("/")
numerator = int(n1) + (int(n2_1) / int(n2_2))
else:
n1, n2 = camera_props.custom_diagram_scale_input1.split("/")
numerator = int(n1) / int(n2)
else:
numerator = 1
denominator = (int(camera_props.custom_diagram_scale_input2) / numerator) * 12
scale = "1" + "/" + str(denominator)
human_scale = camera_props.custom_diagram_scale_input1 + "=" + camera_props.custom_diagram_scale_input2
else:
scale = camera_props.custom_diagram_scale_input1 + "/" + camera_props.custom_diagram_scale_input2
human_scale = camera_props.custom_diagram_scale_input1 + ":" + camera_props.custom_diagram_scale_input2
camera_props.custom_diagram_scale = human_scale + "|" + scale if ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "IsNTS"):
self.human_scale, fraction = camera_props.custom_diagram_scale.split("|")
else:
self.human_scale, fraction = camera_props.diagram_scale.split("|")
if camera_props.is_nts:
self.human_scale = "NTS" self.human_scale = "NTS"
numerator, denominator = fraction.split("/")
self.scale = float(numerator) / float(denominator)
def is_landscape(self, render): def is_landscape(self, render):
return render.resolution_x > render.resolution_y return render.resolution_x > render.resolution_y
@@ -65,12 +65,27 @@ def get_location_hint(self, context):
def update_diagram_scale(self, context): def update_diagram_scale(self, context):
scale = self.diagram_scale try:
if scale == "CUSTOM": element = (
scale = self.custom_diagram_scale tool.Ifc.get()
if "|" not in scale: .by_id(self.id_data.BIMMeshProperties.ifc_definition_id)
.OfProductRepresentation[0]
.ShapeOfProduct[0]
)
except:
return return
human_scale, scale = scale.split("|") diagram_scale = tool.Drawing.get_diagram_scale(tool.Ifc.get_object(element))
if not diagram_scale:
return
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=diagram_scale)
def update_is_nts(self, context):
try: try:
element = ( element = (
tool.Ifc.get() tool.Ifc.get()
@@ -85,9 +100,7 @@ def update_diagram_scale(self, context):
pset = tool.Ifc.get().by_id(pset["id"]) pset = tool.Ifc.get().by_id(pset["id"])
else: else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing") pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run( ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"IsNTS": self.is_nts})
"pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale, "HumanScale": human_scale}
)
def get_diagram_scales(self, context): def get_diagram_scales(self, context):
@@ -351,12 +364,11 @@ class BIMCameraProperties(PropertyGroup):
representation: StringProperty(name="Representation") representation: StringProperty(name="Representation")
view_name: StringProperty(name="View Name") view_name: StringProperty(name="View Name")
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale) diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_scale) custom_scale_numerator: bpy.props.StringProperty(default="1", update=update_diagram_scale)
custom_diagram_scale_input1: StringProperty(name="Custom Scale Input 1", update=update_diagram_scale) custom_scale_denominator: bpy.props.StringProperty(default="100", update=update_diagram_scale)
custom_diagram_scale_input2: StringProperty(name="Custom Scale Input 2", update=update_diagram_scale)
raster_x: IntProperty(name="Raster X", default=1000) raster_x: IntProperty(name="Raster X", default=1000)
raster_y: IntProperty(name="Raster Y", default=1000) raster_y: IntProperty(name="Raster Y", default=1000)
is_nts: BoolProperty(name="Is NTS") is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
active_drawing_style_index: IntProperty(name="Active Drawing Style Index") active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
# For now, this JSON dump are all the parameters that determine a camera's "Block representation" # For now, this JSON dump are all the parameters that determine a camera's "Block representation"
@@ -71,28 +71,19 @@ class BIM_PT_camera(Panel):
row = layout.row() row = layout.row()
row.prop(dprops, "should_extract") row.prop(dprops, "should_extract")
row = layout.row()
row.prop(props, "is_nts")
row = layout.row()
row.operator("bim.resize_text")
row = layout.row() row = layout.row()
row.prop(props, "raster_x") row.prop(props, "raster_x")
row = layout.row() row = layout.row()
row.prop(props, "raster_y") row.prop(props, "raster_y")
row = layout.row() row = layout.row(align=True)
row.prop(props, "diagram_scale") row.prop(props, "diagram_scale")
row.prop(props, "is_nts", text="", icon="MOD_EDGESPLIT")
if props.diagram_scale == "CUSTOM": if props.diagram_scale == "CUSTOM":
row = layout.row(align=True) row = layout.row(align=True)
row.prop(props, "custom_diagram_scale_input1", text="Custom Scale") row.prop(props, "custom_scale_numerator", text="Custom Scale")
if context.scene.unit_settings.system == "IMPERIAL": row.prop(props, "custom_scale_denominator", text="")
separator = " ="
else:
separator = " :"
row.label(text=separator)
row.prop(props, "custom_diagram_scale_input2", text="")
row = layout.row(align=True) row = layout.row(align=True)
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT") row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
+74 -4
View File
@@ -20,6 +20,7 @@ import os
import re import re
import bpy import bpy
import math import math
import lark
import bmesh import bmesh
import shutil import shutil
import logging import logging
@@ -35,11 +36,10 @@ import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.scheduler as scheduler import blenderbim.bim.module.drawing.scheduler as scheduler
import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.helper as helper
from blenderbim.bim.module.drawing.data import FONT_SIZES, DecoratorData from blenderbim.bim.module.drawing.data import FONT_SIZES, DecoratorData
from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS, ANNOTATION_TYPES_DATA from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS, ANNOTATION_TYPES_DATA
from mathutils import Vector from mathutils import Vector
from fractions import Fraction
import collections import collections
@@ -580,13 +580,20 @@ class Drawing(blenderbim.core.tool.Drawing):
camera.BIMCameraProperties.diagram_scale = valid_scales[0] camera.BIMCameraProperties.diagram_scale = valid_scales[0]
else: else:
camera.BIMCameraProperties.diagram_scale = "CUSTOM" camera.BIMCameraProperties.diagram_scale = "CUSTOM"
camera.BIMCameraProperties.custom_diagram_scale = pset["HumanScale"] + "|" + pset["Scale"] if ":" in pset["HumanScale"]:
numerator, denominator = pset["HumanScale"].split(":")
else:
numerator, denominator = pset["HumanScale"].split("=")
camera.BIMCameraProperties.custom_scale_numerator = numerator
camera.BIMCameraProperties.custom_scale_denominator = denominator
if "HasUnderlay" in pset: if "HasUnderlay" in pset:
camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"] camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"]
if "HasLinework" in pset: if "HasLinework" in pset:
camera.BIMCameraProperties.has_linework = pset["HasLinework"] camera.BIMCameraProperties.has_linework = pset["HasLinework"]
if "HasAnnotation" in pset: if "HasAnnotation" in pset:
camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"] camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"]
if "IsNTS" in pset:
camera.BIMCameraProperties.is_nts = pset["IsNTS"]
tool.Loader.link_mesh(shape, camera) tool.Loader.link_mesh(shape, camera)
@@ -1330,7 +1337,8 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod @classmethod
def get_drawing_human_scale(cls, drawing): def get_drawing_human_scale(cls, drawing):
return ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("HumanScale", "NTS") pset = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing") or {}
return "NTS" if pset.get("IsNTS", False) else pset.get("HumanScale", "NTS")
@classmethod @classmethod
def get_drawing_metadata(cls, drawing): def get_drawing_metadata(cls, drawing):
@@ -1610,3 +1618,65 @@ class Drawing(blenderbim.core.tool.Drawing):
bm.free() bm.free()
return verts, edges return verts, edges
@classmethod
def get_scale_ratio(cls, scale):
numerator, denominator = scale.split("/")
return float(numerator) / float(denominator)
@classmethod
def get_diagram_scale(cls, obj):
props = obj.data.BIMCameraProperties
scale = props.diagram_scale
if scale != "CUSTOM":
human_scale, scale = scale.split("|")
return {"HumanScale": human_scale, "Scale": scale}
numerator_string = props.custom_scale_numerator
denominator_string = props.custom_scale_denominator
numerator = tool.Drawing.convert_scale_string(numerator_string)
denominator = tool.Drawing.convert_scale_string(denominator_string)
if not numerator or not denominator:
return
scale = str(Fraction(numerator / denominator).limit_denominator(1000)) # Any ratio >1000 is stupid.
if "'" in scale or '"' in scale:
human_separator = "=" # Imperial scales use "=", like 1" = 1' - 0"
# If for some crazy reason we mix metric and imperial, assume metric is SI units, like 1m = 1'
if "'" not in numerator_string and '"' not in numerator_string:
numerator_string += "m"
if "'" not in denominator_string and '"' not in denominator_string:
denominator_string += "m"
else:
human_separator = ":" # Metric scales use ":", like 1:100
human_scale = f"{numerator_string}{human_separator}{denominator_string}"
return {"HumanScale": human_scale, "Scale": scale}
@classmethod
def convert_scale_string(cls, value):
try:
return float(value)
except:
pass # Perhaps it's imperial?
l = lark.Lark(
"""start: feet? "-"? inches?
feet: NUMBER? "-"? fraction? "'"
inches: NUMBER? "-"? fraction? "\\""
fraction: NUMBER "/" NUMBER
%import common.NUMBER
%import common.WS
%ignore WS // Disregard spaces in text
"""
)
try:
start = l.parse(value)
except:
return 0
result = 0
for dimension in start.children:
factor = 12 if dimension.data == "feet" else 1
for child in dimension.children:
if getattr(child, "data", None) == "fraction":
result += (float(child.children[0]) / float(child.children[1])) * factor
else:
result += float(child) * factor
return result * 0.0254