diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.chinese_s.xaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.chinese_s.xaml
new file mode 100644
index 000000000..99d3c7fbd
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.chinese_s.xaml
@@ -0,0 +1,49 @@
+
+ ⟲ 刷新文档
+ 重新扫描当前族文档,检测新增或删除的参数。切换到其他已打开的族后也很有用。
+ 打乱参数值。在当前滑块范围内为每个可编辑参数随机赋值。请谨慎使用 - 此操作无法在 FamSlide 中撤销。
+ 删除所有未使用的参数。移除未驱动任何标注、未用于公式且非内置的族参数。请谨慎使用。
+ 显示/隐藏可编辑参数。切换可通过滑块或复选框直接修改的参数的可见性。
+ 显示/隐藏标签。切换每个参数名称下方小标签的显示。
+ 关闭
+ 关闭 FamSlide
+ 值参数
+ 内置参数
+ 是/否参数
+ 使用中
+ uF
+ dF
+ 内置
+ 实例
+ 类型
+ 已关联
+ (未加载族)
+ 当前没有活动的族文档
+ 是
+ 否
+ 是否将所有可编辑参数在当前滑块范围内随机打乱?
+
+此操作无法在 FamSlide 中撤销 - 如需撤销请使用 Revit 自带的撤销功能。
+ 是否删除所有未驱动标注、未用于公式且非内置的族参数?
+
+此操作无法在 FamSlide 中撤销 - 如需撤销请使用 Revit 自带的撤销功能。
+ 没有活动的 Revit 文档。
+ FamSlide 仅在族编辑器中可用。
+请打开一个族进行编辑,然后重新运行此工具。
+ 重命名…
+ 转换为实例参数
+ 转换为类型参数
+ 重命名参数
+ 请输入该参数的新名称:
+ 无法重命名参数 "{}"。它可能是内置参数,或新名称已被使用。
+ 无法切换参数 "{}" 的实例/类型。它可能由公式驱动,或受到 Revit 的其他限制。
+ 保存预设。存储此族中所有可编辑参数的当前值,以便稍后快速恢复。
+ 恢复预设。重新应用此族上次通过"保存预设"保存的值。如果尚未保存任何预设,则不执行任何操作。
+ 此族尚未保存任何预设。请先使用"保存预设"。
+ 是否将所有可编辑参数恢复为通过"保存预设"保存的值?
+
+此操作无法在 FamSlide 中撤销 - 如需撤销请使用 Revit 自带的撤销功能。
+ 已为 {} 个参数保存预设。
+
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.de_de.xaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.de_de.xaml
new file mode 100644
index 000000000..924aa5ee7
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.de_de.xaml
@@ -0,0 +1,49 @@
+
+ ⟲ Dokument aktualisieren
+ Durchsucht das aktive Familiendokument erneut nach neuen oder entfernten Parametern. Auch nützlich nach dem Wechsel zu einer anderen geöffneten Familie.
+ Parameterwerte mischen. Weist jedem editierbaren Parameter innerhalb seines aktuellen Schieberegler-Bereichs einen zufälligen Wert zu. Mit Vorsicht verwenden - dies kann in FamSlide nicht rückgängig gemacht werden.
+ Alle ungenutzten Parameter löschen. Entfernt Familienparameter, die keine Bemaßung steuern, in keiner Formel vorkommen und nicht integriert sind. Mit Vorsicht verwenden.
+ Editierbare Parameter ein-/ausblenden. Schaltet die Sichtbarkeit von Parametern um, deren Schieberegler oder Kontrollkästchen direkt geändert werden kann.
+ Beschriftungen ein-/ausblenden. Schaltet die kleinen Tags unter jedem Parameternamen um.
+ Schließen
+ FamSlide schließen
+ Wertparameter
+ Integrierte Parameter
+ Ja/Nein-Parameter
+ verwendet
+ uF
+ dF
+ integriert
+ Instanz
+ Typ
+ verknüpft
+ (keine Familie geladen)
+ Kein Familiendokument aktiv
+ Ja
+ Nein
+ Alle editierbaren Parameter innerhalb ihres aktuellen Schieberegler-Bereichs zufällig mischen?
+
+Dies kann in FamSlide nicht rückgängig gemacht werden - verwenden Sie bei Bedarf die Rückgängig-Funktion von Revit.
+ Alle Familienparameter löschen, die keine Bemaßung steuern, in keiner Formel vorkommen und nicht integriert sind?
+
+Dies kann in FamSlide nicht rückgängig gemacht werden - verwenden Sie bei Bedarf die Rückgängig-Funktion von Revit.
+ Kein aktives Revit-Dokument.
+ FamSlide funktioniert nur innerhalb des Familieneditors.
+Öffnen Sie eine Familie zur Bearbeitung und starten Sie dieses Werkzeug erneut.
+ Umbenennen…
+ Als Instanzparameter festlegen
+ Als Typparameter festlegen
+ Parameter umbenennen
+ Geben Sie einen neuen Namen für diesen Parameter ein:
+ Parameter '{}' konnte nicht umbenannt werden. Er ist möglicherweise integriert, oder der neue Name wird bereits verwendet.
+ Instanz/Typ für Parameter '{}' konnte nicht umgeschaltet werden. Er wird möglicherweise durch eine Formel gesteuert oder anderweitig von Revit eingeschränkt.
+ Voreinstellung speichern. Speichert die aktuellen Werte aller editierbaren Parameter dieser Familie, damit Sie später schnell dorthin zurückkehren können.
+ Voreinstellung wiederherstellen. Wendet die zuletzt mit 'Voreinstellung speichern' gesicherten Werte für diese Familie erneut an. Ohne gespeicherte Voreinstellung passiert nichts.
+ Für diese Familie wurde noch keine Voreinstellung gespeichert. Verwenden Sie zuerst 'Voreinstellung speichern'.
+ Alle editierbaren Parameter auf die mit 'Voreinstellung speichern' gesicherten Werte zurücksetzen?
+
+Dies kann in FamSlide nicht rückgängig gemacht werden - verwenden Sie bei Bedarf die Rückgängig-Funktion von Revit.
+ Voreinstellung für {} Parameter gespeichert.
+
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.en_us.xaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.en_us.xaml
new file mode 100644
index 000000000..90d6d1104
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.en_us.xaml
@@ -0,0 +1,49 @@
+
+ ⟲ Refresh Document
+ Re-scan the active family document for new or removed parameters. Also useful after switching to a different open family.
+ Shuffle parameter values. Randomizes every editable parameter within its current slider range. Use with caution - this cannot be undone from FamSlide itself.
+ Delete all unused parameters. Removes family parameters that are not driving a dimension, not involved in a formula, and are not built-in. Use with caution.
+ Show/hide editable parameters. Toggles visibility of parameters whose slider or checkbox can be changed directly.
+ Show/hide labels. Toggles the small tag pills shown under each parameter name.
+ Close
+ Close FamSlide
+ Value Parameters
+ BuiltIn Parameters
+ Yes/No Parameters
+ in use
+ uF
+ dF
+ built-in
+ instance
+ type
+ associated
+ (no family loaded)
+ No family document is active
+ Yes
+ No
+ Shuffle every editable parameter to a random value within its current slider range?
+
+This cannot be undone from FamSlide - use Revit's own Undo if needed.
+ Delete every family parameter that is not driving a dimension, not part of a formula, and not built-in?
+
+This cannot be undone from FamSlide - use Revit's own Undo if needed.
+ No active Revit document.
+ FamSlide only works inside the Family Editor.
+Open a family for editing and run this tool again.
+ Rename…
+ Make Instance Parameter
+ Make Type Parameter
+ Rename Parameter
+ Enter a new name for this parameter:
+ Could not rename parameter '{}'. It may be built-in, or the new name may already be in use.
+ Could not toggle Instance/Type for parameter '{}'. It may be driven by a formula or otherwise restricted by Revit.
+ Save preset. Stores the current values of every editable parameter for this family, so you can quickly return to them later.
+ Restore preset. Re-applies the values last saved with Save Preset for this family. Does nothing if no preset has been saved yet.
+ No preset has been saved yet for this family. Use Save Preset first.
+ Restore all editable parameters to the values saved with Save Preset?
+
+This cannot be undone from FamSlide - use Revit's own Undo if needed.
+ Preset saved for {} parameter(s).
+
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.es_es.xaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.es_es.xaml
new file mode 100644
index 000000000..25b6c4774
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.es_es.xaml
@@ -0,0 +1,49 @@
+
+ ⟲ Actualizar documento
+ Vuelve a escanear el documento de familia activo en busca de parámetros nuevos o eliminados. También es útil tras cambiar a otra familia abierta.
+ Mezclar valores de parámetros. Asigna un valor aleatorio a cada parámetro editable dentro de su rango de control deslizante actual. Úsese con precaución - esto no se puede deshacer desde FamSlide.
+ Eliminar todos los parámetros no utilizados. Elimina los parámetros de familia que no controlan ninguna cota, no intervienen en ninguna fórmula y no son integrados. Úsese con precaución.
+ Mostrar/ocultar parámetros editables. Alterna la visibilidad de los parámetros cuyo control deslizante o casilla se puede modificar directamente.
+ Mostrar/ocultar etiquetas. Alterna las pequeñas etiquetas mostradas debajo de cada nombre de parámetro.
+ Cerrar
+ Cerrar FamSlide
+ Parámetros de valor
+ Parámetros integrados
+ Parámetros Sí/No
+ en uso
+ uF
+ dF
+ integrado
+ instancia
+ tipo
+ asociado
+ (ninguna familia cargada)
+ No hay ningún documento de familia activo
+ Sí
+ No
+ ¿Mezclar todos los parámetros editables con un valor aleatorio dentro de su rango de control deslizante actual?
+
+Esto no se puede deshacer desde FamSlide - use la función Deshacer de Revit si es necesario.
+ ¿Eliminar todos los parámetros de familia que no controlan ninguna cota, no intervienen en ninguna fórmula y no son integrados?
+
+Esto no se puede deshacer desde FamSlide - use la función Deshacer de Revit si es necesario.
+ No hay ningún documento de Revit activo.
+ FamSlide solo funciona dentro del Editor de familias.
+Abra una familia para editarla y ejecute esta herramienta de nuevo.
+ Cambiar nombre…
+ Convertir en parámetro de instancia
+ Convertir en parámetro de tipo
+ Cambiar nombre del parámetro
+ Introduzca un nuevo nombre para este parámetro:
+ No se pudo cambiar el nombre del parámetro '{}'. Puede ser un parámetro integrado o el nuevo nombre ya está en uso.
+ No se pudo alternar Instancia/Tipo para el parámetro '{}'. Puede estar controlado por una fórmula o restringido de otro modo por Revit.
+ Guardar ajuste preestablecido. Almacena los valores actuales de todos los parámetros editables de esta familia, para poder volver a ellos rápidamente más tarde.
+ Restaurar ajuste preestablecido. Vuelve a aplicar los valores guardados con 'Guardar ajuste preestablecido' para esta familia. No hace nada si aún no se ha guardado ningún ajuste preestablecido.
+ Aún no se ha guardado ningún ajuste preestablecido para esta familia. Use primero 'Guardar ajuste preestablecido'.
+ ¿Restaurar todos los parámetros editables a los valores guardados con 'Guardar ajuste preestablecido'?
+
+Esto no se puede deshacer desde FamSlide - use la función Deshacer de Revit si es necesario.
+ Ajuste preestablecido guardado para {} parámetro(s).
+
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.fr_fr.xaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.fr_fr.xaml
new file mode 100644
index 000000000..8cabd4716
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.fr_fr.xaml
@@ -0,0 +1,49 @@
+
+ ⟲ Actualiser le document
+ Réanalyse le document de famille actif pour détecter les paramètres ajoutés ou supprimés. Utile aussi après avoir changé de famille ouverte.
+ Mélanger les valeurs des paramètres. Attribue une valeur aléatoire à chaque paramètre modifiable dans sa plage de curseur actuelle. À utiliser avec prudence - ceci ne peut pas être annulé depuis FamSlide.
+ Supprimer tous les paramètres inutilisés. Supprime les paramètres de famille qui ne pilotent aucune cote, ne sont impliqués dans aucune formule et ne sont pas intégrés. À utiliser avec prudence.
+ Afficher/masquer les paramètres modifiables. Bascule la visibilité des paramètres dont le curseur ou la case à cocher peut être modifié directement.
+ Afficher/masquer les étiquettes. Bascule les petites étiquettes affichées sous chaque nom de paramètre.
+ Fermer
+ Fermer FamSlide
+ Paramètres de valeur
+ Paramètres intégrés
+ Paramètres Oui/Non
+ utilisé
+ uF
+ dF
+ intégré
+ instance
+ type
+ associé
+ (aucune famille chargée)
+ Aucun document de famille actif
+ Oui
+ Non
+ Mélanger tous les paramètres modifiables avec une valeur aléatoire dans leur plage de curseur actuelle ?
+
+Ceci ne peut pas être annulé depuis FamSlide - utilisez si besoin la fonction Annuler de Revit.
+ Supprimer tous les paramètres de famille qui ne pilotent aucune cote, ne sont impliqués dans aucune formule et ne sont pas intégrés ?
+
+Ceci ne peut pas être annulé depuis FamSlide - utilisez si besoin la fonction Annuler de Revit.
+ Aucun document Revit actif.
+ FamSlide fonctionne uniquement dans l'éditeur de famille.
+Ouvrez une famille en édition et relancez cet outil.
+ Renommer…
+ Convertir en paramètre d'instance
+ Convertir en paramètre de type
+ Renommer le paramètre
+ Entrez un nouveau nom pour ce paramètre :
+ Impossible de renommer le paramètre « {} ». Il est peut-être intégré, ou le nouveau nom est déjà utilisé.
+ Impossible de basculer Instance/Type pour le paramètre « {} ». Il est peut-être piloté par une formule ou restreint par Revit.
+ Enregistrer un préréglage. Mémorise les valeurs actuelles de tous les paramètres modifiables de cette famille, pour pouvoir y revenir rapidement plus tard.
+ Restaurer le préréglage. Réapplique les valeurs enregistrées avec « Enregistrer un préréglage » pour cette famille. Ne fait rien si aucun préréglage n'a encore été enregistré.
+ Aucun préréglage n'a encore été enregistré pour cette famille. Utilisez d'abord « Enregistrer un préréglage ».
+ Restaurer tous les paramètres modifiables aux valeurs enregistrées avec « Enregistrer un préréglage » ?
+
+Ceci ne peut pas être annulé depuis FamSlide - utilisez si besoin la fonction Annuler de Revit.
+ Préréglage enregistré pour {} paramètre(s).
+
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.ko.xaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.ko.xaml
new file mode 100644
index 000000000..d9f7ef505
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.ko.xaml
@@ -0,0 +1,49 @@
+
+ ⟲ 문서 새로고침
+ 활성 패밀리 문서를 다시 스캔하여 추가되거나 제거된 매개변수를 확인합니다. 다른 열린 패밀리로 전환한 후에도 유용합니다.
+ 매개변수 값을 섞습니다. 편집 가능한 모든 매개변수를 현재 슬라이더 범위 내에서 무작위 값으로 설정합니다. 주의해서 사용하세요 - FamSlide에서는 취소할 수 없습니다.
+ 사용하지 않는 매개변수를 모두 삭제합니다. 치수를 구동하지 않고 수식에 포함되지 않으며 내장 매개변수가 아닌 패밀리 매개변수를 제거합니다. 주의해서 사용하세요.
+ 편집 가능한 매개변수 표시/숨김. 슬라이더나 체크박스를 직접 변경할 수 있는 매개변수의 표시 여부를 전환합니다.
+ 레이블 표시/숨김. 각 매개변수 이름 아래에 표시되는 작은 태그를 전환합니다.
+ 닫기
+ FamSlide 닫기
+ 값 매개변수
+ 내장 매개변수
+ 예/아니오 매개변수
+ 사용 중
+ uF
+ dF
+ 내장
+ 인스턴스
+ 타입
+ 연결됨
+ (패밀리가 로드되지 않음)
+ 활성화된 패밀리 문서가 없습니다
+ 예
+ 아니오
+ 편집 가능한 모든 매개변수를 현재 슬라이더 범위 내에서 무작위 값으로 섞으시겠습니까?
+
+FamSlide에서는 취소할 수 없습니다 - 필요하면 Revit 자체의 실행 취소를 사용하세요.
+ 치수를 구동하지 않고 수식의 일부가 아니며 내장 매개변수가 아닌 모든 패밀리 매개변수를 삭제하시겠습니까?
+
+FamSlide에서는 취소할 수 없습니다 - 필요하면 Revit 자체의 실행 취소를 사용하세요.
+ 활성 Revit 문서가 없습니다.
+ FamSlide는 패밀리 편집기 내에서만 작동합니다.
+편집할 패밀리를 열고 이 도구를 다시 실행하세요.
+ 이름 바꾸기…
+ 인스턴스 매개변수로 변경
+ 타입 매개변수로 변경
+ 매개변수 이름 바꾸기
+ 이 매개변수의 새 이름을 입력하세요:
+ 매개변수 '{}'의 이름을 바꿀 수 없습니다. 내장 매개변수이거나 새 이름이 이미 사용 중일 수 있습니다.
+ 매개변수 '{}'의 인스턴스/타입을 전환할 수 없습니다. 수식에 의해 제어되거나 Revit에서 다른 방식으로 제한되었을 수 있습니다.
+ 프리셋 저장. 이 패밀리의 모든 편집 가능한 매개변수의 현재 값을 저장하여 나중에 빠르게 되돌릴 수 있습니다.
+ 프리셋 복원. 이 패밀리에 대해 '프리셋 저장'으로 마지막에 저장한 값을 다시 적용합니다. 아직 저장된 프리셋이 없으면 아무 작업도 하지 않습니다.
+ 이 패밀리에 대해 아직 저장된 프리셋이 없습니다. 먼저 '프리셋 저장'을 사용하세요.
+ 모든 편집 가능한 매개변수를 '프리셋 저장'으로 저장된 값으로 복원하시겠습니까?
+
+FamSlide에서는 취소할 수 없습니다 - 필요하면 Revit 자체의 실행 취소를 사용하세요.
+ 매개변수 {}개에 대한 프리셋이 저장되었습니다.
+
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.pt_br.xaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.pt_br.xaml
new file mode 100644
index 000000000..328dc0696
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.pt_br.xaml
@@ -0,0 +1,49 @@
+
+ ⟲ Atualizar documento
+ Verifica novamente o documento de família ativo em busca de parâmetros novos ou removidos. Também útil após alternar para outra família aberta.
+ Embaralhar valores de parâmetros. Atribui um valor aleatório a cada parâmetro editável dentro do intervalo atual do controle deslizante. Use com cautela - isso não pode ser desfeito pelo FamSlide.
+ Excluir todos os parâmetros não utilizados. Remove parâmetros de família que não controlam nenhuma cota, não fazem parte de nenhuma fórmula e não são integrados. Use com cautela.
+ Mostrar/ocultar parâmetros editáveis. Alterna a visibilidade dos parâmetros cujo controle deslizante ou caixa de seleção pode ser alterado diretamente.
+ Mostrar/ocultar rótulos. Alterna as pequenas etiquetas exibidas abaixo do nome de cada parâmetro.
+ Fechar
+ Fechar FamSlide
+ Parâmetros de valor
+ Parâmetros integrados
+ Parâmetros Sim/Não
+ em uso
+ uF
+ dF
+ integrado
+ instância
+ tipo
+ associado
+ (nenhuma família carregada)
+ Nenhum documento de família ativo
+ Sim
+ Não
+ Embaralhar todos os parâmetros editáveis com um valor aleatório dentro do intervalo atual do controle deslizante?
+
+Isso não pode ser desfeito pelo FamSlide - use o Desfazer do próprio Revit, se necessário.
+ Excluir todos os parâmetros de família que não controlam nenhuma cota, não fazem parte de nenhuma fórmula e não são integrados?
+
+Isso não pode ser desfeito pelo FamSlide - use o Desfazer do próprio Revit, se necessário.
+ Nenhum documento do Revit ativo.
+ O FamSlide funciona apenas dentro do Editor de Famílias.
+Abra uma família para edição e execute esta ferramenta novamente.
+ Renomear…
+ Converter em parâmetro de instância
+ Converter em parâmetro de tipo
+ Renomear parâmetro
+ Digite um novo nome para este parâmetro:
+ Não foi possível renomear o parâmetro '{}'. Ele pode ser integrado, ou o novo nome já está em uso.
+ Não foi possível alternar Instância/Tipo para o parâmetro '{}'. Ele pode ser controlado por uma fórmula ou restrito de outra forma pelo Revit.
+ Salvar predefinição. Armazena os valores atuais de todos os parâmetros editáveis desta família, para que você possa retornar a eles rapidamente depois.
+ Restaurar predefinição. Reaplica os valores salvos com 'Salvar predefinição' para esta família. Não faz nada se nenhuma predefinição tiver sido salva ainda.
+ Ainda não há predefinição salva para esta família. Use 'Salvar predefinição' primeiro.
+ Restaurar todos os parâmetros editáveis para os valores salvos com 'Salvar predefinição'?
+
+Isso não pode ser desfeito pelo FamSlide - use o Desfazer do próprio Revit, se necessário.
+ Predefinição salva para {} parâmetro(s).
+
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.ru.xaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.ru.xaml
new file mode 100644
index 000000000..563ba308b
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.ResourceDictionary.ru.xaml
@@ -0,0 +1,49 @@
+
+ ⟲ Обновить документ
+ Повторно сканирует активный документ семейства на предмет новых или удалённых параметров. Также полезно при переключении на другое открытое семейство.
+ Перемешать значения параметров. Присваивает каждому редактируемому параметру случайное значение в пределах текущего диапазона ползунка. Используйте с осторожностью - это действие нельзя отменить из FamSlide.
+ Удалить все неиспользуемые параметры. Удаляет параметры семейства, которые не управляют размерами, не участвуют в формулах и не являются встроенными. Используйте с осторожностью.
+ Показать/скрыть редактируемые параметры. Переключает видимость параметров, значение которых можно изменить напрямую.
+ Показать/скрыть метки. Переключает отображение небольших меток под именем каждого параметра.
+ Закрыть
+ Закрыть FamSlide
+ Параметры значений
+ Встроенные параметры
+ Параметры Да/Нет
+ используется
+ uF
+ dF
+ встроенный
+ экземпляр
+ тип
+ связан
+ (семейство не загружено)
+ Нет активного документа семейства
+ Да
+ Нет
+ Перемешать все редактируемые параметры, присвоив им случайные значения в пределах текущего диапазона ползунка?
+
+Это действие нельзя отменить из FamSlide - при необходимости используйте отмену Revit.
+ Удалить все параметры семейства, которые не управляют размерами, не участвуют в формулах и не являются встроенными?
+
+Это действие нельзя отменить из FamSlide - при необходимости используйте отмену Revit.
+ Нет активного документа Revit.
+ FamSlide работает только в редакторе семейств.
+Откройте семейство для редактирования и запустите инструмент снова.
+ Переименовать…
+ Сделать параметром экземпляра
+ Сделать параметром типа
+ Переименовать параметр
+ Введите новое имя для этого параметра:
+ Не удалось переименовать параметр «{}». Возможно, он встроенный, или новое имя уже используется.
+ Не удалось переключить Экземпляр/Тип для параметра «{}». Возможно, он управляется формулой или ограничен Revit иным образом.
+ Сохранить пресет. Запоминает текущие значения всех редактируемых параметров этого семейства, чтобы можно было быстро вернуться к ним позже.
+ Восстановить пресет. Повторно применяет значения, сохранённые с помощью 'Сохранить пресет' для этого семейства. Ничего не делает, если пресет ещё не был сохранён.
+ Для этого семейства ещё не сохранён пресет. Сначала используйте 'Сохранить пресет'.
+ Восстановить все редактируемые параметры до значений, сохранённых с помощью 'Сохранить пресет'?
+
+Это действие нельзя отменить из FamSlide - при необходимости используйте отмену Revit.
+ Пресет сохранён для {} параметр(ов).
+
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.xaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.xaml
new file mode 100644
index 000000000..de65f57cb
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/FamSlideWindow.xaml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/bundle.yaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/bundle.yaml
new file mode 100644
index 000000000..95fc7a804
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/bundle.yaml
@@ -0,0 +1,43 @@
+title:
+ en_us: FamSlide
+ ko: FamSlide
+ fr_fr: FamSlide
+ ru: FamSlide
+ chinese_s: FamSlide
+ es_es: FamSlide
+ de_de: FamSlide
+ pt_br: FamSlide
+tooltip:
+ en_us: |
+ Live parameter sliders for the Family Editor - preview shape changes and
+ catch constraint errors without the Family Types dialog.
+ ko: |
+ 패밀리 편집기용 실시간 매개변수 슬라이더 - Family Types 대화상자 없이
+ 형태 변화와 제약 조건 오류를 미리 확인하세요.
+ fr_fr: |
+ Curseurs de paramètres en direct pour l'éditeur de famille - visualisez
+ les changements de forme et les erreurs de contrainte sans passer par la
+ boîte de dialogue Types de famille.
+ ru: |
+ Ползунки параметров в реальном времени для редактора семейств -
+ отслеживайте изменения формы и ошибки ограничений без диалога
+ Типоразмеры семейства.
+ chinese_s: |
+ 族编辑器的实时参数滑块 - 无需打开"族类型"对话框即可预览形状变化并
+ 发现约束错误。
+ es_es: |
+ Controles deslizantes de parámetros en vivo para el Editor de familias -
+ visualice cambios de forma y errores de restricción sin pasar por el
+ cuadro de diálogo Tipos de familia.
+ de_de: |
+ Live-Parameter-Schieberegler für den Familieneditor - Formänderungen und
+ Beschränkungsfehler ohne den Dialog "Familientypen" prüfen.
+ pt_br: |
+ Controles deslizantes de parâmetros em tempo real para o Editor de
+ Famílias - veja mudanças de forma e erros de restrição sem abrir a
+ caixa de diálogo Tipos de Família.
+authors: Archilizer Ltd / dnenov (original concept), wurschdhaud (migration)
+context: doc-family
+min_revit_version: 2022
+engine:
+ persistent: true
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/famslide_actions.py b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/famslide_actions.py
new file mode 100644
index 000000000..29887c86d
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/famslide_actions.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+"""Destructive/bulk actions for FamSlide's bottom toolbar."""
+
+import random
+
+from pyrevit import revit, script
+from pyrevit.revit import Transaction
+from pyrevit import DB
+
+logger = script.get_logger()
+
+
+def shuffle_parameter_values(doc, family_manager, rows):
+ """Randomize every editable numeric/Yes-No parameter within its
+ current slider range. Intentionally does NOT touch formula-driven
+ or built-in parameters that report as read-only elsewhere.
+
+ Caller is responsible for confirming with the user first - this
+ is destructive and cannot be limited to a subset from here.
+ """
+ with Transaction("FamSlide: Shuffle Parameter Values", doc=doc):
+ for row in rows:
+ if not row.is_editable:
+ continue
+ if row.group == "yesno":
+ family_manager.Set(row.param, int(random.randint(0, 1)))
+ elif row.storage_type == DB.StorageType.Double:
+ lo, hi = row.range if hasattr(row, "range") else (0.0, 10.0)
+ family_manager.Set(row.param, float(random.uniform(lo, hi)))
+ elif row.storage_type == DB.StorageType.Integer:
+ lo, hi = row.range if hasattr(row, "range") else (0, 100)
+ family_manager.Set(row.param, int(random.randint(int(lo), int(hi))))
+ revit.uidoc.RefreshActiveView()
+
+
+def delete_unused_parameters(doc, family_manager, rows):
+ """Remove every family parameter flagged as NOT `in_use` (per the
+ heuristic in paramutils._scan_in_use) and not driving/driven by a
+ formula, since those are much likelier to actually be safe to
+ remove. Built-in parameters are never removable via the API and
+ are skipped automatically.
+
+ Caller is responsible for confirming with the user first.
+ """
+ with Transaction("FamSlide: Delete Unused Parameters", doc=doc):
+ for row in rows:
+ if row.is_builtin:
+ continue
+ if row.in_use or row.has_formula or row.used_in_formula:
+ continue
+ if row.is_associated:
+ continue
+ try:
+ family_manager.RemoveParameter(row.param)
+ except Exception:
+ # keep going - some parameters may be removable-blocked
+ # by Revit for reasons not visible from the API
+ # (nested family constraints, etc.)
+ logger.exception(
+ "FamSlide: could not remove parameter '{}'".format(row.name)
+ )
+ continue
+ revit.uidoc.RefreshActiveView()
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/famslide_paramutils.py b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/famslide_paramutils.py
new file mode 100644
index 000000000..56bf64b53
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/famslide_paramutils.py
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+"""Parameter discovery, classification, tag computation and default
+slider ranges for FamSlide.
+"""
+
+from pyrevit import DB
+from pyrevit.revit import is_yesno_parameter
+from pyrevit.revit import query
+from pyrevit.compat import get_elementid_value_func
+
+get_elementid_value = get_elementid_value_func()
+
+
+class ParamRow(object):
+ """One row in the FamSlide UI: wraps a FamilyParameter plus
+ everything the UI needs to render and edit it."""
+
+ def __init__(self, family_parameter):
+ self.param = family_parameter
+ self.name = family_parameter.Definition.Name
+ self.storage_type = family_parameter.StorageType
+ self.is_instance = family_parameter.IsInstance
+ self.is_yesno = is_yesno_parameter(family_parameter.Definition)
+ self.is_angle = family_parameter.Definition.GetDataType() == DB.SpecTypeId.Angle
+ self.is_builtin = get_elementid_value(family_parameter.Id) < 0
+ self.has_formula = bool(getattr(family_parameter, "Formula", None))
+ # self.has_formula = family_parameter.IsDeterminedByFormula # whats the difference?
+ self.is_readonly = family_parameter.IsReadOnly
+ self.locked = False # filled in by classify_parameters
+ self.used_in_formula = False # filled in by classify_parameters
+ self.in_use = False # filled in by classify_parameters
+ self.is_associated = not family_parameter.AssociatedParameters.IsEmpty
+ self.group = None # "value" | "builtin" | "yesno"
+
+ @property
+ def is_editable(self):
+ """bool: whether the slider/checkbox should be enabled.
+
+ Formula-driven parameters cannot be edited directly in Revit
+ (the Family Types dialog greys them out too), so we mirror
+ that here.
+ """
+ return not self.has_formula and not self.is_readonly and not self.locked
+
+ def tags(self):
+ """list[str]: stable tag identifiers to render as pills, in a
+ stable order.
+
+ These are internal identifiers, not display text - the UI layer
+ is responsible for mapping each id to a localized label and a
+ color (see script.TAG_COLORS / script.TAG_LABEL_KEYS). Keeping
+ the id decoupled from the label means the color lookup keeps
+ working regardless of the active locale.
+ """
+ tags = []
+ if self.in_use:
+ tags.append("in_use")
+ if self.used_in_formula:
+ tags.append("used_in_formula")
+ if self.has_formula:
+ tags.append("has_formula")
+ if self.is_builtin:
+ tags.append("built_in")
+ if self.is_instance:
+ tags.append("instance")
+ else:
+ tags.append("type")
+ if self.is_associated:
+ tags.append("associated")
+ return tags
+
+
+def _scan_in_use(doc, rows_by_id):
+ """Best-effort: mark parameters that label at least one dimension."""
+ for param in query.get_family_label_parameters(doc):
+ row = rows_by_id.get(get_elementid_value(param.Id))
+ if row:
+ row.in_use = True
+
+
+def _scan_used_in_formula(rows):
+ """Mark parameters whose *name* appears in another parameter's
+ Formula string. Simple substring heuristic - matches how Revit
+ itself resolves formula references (by parameter name).
+ """
+ named_rows = [(r.name, r) for r in rows]
+ for row in rows:
+ formula = getattr(row.param, "Formula", None)
+ if not formula:
+ continue
+ for other_name, other_row in named_rows:
+ if other_row is row:
+ continue
+ if other_name and other_name in formula:
+ other_row.used_in_formula = True
+
+
+def _scan_locked(family_manager, rows):
+ """Mark parameters that are locked in the family.
+
+ Not every FamilyParameter supports locking; Revit may throw
+ when IsParameterLocked is queried, so treat those as unlocked.
+ """
+ for row in rows:
+ try:
+ row.locked = family_manager.IsParameterLocked(row.param)
+ except Exception:
+ row.locked = False
+
+
+def classify_parameters(doc, family_manager):
+ """list[ParamRow]: every family parameter, classified and tagged,
+ ready for the UI to render.
+ """
+ rows = [ParamRow(p) for p in family_manager.Parameters]
+ rows_by_id = dict((get_elementid_value(r.param.Id), r) for r in rows)
+
+ _scan_in_use(doc, rows_by_id)
+ _scan_used_in_formula(rows)
+ _scan_locked(family_manager, rows)
+
+ for row in rows:
+ if row.is_yesno:
+ row.group = "yesno"
+ elif row.is_builtin:
+ row.group = "builtin"
+ else:
+ row.group = "value"
+
+ # stable, readable ordering: alphabetic within each group
+ rows.sort(key=lambda r: r.name.lower())
+ return rows
+
+
+# ---------------------------------------------------------------------
+# Default slider ranges. Hardcoded.
+# ---------------------------------------------------------------------
+
+_TWO_PI = 6.283185307179586
+
+
+def default_range(row, current_value):
+ """tuple(float min, float max): a reasonable slider range for a
+ Double/Integer family parameter, given its current internal value.
+ """
+ if row.is_angle:
+ return 0.0, _TWO_PI
+
+ if row.storage_type == DB.StorageType.Integer:
+ lo, hi = 0, 100
+ if current_value is not None and current_value > hi:
+ hi = int(current_value * 2)
+ return float(lo), float(hi)
+
+ # generic Double (length, area, volume, number, etc.)
+ if current_value is None or current_value == 0:
+ return 0.0, 10.0
+ if current_value > 0:
+ return 0.0, current_value * 3.0
+ return current_value * 3.0, 0.0
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/icon.dark.png b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/icon.dark.png
new file mode 100644
index 000000000..b5642d306
Binary files /dev/null and b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/icon.dark.png differ
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/icon.png b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/icon.png
new file mode 100644
index 000000000..63be05548
Binary files /dev/null and b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/icon.png differ
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/script.py b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/script.py
new file mode 100644
index 000000000..85de73665
--- /dev/null
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/FamSlide.pushbutton/script.py
@@ -0,0 +1,606 @@
+# -*- coding: utf-8 -*-
+"""FamSlide - live parameter sliders for the Family Editor.
+
+Gives you a modeless panel of sliders / checkboxes / textboxes bound to
+the active family's parameters, so you can see how the model reacts to
+parameter changes (shape changes, constraint errors, etc.) without
+round-tripping through the Family Types dialog every time.
+"""
+
+from pyrevit import forms, revit, script
+from pyrevit import DB
+from pyrevit.revit import events
+from pyrevit.framework import Media, SolidColorBrush
+from pyrevit.framework import Input, Controls, Windows
+from pyrevit.coreutils import applocales
+
+
+import famslide_paramutils
+import famslide_actions
+
+logger = script.get_logger()
+
+# Path to the base XAML file, used to locate this bundle's
+# FamSlideWindow.ResourceDictionary..xaml files for translation
+# lookups (see applocales.get_locale_string_from_xaml).
+XAML_FILE = script.get_bundle_file("FamSlideWindow.xaml")
+
+
+def _t(key):
+ """str: localized string for `key`, falling back to `key` itself."""
+ return applocales.get_locale_string_from_xaml(XAML_FILE, key)
+
+
+# group_key -> resource dictionary key for that group's header text.
+GROUP_TITLE_KEYS = {
+ "value": "GroupTitleValue",
+ "builtin": "GroupTitleBuiltin",
+ "yesno": "GroupTitleYesNo",
+}
+GROUP_ORDER = ["value", "builtin", "yesno"]
+
+# ParamRow.tags() returns stable ids (never localized) so the color
+# lookup keeps working regardless of the active locale. TAG_LABEL_KEYS
+# maps each id to the resource key for its display text.
+TAG_COLORS = {
+ "in_use": "#FFB48A00",
+ "used_in_formula": "#FF7A3FA0",
+ "has_formula": "#FFB33A3A",
+ "built_in": "#FFCC5A1E",
+ "instance": "#FF2E86C1",
+ "type": "#FF1E8449",
+ "associated": "#FF6C757D",
+}
+TAG_LABEL_KEYS = {
+ "in_use": "TagInUse",
+ "used_in_formula": "TagUsedInFormula",
+ "has_formula": "TagHasFormula",
+ "built_in": "TagBuiltIn",
+ "instance": "TagInstance",
+ "type": "TagType",
+ "associated": "TagAssociated",
+}
+
+# module-level handle to the single live FamSlide window, so the
+# doc-changed event hook (which pyRevit binds at import time, outside
+# any instance) can reach it. None while no window is open.
+ui = None
+
+
+class FamSlideWindow(forms.WPFWindow):
+ """Modeless FamSlide panel."""
+
+ def __init__(self, xaml_file):
+ forms.WPFWindow.__init__(self, xaml_file)
+
+ self._rows = []
+ self._show_editable_only = False
+ self._show_labels = True
+ self._expanded_state = {"value": True, "builtin": True, "yesno": True}
+ # doc.Title -> {param_elementid: raw_value}. Keyed per family
+ # document (not globally) so switching between open families
+ # can't accidentally apply one family's preset to another.
+ self._presets = {}
+
+ self._apply_locale()
+
+ self.RefreshButton.Click += self.on_refresh_click
+ self.ShuffleButton.Click += self.on_shuffle_click
+ self.DeleteUnusedButton.Click += self.on_delete_unused_click
+ self.ToggleEditableButton.Click += self.on_toggle_editable_click
+ self.ToggleLabelsButton.Click += self.on_toggle_labels_click
+ self.SavePresetButton.Click += self.on_save_preset_click
+ self.RestorePresetButton.Click += self.on_restore_preset_click
+ self.CloseButton.Click += self.on_close_click
+ self.Closed += self.on_closed
+
+ script.restore_window_position(self)
+
+ self.refresh_from_document()
+
+ def _apply_locale(self):
+ """Push localized text into the XAML-defined controls.
+
+ The XAML ships with English text as a design-time fallback;
+ this overwrites it with the resource-dictionary string for the
+ active locale (or leaves the English fallback untouched if no
+ matching key is found - see get_locale_string_from_xaml).
+ """
+ self.RefreshButton.Content = _t("BtnRefresh")
+ self.RefreshButton.ToolTip = _t("TooltipRefresh")
+ self.ShuffleButton.ToolTip = _t("TooltipShuffle")
+ self.DeleteUnusedButton.ToolTip = _t("TooltipDeleteUnused")
+ self.ToggleEditableButton.ToolTip = _t("TooltipToggleEditable")
+ self.ToggleLabelsButton.ToolTip = _t("TooltipToggleLabels")
+ self.SavePresetButton.ToolTip = _t("TooltipSavePreset")
+ self.RestorePresetButton.ToolTip = _t("TooltipRestorePreset")
+ self.CloseButton.Content = _t("BtnClose")
+ self.CloseButton.ToolTip = _t("TooltipClose")
+ self.FamilyNameLabel.Text = _t("NoFamilyLoaded")
+
+ # ------------------------------------------------------------------
+ # document (re)loading - only ever called from a valid API context
+ # (window construction, the doc-changed/view-activated hook, or
+ # from inside a bridged callable)
+ # ------------------------------------------------------------------
+ def refresh_from_document(self):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument:
+ self.FamilyNameLabel.Text = _t("NoFamilyDocument")
+ self.GroupsHost.Children.Clear()
+ self._rows = []
+ return
+
+ self.FamilyNameLabel.Text = doc.Title
+ fm = doc.FamilyManager
+ self._rows = famslide_paramutils.classify_parameters(doc, fm)
+ self._build_ui(fm)
+
+ def _build_ui(self, fm):
+ self.GroupsHost.Children.Clear()
+ rows_by_group = dict((g, []) for g in GROUP_ORDER)
+ for row in self._rows:
+ rows_by_group[row.group].append(row)
+
+ for group_key in GROUP_ORDER:
+ group_rows = rows_by_group[group_key]
+ if not group_rows:
+ continue
+
+ visible_rows = [
+ r
+ for r in group_rows
+ if not (self._show_editable_only and not r.is_editable)
+ ]
+ if not visible_rows:
+ continue
+
+ expander = Controls.Expander()
+ expander.Header = "{} ({})".format(
+ _t(GROUP_TITLE_KEYS[group_key]), len(visible_rows)
+ )
+ expander.IsExpanded = self._expanded_state.get(group_key, True)
+ expander.Margin = Windows.Thickness(0, 6, 0, 6)
+ expander.Tag = group_key
+ expander.Expanded += self.on_group_toggled
+ expander.Collapsed += self.on_group_toggled
+
+ body = Controls.StackPanel()
+ for row in visible_rows:
+ body.Children.Add(self._build_row(fm, row))
+ expander.Content = body
+ self.GroupsHost.Children.Add(expander)
+
+ def _build_row(self, fm, row):
+ outer = Controls.Border()
+ outer.CornerRadius = Windows.CornerRadius(16)
+ outer.Background = Media.Brushes.White
+ outer.Margin = Windows.Thickness(0, 3, 0, 3)
+ outer.Padding = Windows.Thickness(10, 6, 10, 6)
+
+ # Built-in parameters can't be renamed or converted between
+ # Instance/Type via the API, so there's nothing useful to put
+ # in a context menu for them.
+ if not row.is_builtin:
+ outer.ContextMenu = self._build_context_menu(row)
+
+ grid = Controls.Grid()
+ col_name = Controls.ColumnDefinition()
+ col_name.Width = Windows.GridLength(150)
+ col_slider = Controls.ColumnDefinition()
+ col_slider.Width = Windows.GridLength(1, Windows.GridUnitType.Star)
+ col_value = Controls.ColumnDefinition()
+ col_value.Width = Windows.GridLength(84)
+ grid.ColumnDefinitions.Add(col_name)
+ grid.ColumnDefinitions.Add(col_slider)
+ grid.ColumnDefinitions.Add(col_value)
+
+ # --- name + tag pills -------------------------------------------------
+ name_panel = Controls.StackPanel()
+ name_panel.VerticalAlignment = Windows.VerticalAlignment.Center
+ name_text = Controls.TextBlock()
+ name_text.Text = row.name
+ name_text.FontWeight = Windows.FontWeights.SemiBold
+ name_text.TextWrapping = Windows.TextWrapping.Wrap
+ name_panel.Children.Add(name_text)
+
+ tags = row.tags()
+ if self._show_labels and tags:
+ tag_wrap = Controls.WrapPanel()
+ tag_wrap.Margin = Windows.Thickness(0, 3, 0, 0)
+ for tag in tags:
+ tag_wrap.Children.Add(self._build_tag_pill(tag))
+ name_panel.Children.Add(tag_wrap)
+
+ Controls.Grid.SetColumn(name_panel, 0)
+ grid.Children.Add(name_panel)
+
+ # --- control: slider / checkbox ---------------------------------------
+ if row.group == "yesno":
+ raw = fm.CurrentType.AsInteger(row.param)
+ checkbox = Controls.CheckBox()
+ checkbox.VerticalAlignment = Windows.VerticalAlignment.Center
+ checkbox.HorizontalAlignment = Windows.HorizontalAlignment.Left
+ checkbox.IsEnabled = row.is_editable
+ checkbox.IsChecked = bool(raw)
+ checkbox.Tag = row
+ checkbox.Click += self.on_checkbox_click
+ Controls.Grid.SetColumn(checkbox, 1)
+ grid.Children.Add(checkbox)
+ value_display = _t("ValueYes") if raw else _t("ValueNo")
+
+ elif row.storage_type in (DB.StorageType.Double, DB.StorageType.Integer):
+ if row.storage_type == DB.StorageType.Double:
+ current_raw = fm.CurrentType.AsDouble(row.param)
+ else:
+ current_raw = fm.CurrentType.AsInteger(row.param)
+
+ lo, hi = famslide_paramutils.default_range(row, current_raw)
+ row.range = (lo, hi)
+
+ slider = Controls.Slider()
+ slider.Minimum = lo
+ slider.Maximum = hi
+ slider.Value = current_raw if current_raw is not None else lo
+ slider.IsEnabled = row.is_editable
+ slider.VerticalAlignment = Windows.VerticalAlignment.Center
+ slider.Margin = Windows.Thickness(8, 0, 8, 0)
+ slider.IsMoveToPointEnabled = True
+ slider.AutoToolTipPlacement = (
+ Controls.Primitives.AutoToolTipPlacement.TopLeft
+ )
+ slider.Tag = row
+ slider.PreviewMouseUp += self.on_slider_mouse_up
+ slider.KeyUp += self.on_slider_key_up
+ Controls.Grid.SetColumn(slider, 1)
+ grid.Children.Add(slider)
+ value_display = fm.CurrentType.AsValueString(row.param)
+
+ else:
+ # String / ElementId user parameters: no meaningful slider.
+ spacer = Controls.TextBlock()
+ Controls.Grid.SetColumn(spacer, 1)
+ grid.Children.Add(spacer)
+ value_display = fm.CurrentType.AsValueString(row.param)
+
+ # --- value textbox ------------------------------------------------------
+ value_border = Controls.Border()
+ value_border.CornerRadius = Windows.CornerRadius(14)
+ value_border.Background = Media.Brushes.White
+ value_border.BorderBrush = Media.Brushes.LightGray
+ value_border.BorderThickness = Windows.Thickness(1)
+ value_border.VerticalAlignment = Windows.VerticalAlignment.Center
+
+ value_box = Controls.TextBox()
+ value_box.Text = value_display
+ value_box.BorderThickness = Windows.Thickness(0)
+ value_box.Padding = Windows.Thickness(6, 4, 6, 4)
+ value_box.TextAlignment = Windows.TextAlignment.Center
+ value_box.IsReadOnly = not row.is_editable
+ value_box.Tag = row
+ value_box.KeyDown += self.on_value_box_key_down
+ value_box.LostFocus += self.on_value_box_lost_focus
+ value_border.Child = value_box
+
+ Controls.Grid.SetColumn(value_border, 2)
+ grid.Children.Add(value_border)
+
+ outer.Child = grid
+ return outer
+
+ def _build_tag_pill(self, tag_id):
+ pill = Controls.Border()
+ pill.CornerRadius = Windows.CornerRadius(9)
+ pill.Padding = Windows.Thickness(6, 1, 6, 1)
+ pill.Margin = Windows.Thickness(0, 0, 3, 3)
+ pill.Background = SolidColorBrush(
+ Media.ColorConverter.ConvertFromString(TAG_COLORS.get(tag_id, "#FF8A8A8A"))
+ )
+ text = Controls.TextBlock()
+ text.Text = _t(TAG_LABEL_KEYS.get(tag_id, tag_id))
+ text.Foreground = Media.Brushes.White
+ text.FontSize = 10.0
+ pill.Child = text
+ return pill
+
+ def _build_context_menu(self, row):
+ """ContextMenu: right-click actions for a single parameter row.
+
+ Only offered for non-built-in parameters (see _build_row).
+ """
+ menu = Controls.ContextMenu()
+
+ rename_item = Controls.MenuItem()
+ rename_item.Header = _t("MenuRename")
+ rename_item.Tag = row
+ rename_item.Click += self.on_rename_click
+ menu.Items.Add(rename_item)
+
+ # Formula-driven parameters are forced to be Type parameters by
+ # Revit itself, so offering to flip them to Instance would only
+ # fail - disable the item instead of letting the user hit an
+ # avoidable error.
+ toggle_item = Controls.MenuItem()
+ toggle_item.Header = _t("MenuMakeType") if row.is_instance else _t("MenuMakeInstance")
+ toggle_item.Tag = row
+ toggle_item.IsEnabled = not row.has_formula
+ toggle_item.Click += self.on_toggle_instance_type_click
+ menu.Items.Add(toggle_item)
+
+ return menu
+
+ # ------------------------------------------------------------------
+ # UI-thread-only handlers (no Revit API access here directly)
+ # ------------------------------------------------------------------
+ def on_group_toggled(self, sender, args):
+ self._expanded_state[sender.Tag] = sender.IsExpanded
+
+ def on_toggle_editable_click(self, sender, args):
+ self._show_editable_only = not self._show_editable_only
+ doc = revit.doc
+ if doc is not None and doc.IsFamilyDocument:
+ self._build_ui(doc.FamilyManager)
+
+ def on_toggle_labels_click(self, sender, args):
+ self._show_labels = not self._show_labels
+ doc = revit.doc
+ if doc is not None and doc.IsFamilyDocument:
+ self._build_ui(doc.FamilyManager)
+
+ def on_close_click(self, sender, args):
+ self.Close()
+
+ def on_closed(self, sender, args):
+ global ui
+ events.stop_events()
+ script.save_window_position(self)
+ ui = None
+
+ # --- controls that must hand off to Revit --------------------------------
+ def on_rename_click(self, sender, args):
+ row = sender.Tag
+ new_name = forms.ask_for_string(
+ default=row.name,
+ prompt=_t("PromptRenameParameter"),
+ title=_t("DialogRenameTitle"),
+ )
+ if not new_name or new_name == row.name:
+ return
+ events.execute_in_revit_context(self._commit_rename, row, new_name)
+
+ def on_toggle_instance_type_click(self, sender, args):
+ row = sender.Tag
+ events.execute_in_revit_context(self._commit_toggle_instance_type, row)
+
+ def on_save_preset_click(self, sender, args):
+ events.execute_in_revit_context(self._do_save_preset)
+
+ def on_restore_preset_click(self, sender, args):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument or doc.Title not in self._presets:
+ forms.alert(_t("AlertNoPresetSaved"))
+ return
+ if not forms.alert(
+ _t("ConfirmRestorePresetMessage"),
+ yes=True,
+ no=True,
+ ):
+ return
+ events.execute_in_revit_context(self._do_restore_preset)
+
+ def on_slider_mouse_up(self, sender, args):
+ row = sender.Tag
+ value = sender.Value
+ events.execute_in_revit_context(self._commit_numeric, row, value)
+
+ def on_slider_key_up(self, sender, args):
+ # arrow-key nudges also count as a "release"
+ if args.Key in (
+ Input.Key.Left,
+ Input.Key.Right,
+ Input.Key.Up,
+ Input.Key.Down,
+ Input.Key.Home,
+ Input.Key.End,
+ ):
+ row = sender.Tag
+ value = sender.Value
+ events.execute_in_revit_context(self._commit_numeric, row, value)
+
+ def on_checkbox_click(self, sender, args):
+ row = sender.Tag
+ new_value = 1 if sender.IsChecked else 0
+ events.execute_in_revit_context(self._commit_numeric, row, new_value)
+
+ def on_value_box_key_down(self, sender, args):
+ row = sender.Tag
+ if not row.is_editable:
+ return
+ if args.Key == Input.Key.Enter:
+ text = sender.Text
+ events.execute_in_revit_context(self._commit_text, row, text)
+
+ def on_value_box_lost_focus(self, sender, args):
+ row = sender.Tag
+ if not row.is_editable:
+ return
+ text = sender.Text
+ events.execute_in_revit_context(self._commit_text, row, text)
+
+ def on_refresh_click(self, sender, args):
+ events.execute_in_revit_context(self.refresh_from_document)
+
+ def on_shuffle_click(self, sender, args):
+ if not forms.alert(
+ _t("ConfirmShuffleMessage"),
+ yes=True,
+ no=True,
+ ):
+ return
+ events.execute_in_revit_context(self._do_shuffle)
+
+ def on_delete_unused_click(self, sender, args):
+ if not forms.alert(
+ _t("ConfirmDeleteUnusedMessage"),
+ yes=True,
+ no=True,
+ ):
+ return
+ events.execute_in_revit_context(self._do_delete_unused)
+
+ # ------------------------------------------------------------------
+ # bridged callables - these run inside a valid Revit API context
+ # ------------------------------------------------------------------
+ def _commit_numeric(self, row, value):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument or not row.is_editable:
+ return
+ fm = doc.FamilyManager
+ with revit.Transaction("FamSlide: Set {}".format(row.name), doc=doc):
+ if row.group == "yesno" or row.storage_type == DB.StorageType.Integer:
+ fm.Set(row.param, int(round(value)))
+ else:
+ fm.Set(row.param, float(value))
+
+ def _commit_text(self, row, text):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument or not row.is_editable:
+ return
+ fm = doc.FamilyManager
+ with revit.Transaction("FamSlide: Set {}".format(row.name), doc=doc):
+ fm.SetValueString(row.param, text)
+
+ def _do_shuffle(self):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument:
+ return
+ famslide_actions.shuffle_parameter_values(doc, doc.FamilyManager, self._rows)
+
+ def _do_delete_unused(self):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument:
+ return
+ famslide_actions.delete_unused_parameters(doc, doc.FamilyManager, self._rows)
+
+ def _commit_rename(self, row, new_name):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument or row.is_builtin:
+ return
+ fm = doc.FamilyManager
+ with revit.Transaction("FamSlide: Rename {}".format(row.name), doc=doc):
+ try:
+ fm.RenameParameter(row.param, new_name)
+ except Exception:
+ # most likely a duplicate name - nothing was changed,
+ # so just let the transaction commit as a no-op.
+ logger.exception(
+ "FamSlide: could not rename parameter '{}'".format(row.name)
+ )
+ forms.alert(_t("AlertRenameFailed").format(row.name))
+
+ def _commit_toggle_instance_type(self, row):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument or row.is_builtin:
+ return
+ fm = doc.FamilyManager
+ with revit.Transaction(
+ "FamSlide: Toggle Instance/Type {}".format(row.name), doc=doc
+ ):
+ try:
+ if row.is_instance:
+ fm.MakeType(row.param)
+ else:
+ fm.MakeInstance(row.param)
+ except Exception:
+ # e.g. formula-driven parameters must stay Type - the
+ # menu item is disabled for those, but guard here too
+ # in case classification is stale after a quick edit.
+ logger.exception(
+ "FamSlide: could not toggle instance/type for '{}'".format(
+ row.name
+ )
+ )
+ forms.alert(_t("AlertToggleInstanceTypeFailed").format(row.name))
+
+ def _do_save_preset(self):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument:
+ return
+ fm = doc.FamilyManager
+ preset = {}
+ for row in self._rows:
+ if not row.is_editable:
+ continue
+ param_id = famslide_paramutils.get_elementid_value(row.param.Id)
+ if row.group == "yesno" or row.storage_type == DB.StorageType.Integer:
+ preset[param_id] = fm.CurrentType.AsInteger(row.param)
+ elif row.storage_type == DB.StorageType.Double:
+ preset[param_id] = fm.CurrentType.AsDouble(row.param)
+ else:
+ preset[param_id] = fm.CurrentType.AsValueString(row.param)
+ self._presets[doc.Title] = preset
+ forms.alert(_t("AlertPresetSaved").format(len(preset)))
+
+ def _do_restore_preset(self):
+ doc = revit.doc
+ if doc is None or not doc.IsFamilyDocument:
+ return
+ preset = self._presets.get(doc.Title)
+ if not preset:
+ return
+ fm = doc.FamilyManager
+ with revit.Transaction("FamSlide: Restore Preset", doc=doc):
+ for row in self._rows:
+ if not row.is_editable:
+ continue
+ param_id = famslide_paramutils.get_elementid_value(row.param.Id)
+ if param_id not in preset:
+ continue
+ value = preset[param_id]
+ try:
+ if row.group == "yesno" or row.storage_type == DB.StorageType.Integer:
+ fm.Set(row.param, int(value))
+ elif row.storage_type == DB.StorageType.Double:
+ fm.Set(row.param, float(value))
+ else:
+ fm.SetValueString(row.param, value)
+ except Exception:
+ logger.exception(
+ "FamSlide: could not restore preset value for '{}'".format(
+ row.name
+ )
+ )
+ continue
+ revit.uidoc.RefreshActiveView()
+
+
+# ---------------------------------------------------------------------
+# Reactive refresh: fires on parameter edits, undo/redo, and switching
+# between open family documents (view-activated covers window
+# switches). Runs in a valid Revit API context, so it is safe for
+# ui.refresh_from_document() to touch Revit data here.
+# ---------------------------------------------------------------------
+@events.handle("doc-changed", "view-activated", "doc-opened")
+def famslide_on_revit_event(sender, args):
+ if ui is not None:
+ ui.refresh_from_document()
+
+
+def _gate_family_editor():
+ doc = revit.doc
+ if doc is None:
+ forms.alert(_t("AlertNoActiveDocument"), exitscript=True)
+ if not doc.IsFamilyDocument:
+ forms.alert(_t("AlertFamilyEditorOnly"), exitscript=True)
+
+
+if __name__ == "__main__":
+ if ui is not None:
+ # already open from a previous click under the persistent
+ # engine - just bring it forward instead of opening a second
+ # copy (which would double-subscribe the doc-changed hook).
+ ui.Activate()
+ else:
+ _gate_family_editor()
+ ui = FamSlideWindow("FamSlideWindow.xaml")
+ ui.show(modal=False)
diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/bundle.yaml b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/bundle.yaml
index 9c8208860..00b551ab2 100644
--- a/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/bundle.yaml
+++ b/extensions/pyRevitTools.extension/pyRevit.tab/Project.panel/ptools.stack/Family.pulldown/bundle.yaml
@@ -17,6 +17,7 @@ layout:
- Export Family Config
- Import Family Config
- '-----'
+ - FamSlide
- Wipe Family Parameters
- '-----'
- Load Families