Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions components/model-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ interface PropertyRowProps {
icon?: React.ReactNode;
copyValue?: string;
t?: (key: string, options?: any) => string;
propertyPath?: string;
}

const PropertyRow: React.FC<PropertyRowProps> = ({
Expand All @@ -211,11 +212,17 @@ const PropertyRow: React.FC<PropertyRowProps> = ({
icon,
copyValue,
t,
propertyPath,
}) => {
const { ifcApi } = useIFCContext();
const { ifcApi, selectAllByProperty } = useIFCContext();
const handleCopy = () => {
if (copyValue !== undefined) navigator.clipboard.writeText(copyValue);
};
const handleSelectAll = () => {
if (propertyPath) {
selectAllByProperty(propertyPath, propValue);
}
};
return (
<div className="grid grid-cols-[auto_1fr] gap-x-3 items-start py-1.5 border-b border-border/50 last:border-b-0">
<div className="flex items-center text-muted-foreground text-xs font-medium">
Expand All @@ -233,6 +240,23 @@ const PropertyRow: React.FC<PropertyRowProps> = ({
}
>
{renderPropertyValue(propValue, propKey, ifcApi, t)}
{propertyPath && (
<TooltipProvider delayDuration={200}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={handleSelectAll}
className="opacity-60 hover:opacity-100"
>
<MousePointer2 className="w-3 h-3" />
</button>
</TooltipTrigger>
<TooltipContent side="top">
{t?.('selectAllWithValue') || 'Select all with this value'}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{copyValue !== undefined && (
<button onClick={handleCopy} className="opacity-60 hover:opacity-100">
<Copy className="w-3 h-3" />
Expand Down Expand Up @@ -361,6 +385,7 @@ export function ModelInfo() {
getNaturalIfcClassName,
getClassificationsForElement,
getElementPropertiesCached,
selectAllByProperty,
} = useIFCContext();
const { t, i18n } = useTranslation();

Expand Down Expand Up @@ -594,8 +619,28 @@ export function ModelInfo() {
<Box className="w-3.5 h-3.5 mr-1.5 opacity-80" />
<span>{t('IFC Class')}:</span>
</div>
<div className="text-xs truncate text-right font-medium">
<div className="text-xs truncate text-right font-medium flex items-center justify-end gap-1">
{ifcType || 'Unknown'}
{ifcType && (
<TooltipProvider delayDuration={200}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={(e) => {
e.stopPropagation();
selectAllByProperty('ifcType', ifcType);
}}
className="opacity-60 hover:opacity-100"
>
<MousePointer2 className="w-3 h-3" />
</button>
</TooltipTrigger>
<TooltipContent side="top">
{t('selectAllWithValue') || 'Select all with this value'}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
</div>
</TooltipTrigger>
Expand Down Expand Up @@ -654,6 +699,7 @@ export function ModelInfo() {
propValue={rawAttributes.Name.value || rawAttributes.Name}
icon={<Info className="w-3.5 h-3.5" />}
t={t}
propertyPath="attributes.Name"
/>
)}
{rawAttributes.Description && (
Expand All @@ -664,6 +710,7 @@ export function ModelInfo() {
}
icon={<Info className="w-3.5 h-3.5" />}
t={t}
propertyPath="attributes.Description"
/>
)}
{rawAttributes.ObjectType && (
Expand All @@ -674,6 +721,7 @@ export function ModelInfo() {
}
icon={<Info className="w-3.5 h-3.5" />}
t={t}
propertyPath="attributes.ObjectType"
/>
)}
<PropertyRow
Expand All @@ -695,6 +743,7 @@ export function ModelInfo() {
icon={<Hash className="w-3.5 h-3.5" />}
copyValue={rawAttributes.GlobalId.value || rawAttributes.GlobalId}
t={t}
propertyPath="attributes.GlobalId"
/>
)}
{Object.entries(displayableAttributes).map(([key, value]) => (
Expand All @@ -704,6 +753,7 @@ export function ModelInfo() {
propValue={value}
icon={getPropertyIcon(key)}
t={t}
propertyPath={`attributes.${key}`}
/>
))}
</CollapsibleSection>
Expand Down Expand Up @@ -786,6 +836,7 @@ export function ModelInfo() {
propValue={propValue}
icon={getPropertyIcon(propName)}
t={t}
propertyPath={`propertySets.${psetName}.${propName}`}
/>
),
)
Expand Down Expand Up @@ -821,6 +872,7 @@ export function ModelInfo() {
propValue={propValue}
icon={getPropertyIcon(propName)}
t={t}
propertyPath={`typeSets.${psetName}.${propName}`}
/>
))}
</CollapsibleSection>
Expand Down
47 changes: 47 additions & 0 deletions context/ifc-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ interface IFCContextType {
modelID: number,
expressID: number,
) => Promise<ParsedElementProperties | null>;
selectAllByProperty: (path: string, value: any) => Promise<void>;
toggleShowAllClassificationColors: () => void; // Added new toggle function

// Classification and Rule methods (can remain global or be refactored later if needed per model)
Expand Down Expand Up @@ -1716,6 +1717,51 @@ export function IFCContextProvider({ children }: { children: ReactNode }) {
[ifcApiInternal],
);

const selectAllByProperty = useCallback(
async (path: string, value: any) => {
if (!ifcApiInternal) return;
const matches: SelectedElementInfo[] = [];
for (const model of loadedModels) {
if (model.modelID == null) continue;
const elements = IFCElementExtractor.getAllElements(ifcApiInternal, model.modelID);
const propsMap = await PropertyCache.getBatchProperties(
ifcApiInternal,
model.modelID,
elements.map((e) => e.expressID),
);
for (const el of elements) {
let elementVal: any;
if (path === 'ifcType') {
elementVal = el.type;
} else {
const props = propsMap.get(el.expressID);
if (!props) continue;
const parts = path.split('.');
if (parts[0] === 'attributes') {
elementVal = props.attributes?.[parts[1]];
} else if (parts[0] === 'propertySets') {
elementVal = props.propertySets?.[parts[1]]?.[parts[2]];
} else if (parts[0] === 'typeSets') {
elementVal = props.typeSets?.[parts[1]]?.[parts[2]];
} else if (parts[0] === 'materialSets') {
elementVal = props.materialSets?.[parts[1]]?.[parts[2]];
}
}
if (
elementVal !== undefined &&
JSON.stringify(elementVal) === JSON.stringify(value)
) {
matches.push({ modelID: model.modelID, expressID: el.expressID });
}
}
}
if (matches.length) {
selectElements(matches);
}
},
[ifcApiInternal, loadedModels, selectElements],
);

const showElements = useCallback((elements: SelectedElementInfo[]) => {
setUserHiddenElements((prev) =>
prev.filter(
Expand Down Expand Up @@ -2304,6 +2350,7 @@ export function IFCContextProvider({ children }: { children: ReactNode }) {
setAvailableProperties,
setIfcApi,
getElementPropertiesCached,
selectAllByProperty,
toggleShowAllClassificationColors,
toggleIsolateUnclassified,
baseCoordinationMatrix,
Expand Down