mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-30 16:43:00 +00:00
552 lines
23 KiB
Python
552 lines
23 KiB
Python
# Bonsai - OpenBIM Blender Add-on
|
|
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
|
#
|
|
# This file is part of Bonsai.
|
|
#
|
|
# Bonsai is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Bonsai is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import bpy
|
|
import ifcopenshell.util.element
|
|
import ifcopenshell.util.placement
|
|
import bonsai.tool as tool
|
|
import bonsai.bim.helper
|
|
from bonsai.bim.module.project.data import ProjectData, ProjectLibraryData
|
|
from bonsai.bim.ifc import IfcStore
|
|
from bonsai.bim.prop import StrProperty, ObjProperty, Attribute
|
|
from bpy.types import PropertyGroup
|
|
from bpy.props import (
|
|
PointerProperty,
|
|
BoolProperty,
|
|
CollectionProperty,
|
|
EnumProperty,
|
|
FloatProperty,
|
|
IntProperty,
|
|
StringProperty,
|
|
)
|
|
from typing import TYPE_CHECKING, Literal, Union, get_args, assert_never
|
|
from collections.abc import Generator
|
|
|
|
|
|
def get_export_schema(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
|
if not ProjectData.is_loaded:
|
|
ProjectData.load()
|
|
return ProjectData.data["export_schema"]
|
|
|
|
|
|
def update_export_schema(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
|
# Avoid breaking empty enum.
|
|
self["template_file"] = 0
|
|
|
|
|
|
def get_template_file(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
|
if not ProjectData.is_loaded:
|
|
ProjectData.load()
|
|
return ProjectData.data["template_file"][self.export_schema]
|
|
|
|
|
|
def get_library_file(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
|
if not ProjectData.is_loaded:
|
|
ProjectData.load()
|
|
return ProjectData.data["library_file"]
|
|
|
|
|
|
def update_library_file(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
|
if self.library_file != "0":
|
|
filepath = next(p for p in tool.Blender.get_data_dir_paths("libraries", "*.ifc") if p.name == self.library_file)
|
|
bpy.ops.bim.select_library_file(filepath=filepath.__str__())
|
|
ProjectLibraryData.load()
|
|
props = tool.Project.get_project_props()
|
|
library_file = IfcStore.library_file
|
|
assert library_file
|
|
project_library = next(iter(library_file.by_type("IfcProjectLibrary")), None)
|
|
props.selected_project_library = str(project_library.id()) if project_library else "-"
|
|
|
|
|
|
def update_selected_project_library(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
|
# Ensure `.is_declared` up to date.
|
|
tool.Project.update_current_library_page()
|
|
|
|
|
|
def get_project_libaries(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
|
if not ProjectLibraryData.is_loaded:
|
|
ProjectLibraryData.load()
|
|
return ProjectLibraryData.data["project_libraries_enum"]
|
|
|
|
|
|
def show_library_tree_update(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
|
bpy.ops.bim.refresh_library()
|
|
|
|
|
|
def is_editing_project_library_update(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
|
if self.is_editing_project_library:
|
|
library_file = IfcStore.library_file
|
|
assert library_file
|
|
self.editing_project_library_id = int(self.selected_project_library)
|
|
project_library = library_file.by_id(int(self.selected_project_library))
|
|
self.project_library_attributes.clear()
|
|
bonsai.bim.helper.import_attributes(project_library, self.project_library_attributes)
|
|
self.parent_library = str(tool.Project.get_parent_library(project_library).id())
|
|
ProjectLibraryData.load() # Show edit icon in enum.
|
|
return
|
|
|
|
del self["is_editing_project_library"]
|
|
del self["editing_project_library_id"]
|
|
self.project_library_attributes.clear()
|
|
del self["parent_library"]
|
|
ProjectLibraryData.load() # Hide edit icon in enum.
|
|
|
|
|
|
def get_parent_libaries(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
|
if not ProjectLibraryData.is_loaded:
|
|
ProjectLibraryData.load()
|
|
edited_library = str(self.editing_project_library_id)
|
|
# Prevent assigning library to itself.
|
|
enum_items = [i for i in ProjectLibraryData.data["parent_libraries_enum"] if i[0] != edited_library]
|
|
return enum_items
|
|
|
|
|
|
def update_filter_mode(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
|
self.filter_categories.clear()
|
|
if self.filter_mode == "NONE":
|
|
return
|
|
file = tool.Ifc.get()
|
|
if self.filter_mode == "DECOMPOSITION":
|
|
if file.schema == "IFC2X3":
|
|
elements = file.by_type("IfcSpatialStructureElement")
|
|
else:
|
|
elements = file.by_type("IfcSpatialElement")
|
|
elements = [(e, ifcopenshell.util.placement.get_storey_elevation(e)) for e in elements]
|
|
elements = sorted(elements, key=lambda e: e[1])
|
|
for element in elements:
|
|
element = element[0]
|
|
new = self.filter_categories.add()
|
|
new.name = "{}/{}".format(element.is_a(), element.Name or "Unnamed")
|
|
new.ifc_definition_id = element.id()
|
|
new.total_elements = sum([len(r.RelatedElements) for r in element.ContainsElements])
|
|
elif self.filter_mode == "IFC_CLASS":
|
|
for ifc_class in sorted(list({e.is_a() for e in file.by_type("IfcElement")})):
|
|
new = self.filter_categories.add()
|
|
new.name = ifc_class
|
|
new.total_elements = len(file.by_type(ifc_class, include_subtypes=False))
|
|
elif self.filter_mode == "IFC_TYPE":
|
|
for ifc_type in sorted(file.by_type("IfcElementType"), key=lambda e: e.Name or "Unnamed"):
|
|
new = self.filter_categories.add()
|
|
new.name = ifc_type.is_a() + "/" + (ifc_type.Name or "Unnamed")
|
|
new.ifc_definition_id = ifc_type.id()
|
|
new.total_elements = len(ifcopenshell.util.element.get_types(ifc_type))
|
|
|
|
|
|
def update_library_element_name(self: "LibraryElement", context: bpy.types.Context) -> None:
|
|
library_file = IfcStore.library_file
|
|
assert library_file
|
|
|
|
if self.element_type == "CLASS":
|
|
raise Exception("Unexpected element type for rename: 'CLASS'.")
|
|
|
|
def update_element_name(ifc_definition_id: int, name: str) -> None:
|
|
element = library_file.by_id(ifc_definition_id)
|
|
attr_name = tool.Project.get_library_element_attr_name(element)
|
|
previous_name = getattr(element, attr_name)
|
|
if name == previous_name:
|
|
return
|
|
setattr(element, attr_name, name)
|
|
|
|
if self.element_type == "ASSET":
|
|
update_element_name(self.ifc_definition_id, self.name)
|
|
elif self.element_type == "LIBRARY":
|
|
assert self.ifc_definition_id, "Renaming for unassigned elements library is not supported."
|
|
update_element_name(self.ifc_definition_id, self.name)
|
|
else:
|
|
assert_never(self.element_type)
|
|
|
|
|
|
LibraryElementType = Literal["ASSET", "CLASS", "LIBRARY"]
|
|
|
|
|
|
class LibraryElement(PropertyGroup):
|
|
name: StringProperty(name="Name", update=update_library_element_name)
|
|
element_type: EnumProperty(items=[(i, i, "") for i in get_args(LibraryElementType)], name="Element Type")
|
|
# Asset group.
|
|
asset_count: IntProperty(name="Asset Count")
|
|
# Asset library.
|
|
has_sublibraries: BoolProperty(name="Has Sublibraries", default=False)
|
|
# Asset.
|
|
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
|
is_declared: BoolProperty(name="Is Declared", default=False)
|
|
is_appended: BoolProperty(name="Is Appended", default=False)
|
|
is_declarable: BoolProperty(
|
|
name="Is Declarable",
|
|
description="Whether element support being declared as part of IfcProjectLibrary",
|
|
default=False,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
name: str
|
|
element_type: LibraryElementType
|
|
asset_count: int
|
|
has_sublibraries: bool
|
|
ifc_definition_id: int
|
|
is_declared: bool
|
|
is_appended: bool
|
|
is_declarable: bool
|
|
|
|
|
|
class FilterCategory(PropertyGroup):
|
|
name: StringProperty(name="Name")
|
|
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
|
is_selected: BoolProperty(name="Is Selected", default=False)
|
|
total_elements: IntProperty(name="Total Elements")
|
|
|
|
if TYPE_CHECKING:
|
|
name: str
|
|
ifc_definition_id: int
|
|
is_selected: bool
|
|
total_elements: int
|
|
|
|
|
|
class Link(PropertyGroup):
|
|
name: StringProperty(
|
|
name="Name",
|
|
description="Filepath to linked .ifc file, stored in posix format (could be relative to .ifc file, not to .blend)",
|
|
)
|
|
is_loaded: BoolProperty(name="Is Loaded", default=False)
|
|
is_selectable: BoolProperty(name="Is Selectable", default=True)
|
|
is_wireframe: BoolProperty(name="Is Wireframe", default=False)
|
|
is_hidden: BoolProperty(name="Is Hidden", default=False)
|
|
include_in_drawings: BoolProperty(name="Include in Drawings", default=True, options=set())
|
|
empty_handle: PointerProperty(
|
|
name="Empty Object Handle",
|
|
description="We use empty object handle to allow simple manipulations with a linked model (moving, scaling, rotating)",
|
|
type=bpy.types.Object,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
name: str
|
|
is_loaded: bool
|
|
is_selectable: bool
|
|
is_wireframe: bool
|
|
is_hidden: bool
|
|
include_in_drawings: bool
|
|
empty_handle: Union[bpy.types.Object, None]
|
|
|
|
|
|
class EditedObj(PropertyGroup):
|
|
obj: PointerProperty(type=bpy.types.Object)
|
|
|
|
if TYPE_CHECKING:
|
|
obj: Union[bpy.types.Object, None]
|
|
|
|
|
|
BreadcrumbType = Literal["LIBRARY", "CLASS"]
|
|
|
|
|
|
class LibraryBreadcrumb(PropertyGroup):
|
|
breadcrumb_type: EnumProperty(items=[(i, i, "") for i in get_args(BreadcrumbType)])
|
|
library_id: IntProperty(description="IFC Definition ID for libraries.")
|
|
|
|
if TYPE_CHECKING:
|
|
breadcrumb_type: BreadcrumbType
|
|
library_id: int
|
|
|
|
|
|
class BIMProjectProperties(PropertyGroup):
|
|
is_editing: BoolProperty(name="Is Editing", default=False)
|
|
is_loading: BoolProperty(name="Is Loading", default=False)
|
|
mvd: StringProperty(name="MVD")
|
|
author_name: StringProperty(name="Author")
|
|
author_email: StringProperty(name="Author Email")
|
|
organisation_name: StringProperty(name="Organisation")
|
|
organisation_email: StringProperty(name="Organisation Email")
|
|
authorisation: StringProperty(name="Authoriser")
|
|
library_breadcrumb: CollectionProperty(name="Library Breadcrumb", type=LibraryBreadcrumb)
|
|
library_elements: CollectionProperty(name="Library Elements", type=LibraryElement)
|
|
active_library_element_index: IntProperty(name="Active Library Element Index")
|
|
filter_mode: bpy.props.EnumProperty(
|
|
items=[
|
|
("NONE", "None", "No filtering is performed"),
|
|
("DECOMPOSITION", "Decomposition", "Filter objects by decomposition"),
|
|
("IFC_CLASS", "IFC Class", "Filter objects by class"),
|
|
("IFC_TYPE", "IFC Type", "Filter objects by type"),
|
|
("WHITELIST", "Whitelist", "Filter objects using a custom whitelist query"),
|
|
("BLACKLIST", "Blacklist", "Filter objects using a custom blacklist query"),
|
|
],
|
|
name="Filter Mode",
|
|
update=update_filter_mode,
|
|
)
|
|
total_elements: IntProperty(name="Total Elements", default=0)
|
|
filter_categories: CollectionProperty(name="Filter Categories", type=FilterCategory)
|
|
active_filter_category_index: IntProperty(name="Active Filter Category Index")
|
|
filter_query: StringProperty(name="Filter Query")
|
|
should_filter_spatial_elements: BoolProperty(name="Filter Spatial Elements", default=False)
|
|
geometry_library: bpy.props.EnumProperty(
|
|
items=[
|
|
("opencascade", "OpenCASCADE", "Best for stability and accuracy"),
|
|
("cgal", "CGAL", "Best for speed"),
|
|
("cgal-simple", "CGAL Simple", "CGAL without booleans"),
|
|
("hybrid-cgal-simple-opencascade", "Hybrid CGAL-OCC", "First CGAL then fallback to OCC"),
|
|
],
|
|
name="Geometry Library",
|
|
default="hybrid-cgal-simple-opencascade",
|
|
)
|
|
should_use_cpu_multiprocessing: BoolProperty(name="CPU Multiprocessing", default=True)
|
|
should_merge_materials_by_colour: BoolProperty(name="Merge Materials by Colour", default=False)
|
|
should_load_geometry: BoolProperty(name="Load Geometry", default=True)
|
|
should_clean_mesh: BoolProperty(
|
|
name="Clean Meshes",
|
|
description=(
|
|
"Convert all triangles to quads for meshes. "
|
|
"By default Bonsai is importing meshes triangulated (even if they are not stored as triangulated in IFC)."
|
|
),
|
|
default=False,
|
|
)
|
|
should_cache: BoolProperty( # pyright: ignore[reportRedeclaration]
|
|
name="Cache",
|
|
description=(
|
|
"Cache loaded geometry to .h5 file in your cache directory (see in preferences) "
|
|
"for faster imports and geometry reloads."
|
|
),
|
|
default=False,
|
|
)
|
|
deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.001)
|
|
angular_tolerance: FloatProperty(name="Angular Tolerance", default=0.5)
|
|
void_limit: IntProperty(
|
|
name="Void Limit",
|
|
default=30,
|
|
description="Maxium number of openings that object can have. If object has more openings, it will be loaded without openings",
|
|
)
|
|
style_limit: IntProperty(
|
|
name="Style Limit",
|
|
default=300,
|
|
description="Maxium number of styles before styles are automatically merged",
|
|
)
|
|
distance_limit: FloatProperty(name="Distance Limit", default=1000, subtype="DISTANCE")
|
|
false_origin_mode: bpy.props.EnumProperty(
|
|
items=[
|
|
(
|
|
"AUTOMATIC",
|
|
"Automatic",
|
|
"An automatic false origin will be detected from geometry with large coordinates",
|
|
),
|
|
("MANUAL", "Manual", "You can specify the false origin coordinates"),
|
|
("DISABLED", "Disabled", "The model in original local coordinates will be shown as is"),
|
|
],
|
|
name="False Origin Mode",
|
|
default="AUTOMATIC",
|
|
)
|
|
false_origin: StringProperty(
|
|
name="False Origin",
|
|
description="False origin in project units that the Blender origin will correlate to",
|
|
default="0,0,0",
|
|
)
|
|
project_north: StringProperty(
|
|
name="Angle to Grid North",
|
|
description="The angle (postive is anticlockwise) pointing to grid north relative to project north",
|
|
default="0",
|
|
)
|
|
element_limit_mode: bpy.props.EnumProperty(
|
|
items=[
|
|
("UNLIMITED", "Load Everything", "Load all elements"),
|
|
("RANGE", "Load Subset of Elements", "Only load the first N elements"),
|
|
],
|
|
name="Element limit",
|
|
default="UNLIMITED",
|
|
)
|
|
element_offset: IntProperty(name="Element Offset", default=0)
|
|
element_limit: IntProperty(name="Element Offset", default=30000)
|
|
load_indexed_maps: BoolProperty(
|
|
name="Load Indexed Maps",
|
|
description="Load indexed maps (UV and color maps)",
|
|
default=True,
|
|
)
|
|
links: CollectionProperty(name="Links", type=Link)
|
|
active_link_index: IntProperty(name="Active Link Index")
|
|
export_schema: EnumProperty(items=get_export_schema, name="IFC Schema", update=update_export_schema)
|
|
template_file: EnumProperty(
|
|
items=get_template_file,
|
|
name="Template File",
|
|
description=(
|
|
"Template to use for a new project. All types from the template will be appended to a new project."
|
|
),
|
|
)
|
|
|
|
# Project library UI.
|
|
library_file: EnumProperty(items=get_library_file, name="Library File", update=update_library_file)
|
|
selected_project_library: EnumProperty(
|
|
items=get_project_libaries,
|
|
name="Selected Project Library",
|
|
description="Selected project library to edit or to assign elements to",
|
|
update=update_selected_project_library,
|
|
)
|
|
show_library_tree: BoolProperty(
|
|
name="Show Library Tree",
|
|
description="Show project libraries hierarchy or just show the assets classes.",
|
|
default=True,
|
|
update=show_library_tree_update,
|
|
)
|
|
is_editing_project_library: BoolProperty(
|
|
name="Is Editing Project Library",
|
|
description="Toggle editing for currently selected project library",
|
|
update=is_editing_project_library_update,
|
|
)
|
|
editing_project_library_id: IntProperty(
|
|
description="Needed to keep track of currently edited library when user changes currently selected library in dropdown."
|
|
)
|
|
project_library_attributes: CollectionProperty(name="Project Library Attributes", type=Attribute)
|
|
parent_library: EnumProperty(
|
|
name="Parent Library",
|
|
description="Parent library that library is assigned to (either IfcProject or IfcProjectLibrary).",
|
|
items=get_parent_libaries,
|
|
)
|
|
|
|
use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False)
|
|
should_save_metadata_for_this_file: BoolProperty(
|
|
name="Save Session Data for This File",
|
|
description="Enable saving session data (window layout, settings) to a metadata blend file for this specific IFC file",
|
|
default=False,
|
|
)
|
|
queried_obj: bpy.props.PointerProperty(type=bpy.types.Object)
|
|
queried_obj_root: bpy.props.PointerProperty(type=bpy.types.Object)
|
|
clipping_planes: bpy.props.CollectionProperty(type=ObjProperty)
|
|
clipping_planes_active_index: bpy.props.IntProperty(min=0, default=0, max=5)
|
|
edited_objs: bpy.props.CollectionProperty(type=EditedObj)
|
|
|
|
@property
|
|
def active_clipping_plane(self) -> ObjProperty | None:
|
|
return tool.Blender.get_active_uilist_element(self.clipping_planes, self.clipping_planes_active_index)
|
|
|
|
@property
|
|
def clipping_planes_objs(self) -> list[bpy.types.Object]:
|
|
return list({cp.obj for cp in self.clipping_planes if cp.obj})
|
|
|
|
def add_library_project_library(
|
|
self, name: str, asset_count: int, ifc_definition_id: int, has_sublibraries: bool
|
|
) -> LibraryElement:
|
|
new = self.library_elements.add()
|
|
new["name"] = name
|
|
new.asset_count = asset_count
|
|
new.element_type = "LIBRARY"
|
|
new.ifc_definition_id = ifc_definition_id
|
|
new.has_sublibraries = has_sublibraries
|
|
return new
|
|
|
|
def add_library_asset_class(self, name: str, asset_count: int) -> LibraryElement:
|
|
new = self.library_elements.add()
|
|
new["name"] = name
|
|
new.asset_count = asset_count
|
|
new.element_type = "CLASS"
|
|
return new
|
|
|
|
def get_library_element_index(self, lib_element: LibraryElement) -> int:
|
|
return next(i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element)
|
|
|
|
def load_header_data(self, header_data: tool.Project.HeaderData) -> None:
|
|
self.mvd = header_data.mvd
|
|
self.author_name = header_data.author_name
|
|
self.author_email = header_data.author_email
|
|
self.organisation_name = header_data.organisation_name
|
|
self.organisation_email = header_data.organisation_email
|
|
self.authorisation = header_data.authorisation
|
|
|
|
if TYPE_CHECKING:
|
|
is_editing: bool
|
|
is_loading: bool
|
|
mvd: str
|
|
author_name: str
|
|
author_email: str
|
|
organisation_name: str
|
|
organisation_email: str
|
|
authorisation: str
|
|
library_breadcrumb: bpy.types.bpy_prop_collection_idprop[LibraryBreadcrumb]
|
|
library_elements: bpy.types.bpy_prop_collection_idprop[LibraryElement]
|
|
active_library_element_index: int
|
|
filter_mode: Literal["NONE", "DECOMPOSITION", "IFC_CLASS", "IFC_TYPE", "WHITELIST", "BLACKLIST"]
|
|
total_elements: int
|
|
filter_categories: bpy.types.bpy_prop_collection_idprop[FilterCategory]
|
|
active_filter_category_index: int
|
|
filter_query: str
|
|
should_filter_spatial_elements: bool
|
|
geometry_library: Literal["opencascade", "cgal", "cgal-simple", "hybrid-cgal-simple-opencascade"]
|
|
should_use_cpu_multiprocessing: bool
|
|
should_merge_materials_by_colour: bool
|
|
should_load_geometry: bool
|
|
should_clean_mesh: bool
|
|
should_cache: bool
|
|
deflection_tolerance: float
|
|
angular_tolerance: float
|
|
void_limit: int
|
|
style_limit: int
|
|
distance_limit: float
|
|
false_origin_mode: Literal["AUTOMATIC", "MANUAL", "DISABLED"]
|
|
false_origin: str
|
|
project_north: str
|
|
element_limit_mode: Literal["UNLIMITED", "RANGE"]
|
|
element_offset: int
|
|
element_limit: int
|
|
load_indexed_maps: bool
|
|
links: bpy.types.bpy_prop_collection_idprop[Link]
|
|
active_link_index: int
|
|
export_schema: str
|
|
template_file: str
|
|
|
|
library_file: str
|
|
selected_project_library: Union[Literal["-"], str]
|
|
show_library_tree: bool
|
|
is_editing_project_library: bool
|
|
editing_project_library_id: int
|
|
project_library_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
|
parent_library: str
|
|
|
|
use_relative_project_path: bool
|
|
should_save_metadata_for_this_file: bool
|
|
queried_obj: Union[bpy.types.Object, None]
|
|
queried_obj_root: Union[bpy.types.Object, None]
|
|
clipping_planes: bpy.types.bpy_prop_collection_idprop[ObjProperty]
|
|
clipping_planes_active_index: int
|
|
edited_objs: bpy.types.bpy_prop_collection_idprop[EditedObj]
|
|
|
|
def get_active_library_breadcrumb(self) -> Union[LibraryBreadcrumb, None]:
|
|
if self.library_breadcrumb:
|
|
return self.library_breadcrumb[-1]
|
|
return None
|
|
|
|
def get_loaded_links(self) -> Generator[Link]:
|
|
for link in self.links:
|
|
if not link.is_loaded:
|
|
continue
|
|
yield link
|
|
|
|
def get_loaded_links_for_drawings(self) -> Generator[Link]:
|
|
props = tool.Drawing.get_document_props()
|
|
if not props.should_draw_linked_projects:
|
|
return
|
|
for link in self.get_loaded_links():
|
|
if not link.include_in_drawings:
|
|
continue
|
|
yield link
|
|
|
|
|
|
class MeasureToolSettings(PropertyGroup):
|
|
measurement_type_items = [
|
|
("SINGLE", "SINGLE", "Single", "FIXED_SIZE", 1),
|
|
("POLYLINE", "POLYLINE", "Polyline", "DRIVER_ROTATIONAL_DIFFERENCE", 2),
|
|
("POLY_AREA", "POLY_AREA", "Poyline Area", "OUTLINER_DATA_LIGHTPROBE", 3),
|
|
("FACE_AREA", "FACE_AREA", "Face Area", "FACESEL", 4),
|
|
]
|
|
|
|
measurement_type: bpy.props.EnumProperty(items=measurement_type_items, default="POLYLINE")
|
|
|
|
if TYPE_CHECKING:
|
|
measurement_type: Literal["SINGLE", "POLYLINE", "AREA"]
|