mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 10:11:46 +00:00
A little progress on refactoring the old drawing module into an IfcConvert based system. See #1153.
This commit is contained in:
@@ -86,7 +86,6 @@ if bpy is not None:
|
|||||||
operator.AddIfcFile,
|
operator.AddIfcFile,
|
||||||
operator.RemoveIfcFile,
|
operator.RemoveIfcFile,
|
||||||
operator.SelectDocIfcFile,
|
operator.SelectDocIfcFile,
|
||||||
operator.AddAnnotation,
|
|
||||||
operator.GenerateReferences,
|
operator.GenerateReferences,
|
||||||
operator.ResizeText,
|
operator.ResizeText,
|
||||||
operator.AddVariable,
|
operator.AddVariable,
|
||||||
|
|||||||
@@ -78,145 +78,6 @@ def export_attributes(props, callback=None):
|
|||||||
# TODO: migrate the below helper functions into the drawing module, since it is specific to that module
|
# TODO: migrate the below helper functions into the drawing module, since it is specific to that module
|
||||||
|
|
||||||
|
|
||||||
# This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py
|
|
||||||
# MeasureIt-ARCH is GPL-v3
|
|
||||||
# In the future I will need to rewrite this to allow the user to have custom
|
|
||||||
# settings for each annotation object, not read from Blender.
|
|
||||||
def format_distance(value, isArea=False, hide_units=True):
|
|
||||||
s_code = "\u00b2" # Superscript two THIS IS LEGACY (but being kept for when Area Measurements are re-implimented)
|
|
||||||
|
|
||||||
# Get Scene Unit Settings
|
|
||||||
scaleFactor = bpy.context.scene.unit_settings.scale_length
|
|
||||||
unit_system = bpy.context.scene.unit_settings.system
|
|
||||||
unit_length = bpy.context.scene.unit_settings.length_unit
|
|
||||||
|
|
||||||
toInches = 39.3700787401574887
|
|
||||||
inPerFoot = 11.999
|
|
||||||
|
|
||||||
if isArea:
|
|
||||||
toInches = 1550
|
|
||||||
inPerFoot = 143.999
|
|
||||||
|
|
||||||
value *= scaleFactor
|
|
||||||
|
|
||||||
# Imperial Formating
|
|
||||||
if unit_system == "IMPERIAL":
|
|
||||||
precision = bpy.context.scene.BIMProperties.imperial_precision
|
|
||||||
if precision == "NONE":
|
|
||||||
precision = 256
|
|
||||||
elif precision == "1":
|
|
||||||
precision = 1
|
|
||||||
elif "/" in precision:
|
|
||||||
precision = int(precision.split("/")[1])
|
|
||||||
|
|
||||||
base = int(precision)
|
|
||||||
decInches = value * toInches
|
|
||||||
|
|
||||||
# Seperate ft and inches
|
|
||||||
# Unless Inches are the specified Length Unit
|
|
||||||
if unit_length != "INCHES":
|
|
||||||
feet = math.floor(decInches / inPerFoot)
|
|
||||||
decInches -= feet * inPerFoot
|
|
||||||
else:
|
|
||||||
feet = 0
|
|
||||||
|
|
||||||
# Seperate Fractional Inches
|
|
||||||
inches = math.floor(decInches)
|
|
||||||
if inches != 0:
|
|
||||||
frac = round(base * (decInches - inches))
|
|
||||||
else:
|
|
||||||
frac = round(base * (decInches))
|
|
||||||
|
|
||||||
# Set proper numerator and denominator
|
|
||||||
if frac != base:
|
|
||||||
numcycles = int(math.log2(base))
|
|
||||||
for i in range(numcycles):
|
|
||||||
if frac % 2 == 0:
|
|
||||||
frac = int(frac / 2)
|
|
||||||
base = int(base / 2)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
frac = 0
|
|
||||||
inches += 1
|
|
||||||
|
|
||||||
# Check values and compose string
|
|
||||||
if inches == 12:
|
|
||||||
feet += 1
|
|
||||||
inches = 0
|
|
||||||
|
|
||||||
if not isArea:
|
|
||||||
tx_dist = ""
|
|
||||||
if feet:
|
|
||||||
tx_dist += str(feet) + "'"
|
|
||||||
if feet and inches:
|
|
||||||
tx_dist += " - "
|
|
||||||
if inches:
|
|
||||||
tx_dist += str(inches)
|
|
||||||
if inches and frac:
|
|
||||||
tx_dist += " "
|
|
||||||
if frac:
|
|
||||||
tx_dist += str(frac) + "/" + str(base)
|
|
||||||
if inches or frac:
|
|
||||||
tx_dist += '"'
|
|
||||||
else:
|
|
||||||
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
|
|
||||||
|
|
||||||
# METRIC FORMATING
|
|
||||||
elif unit_system == "METRIC":
|
|
||||||
precision = bpy.context.scene.BIMProperties.metric_precision
|
|
||||||
if precision != 0:
|
|
||||||
value = precision * round(float(value) / precision)
|
|
||||||
|
|
||||||
# Meters
|
|
||||||
if unit_length == "METERS":
|
|
||||||
fmt = "%1.3f"
|
|
||||||
if hide_units is False:
|
|
||||||
fmt += " m"
|
|
||||||
tx_dist = fmt % value
|
|
||||||
# Centimeters
|
|
||||||
elif unit_length == "CENTIMETERS":
|
|
||||||
fmt = "%1.1f"
|
|
||||||
if hide_units is False:
|
|
||||||
fmt += " cm"
|
|
||||||
d_cm = value * (100)
|
|
||||||
tx_dist = fmt % d_cm
|
|
||||||
# Millimeters
|
|
||||||
elif unit_length == "MILLIMETERS":
|
|
||||||
fmt = "%1.0f"
|
|
||||||
if hide_units is False:
|
|
||||||
fmt += " mm"
|
|
||||||
d_mm = value * (1000)
|
|
||||||
tx_dist = fmt % d_mm
|
|
||||||
|
|
||||||
# Otherwise Use Adaptive Units
|
|
||||||
else:
|
|
||||||
if round(value, 2) >= 1.0:
|
|
||||||
fmt = "%1.3f"
|
|
||||||
if hide_units is False:
|
|
||||||
fmt += " m"
|
|
||||||
tx_dist = fmt % value
|
|
||||||
else:
|
|
||||||
if round(value, 2) >= 0.01:
|
|
||||||
fmt = "%1.1f"
|
|
||||||
if hide_units is False:
|
|
||||||
fmt += " cm"
|
|
||||||
d_cm = value * (100)
|
|
||||||
tx_dist = fmt % d_cm
|
|
||||||
else:
|
|
||||||
fmt = "%1.0f"
|
|
||||||
if hide_units is False:
|
|
||||||
fmt += " mm"
|
|
||||||
d_mm = value * (1000)
|
|
||||||
tx_dist = fmt % d_mm
|
|
||||||
if isArea:
|
|
||||||
tx_dist += s_code
|
|
||||||
else:
|
|
||||||
tx_dist = fmt % value
|
|
||||||
|
|
||||||
return tx_dist
|
|
||||||
|
|
||||||
|
|
||||||
def parse_diagram_scale(camera):
|
def parse_diagram_scale(camera):
|
||||||
"""Returns numeric value of scale"""
|
"""Returns numeric value of scale"""
|
||||||
if camera.BIMCameraProperties.diagram_scale == "CUSTOM":
|
if camera.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from . import ui, operator
|
|||||||
classes = (
|
classes = (
|
||||||
operator.AddDrawing,
|
operator.AddDrawing,
|
||||||
operator.CreateDrawing,
|
operator.CreateDrawing,
|
||||||
|
operator.AddAnnotation,
|
||||||
ui.BIM_PT_camera,
|
ui.BIM_PT_camera,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import bpy
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
# This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py
|
||||||
|
# MeasureIt-ARCH is GPL-v3
|
||||||
|
# In the future I will need to rewrite this to allow the user to have custom
|
||||||
|
# settings for each annotation object, not read from Blender.
|
||||||
|
def format_distance(value, isArea=False, hide_units=True):
|
||||||
|
s_code = "\u00b2" # Superscript two THIS IS LEGACY (but being kept for when Area Measurements are re-implimented)
|
||||||
|
|
||||||
|
# Get Scene Unit Settings
|
||||||
|
scaleFactor = bpy.context.scene.unit_settings.scale_length
|
||||||
|
unit_system = bpy.context.scene.unit_settings.system
|
||||||
|
unit_length = bpy.context.scene.unit_settings.length_unit
|
||||||
|
|
||||||
|
toInches = 39.3700787401574887
|
||||||
|
inPerFoot = 11.999
|
||||||
|
|
||||||
|
if isArea:
|
||||||
|
toInches = 1550
|
||||||
|
inPerFoot = 143.999
|
||||||
|
|
||||||
|
value *= scaleFactor
|
||||||
|
|
||||||
|
# Imperial Formating
|
||||||
|
if unit_system == "IMPERIAL":
|
||||||
|
precision = bpy.context.scene.BIMProperties.imperial_precision
|
||||||
|
if precision == "NONE":
|
||||||
|
precision = 256
|
||||||
|
elif precision == "1":
|
||||||
|
precision = 1
|
||||||
|
elif "/" in precision:
|
||||||
|
precision = int(precision.split("/")[1])
|
||||||
|
|
||||||
|
base = int(precision)
|
||||||
|
decInches = value * toInches
|
||||||
|
|
||||||
|
# Seperate ft and inches
|
||||||
|
# Unless Inches are the specified Length Unit
|
||||||
|
if unit_length != "INCHES":
|
||||||
|
feet = math.floor(decInches / inPerFoot)
|
||||||
|
decInches -= feet * inPerFoot
|
||||||
|
else:
|
||||||
|
feet = 0
|
||||||
|
|
||||||
|
# Seperate Fractional Inches
|
||||||
|
inches = math.floor(decInches)
|
||||||
|
if inches != 0:
|
||||||
|
frac = round(base * (decInches - inches))
|
||||||
|
else:
|
||||||
|
frac = round(base * (decInches))
|
||||||
|
|
||||||
|
# Set proper numerator and denominator
|
||||||
|
if frac != base:
|
||||||
|
numcycles = int(math.log2(base))
|
||||||
|
for i in range(numcycles):
|
||||||
|
if frac % 2 == 0:
|
||||||
|
frac = int(frac / 2)
|
||||||
|
base = int(base / 2)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
frac = 0
|
||||||
|
inches += 1
|
||||||
|
|
||||||
|
# Check values and compose string
|
||||||
|
if inches == 12:
|
||||||
|
feet += 1
|
||||||
|
inches = 0
|
||||||
|
|
||||||
|
if not isArea:
|
||||||
|
tx_dist = ""
|
||||||
|
if feet:
|
||||||
|
tx_dist += str(feet) + "'"
|
||||||
|
if feet and inches:
|
||||||
|
tx_dist += " - "
|
||||||
|
if inches:
|
||||||
|
tx_dist += str(inches)
|
||||||
|
if inches and frac:
|
||||||
|
tx_dist += " "
|
||||||
|
if frac:
|
||||||
|
tx_dist += str(frac) + "/" + str(base)
|
||||||
|
if inches or frac:
|
||||||
|
tx_dist += '"'
|
||||||
|
else:
|
||||||
|
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
|
||||||
|
|
||||||
|
# METRIC FORMATING
|
||||||
|
elif unit_system == "METRIC":
|
||||||
|
precision = bpy.context.scene.BIMProperties.metric_precision
|
||||||
|
if precision != 0:
|
||||||
|
value = precision * round(float(value) / precision)
|
||||||
|
|
||||||
|
# Meters
|
||||||
|
if unit_length == "METERS":
|
||||||
|
fmt = "%1.3f"
|
||||||
|
if hide_units is False:
|
||||||
|
fmt += " m"
|
||||||
|
tx_dist = fmt % value
|
||||||
|
# Centimeters
|
||||||
|
elif unit_length == "CENTIMETERS":
|
||||||
|
fmt = "%1.1f"
|
||||||
|
if hide_units is False:
|
||||||
|
fmt += " cm"
|
||||||
|
d_cm = value * (100)
|
||||||
|
tx_dist = fmt % d_cm
|
||||||
|
# Millimeters
|
||||||
|
elif unit_length == "MILLIMETERS":
|
||||||
|
fmt = "%1.0f"
|
||||||
|
if hide_units is False:
|
||||||
|
fmt += " mm"
|
||||||
|
d_mm = value * (1000)
|
||||||
|
tx_dist = fmt % d_mm
|
||||||
|
|
||||||
|
# Otherwise Use Adaptive Units
|
||||||
|
else:
|
||||||
|
if round(value, 2) >= 1.0:
|
||||||
|
fmt = "%1.3f"
|
||||||
|
if hide_units is False:
|
||||||
|
fmt += " m"
|
||||||
|
tx_dist = fmt % value
|
||||||
|
else:
|
||||||
|
if round(value, 2) >= 0.01:
|
||||||
|
fmt = "%1.1f"
|
||||||
|
if hide_units is False:
|
||||||
|
fmt += " cm"
|
||||||
|
d_cm = value * (100)
|
||||||
|
tx_dist = fmt % d_cm
|
||||||
|
else:
|
||||||
|
fmt = "%1.0f"
|
||||||
|
if hide_units is False:
|
||||||
|
fmt += " mm"
|
||||||
|
d_mm = value * (1000)
|
||||||
|
tx_dist = fmt % d_mm
|
||||||
|
if isArea:
|
||||||
|
tx_dist += s_code
|
||||||
|
else:
|
||||||
|
tx_dist = fmt % value
|
||||||
|
|
||||||
|
return tx_dist
|
||||||
@@ -3,6 +3,9 @@ import bpy
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
import blenderbim.bim.module.drawing.svgwriter as svgwriter
|
||||||
|
import blenderbim.bim.module.drawing.annotation as annotation
|
||||||
|
from mathutils import Vector, Matrix, Euler, geometry
|
||||||
from blenderbim.bim.operator import open_with_user_command
|
from blenderbim.bim.operator import open_with_user_command
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from ifcopenshell.api.group.data import Data as GroupData
|
from ifcopenshell.api.group.data import Data as GroupData
|
||||||
@@ -57,33 +60,156 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
bl_label = "Create Drawing"
|
bl_label = "Create Drawing"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
base_svg = self.ifc_to_svg(context)
|
||||||
|
annotation_svg = self.annotation_to_svg(context)
|
||||||
|
svg_path = self.combine_svgs(context, base_svg, annotation_svg)
|
||||||
|
open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def combine_svgs(self, context, base, annotation):
|
||||||
|
# Hacky :)
|
||||||
|
svg_path = context.scene.BIMProperties.ifc_file[0:-4] + ".svg"
|
||||||
|
with open(svg_path, 'w') as outfile:
|
||||||
|
with open(base) as infile:
|
||||||
|
for line in infile:
|
||||||
|
if "</svg>" in line:
|
||||||
|
continue
|
||||||
|
outfile.write(line)
|
||||||
|
with open(annotation) as infile:
|
||||||
|
for i, line in enumerate(infile):
|
||||||
|
if i == 0 or i == 1:
|
||||||
|
continue
|
||||||
|
outfile.write(line)
|
||||||
|
return svg_path
|
||||||
|
|
||||||
|
def ifc_to_svg(self, context):
|
||||||
|
svg_path = context.scene.BIMProperties.ifc_file[0:-4] + "-base.svg"
|
||||||
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
|
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[
|
[
|
||||||
ifcconvert_path,
|
ifcconvert_path,
|
||||||
context.scene.BIMProperties.ifc_file,
|
context.scene.BIMProperties.ifc_file,
|
||||||
"-yv",
|
"-yv",
|
||||||
context.scene.BIMProperties.ifc_file[0:-4] + ".svg",
|
svg_path,
|
||||||
"--plan",
|
"--plan",
|
||||||
"--model",
|
"--model",
|
||||||
"--section-height-from-storeys",
|
|
||||||
"--section-height=1.2",
|
|
||||||
"--door-arcs",
|
|
||||||
"--print-space-names",
|
|
||||||
"--print-space-areas",
|
|
||||||
"--bounds=1024x1024",
|
|
||||||
"--elevation-ref=DRAWING",
|
"--elevation-ref=DRAWING",
|
||||||
"--svg-xmlns",
|
"--svg-xmlns",
|
||||||
"--svg-project",
|
"--svg-project",
|
||||||
"--svg-poly",
|
"--svg-poly",
|
||||||
|
"--svg-without-storeys",
|
||||||
"--exclude",
|
"--exclude",
|
||||||
"entities",
|
"entities",
|
||||||
"IfcSpace",
|
"IfcSpace",
|
||||||
"IfcOpeningElement",
|
"IfcOpeningElement",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
open_with_user_command(
|
return svg_path
|
||||||
bpy.context.preferences.addons["blenderbim"].preferences.svg_command,
|
|
||||||
context.scene.BIMProperties.ifc_file[0:-4] + ".svg",
|
def annotation_to_svg(self, context):
|
||||||
)
|
camera = context.scene.camera
|
||||||
|
|
||||||
|
if not (camera.type == "CAMERA" and camera.data.type == "ORTHO"):
|
||||||
|
return
|
||||||
|
|
||||||
|
svg_writer = svgwriter.SvgWriter()
|
||||||
|
|
||||||
|
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("/")
|
||||||
|
|
||||||
|
if camera.data.BIMCameraProperties.is_nts:
|
||||||
|
svg_writer.human_scale = "NTS"
|
||||||
|
else:
|
||||||
|
svg_writer.human_scale = human_scale
|
||||||
|
|
||||||
|
drawing_style = bpy.context.scene.DocProperties.drawing_styles[
|
||||||
|
camera.data.BIMCameraProperties.active_drawing_style_index
|
||||||
|
]
|
||||||
|
|
||||||
|
render = bpy.context.scene.render
|
||||||
|
if self.is_landscape():
|
||||||
|
width = camera.data.ortho_scale
|
||||||
|
height = width / render.resolution_x * render.resolution_y
|
||||||
|
else:
|
||||||
|
height = camera.data.ortho_scale
|
||||||
|
width = height / render.resolution_y * render.resolution_x
|
||||||
|
|
||||||
|
svg_writer.scale = float(numerator) / float(denominator)
|
||||||
|
svg_writer.output = context.scene.BIMProperties.ifc_file[0:-4] + "-annotation.svg"
|
||||||
|
svg_writer.data_dir = bpy.context.scene.BIMProperties.data_dir
|
||||||
|
svg_writer.vector_style = drawing_style.vector_style
|
||||||
|
svg_writer.camera = camera
|
||||||
|
svg_writer.camera_width = width
|
||||||
|
svg_writer.camera_height = height
|
||||||
|
svg_writer.camera_projection = tuple(camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
||||||
|
|
||||||
|
for obj in camera.users_collection[0].objects:
|
||||||
|
if "IfcGrid" in obj.name:
|
||||||
|
svg_writer.annotations.setdefault("grid_objs", []).append(obj)
|
||||||
|
elif obj.type == "CAMERA":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "IfcAnnotation/" not in obj.name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "Leader" in obj.name:
|
||||||
|
svg_writer.annotations["leader_obj"] = (obj, obj.data)
|
||||||
|
elif "Stair" in obj.name:
|
||||||
|
svg_writer.annotations["stair_obj"] = obj
|
||||||
|
elif "Equal" in obj.name:
|
||||||
|
svg_writer.annotations.setdefault("equal_objs", []).append(obj)
|
||||||
|
elif "Dimension" in obj.name:
|
||||||
|
svg_writer.annotations.setdefault("dimension_objs", []).append(obj)
|
||||||
|
elif "Break" in obj.name:
|
||||||
|
svg_writer.annotations["break_obj"] = obj
|
||||||
|
elif "Hidden" in obj.name:
|
||||||
|
svg_writer.annotations.setdefault("hidden_objs", []).append((obj, obj.data))
|
||||||
|
elif "Solid" in obj.name:
|
||||||
|
svg_writer.annotations.setdefault("solid_objs", []).append((obj, obj.data))
|
||||||
|
elif "Plan Level" in obj.name:
|
||||||
|
svg_writer.annotations["plan_level_obj"] = obj
|
||||||
|
elif "Section Level" in obj.name:
|
||||||
|
svg_writer.annotations["section_level_obj"] = obj
|
||||||
|
elif obj.type == "FONT":
|
||||||
|
svg_writer.annotations.setdefault("text_objs", []).append(obj)
|
||||||
|
else:
|
||||||
|
svg_writer.annotations.setdefault("misc_objs", []).append(obj)
|
||||||
|
|
||||||
|
svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes]
|
||||||
|
|
||||||
|
svg_writer.write()
|
||||||
|
return svg_writer.output
|
||||||
|
|
||||||
|
def is_landscape(self):
|
||||||
|
return bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y
|
||||||
|
|
||||||
|
|
||||||
|
class AddAnnotation(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.add_annotation"
|
||||||
|
bl_label = "Add Annotation"
|
||||||
|
obj_name: bpy.props.StringProperty()
|
||||||
|
data_type: bpy.props.StringProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
if not bpy.context.scene.camera:
|
||||||
|
return {"FINISHED"}
|
||||||
|
if self.data_type == "text":
|
||||||
|
if bpy.context.selected_objects:
|
||||||
|
for selected_object in bpy.context.selected_objects:
|
||||||
|
obj = annotation.Annotator.add_text(related_element=selected_object)
|
||||||
|
else:
|
||||||
|
obj = annotation.Annotator.add_text()
|
||||||
|
else:
|
||||||
|
obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type)
|
||||||
|
if self.obj_name == "Break":
|
||||||
|
obj = annotation.Annotator.add_plane_to_annotation(obj)
|
||||||
|
else:
|
||||||
|
obj = annotation.Annotator.add_line_to_annotation(obj)
|
||||||
|
bpy.ops.object.select_all(action="DESELECT")
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
+48
-43
@@ -6,8 +6,8 @@ import pystache
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import svgwrite
|
import svgwrite
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
from . import annotation
|
import blenderbim.bim.module.drawing.helper as helper
|
||||||
from . import helper
|
import blenderbim.bim.module.drawing.annotation as annotation
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from mathutils import geometry
|
from mathutils import geometry
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
@@ -39,14 +39,16 @@ class External(svgwrite.container.Group):
|
|||||||
|
|
||||||
|
|
||||||
class SvgWriter:
|
class SvgWriter:
|
||||||
def __init__(self, ifc_cutter):
|
def __init__(self):
|
||||||
self.ifc_cutter = ifc_cutter
|
self.output = "out.svg"
|
||||||
|
self.data_dir = None
|
||||||
|
self.vector_style = None
|
||||||
self.human_scale = "NTS"
|
self.human_scale = "NTS"
|
||||||
|
self.annotations = {}
|
||||||
self.scale = 1 / 100 # 1:100
|
self.scale = 1 / 100 # 1:100
|
||||||
|
|
||||||
def write(self):
|
def write(self):
|
||||||
self.calculate_scale()
|
self.calculate_scale()
|
||||||
self.output = os.path.join(self.ifc_cutter.data_dir, "diagrams", self.ifc_cutter.diagram_name + ".svg")
|
|
||||||
self.svg = svgwrite.Drawing(
|
self.svg = svgwrite.Drawing(
|
||||||
self.output,
|
self.output,
|
||||||
debug=False,
|
debug=False,
|
||||||
@@ -60,42 +62,43 @@ class SvgWriter:
|
|||||||
self.add_markers()
|
self.add_markers()
|
||||||
self.add_symbols()
|
self.add_symbols()
|
||||||
self.add_patterns()
|
self.add_patterns()
|
||||||
self.draw_background_image()
|
# self.draw_background_image()
|
||||||
self.draw_background_elements()
|
# self.draw_background_elements()
|
||||||
self.draw_cut_polygons()
|
# self.draw_cut_polygons()
|
||||||
self.draw_annotations()
|
self.draw_annotations()
|
||||||
self.svg.save(pretty=True)
|
self.svg.save(pretty=True)
|
||||||
|
|
||||||
def calculate_scale(self):
|
def calculate_scale(self):
|
||||||
self.scale *= 1000 # IFC is in meters, SVG is in mm
|
self.scale *= 1000 # IFC is in meters, SVG is in mm
|
||||||
self.raw_width = self.ifc_cutter.section_box["x"]
|
self.raw_width = self.camera_width
|
||||||
self.raw_height = self.ifc_cutter.section_box["y"]
|
self.raw_height = self.camera_height
|
||||||
self.width = self.raw_width * self.scale
|
self.width = self.raw_width * self.scale
|
||||||
self.height = self.raw_height * self.scale
|
self.height = self.raw_height * self.scale
|
||||||
|
|
||||||
def add_stylesheet(self):
|
def add_stylesheet(self):
|
||||||
with open("{}styles/{}.css".format(self.ifc_cutter.data_dir, self.ifc_cutter.vector_style), "r") as stylesheet:
|
with open("{}styles/{}.css".format(self.data_dir, self.vector_style), "r") as stylesheet:
|
||||||
self.svg.defs.add(self.svg.style(stylesheet.read()))
|
self.svg.defs.add(self.svg.style(stylesheet.read()))
|
||||||
|
|
||||||
def add_markers(self):
|
def add_markers(self):
|
||||||
tree = ET.parse("{}templates/markers.svg".format(self.ifc_cutter.data_dir))
|
tree = ET.parse("{}templates/markers.svg".format(self.data_dir))
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
for child in root.getchildren():
|
for child in root.getchildren():
|
||||||
self.svg.defs.add(External(child))
|
self.svg.defs.add(External(child))
|
||||||
|
|
||||||
def add_symbols(self):
|
def add_symbols(self):
|
||||||
tree = ET.parse("{}templates/symbols.svg".format(self.ifc_cutter.data_dir))
|
tree = ET.parse("{}templates/symbols.svg".format(self.data_dir))
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
for child in root.getchildren():
|
for child in root.getchildren():
|
||||||
self.svg.defs.add(External(child))
|
self.svg.defs.add(External(child))
|
||||||
|
|
||||||
def add_patterns(self):
|
def add_patterns(self):
|
||||||
tree = ET.parse("{}templates/patterns.svg".format(self.ifc_cutter.data_dir))
|
tree = ET.parse("{}templates/patterns.svg".format(self.data_dir))
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
for child in root.getchildren():
|
for child in root.getchildren():
|
||||||
self.svg.defs.add(External(child))
|
self.svg.defs.add(External(child))
|
||||||
|
|
||||||
def draw_background_image(self):
|
def draw_background_image(self):
|
||||||
|
return # TODO reimplement for artistic drawing options
|
||||||
self.svg.add(
|
self.svg.add(
|
||||||
self.svg.image(
|
self.svg.image(
|
||||||
os.path.join("..", "diagrams", os.path.basename(self.ifc_cutter.background_image)),
|
os.path.join("..", "diagrams", os.path.basename(self.ifc_cutter.background_image)),
|
||||||
@@ -104,6 +107,7 @@ class SvgWriter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def draw_background_elements(self):
|
def draw_background_elements(self):
|
||||||
|
return # TODO purge?
|
||||||
for element in self.ifc_cutter.background_elements:
|
for element in self.ifc_cutter.background_elements:
|
||||||
if element["type"] == "polygon":
|
if element["type"] == "polygon":
|
||||||
self.draw_polygon(element, "background")
|
self.draw_polygon(element, "background")
|
||||||
@@ -116,16 +120,16 @@ class SvgWriter:
|
|||||||
x_offset = self.raw_width / 2
|
x_offset = self.raw_width / 2
|
||||||
y_offset = self.raw_height / 2
|
y_offset = self.raw_height / 2
|
||||||
|
|
||||||
for obj in self.ifc_cutter.equal_objs:
|
for obj in self.annotations.get("equal_objs", []):
|
||||||
self.draw_dimension_annotations(obj, text_override="EQ")
|
self.draw_dimension_annotations(obj, text_override="EQ")
|
||||||
for obj in self.ifc_cutter.dimension_objs:
|
for obj in self.annotations.get("dimension_objs", []):
|
||||||
self.draw_dimension_annotations(obj)
|
self.draw_dimension_annotations(obj)
|
||||||
self.draw_measureit_arch_dimension_annotations()
|
self.draw_measureit_arch_dimension_annotations()
|
||||||
|
|
||||||
if self.ifc_cutter.break_obj:
|
if self.annotations.get("break_obj"):
|
||||||
self.draw_break_annotations(self.ifc_cutter.break_obj)
|
self.draw_break_annotations(self.annotations["break_obj"])
|
||||||
|
|
||||||
for grid_obj in self.ifc_cutter.grid_objs:
|
for grid_obj in self.annotations.get("grid_objs", []):
|
||||||
matrix_world = grid_obj.matrix_world
|
matrix_world = grid_obj.matrix_world
|
||||||
for edge in grid_obj.data.edges:
|
for edge in grid_obj.data.edges:
|
||||||
classes = ["annotation", "grid"]
|
classes = ["annotation", "grid"]
|
||||||
@@ -174,21 +178,21 @@ class SvgWriter:
|
|||||||
|
|
||||||
self.draw_ifc_annotation()
|
self.draw_ifc_annotation()
|
||||||
|
|
||||||
for obj in self.ifc_cutter.misc_objs:
|
for obj in self.annotations.get("misc_objs", []):
|
||||||
self.draw_misc_annotation(obj, ["IfcAnnotation"])
|
self.draw_misc_annotation(obj, ["IfcAnnotation"])
|
||||||
|
|
||||||
for obj_data in self.ifc_cutter.hidden_objs:
|
for obj_data in self.annotations.get("hidden_objs", []):
|
||||||
self.draw_line_annotation(obj_data, ["hidden"])
|
self.draw_line_annotation(obj_data, ["hidden"])
|
||||||
|
|
||||||
for obj_data in self.ifc_cutter.solid_objs:
|
for obj_data in self.annotations.get("solid_objs", []):
|
||||||
self.draw_line_annotation(obj_data, ["solid"])
|
self.draw_line_annotation(obj_data, ["solid"])
|
||||||
|
|
||||||
if self.ifc_cutter.leader_obj:
|
if self.annotations.get("leader_obj"):
|
||||||
self.draw_line_annotation(self.ifc_cutter.leader_obj, ["leader"])
|
self.draw_line_annotation(self.annotations["leader_obj"], ["leader"])
|
||||||
|
|
||||||
if self.ifc_cutter.plan_level_obj:
|
if self.annotations.get("plan_level_obj"):
|
||||||
matrix_world = self.ifc_cutter.plan_level_obj.matrix_world
|
matrix_world = self.annotations["plan_level_obj"].matrix_world
|
||||||
for spline in self.ifc_cutter.plan_level_obj.data.splines:
|
for spline in self.annotations["plan_level_obj"].data.splines:
|
||||||
classes = ["annotation", "plan-level"]
|
classes = ["annotation", "plan-level"]
|
||||||
points = self.get_spline_points(spline)
|
points = self.get_spline_points(spline)
|
||||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||||
@@ -208,7 +212,7 @@ class SvgWriter:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
# TODO: allow metric to be configurable
|
# TODO: allow metric to be configurable
|
||||||
rl = ((matrix_world @ points[0].co).xyz + self.ifc_cutter.plan_level_obj.location).z
|
rl = ((matrix_world @ points[0].co).xyz + self.annotations["plan_level_obj"].location).z
|
||||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||||
rl = helper.format_distance(rl)
|
rl = helper.format_distance(rl)
|
||||||
else:
|
else:
|
||||||
@@ -231,9 +235,9 @@ class SvgWriter:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.ifc_cutter.section_level_obj:
|
if self.annotations.get("section_level_obj"):
|
||||||
matrix_world = self.ifc_cutter.section_level_obj.matrix_world
|
matrix_world = self.annotations["section_level_obj"].matrix_world
|
||||||
for spline in self.ifc_cutter.section_level_obj.data.splines:
|
for spline in self.annotations["section_level_obj"].data.splines:
|
||||||
classes = ["annotation", "section-level"]
|
classes = ["annotation", "section-level"]
|
||||||
points = self.get_spline_points(spline)
|
points = self.get_spline_points(spline)
|
||||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||||
@@ -273,9 +277,9 @@ class SvgWriter:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.ifc_cutter.stair_obj:
|
if self.annotations.get("stair_obj"):
|
||||||
matrix_world = self.ifc_cutter.stair_obj.matrix_world
|
matrix_world = self.annotations["stair_obj"].matrix_world
|
||||||
for spline in self.ifc_cutter.stair_obj.data.splines:
|
for spline in self.annotations["stair_obj"].data.splines:
|
||||||
classes = ["annotation", "stair"]
|
classes = ["annotation", "stair"]
|
||||||
points = self.get_spline_points(spline)
|
points = self.get_spline_points(spline)
|
||||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||||
@@ -308,7 +312,7 @@ class SvgWriter:
|
|||||||
def draw_ifc_annotation(self):
|
def draw_ifc_annotation(self):
|
||||||
x_offset = self.raw_width / 2
|
x_offset = self.raw_width / 2
|
||||||
y_offset = self.raw_height / 2
|
y_offset = self.raw_height / 2
|
||||||
for annotation in self.ifc_cutter.annotation_objs:
|
for annotation in self.annotations.get("annotation_objs", []):
|
||||||
for edge in annotation["edges"]:
|
for edge in annotation["edges"]:
|
||||||
v0_global = annotation["vertices"][edge[0]]
|
v0_global = annotation["vertices"][edge[0]]
|
||||||
v1_global = annotation["vertices"][edge[1]]
|
v1_global = annotation["vertices"][edge[1]]
|
||||||
@@ -357,7 +361,7 @@ class SvgWriter:
|
|||||||
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", slot.material.name)))
|
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", slot.material.name)))
|
||||||
global_id = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
|
global_id = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
|
||||||
classes.append("globalid-{}".format(global_id))
|
classes.append("globalid-{}".format(global_id))
|
||||||
for attribute in self.ifc_cutter.attributes:
|
for attribute in self.annotations.get("attributes", []):
|
||||||
result = self.get_obj_value(obj, attribute)
|
result = self.get_obj_value(obj, attribute)
|
||||||
if result:
|
if result:
|
||||||
classes.append(
|
classes.append(
|
||||||
@@ -435,7 +439,7 @@ class SvgWriter:
|
|||||||
x_offset = self.raw_width / 2
|
x_offset = self.raw_width / 2
|
||||||
y_offset = self.raw_height / 2
|
y_offset = self.raw_height / 2
|
||||||
|
|
||||||
for text_obj in self.ifc_cutter.text_objs:
|
for text_obj in self.annotations.get("text_objs", []):
|
||||||
text_position = self.project_point_onto_camera(text_obj.location)
|
text_position = self.project_point_onto_camera(text_obj.location)
|
||||||
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
|
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
|
||||||
|
|
||||||
@@ -473,8 +477,8 @@ class SvgWriter:
|
|||||||
alignment_baseline = "baseline"
|
alignment_baseline = "baseline"
|
||||||
|
|
||||||
text_body = text_obj.data.body
|
text_body = text_obj.data.body
|
||||||
if text_obj.name in self.ifc_cutter.template_variables:
|
if text_obj.name in self.annotations.get("template_variables", {}):
|
||||||
text_body = pystache.render(text_body, self.ifc_cutter.template_variables[text_obj.name])
|
text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name])
|
||||||
|
|
||||||
for line_number, text_line in enumerate(text_body.split("\n")):
|
for line_number, text_line in enumerate(text_body.split("\n")):
|
||||||
self.svg.add(
|
self.svg.add(
|
||||||
@@ -587,17 +591,18 @@ class SvgWriter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def project_point_onto_camera(self, point):
|
def project_point_onto_camera(self, point):
|
||||||
return self.ifc_cutter.camera_obj.matrix_world.inverted() @ geometry.intersect_line_plane(
|
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
|
||||||
point.xyz,
|
point.xyz,
|
||||||
point.xyz - Vector(self.ifc_cutter.section_box["projection"]),
|
point.xyz - Vector(self.camera_projection),
|
||||||
self.ifc_cutter.camera_obj.location,
|
self.camera.location,
|
||||||
Vector(self.ifc_cutter.section_box["projection"]),
|
Vector(self.camera_projection),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_spline_points(self, spline):
|
def get_spline_points(self, spline):
|
||||||
return spline.bezier_points if spline.bezier_points else spline.points
|
return spline.bezier_points if spline.bezier_points else spline.points
|
||||||
|
|
||||||
def draw_cut_polygons(self):
|
def draw_cut_polygons(self):
|
||||||
|
return # deprecate?
|
||||||
for polygon in self.ifc_cutter.cut_polygons:
|
for polygon in self.ifc_cutter.cut_polygons:
|
||||||
self.draw_polygon(polygon, "cut")
|
self.draw_polygon(polygon, "cut")
|
||||||
|
|
||||||
@@ -16,12 +16,10 @@ import numpy as np
|
|||||||
from . import export_ifc
|
from . import export_ifc
|
||||||
from . import import_ifc
|
from . import import_ifc
|
||||||
from . import cut_ifc
|
from . import cut_ifc
|
||||||
from . import svgwriter
|
|
||||||
from . import sheeter
|
from . import sheeter
|
||||||
from . import scheduler
|
from . import scheduler
|
||||||
from . import schema
|
from . import schema
|
||||||
from . import ifc
|
from . import ifc
|
||||||
from . import annotation
|
|
||||||
from . import helper
|
from . import helper
|
||||||
from bpy_extras.io_utils import ImportHelper
|
from bpy_extras.io_utils import ImportHelper
|
||||||
from mathutils import Vector, Matrix, Euler, geometry
|
from mathutils import Vector, Matrix, Euler, geometry
|
||||||
@@ -1102,33 +1100,6 @@ class SelectDocIfcFile(bpy.types.Operator):
|
|||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
|
|
||||||
class AddAnnotation(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.add_annotation"
|
|
||||||
bl_label = "Add Annotation"
|
|
||||||
obj_name: bpy.props.StringProperty()
|
|
||||||
data_type: bpy.props.StringProperty()
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
if not bpy.context.scene.camera:
|
|
||||||
return {"FINISHED"}
|
|
||||||
if self.data_type == "text":
|
|
||||||
if bpy.context.selected_objects:
|
|
||||||
for selected_object in bpy.context.selected_objects:
|
|
||||||
obj = annotation.Annotator.add_text(related_element=selected_object)
|
|
||||||
else:
|
|
||||||
obj = annotation.Annotator.add_text()
|
|
||||||
else:
|
|
||||||
obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type)
|
|
||||||
if self.obj_name == "Break":
|
|
||||||
obj = annotation.Annotator.add_plane_to_annotation(obj)
|
|
||||||
else:
|
|
||||||
obj = annotation.Annotator.add_line_to_annotation(obj)
|
|
||||||
bpy.ops.object.select_all(action="DESELECT")
|
|
||||||
bpy.context.view_layer.objects.active = obj
|
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class GenerateReferences(bpy.types.Operator):
|
class GenerateReferences(bpy.types.Operator):
|
||||||
bl_idname = "bim.generate_references"
|
bl_idname = "bim.generate_references"
|
||||||
bl_label = "Generate References"
|
bl_label = "Generate References"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import ifcopenshell
|
|||||||
from . import export_ifc
|
from . import export_ifc
|
||||||
from . import schema
|
from . import schema
|
||||||
from . import ifc
|
from . import ifc
|
||||||
from . import annotation
|
import blenderbim.bim.module.drawing.annotation as annotation
|
||||||
from . import decoration
|
from . import decoration
|
||||||
import bpy
|
import bpy
|
||||||
from blenderbim.bim.handler import purge_module_data
|
from blenderbim.bim.handler import purge_module_data
|
||||||
|
|||||||
Reference in New Issue
Block a user