mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-20 20:22:09 +00:00
Bonsai: include/exclude filter pair for linked models
A single selector query cannot express set differences (the grammar only unions groups, and the parent facet cannot negate), so links now carry an Exclude query beside the include, mirroring the drawing Include/Exclude pattern: final set = include (or the default set when empty) minus exclude. Applied in LoadLinkedProject and per link in create_drawing so prints match the viewport. The cache key hashes both strings when an exclude exists - keying on the query alone would let same-include/different-exclude links serve each other's geometry. Include-only filters keep the pre-exclude hash and empty filters the legacy names, so existing caches stay valid. Persistence in IfcDocumentReference.Description stays backwards compatible: a plain include is stored as-is, an exclude promotes the value to a small JSON blob, and non-JSON decodes as a legacy include. The Exclude field appears in Link IFC and the Reload Link dialog (carried through the file browser round trip, SKIP_SAVE like the rest). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -946,9 +946,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# One entry per file *and* per link - the same file can be linked
|
||||
# several times with different queries and transformations, so links
|
||||
# cannot be collapsed into a dict keyed by filepath.
|
||||
# Each entry is (path, file, link transformation or None, link query).
|
||||
file_entries: list[tuple[str, ifcopenshell.file, Optional[np.ndarray], str]] = [
|
||||
(bim_props.ifc_file, tool.Ifc.get(), None, "")
|
||||
# Each entry is (path, file, link transformation or None, link query, link exclude).
|
||||
file_entries: list[tuple[str, ifcopenshell.file, Optional[np.ndarray], str, str]] = [
|
||||
(bim_props.ifc_file, tool.Ifc.get(), None, "", "")
|
||||
]
|
||||
for link in props.get_loaded_links_for_drawings():
|
||||
file_entries.append(
|
||||
@@ -957,6 +957,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.get_linked_file(link),
|
||||
tool.Project.get_link_transformation_matrix(link),
|
||||
link.query,
|
||||
link.exclude,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -972,7 +973,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
raycast_objs = set()
|
||||
elements_with_faces = set()
|
||||
|
||||
for ifc_path, ifc, link_transform, link_query in file_entries:
|
||||
for ifc_path, ifc, link_transform, link_query, link_exclude in file_entries:
|
||||
# Don't use draw.main() just whilst we're prototyping and experimenting
|
||||
# TODO: hash paths are never used
|
||||
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
|
||||
@@ -981,8 +982,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.serialiser.setFile(ifc)
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
|
||||
if link_query:
|
||||
# Draw only what the link's selector query loaded in the viewport.
|
||||
# Draw only what the link's selector filter loaded in the viewport.
|
||||
drawing_elements &= ifcopenshell.util.selector.filter_elements(ifc, link_query)
|
||||
if link_exclude:
|
||||
drawing_elements -= ifcopenshell.util.selector.filter_elements(ifc, link_exclude)
|
||||
|
||||
if self.cprops.fill_mode == "SHAPELY":
|
||||
for element in drawing_elements.copy():
|
||||
|
||||
@@ -1366,6 +1366,14 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
"Default query - IfcElement, but excluding IfcProxy, IfcSpatialStructureElement, IfcSpatialElement, IfcFeatureElement."
|
||||
),
|
||||
)
|
||||
exclude: bpy.props.StringProperty(
|
||||
name="Exclude",
|
||||
description=(
|
||||
"Selector query whose matches are excluded from the loaded elements.\n\n"
|
||||
"Applied on top of the query (or the default set), providing the set "
|
||||
"difference a single query cannot express. E.g. 'IfcSlab, parent=\"X\"'."
|
||||
),
|
||||
)
|
||||
|
||||
filename_ext = ".ifc"
|
||||
|
||||
@@ -1377,6 +1385,7 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
use_relative_path: bool
|
||||
use_cache: bool
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
@@ -1395,6 +1404,7 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "project_north")
|
||||
self.layout.prop(self, "query", placeholder="IfcElement")
|
||||
self.layout.prop(self, "exclude", placeholder='IfcSlab, parent="..."')
|
||||
|
||||
def _execute(self, context):
|
||||
start = time.time()
|
||||
@@ -1427,14 +1437,16 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
reference = ifcopenshell.api.document.add_reference(tool.Ifc.get(), information=document)
|
||||
reference[1] = ",".join([str(o) for o in np.eye(4).flatten().tolist()])
|
||||
reference.Location = filepath.replace("\\", "/")
|
||||
# Persist the query per reference (Description is IFC4+ only).
|
||||
if self.query and hasattr(reference, "Description"):
|
||||
reference.Description = self.query
|
||||
# Persist the filter per reference (Description is IFC4+ only).
|
||||
description = tool.Project.encode_link_filter(self.query, self.exclude)
|
||||
if description and hasattr(reference, "Description"):
|
||||
reference.Description = description
|
||||
new.ifc_definition_id = reference.id()
|
||||
new.name = filepath
|
||||
new.filepath = filepath
|
||||
new.query = self.query
|
||||
bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache, query=self.query)
|
||||
new.exclude = self.exclude
|
||||
bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache, query=self.query, exclude=self.exclude)
|
||||
|
||||
|
||||
class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -1499,18 +1511,22 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True, options={"SKIP_SAVE"})
|
||||
query: bpy.props.StringProperty(options={"SKIP_SAVE"})
|
||||
exclude: bpy.props.StringProperty(options={"SKIP_SAVE"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
use_cache: bool
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
def _execute(self, context):
|
||||
self.link = tool.Project.get_project_props().links[self.link_index]
|
||||
# Fall back to the Link's stored query so callers that omit it
|
||||
# Fall back to the Link's stored filter so callers that omit it
|
||||
# still replay the filter the link was created with.
|
||||
if not self.query and self.link.query:
|
||||
self.query = self.link.query
|
||||
if not self.exclude and self.link.exclude:
|
||||
self.exclude = self.link.exclude
|
||||
filepath = Path(tool.Ifc.resolve_uri(self.link.filepath))
|
||||
if not filepath.exists():
|
||||
self.report({"ERROR"}, f"File does not exist: '{filepath}'")
|
||||
@@ -1547,7 +1563,7 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.link.is_loaded = False
|
||||
|
||||
def link_ifc(self) -> Union[set[str], None]:
|
||||
blend_filepath, json_filepath = tool.Project.get_link_cache_paths(self.filepath_, self.query)
|
||||
blend_filepath, json_filepath = tool.Project.get_link_cache_paths(self.filepath_, self.query, self.exclude)
|
||||
|
||||
def should_clear_cache() -> bool:
|
||||
if not self.use_cache:
|
||||
@@ -1559,8 +1575,7 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data = json.loads(json_filepath.read_text())
|
||||
# Empty 'query' - model loaded without custom query.
|
||||
# Missing 'query' - model was loaded before custom queries were introduced in Bonsai.
|
||||
query = data.get("query", "")
|
||||
return query != self.query
|
||||
return data.get("query", "") != self.query or data.get("exclude", "") != self.exclude
|
||||
|
||||
if should_clear_cache() and blend_filepath.exists():
|
||||
os.remove(blend_filepath)
|
||||
@@ -1590,7 +1605,7 @@ def run():
|
||||
pprops.project_north = "{pprops.project_north}"
|
||||
# Use absolute path to be safe from cwd changes.
|
||||
try:
|
||||
bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}", query={repr(self.query)})
|
||||
bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}", query={repr(self.query)}, exclude={repr(self.exclude)})
|
||||
except RuntimeError as e:
|
||||
# Operator failed (returned CANCELLED with error report)
|
||||
print(f"Failed to load linked project: {{e}}")
|
||||
@@ -1639,7 +1654,7 @@ except Exception as e:
|
||||
if len(tool.Project.get_project_props().links) > 1:
|
||||
return # Only the first link sets the origin
|
||||
|
||||
json_filepath = tool.Project.get_link_cache_paths(self.filepath_, self.query)[1]
|
||||
json_filepath = tool.Project.get_link_cache_paths(self.filepath_, self.query, self.exclude)[1]
|
||||
if not json_filepath.exists():
|
||||
return
|
||||
|
||||
@@ -1658,7 +1673,7 @@ except Exception as e:
|
||||
if not (crs_name := (ifcopenshell.util.geolocation.get_crs(tool.Ifc.get()) or {}).get("Name", "")):
|
||||
self.link.georeferenced = "NONE"
|
||||
return
|
||||
json_filepath = tool.Project.get_link_cache_paths(self.filepath_, self.query)[1]
|
||||
json_filepath = tool.Project.get_link_cache_paths(self.filepath_, self.query, self.exclude)[1]
|
||||
if not json_filepath.exists():
|
||||
self.link.georeferenced = "NONE"
|
||||
return
|
||||
@@ -1705,6 +1720,15 @@ class ReloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
),
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
exclude: bpy.props.StringProperty(
|
||||
name="Exclude",
|
||||
description=(
|
||||
"Selector query whose matches are excluded from the loaded elements.\n\n"
|
||||
"Applied on top of the query (or the default set), providing the set "
|
||||
"difference a single query cannot express. E.g. 'IfcSlab, parent=\"X\"'."
|
||||
),
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
@@ -1712,6 +1736,7 @@ class ReloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
use_relative_path: bool
|
||||
use_cache: bool
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
def invoke(self, context, event):
|
||||
link = tool.Project.get_project_props().links[self.link_index]
|
||||
@@ -1723,6 +1748,8 @@ class ReloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.use_relative_path = not Path(link.filepath).is_absolute()
|
||||
if not self.properties.is_property_set("query"):
|
||||
self.query = link.query
|
||||
if not self.properties.is_property_set("exclude"):
|
||||
self.exclude = link.exclude
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
@@ -1736,6 +1763,7 @@ class ReloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
op.use_relative_path = self.use_relative_path
|
||||
op.use_cache = self.use_cache
|
||||
op.query = self.query
|
||||
op.exclude = self.exclude
|
||||
row = self.layout.row()
|
||||
row.prop(self, "use_relative_path")
|
||||
row = self.layout.row()
|
||||
@@ -1750,6 +1778,7 @@ class ReloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "project_north")
|
||||
self.layout.prop(self, "query", placeholder="IfcElement")
|
||||
self.layout.prop(self, "exclude", placeholder='IfcSlab, parent="..."')
|
||||
|
||||
def _execute(self, context):
|
||||
link = tool.Project.get_project_props().links[self.link_index]
|
||||
@@ -1758,6 +1787,8 @@ class ReloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# of overwriting them with the defaults.
|
||||
if self.properties.is_property_set("query"):
|
||||
link.query = self.query
|
||||
if self.properties.is_property_set("exclude"):
|
||||
link.exclude = self.exclude
|
||||
|
||||
filepath = self.filepath if self.properties.is_property_set("filepath") else link.filepath
|
||||
if self.properties.is_property_set("use_relative_path"):
|
||||
@@ -1781,12 +1812,12 @@ class ReloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if tool.Ifc.get() and link.ifc_definition_id:
|
||||
reference = tool.Ifc.get().by_id(link.ifc_definition_id)
|
||||
if hasattr(reference, "Description"):
|
||||
reference.Description = link.query or None
|
||||
reference.Description = tool.Project.encode_link_filter(link.query, link.exclude)
|
||||
|
||||
bpy.ops.bim.unload_link(link_index=self.link_index)
|
||||
return bpy.ops.bim.load_link(link_index=self.link_index, use_cache=self.use_cache, query=link.query) or {
|
||||
"FINISHED"
|
||||
}
|
||||
return bpy.ops.bim.load_link(
|
||||
link_index=self.link_index, use_cache=self.use_cache, query=link.query, exclude=link.exclude
|
||||
) or {"FINISHED"}
|
||||
|
||||
|
||||
class SelectLinkFilepath(bpy.types.Operator):
|
||||
@@ -1802,6 +1833,7 @@ class SelectLinkFilepath(bpy.types.Operator):
|
||||
use_relative_path: bpy.props.BoolProperty(options={"HIDDEN"})
|
||||
use_cache: bpy.props.BoolProperty(options={"HIDDEN"})
|
||||
query: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
exclude: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
@@ -1810,6 +1842,7 @@ class SelectLinkFilepath(bpy.types.Operator):
|
||||
use_relative_path: bool
|
||||
use_cache: bool
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
def invoke(self, context, event):
|
||||
link = tool.Project.get_project_props().links[self.link_index]
|
||||
@@ -1825,6 +1858,7 @@ class SelectLinkFilepath(bpy.types.Operator):
|
||||
use_relative_path=self.use_relative_path,
|
||||
use_cache=self.use_cache,
|
||||
query=self.query,
|
||||
exclude=self.exclude,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1844,7 +1878,7 @@ class ToggleLinkSelectability(bpy.types.Operator):
|
||||
props = tool.Project.get_project_props()
|
||||
link = props.links[self.link_index]
|
||||
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(
|
||||
tool.Project.get_link_cache_paths(link.filepath, link.query)[0]
|
||||
tool.Project.get_link_cache_paths(link.filepath, link.query, link.exclude)[0]
|
||||
)
|
||||
link.is_selectable = (is_selectable := not link.is_selectable)
|
||||
for collection in self.get_linked_collections():
|
||||
@@ -1881,7 +1915,7 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
props = tool.Project.get_project_props()
|
||||
link = props.links[self.link_index]
|
||||
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(
|
||||
tool.Project.get_link_cache_paths(link.filepath, link.query)[0]
|
||||
tool.Project.get_link_cache_paths(link.filepath, link.query, link.exclude)[0]
|
||||
)
|
||||
if self.mode == "WIREFRAME":
|
||||
self.toggle_wireframe(link)
|
||||
@@ -2208,9 +2242,12 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
|
||||
query: bpy.props.StringProperty()
|
||||
"""See ``bim.link_ifc``."""
|
||||
exclude: bpy.props.StringProperty()
|
||||
"""See ``bim.link_ifc``."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
file: ifcopenshell.file
|
||||
meshes: dict[str, bpy.types.Mesh]
|
||||
@@ -2278,6 +2315,9 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
else:
|
||||
self.elements |= set(self.file.by_type("IfcSpatialElement"))
|
||||
self.elements -= set(self.file.by_type("IfcFeatureElement"))
|
||||
if self.exclude:
|
||||
# The set difference a single selector query cannot express.
|
||||
self.elements -= ifcopenshell.util.selector.filter_elements(self.file, self.exclude)
|
||||
|
||||
if tool.Loader.settings.false_origin_mode == "MANUAL" and tool.Loader.settings.false_origin:
|
||||
tool.Loader.set_manual_blender_offset(self.file)
|
||||
@@ -2285,7 +2325,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
tool.Loader.guess_false_origin(self.file)
|
||||
|
||||
tool.Georeference.set_model_origin()
|
||||
self.json_filepath = str(tool.Project.get_link_cache_paths(self.filepath, self.query)[1])
|
||||
self.json_filepath = str(tool.Project.get_link_cache_paths(self.filepath, self.query, self.exclude)[1])
|
||||
data = {
|
||||
"model_is_georeferenced": gprops.model_is_georeferenced,
|
||||
"model_crs": gprops.model_crs,
|
||||
@@ -2303,6 +2343,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
"false_origin": pprops.false_origin,
|
||||
"project_north": pprops.project_north,
|
||||
"query": self.query,
|
||||
"exclude": self.exclude,
|
||||
}
|
||||
with open(self.json_filepath, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
@@ -265,6 +265,11 @@ class Link(PropertyGroup):
|
||||
description="Selector query used to filter elements when loading the linked model",
|
||||
default="",
|
||||
)
|
||||
exclude: StringProperty(
|
||||
name="Exclude",
|
||||
description="Selector query whose matches are excluded when loading the linked model",
|
||||
default="",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
@@ -281,6 +286,7 @@ class Link(PropertyGroup):
|
||||
empty_handle: Union[bpy.types.Object, None]
|
||||
ifc_definition_id: int
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
|
||||
class EditedObj(PropertyGroup):
|
||||
|
||||
@@ -91,24 +91,56 @@ class Project(bonsai.core.tool.Project):
|
||||
link.empty_handle = empty
|
||||
|
||||
@classmethod
|
||||
def get_link_cache_paths(cls, filepath: Union[Path, str], query: str) -> tuple[Path, Path]:
|
||||
"""Get the (blend, json) cache paths for a linked model loaded with ``query``.
|
||||
def get_link_cache_paths(cls, filepath: Union[Path, str], query: str, exclude: str = "") -> tuple[Path, Path]:
|
||||
"""Get the (blend, json) cache paths for a linked model's filter.
|
||||
|
||||
Cache files are per-query so the same IFC file can be linked several
|
||||
times with different queries without the caches overwriting each
|
||||
other. An empty query keeps the legacy un-suffixed names.
|
||||
Cache files are per-filter so the same IFC file can be linked several
|
||||
times with different include/exclude queries without the caches
|
||||
overwriting each other. An empty filter keeps the legacy un-suffixed
|
||||
names, and an include-only filter keeps the pre-exclude hash so
|
||||
existing caches stay valid.
|
||||
"""
|
||||
filepath = Path(filepath)
|
||||
suffix = "" if not query else "." + hashlib.md5(query.encode("utf-8")).hexdigest()[:8]
|
||||
if not query and not exclude:
|
||||
suffix = ""
|
||||
elif not exclude:
|
||||
suffix = "." + hashlib.md5(query.encode("utf-8")).hexdigest()[:8]
|
||||
else:
|
||||
suffix = "." + hashlib.md5(f"{query}\0{exclude}".encode("utf-8")).hexdigest()[:8]
|
||||
return (
|
||||
filepath.with_suffix(f".ifc.cache{suffix}.blend"),
|
||||
filepath.with_suffix(f".ifc.cache{suffix}.json"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def encode_link_filter(cls, query: str, exclude: str) -> Union[str, None]:
|
||||
"""Serialize a link's filter for IfcDocumentReference.Description.
|
||||
|
||||
A plain include query is stored as-is (backwards compatible); an
|
||||
exclude promotes the value to a small JSON blob.
|
||||
"""
|
||||
if exclude:
|
||||
return json.dumps({"include": query, "exclude": exclude})
|
||||
return query or None
|
||||
|
||||
@classmethod
|
||||
def decode_link_filter(cls, description: Union[str, None]) -> tuple[str, str]:
|
||||
"""Get (query, exclude) from a reference Description written by encode_link_filter."""
|
||||
if not description:
|
||||
return "", ""
|
||||
if description.startswith("{"):
|
||||
try:
|
||||
data = json.loads(description)
|
||||
if isinstance(data, dict):
|
||||
return data.get("include", "") or "", data.get("exclude", "") or ""
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
return description, ""
|
||||
|
||||
@classmethod
|
||||
def calculate_link_matrix(cls, link: Link) -> Matrix:
|
||||
filepath = Path(tool.Ifc.resolve_uri(link.filepath))
|
||||
with open(cls.get_link_cache_paths(filepath, link.query)[1], "r") as f:
|
||||
with open(cls.get_link_cache_paths(filepath, link.query, link.exclude)[1], "r") as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
rot = ifcopenshell.util.shape_builder.np_rotation_matrix(
|
||||
@@ -181,7 +213,7 @@ class Project(bonsai.core.tool.Project):
|
||||
new_obj_matrix = np.array(obj.matrix_world)
|
||||
|
||||
filepath = Path(tool.Ifc.resolve_uri(link.filepath))
|
||||
with open(cls.get_link_cache_paths(filepath, link.query)[1], "r") as f:
|
||||
with open(cls.get_link_cache_paths(filepath, link.query, link.exclude)[1], "r") as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
rot = ifcopenshell.util.shape_builder.np_rotation_matrix(
|
||||
@@ -441,11 +473,11 @@ class Project(bonsai.core.tool.Project):
|
||||
if reference[1]:
|
||||
m = np.fromstring(reference[1], sep=",", dtype=np.float64).reshape(4, 4)
|
||||
link.has_transformation = not np.allclose(m, np.eye(4))
|
||||
# The selector query used at link time is persisted per
|
||||
# The selector filter used at link time is persisted per
|
||||
# reference in its Description (IFC4+); restore it so
|
||||
# Reload/Load replay the filter.
|
||||
query = getattr(reference, "Description", None) or ""
|
||||
if not query and location_counts[filepath] == 1:
|
||||
query, exclude = cls.decode_link_filter(getattr(reference, "Description", None))
|
||||
if not query and not exclude and location_counts[filepath] == 1:
|
||||
# Fall back to the legacy sidecar cache JSON where older
|
||||
# versions persisted the query. Only unambiguous: with
|
||||
# several links to one file the shared JSON can't say
|
||||
@@ -457,6 +489,7 @@ class Project(bonsai.core.tool.Project):
|
||||
except (OSError, json.JSONDecodeError):
|
||||
pass
|
||||
link.query = query
|
||||
link.exclude = exclude
|
||||
|
||||
@classmethod
|
||||
def get_project_library_elements(
|
||||
|
||||
Reference in New Issue
Block a user