Compare commits
14 Commits
v0.1.1
...
refactor/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c59c704da3 | ||
|
|
c858706d48 | ||
|
|
724440dcb7 | ||
|
|
2f594dac0a | ||
|
|
939b81385e | ||
|
|
84b69b935b | ||
| a6e84552da | |||
| 015df38328 | |||
| db85277f26 | |||
| 679aaec6d4 | |||
| deeb6376f7 | |||
| 103fc28bc6 | |||
| 79c85ed2e5 | |||
| 38358e431d |
2
.gitmodules
vendored
@@ -15,4 +15,4 @@
|
||||
url = https://git.kindred-systems.com/forbes/ztools.git
|
||||
[submodule "mods/silo"]
|
||||
path = mods/silo
|
||||
url = https://git.kindred-systems.com/kindred/silo.git
|
||||
url = https://git.kindred-systems.com/kindred/silo-mod.git
|
||||
|
||||
@@ -1,6 +1,55 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
||||
<path d="M12 20 L8 20 A4 4 0 0 1 8 12 L12 12" fill="none" stroke="#89b4fa" stroke-width="2"/>
|
||||
<path d="M20 12 L24 12 A4 4 0 0 1 24 20 L20 20" fill="none" stroke="#74c7ec" stroke-width="2"/>
|
||||
<line x1="12" y1="16" x2="20" y2="16" stroke="#89b4fa" stroke-width="2"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
sodipodi:docname="Link.svg"
|
||||
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<sodipodi:namedview
|
||||
id="namedview3"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="25.632621"
|
||||
inkscape:cx="14.005591"
|
||||
inkscape:cy="15.839192"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3" />
|
||||
<rect
|
||||
width="32"
|
||||
height="32"
|
||||
rx="4"
|
||||
fill="#313244"
|
||||
id="rect1" />
|
||||
<path
|
||||
d="M 12.013793,20 H 8.0137931 a 4,4 0 0 1 0,-8 h 3.9999999"
|
||||
fill="none"
|
||||
stroke="#89b4fa"
|
||||
stroke-width="1.5"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 20.013793,12 h 4 a 4,4 0 0 1 0,8 h -4"
|
||||
fill="none"
|
||||
stroke="#74c7ec"
|
||||
stroke-width="1.5"
|
||||
id="path2"
|
||||
style="stroke:#89b4fa;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;stroke:#89b4fa;stroke-width:1.77369;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 9.6965515,16 H 22.303449"
|
||||
id="path3" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 1.5 KiB |
@@ -1,7 +1,69 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
||||
<path d="M10 18 L6 18 A4 4 0 0 1 6 10 L10 10" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
|
||||
<path d="M18 10 L22 10 A4 4 0 0 1 22 18 L18 18" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
|
||||
<path d="M24 22 L24 28 L16 24 Z" fill="#a6e3a1"/>
|
||||
<line x1="24" y1="28" x2="24" y2="20" stroke="#a6e3a1" stroke-width="2"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
sodipodi:docname="LinkImport.svg"
|
||||
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<sodipodi:namedview
|
||||
id="namedview3"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="25.632621"
|
||||
inkscape:cx="14.005591"
|
||||
inkscape:cy="15.800179"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3" />
|
||||
<rect
|
||||
width="32"
|
||||
height="32"
|
||||
rx="4"
|
||||
fill="#313244"
|
||||
id="rect1" />
|
||||
<path
|
||||
d="M 12.013793,20 H 8.0137931 a 4,4 0 0 1 0,-8 h 3.9999999"
|
||||
fill="none"
|
||||
stroke="#89b4fa"
|
||||
stroke-width="1.5"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 20.013793,12 h 4 a 4,4 0 0 1 0,8 h -4"
|
||||
fill="none"
|
||||
stroke="#74c7ec"
|
||||
stroke-width="1.5"
|
||||
id="path2"
|
||||
style="stroke:#89b4fa;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;stroke:#89b4fa;stroke-width:1.77369;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 9.6965515,16 H 22.303449"
|
||||
id="path3" />
|
||||
<circle
|
||||
cx="25.513792"
|
||||
cy="-9.2321157"
|
||||
fill="#a6e3a1"
|
||||
id="circle2"
|
||||
style="stroke-width:0.999999"
|
||||
transform="scale(1,-1)"
|
||||
r="5" />
|
||||
<path
|
||||
d="M 25.51379,11.732116 V 6.7321179 m -2.499999,2.4999988 h 5"
|
||||
stroke="#1e1e2e"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
id="path3-7" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 1.9 KiB |
@@ -1,7 +1,62 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
||||
<path d="M12 18 L8 18 A4 4 0 0 1 8 10 L12 10" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
|
||||
<path d="M18 10 L22 10 A4 4 0 0 1 22 18 L18 18" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
|
||||
<line x1="12" y1="14" x2="18" y2="14" stroke="#89b4fa" stroke-width="1.5"/>
|
||||
<rect x="14" y="20" width="12" height="8" rx="1" fill="none" stroke="#f9e2af" stroke-width="1.5" stroke-dasharray="2,2"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
sodipodi:docname="LinkSelect.svg"
|
||||
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<sodipodi:namedview
|
||||
id="namedview3"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="25.632621"
|
||||
inkscape:cx="14.005591"
|
||||
inkscape:cy="15.800179"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3" />
|
||||
<rect
|
||||
width="32"
|
||||
height="32"
|
||||
rx="4"
|
||||
fill="#313244"
|
||||
id="rect1" />
|
||||
<path
|
||||
d="M 12.013793,20 H 8.0137931 a 4,4 0 0 1 0,-8 h 3.9999999"
|
||||
fill="none"
|
||||
stroke="#89b4fa"
|
||||
stroke-width="1.5"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 20.013793,12 h 4 a 4,4 0 0 1 0,8 h -4"
|
||||
fill="none"
|
||||
stroke="#74c7ec"
|
||||
stroke-width="1.5"
|
||||
id="path2"
|
||||
style="stroke:#89b4fa;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;stroke:#89b4fa;stroke-width:1.77369;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 9.6965515,16 H 22.303449"
|
||||
id="path3" />
|
||||
<rect
|
||||
style="fill:none;stroke:#fab387;stroke-width:1.056;stroke-dasharray:1.05599999, 2.11199999;stroke-linejoin:round;stroke-linecap:round;stroke-dashoffset:15.41759968;stroke-opacity:1"
|
||||
id="rect2"
|
||||
width="28.12822"
|
||||
height="14.785847"
|
||||
x="1.9358902"
|
||||
y="8.6070766" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 523 B After Width: | Height: | Size: 1.8 KiB |
@@ -1,12 +1,78 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
|
||||
<!-- Block with rounded edge -->
|
||||
<path d="M6 24 L6 10 L16 6 L26 10 L26 24 L16 28 Z" fill="#45475a" stroke="#89b4fa" stroke-width="1.5"/>
|
||||
<path d="M6 10 L16 14 L26 10" stroke="#89b4fa" stroke-width="1.5" fill="none"/>
|
||||
<path d="M16 14 L16 28" stroke="#89b4fa" stroke-width="1.5"/>
|
||||
<!-- Fillet radius on edge -->
|
||||
<path d="M6 10 Q10 10 12 14" stroke="#a6e3a1" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
||||
<!-- Radius indicator -->
|
||||
<path d="M6 10 L9 12" stroke="#cdd6f4" stroke-width="1" stroke-dasharray="2,1"/>
|
||||
<text x="4" y="8" font-family="sans-serif" font-size="6" fill="#a6e3a1">R</text>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
sodipodi:docname="PartDesign_Fillet.svg"
|
||||
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs5" />
|
||||
<sodipodi:namedview
|
||||
id="namedview5"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="2.8284271"
|
||||
inkscape:cy="17.412504"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg5" />
|
||||
<rect
|
||||
x="2"
|
||||
y="2"
|
||||
width="28"
|
||||
height="28"
|
||||
rx="4"
|
||||
fill="#313244"
|
||||
id="rect1" />
|
||||
<!-- Block with chamfered edge -->
|
||||
<path
|
||||
d="M 6,24 6.1584709,15.885723 10.508095,8.6005883 16,6 26,10 V 24 L 16,28 Z M 20.923535,12.327039 26.006643,9.9805673 Z"
|
||||
fill="#45475a"
|
||||
stroke="#89b4fa"
|
||||
stroke-width="1.5"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccccccccccc" />
|
||||
<path
|
||||
d="M 16,19.88 V 28"
|
||||
stroke="#89b4fa"
|
||||
stroke-width="1.5"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
d="M 5.721457,16 10.416889,7.8973687 21,12 l -3.708852,3.943415 -1.158565,4.20175 z"
|
||||
fill="#a6e3a1"
|
||||
fill-opacity="0.3"
|
||||
id="path5"
|
||||
sodipodi:nodetypes="cccccc"
|
||||
style="opacity:1;fill:#a6e3a1;fill-opacity:1" />
|
||||
<!-- Chamfer cut on edge -->
|
||||
<!-- Chamfer face -->
|
||||
<path
|
||||
d="m 25.554002,10.020735 c 0,0 -2.839088,1.13525 -4.950437,2.544214 -1.652011,1.102435 -2.601422,2.423855 -3.100562,3.159378 -1.011406,1.490386 -1.47202,3.667857 -1.538841,4.165681 -0.0066,0.04949 -0.01392,7.861307 -0.01392,7.861307 m 7.653323,-14.186366"
|
||||
stroke="#89b4fa"
|
||||
stroke-width="1.5847"
|
||||
id="path3-0"
|
||||
sodipodi:nodetypes="csssc"
|
||||
style="fill:none" />
|
||||
<path
|
||||
d="m 16.23145,6.006509 c 0,0 -3.524098,1.0247646 -5.635447,2.4337286 -1.6520107,1.1024351 -2.6014217,2.4238554 -3.1005617,3.1593784 -1.011406,1.490386 -1.47202,3.667857 -1.538841,4.165681 -0.0066,0.04949 0.2070509,8.612608 0.2070509,8.612608"
|
||||
stroke="#89b4fa"
|
||||
stroke-width="1.5847"
|
||||
id="path3-0-6"
|
||||
sodipodi:nodetypes="csssc"
|
||||
style="fill:none" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 753 B After Width: | Height: | Size: 2.6 KiB |
@@ -1,13 +1,64 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
sodipodi:docname="PartDesign_NewSketch.svg"
|
||||
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<sodipodi:namedview
|
||||
id="namedview3"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="36.25"
|
||||
inkscape:cx="16"
|
||||
inkscape:cy="16"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3" />
|
||||
<rect
|
||||
x="2"
|
||||
y="2"
|
||||
width="28"
|
||||
height="28"
|
||||
rx="4"
|
||||
fill="#313244"
|
||||
id="rect1" />
|
||||
<!-- Sketch plane -->
|
||||
<path d="M4 20 L16 12 L28 20 L16 28 Z" fill="#45475a" stroke="#f9e2af" stroke-width="1.5"/>
|
||||
<path
|
||||
d="m 4,17.158621 12,-8.0000003 12,8.0000003 -12,8 z"
|
||||
fill="#45475a"
|
||||
stroke="#f9e2af"
|
||||
stroke-width="1.5"
|
||||
id="path1" />
|
||||
<!-- Grid on plane -->
|
||||
<line x1="10" y1="20" x2="22" y2="20" stroke="#6c7086" stroke-width="0.75"/>
|
||||
<line x1="16" y1="16" x2="16" y2="24" stroke="#6c7086" stroke-width="0.75"/>
|
||||
<!-- Sketch geometry -->
|
||||
<path d="M12 20 L16 16 L20 20 L16 22 Z" fill="none" stroke="#fab387" stroke-width="1.5"/>
|
||||
<!-- Plus sign for "new" -->
|
||||
<circle cx="24" cy="8" r="5" fill="#a6e3a1"/>
|
||||
<path d="M24 5.5 L24 10.5 M21.5 8 L26.5 8" stroke="#1e1e2e" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<circle
|
||||
cx="23.092838"
|
||||
cy="-10.553846"
|
||||
fill="#a6e3a1"
|
||||
id="circle2"
|
||||
style="stroke-width:0.999999"
|
||||
transform="scale(1,-1)"
|
||||
r="5" />
|
||||
<path
|
||||
d="M 23.092837,13.053846 V 8.0538483 m -2.499999,2.4999987 h 5"
|
||||
stroke="#1e1e2e"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
id="path3-7" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 740 B After Width: | Height: | Size: 1.7 KiB |
@@ -1,9 +1,98 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
||||
<circle cx="16" cy="13" r="7" fill="none" stroke="#f9e2af" stroke-width="1.5"/>
|
||||
<path d="M12 20 L12 24 L20 24 L20 20" fill="none" stroke="#fab387" stroke-width="1.5"/>
|
||||
<line x1="13" y1="27" x2="19" y2="27" stroke="#fab387" stroke-width="1.5"/>
|
||||
<line x1="16" y1="6" x2="16" y2="4" stroke="#f9e2af" stroke-width="1.5"/>
|
||||
<line x1="8" y1="8" x2="6" y2="6" stroke="#f9e2af" stroke-width="1.5"/>
|
||||
<line x1="24" y1="8" x2="26" y2="6" stroke="#f9e2af" stroke-width="1.5"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="bulb.svg"
|
||||
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="namedview4"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="18.125"
|
||||
inkscape:cx="12.634483"
|
||||
inkscape:cy="17.048276"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<rect
|
||||
width="32"
|
||||
height="32"
|
||||
rx="4"
|
||||
fill="#313244"
|
||||
id="rect1" />
|
||||
<path
|
||||
d="m 13.398145,19.664706 v 4.206463 h 5.120952 v -4.206463"
|
||||
fill="none"
|
||||
stroke="#fab387"
|
||||
stroke-width="1.23069"
|
||||
id="path1" />
|
||||
<line
|
||||
x1="12.95862"
|
||||
y1="26.492428"
|
||||
x2="18.958622"
|
||||
y2="26.492428"
|
||||
stroke="#fab387"
|
||||
stroke-width="1.5"
|
||||
id="line1"
|
||||
style="stroke-width:1.3;stroke-dasharray:none" />
|
||||
<line
|
||||
x1="12.95862"
|
||||
y1="28.743103"
|
||||
x2="18.958622"
|
||||
y2="28.743103"
|
||||
stroke="#fab387"
|
||||
stroke-width="1.5"
|
||||
id="line1-2"
|
||||
style="stroke-width:1.2;stroke-dasharray:none" />
|
||||
<line
|
||||
x1="16"
|
||||
y1="8.2896547"
|
||||
x2="16"
|
||||
y2="4"
|
||||
stroke="#f9e2af"
|
||||
stroke-width="2.19678"
|
||||
id="line2"
|
||||
style="stroke-width:1.45;stroke-dasharray:none" />
|
||||
<line
|
||||
x1="21.842089"
|
||||
y1="9.2134304"
|
||||
x2="24.801268"
|
||||
y2="6.1714916"
|
||||
stroke="#f9e2af"
|
||||
stroke-width="2.25021"
|
||||
id="line4"
|
||||
style="stroke-width:1.45;stroke-dasharray:none" />
|
||||
<line
|
||||
x1="9.5786028"
|
||||
y1="9.2134304"
|
||||
x2="6.6194234"
|
||||
y2="6.1714916"
|
||||
stroke="#f9e2af"
|
||||
stroke-width="2.25021"
|
||||
id="line4-5"
|
||||
style="stroke-width:1.45;stroke-dasharray:none" />
|
||||
<ellipse
|
||||
cx="15.710345"
|
||||
cy="15.234483"
|
||||
fill="none"
|
||||
stroke="#f9e2af"
|
||||
stroke-width="1.07682"
|
||||
id="circle1"
|
||||
rx="5.0736599"
|
||||
ry="4.977108" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 2.4 KiB |
@@ -1,5 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
||||
<path d="M22 10 A8 8 0 1 1 10 10" fill="none" stroke="#cba6f7" stroke-width="2"/>
|
||||
<path d="M22 6 L22 14 L14 10 Z" fill="#b4befe"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="button_rotate.svg"
|
||||
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="25.632621"
|
||||
inkscape:cx="7.8025576"
|
||||
inkscape:cy="17.282665"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<rect
|
||||
width="32"
|
||||
height="32"
|
||||
rx="4"
|
||||
fill="#313244"
|
||||
id="rect1" />
|
||||
<path
|
||||
d="M22 10 A8 8 0 1 1 10 10"
|
||||
fill="none"
|
||||
stroke="#cba6f7"
|
||||
stroke-width="2"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M 26.440057,9.4371459 19.892123,14.033292 18.569944,5.1872849 Z"
|
||||
fill="#b4befe"
|
||||
id="path2" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 258 B After Width: | Height: | Size: 1.3 KiB |
@@ -1,10 +1,64 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="help-browser.svg"
|
||||
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="36.25"
|
||||
inkscape:cx="16"
|
||||
inkscape:cy="16"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<rect
|
||||
x="2"
|
||||
y="2"
|
||||
width="28"
|
||||
height="28"
|
||||
rx="4"
|
||||
fill="#313244"
|
||||
id="rect1" />
|
||||
<!-- Circle background -->
|
||||
<circle cx="16" cy="16" r="10" fill="#45475a" stroke="#cba6f7" stroke-width="2"/>
|
||||
<circle
|
||||
cx="16.020691"
|
||||
cy="15.889656"
|
||||
r="10"
|
||||
fill="#45475a"
|
||||
stroke="#cba6f7"
|
||||
stroke-width="2"
|
||||
id="circle1" />
|
||||
<!-- Question mark -->
|
||||
<path d="M13 12 C13 9 15 8 17 8 C19 8 21 9.5 21 12 C21 14 19 14.5 17 15.5 L17 18"
|
||||
stroke="#b4befe" stroke-width="2.5" stroke-linecap="round" fill="none"/>
|
||||
<path
|
||||
d="m 12.000002,12.965517 c 0,-2.9999996 2,-3.9999998 4,-3.9999998 2,0 4,1.4999998 4,3.9999998 0,2 -2,2.5 -4,3.5 v 2.5"
|
||||
stroke="#b4befe"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
fill="none"
|
||||
id="path1" />
|
||||
<!-- Dot -->
|
||||
<circle cx="17" cy="22" r="1.5" fill="#b4befe"/>
|
||||
<circle
|
||||
cx="16"
|
||||
cy="22.965519"
|
||||
r="1.5"
|
||||
fill="#b4befe"
|
||||
id="circle2" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 1.6 KiB |
@@ -22,7 +22,7 @@ requirements:
|
||||
- noqt5
|
||||
- python>=3.11,<3.12
|
||||
- qt6-main>=6.8,<6.9
|
||||
- swig
|
||||
- swig >=4.0,<4.4
|
||||
|
||||
- if: linux and x86_64
|
||||
then:
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
#include "Workbench.h"
|
||||
#include "WorkbenchManager.h"
|
||||
#include "WorkbenchSelector.h"
|
||||
#include "OriginSelectorWidget.h"
|
||||
#include "ShortcutManager.h"
|
||||
#include "Tools.h"
|
||||
|
||||
@@ -1470,4 +1471,25 @@ void WindowAction::addTo(QWidget* widget)
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
OriginSelectorAction::OriginSelectorAction(Command* pcCmd, QObject* parent)
|
||||
: Action(pcCmd, parent)
|
||||
{}
|
||||
|
||||
OriginSelectorAction::~OriginSelectorAction() = default;
|
||||
|
||||
void OriginSelectorAction::addTo(QWidget* widget)
|
||||
{
|
||||
if (widget->inherits("QToolBar")) {
|
||||
auto* toolbar = static_cast<QToolBar*>(widget);
|
||||
auto* selector = new OriginSelectorWidget(widget);
|
||||
toolbar->addWidget(selector);
|
||||
}
|
||||
else {
|
||||
// For menus, just add the action
|
||||
widget->addAction(action());
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_Action.cpp"
|
||||
|
||||
@@ -421,6 +421,25 @@ private:
|
||||
Q_DISABLE_COPY(WindowAction)
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Action for origin selector widget in toolbars.
|
||||
* Creates OriginSelectorWidget when added to a toolbar.
|
||||
*/
|
||||
class GuiExport OriginSelectorAction: public Action
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OriginSelectorAction(Command* pcCmd, QObject* parent = nullptr);
|
||||
~OriginSelectorAction() override;
|
||||
void addTo(QWidget* widget) override;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(OriginSelectorAction)
|
||||
};
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
#endif // GUI_ACTION_H
|
||||
|
||||
@@ -1022,6 +1022,7 @@ void Application::createStandardOperations()
|
||||
Gui::CreateStructureCommands();
|
||||
Gui::CreateTestCommands();
|
||||
Gui::CreateLinkCommands();
|
||||
Gui::CreateOriginCommands();
|
||||
}
|
||||
|
||||
void Application::slotNewDocument(const App::Document& Doc, bool isMainDoc)
|
||||
|
||||
@@ -529,6 +529,7 @@ SET(Command_CPP_SRCS
|
||||
CommandFeat.cpp
|
||||
CommandMacro.cpp
|
||||
CommandStd.cpp
|
||||
CommandOrigin.cpp
|
||||
CommandWindow.cpp
|
||||
CommandTest.cpp
|
||||
CommandView.cpp
|
||||
@@ -592,6 +593,7 @@ SET(Dialog_CPP_SRCS
|
||||
Dialogs/DlgObjectSelection.cpp
|
||||
Dialogs/DlgAddProperty.cpp
|
||||
VectorListEditor.cpp
|
||||
OriginManagerDialog.cpp
|
||||
)
|
||||
|
||||
SET(Dialog_HPP_SRCS
|
||||
@@ -634,6 +636,7 @@ SET(Dialog_HPP_SRCS
|
||||
Dialogs/DlgObjectSelection.h
|
||||
Dialogs/DlgAddProperty.h
|
||||
VectorListEditor.h
|
||||
OriginManagerDialog.h
|
||||
)
|
||||
|
||||
SET(Dialog_SRCS
|
||||
@@ -1236,6 +1239,7 @@ SET(Widget_CPP_SRCS
|
||||
ElideCheckBox.cpp
|
||||
FontScaledSVG.cpp
|
||||
SplitButton.cpp
|
||||
OriginSelectorWidget.cpp
|
||||
)
|
||||
SET(Widget_HPP_SRCS
|
||||
ComboLinks.h
|
||||
@@ -1262,6 +1266,7 @@ SET(Widget_HPP_SRCS
|
||||
ElideCheckBox.h
|
||||
FontScaledSVG.h
|
||||
SplitButton.h
|
||||
OriginSelectorWidget.h
|
||||
)
|
||||
SET(Widget_SRCS
|
||||
${Widget_CPP_SRCS}
|
||||
|
||||
@@ -247,6 +247,7 @@ void CreateWindowStdCommands();
|
||||
void CreateStructureCommands();
|
||||
void CreateTestCommands();
|
||||
void CreateLinkCommands();
|
||||
void CreateOriginCommands();
|
||||
|
||||
|
||||
/** The CommandBase class
|
||||
|
||||
@@ -49,7 +49,9 @@
|
||||
#include "Control.h"
|
||||
#include "DockWindowManager.h"
|
||||
#include "FileDialog.h"
|
||||
#include "FileOrigin.h"
|
||||
#include "MainWindow.h"
|
||||
#include "OriginManager.h"
|
||||
#include "Selection.h"
|
||||
#include "Dialogs/DlgObjectSelection.h"
|
||||
#include "Dialogs/DlgProjectInformationImp.h"
|
||||
@@ -95,81 +97,25 @@ void StdCmdOpen::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
|
||||
// fill the list of registered endings
|
||||
QString formatList;
|
||||
const char* supported = QT_TR_NOOP("Supported formats");
|
||||
const char* allFiles = QT_TR_NOOP("All files (*.*)");
|
||||
formatList = QObject::tr(supported);
|
||||
formatList += QLatin1String(" (");
|
||||
|
||||
std::vector<std::string> filetypes = App::GetApplication().getImportTypes();
|
||||
// Make sure FCStd is the very first fileformat
|
||||
auto it = std::ranges::find(filetypes, "FCStd");
|
||||
if (it != filetypes.end()) {
|
||||
filetypes.erase(it);
|
||||
filetypes.insert(filetypes.begin(), "FCStd");
|
||||
}
|
||||
for (it = filetypes.begin(); it != filetypes.end(); ++it) {
|
||||
formatList += QLatin1String(" *.");
|
||||
formatList += QLatin1String(it->c_str());
|
||||
}
|
||||
|
||||
formatList += QLatin1String(");;");
|
||||
|
||||
std::map<std::string, std::string> FilterList = App::GetApplication().getImportFilters();
|
||||
std::map<std::string, std::string>::iterator jt;
|
||||
// Make sure the format name for FCStd is the very first in the list
|
||||
for (jt = FilterList.begin(); jt != FilterList.end(); ++jt) {
|
||||
if (jt->first.find("*.FCStd") != std::string::npos) {
|
||||
formatList += QLatin1String(jt->first.c_str());
|
||||
formatList += QLatin1String(";;");
|
||||
FilterList.erase(jt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (jt = FilterList.begin(); jt != FilterList.end(); ++jt) {
|
||||
formatList += QLatin1String(jt->first.c_str());
|
||||
formatList += QLatin1String(";;");
|
||||
}
|
||||
formatList += QObject::tr(allFiles);
|
||||
|
||||
QString selectedFilter;
|
||||
QStringList fileList = FileDialog::getOpenFileNames(
|
||||
getMainWindow(),
|
||||
QObject::tr("Open Document"),
|
||||
QString(),
|
||||
formatList,
|
||||
&selectedFilter
|
||||
);
|
||||
if (fileList.isEmpty()) {
|
||||
// Delegate to current origin
|
||||
FileOrigin* origin = OriginManager::instance()->currentOrigin();
|
||||
if (!origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// load the files with the associated modules
|
||||
SelectModule::Dict dict = SelectModule::importHandler(fileList, selectedFilter);
|
||||
if (dict.isEmpty()) {
|
||||
QMessageBox::critical(
|
||||
// Check connection for origins that require authentication
|
||||
if (origin->requiresAuthentication() &&
|
||||
origin->connectionState() != ConnectionState::Connected) {
|
||||
QMessageBox::warning(
|
||||
getMainWindow(),
|
||||
qApp->translate("StdCmdOpen", "Cannot Open File"),
|
||||
qApp->translate("StdCmdOpen", "Loading the file %1 is not supported").arg(fileList.front())
|
||||
qApp->translate("StdCmdOpen", "Not Connected"),
|
||||
qApp->translate("StdCmdOpen", "Please connect to %1 before opening files.")
|
||||
.arg(QString::fromStdString(origin->name()))
|
||||
);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
|
||||
|
||||
// Set flag indicating that this load/restore has been initiated by the user (not by a macro)
|
||||
getGuiApplication()->setStatus(Gui::Application::UserInitiatedOpenDocument, true);
|
||||
|
||||
getGuiApplication()->open(it.key().toUtf8(), it.value().toLatin1());
|
||||
|
||||
getGuiApplication()->setStatus(Gui::Application::UserInitiatedOpenDocument, false);
|
||||
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
|
||||
getGuiApplication()->checkPartialRestore(doc);
|
||||
getGuiApplication()->checkRestoreError(doc);
|
||||
}
|
||||
}
|
||||
origin->openDocumentInteractive();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
@@ -715,10 +661,28 @@ StdCmdNew::StdCmdNew()
|
||||
void StdCmdNew::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
QString cmd;
|
||||
cmd = QStringLiteral("App.newDocument()");
|
||||
runCommand(Command::Doc, cmd.toUtf8());
|
||||
doCommand(Command::Gui, "Gui.activeDocument().activeView().viewDefaultOrientation()");
|
||||
|
||||
// Delegate to current origin
|
||||
FileOrigin* origin = OriginManager::instance()->currentOrigin();
|
||||
if (!origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
App::Document* doc = origin->newDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set default view orientation for the new document
|
||||
auto hGrp = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/View"
|
||||
);
|
||||
std::string default_view = hGrp->GetASCII("NewDocumentCameraOrientation", "Top");
|
||||
doCommand(
|
||||
Command::Gui,
|
||||
"Gui.activeDocument().activeView().viewDefaultOrientation('%s',0)",
|
||||
default_view.c_str()
|
||||
);
|
||||
|
||||
ParameterGrp::handle hViewGrp = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/View"
|
||||
@@ -749,12 +713,33 @@ StdCmdSave::StdCmdSave()
|
||||
void StdCmdSave::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"Save\")");
|
||||
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use document's origin for save, not current origin
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
if (!origin) {
|
||||
// Document has no origin yet - use current origin for first save
|
||||
origin = OriginManager::instance()->currentOrigin();
|
||||
}
|
||||
|
||||
if (!origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to save the document
|
||||
if (!origin->saveDocument(doc)) {
|
||||
// If save failed (e.g., no filename), try SaveAs
|
||||
origin->saveDocumentAsInteractive(doc);
|
||||
}
|
||||
}
|
||||
|
||||
bool StdCmdSave::isActive()
|
||||
{
|
||||
return getGuiApplication()->sendHasMsgToActiveView("Save");
|
||||
return App::GetApplication().getActiveDocument() != nullptr;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
@@ -778,12 +763,51 @@ StdCmdSaveAs::StdCmdSaveAs()
|
||||
void StdCmdSaveAs::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"SaveAs\")");
|
||||
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* mgr = OriginManager::instance();
|
||||
FileOrigin* currentOrigin = mgr->currentOrigin();
|
||||
FileOrigin* docOrigin = mgr->originForDocument(doc);
|
||||
|
||||
if (!currentOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine workflow based on document and target origins
|
||||
OriginType currentType = currentOrigin->type();
|
||||
OriginType docType = docOrigin ? docOrigin->type() : OriginType::Local;
|
||||
|
||||
if (docOrigin == currentOrigin || !docOrigin) {
|
||||
// Same origin or new document - standard SaveAs
|
||||
currentOrigin->saveDocumentAsInteractive(doc);
|
||||
}
|
||||
else if (currentType == OriginType::PLM && docType == OriginType::Local) {
|
||||
// Local → PLM: Migration workflow
|
||||
// The PLM origin's saveDocumentAsInteractive should handle this
|
||||
currentOrigin->saveDocumentAsInteractive(doc);
|
||||
}
|
||||
else if (currentType == OriginType::Local && docType == OriginType::PLM) {
|
||||
// PLM → Local: Export workflow
|
||||
// Use local origin to save without PLM tracking
|
||||
currentOrigin->saveDocumentAsInteractive(doc);
|
||||
}
|
||||
else if (currentType == OriginType::PLM && docType == OriginType::PLM) {
|
||||
// PLM → Different PLM: Transfer workflow
|
||||
currentOrigin->saveDocumentAsInteractive(doc);
|
||||
}
|
||||
else {
|
||||
// Default: use current origin
|
||||
currentOrigin->saveDocumentAsInteractive(doc);
|
||||
}
|
||||
}
|
||||
|
||||
bool StdCmdSaveAs::isActive()
|
||||
{
|
||||
return getGuiApplication()->sendHasMsgToActiveView("SaveAs");
|
||||
return App::GetApplication().getActiveDocument() != nullptr;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
||||
277
src/Gui/CommandOrigin.cpp
Normal file
@@ -0,0 +1,277 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2025 Kindred Systems *
|
||||
* *
|
||||
* This file is part of FreeCAD. *
|
||||
* *
|
||||
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU Lesser General Public License as *
|
||||
* published by the Free Software Foundation, either version 2.1 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* FreeCAD 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with FreeCAD. If not, see *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
/**
|
||||
* @file CommandOrigin.cpp
|
||||
* @brief Unified origin commands that work with the current origin
|
||||
*
|
||||
* These commands delegate to the current FileOrigin's extended operations.
|
||||
* They are only active when the current origin supports the required capability.
|
||||
*/
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "BitmapFactory.h"
|
||||
#include "Command.h"
|
||||
#include "Document.h"
|
||||
#include "FileOrigin.h"
|
||||
#include "MainWindow.h"
|
||||
#include "OriginManager.h"
|
||||
|
||||
|
||||
using namespace Gui;
|
||||
|
||||
//===========================================================================
|
||||
// Origin_Commit
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD_A(OriginCmdCommit)
|
||||
|
||||
OriginCmdCommit::OriginCmdCommit()
|
||||
: Command("Origin_Commit")
|
||||
{
|
||||
sGroup = "File";
|
||||
sMenuText = QT_TR_NOOP("&Commit");
|
||||
sToolTipText = QT_TR_NOOP("Commit changes as a new revision");
|
||||
sWhatsThis = "Origin_Commit";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "silo-commit";
|
||||
sAccel = "Ctrl+Shift+C";
|
||||
eType = AlterDoc;
|
||||
}
|
||||
|
||||
void OriginCmdCommit::activated(int /*iMsg*/)
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
if (origin && origin->supportsRevisions()) {
|
||||
origin->commitDocument(doc);
|
||||
}
|
||||
}
|
||||
|
||||
bool OriginCmdCommit::isActive()
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
return origin && origin->supportsRevisions();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Origin_Pull
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD_A(OriginCmdPull)
|
||||
|
||||
OriginCmdPull::OriginCmdPull()
|
||||
: Command("Origin_Pull")
|
||||
{
|
||||
sGroup = "File";
|
||||
sMenuText = QT_TR_NOOP("&Pull");
|
||||
sToolTipText = QT_TR_NOOP("Pull a specific revision from the origin");
|
||||
sWhatsThis = "Origin_Pull";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "silo-pull";
|
||||
sAccel = "Ctrl+Shift+P";
|
||||
eType = AlterDoc;
|
||||
}
|
||||
|
||||
void OriginCmdPull::activated(int /*iMsg*/)
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
if (origin && origin->supportsRevisions()) {
|
||||
origin->pullDocument(doc);
|
||||
}
|
||||
}
|
||||
|
||||
bool OriginCmdPull::isActive()
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
return origin && origin->supportsRevisions();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Origin_Push
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD_A(OriginCmdPush)
|
||||
|
||||
OriginCmdPush::OriginCmdPush()
|
||||
: Command("Origin_Push")
|
||||
{
|
||||
sGroup = "File";
|
||||
sMenuText = QT_TR_NOOP("Pu&sh");
|
||||
sToolTipText = QT_TR_NOOP("Push local changes to the origin");
|
||||
sWhatsThis = "Origin_Push";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "silo-push";
|
||||
sAccel = "Ctrl+Shift+U";
|
||||
eType = AlterDoc;
|
||||
}
|
||||
|
||||
void OriginCmdPush::activated(int /*iMsg*/)
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
if (origin && origin->supportsRevisions()) {
|
||||
origin->pushDocument(doc);
|
||||
}
|
||||
}
|
||||
|
||||
bool OriginCmdPush::isActive()
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
return origin && origin->supportsRevisions();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Origin_Info
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD_A(OriginCmdInfo)
|
||||
|
||||
OriginCmdInfo::OriginCmdInfo()
|
||||
: Command("Origin_Info")
|
||||
{
|
||||
sGroup = "File";
|
||||
sMenuText = QT_TR_NOOP("&Info");
|
||||
sToolTipText = QT_TR_NOOP("Show document information from origin");
|
||||
sWhatsThis = "Origin_Info";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "silo-info";
|
||||
eType = 0;
|
||||
}
|
||||
|
||||
void OriginCmdInfo::activated(int /*iMsg*/)
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
if (origin && origin->supportsPartNumbers()) {
|
||||
origin->showInfo(doc);
|
||||
}
|
||||
}
|
||||
|
||||
bool OriginCmdInfo::isActive()
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
return origin && origin->supportsPartNumbers();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Origin_BOM
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD_A(OriginCmdBOM)
|
||||
|
||||
OriginCmdBOM::OriginCmdBOM()
|
||||
: Command("Origin_BOM")
|
||||
{
|
||||
sGroup = "File";
|
||||
sMenuText = QT_TR_NOOP("&Bill of Materials");
|
||||
sToolTipText = QT_TR_NOOP("Show Bill of Materials for this document");
|
||||
sWhatsThis = "Origin_BOM";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "silo-bom";
|
||||
eType = 0;
|
||||
}
|
||||
|
||||
void OriginCmdBOM::activated(int /*iMsg*/)
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
if (origin && origin->supportsBOM()) {
|
||||
origin->showBOM(doc);
|
||||
}
|
||||
}
|
||||
|
||||
bool OriginCmdBOM::isActive()
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||
return origin && origin->supportsBOM();
|
||||
}
|
||||
|
||||
|
||||
//===========================================================================
|
||||
// Command Registration
|
||||
//===========================================================================
|
||||
|
||||
namespace Gui {
|
||||
|
||||
void CreateOriginCommands()
|
||||
{
|
||||
CommandManager& rcCmdMgr = Application::Instance->commandManager();
|
||||
|
||||
rcCmdMgr.addCommand(new OriginCmdCommit());
|
||||
rcCmdMgr.addCommand(new OriginCmdPull());
|
||||
rcCmdMgr.addCommand(new OriginCmdPush());
|
||||
rcCmdMgr.addCommand(new OriginCmdInfo());
|
||||
rcCmdMgr.addCommand(new OriginCmdBOM());
|
||||
}
|
||||
|
||||
} // namespace Gui
|
||||
@@ -132,6 +132,45 @@ Action* StdCmdWorkbench::createAction()
|
||||
return pcAction;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Std_Origin
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD_AC(StdCmdOrigin)
|
||||
|
||||
StdCmdOrigin::StdCmdOrigin()
|
||||
: Command("Std_Origin")
|
||||
{
|
||||
sGroup = "File";
|
||||
sMenuText = QT_TR_NOOP("&Origin");
|
||||
sToolTipText = QT_TR_NOOP("Select file origin (Local Files, Silo, etc.)");
|
||||
sWhatsThis = "Std_Origin";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "folder";
|
||||
eType = 0;
|
||||
}
|
||||
|
||||
void StdCmdOrigin::activated(int /*iMsg*/)
|
||||
{
|
||||
// Action is handled by OriginSelectorWidget
|
||||
}
|
||||
|
||||
bool StdCmdOrigin::isActive()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Action* StdCmdOrigin::createAction()
|
||||
{
|
||||
Action* pcAction = new OriginSelectorAction(this, getMainWindow());
|
||||
pcAction->setShortcut(QString::fromLatin1(getAccel()));
|
||||
applyCommandData(this->className(), pcAction);
|
||||
if (getPixmap()) {
|
||||
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(getPixmap()));
|
||||
}
|
||||
return pcAction;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Std_RecentFiles
|
||||
//===========================================================================
|
||||
@@ -1057,6 +1096,7 @@ void CreateStdCommands()
|
||||
rcCmdMgr.addCommand(new StdCmdDlgCustomize());
|
||||
rcCmdMgr.addCommand(new StdCmdCommandLine());
|
||||
rcCmdMgr.addCommand(new StdCmdWorkbench());
|
||||
rcCmdMgr.addCommand(new StdCmdOrigin());
|
||||
rcCmdMgr.addCommand(new StdCmdRecentFiles());
|
||||
rcCmdMgr.addCommand(new StdCmdRecentMacros());
|
||||
rcCmdMgr.addCommand(new StdCmdWhatsThis());
|
||||
|
||||
@@ -22,15 +22,22 @@
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/PropertyStandard.h>
|
||||
|
||||
#include "FileOrigin.h"
|
||||
#include "Application.h"
|
||||
#include "BitmapFactory.h"
|
||||
#include "Document.h"
|
||||
#include "Application.h"
|
||||
#include "FileDialog.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
|
||||
namespace Gui {
|
||||
@@ -99,6 +106,86 @@ App::Document* LocalFileOrigin::openDocument(const std::string& identity)
|
||||
return App::GetApplication().openDocument(identity.c_str());
|
||||
}
|
||||
|
||||
App::Document* LocalFileOrigin::openDocumentInteractive()
|
||||
{
|
||||
// Build file filter list for Open dialog
|
||||
QString formatList;
|
||||
const char* supported = QT_TR_NOOP("Supported formats");
|
||||
const char* allFiles = QT_TR_NOOP("All files (*.*)");
|
||||
formatList = QObject::tr(supported);
|
||||
formatList += QLatin1String(" (");
|
||||
|
||||
std::vector<std::string> filetypes = App::GetApplication().getImportTypes();
|
||||
// Make sure FCStd is the very first fileformat
|
||||
auto it = std::find(filetypes.begin(), filetypes.end(), "FCStd");
|
||||
if (it != filetypes.end()) {
|
||||
filetypes.erase(it);
|
||||
filetypes.insert(filetypes.begin(), "FCStd");
|
||||
}
|
||||
for (it = filetypes.begin(); it != filetypes.end(); ++it) {
|
||||
formatList += QLatin1String(" *.");
|
||||
formatList += QLatin1String(it->c_str());
|
||||
}
|
||||
|
||||
formatList += QLatin1String(");;");
|
||||
|
||||
std::map<std::string, std::string> FilterList = App::GetApplication().getImportFilters();
|
||||
// Make sure the format name for FCStd is the very first in the list
|
||||
for (auto jt = FilterList.begin(); jt != FilterList.end(); ++jt) {
|
||||
if (jt->first.find("*.FCStd") != std::string::npos) {
|
||||
formatList += QLatin1String(jt->first.c_str());
|
||||
formatList += QLatin1String(";;");
|
||||
FilterList.erase(jt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& filter : FilterList) {
|
||||
formatList += QLatin1String(filter.first.c_str());
|
||||
formatList += QLatin1String(";;");
|
||||
}
|
||||
formatList += QObject::tr(allFiles);
|
||||
|
||||
QString selectedFilter;
|
||||
QStringList fileList = FileDialog::getOpenFileNames(
|
||||
getMainWindow(),
|
||||
QObject::tr("Open Document"),
|
||||
QString(),
|
||||
formatList,
|
||||
&selectedFilter
|
||||
);
|
||||
if (fileList.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Load the files with the associated modules
|
||||
SelectModule::Dict dict = SelectModule::importHandler(fileList, selectedFilter);
|
||||
if (dict.isEmpty()) {
|
||||
QMessageBox::critical(
|
||||
getMainWindow(),
|
||||
qApp->translate("StdCmdOpen", "Cannot Open File"),
|
||||
qApp->translate("StdCmdOpen", "Loading the file %1 is not supported").arg(fileList.front())
|
||||
);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
App::Document* lastDoc = nullptr;
|
||||
for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
|
||||
// Set flag indicating that this load/restore has been initiated by the user
|
||||
Application::Instance->setStatus(Gui::Application::UserInitiatedOpenDocument, true);
|
||||
|
||||
Application::Instance->open(it.key().toUtf8(), it.value().toLatin1());
|
||||
|
||||
Application::Instance->setStatus(Gui::Application::UserInitiatedOpenDocument, false);
|
||||
|
||||
lastDoc = App::GetApplication().getActiveDocument();
|
||||
|
||||
Application::Instance->checkPartialRestore(lastDoc);
|
||||
Application::Instance->checkRestoreError(lastDoc);
|
||||
}
|
||||
|
||||
return lastDoc;
|
||||
}
|
||||
|
||||
bool LocalFileOrigin::saveDocument(App::Document* doc)
|
||||
{
|
||||
if (!doc) {
|
||||
@@ -125,4 +212,20 @@ bool LocalFileOrigin::saveDocumentAs(App::Document* doc, const std::string& newI
|
||||
return doc->saveAs(newIdentity.c_str());
|
||||
}
|
||||
|
||||
bool LocalFileOrigin::saveDocumentAsInteractive(App::Document* doc)
|
||||
{
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get Gui document for save dialog
|
||||
Gui::Document* guiDoc = Application::Instance->getDocument(doc);
|
||||
if (!guiDoc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use Gui::Document::saveAs() which handles the file dialog
|
||||
return guiDoc->saveAs();
|
||||
}
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
@@ -168,7 +168,7 @@ public:
|
||||
virtual App::Document* newDocument(const std::string& name = "") = 0;
|
||||
|
||||
/**
|
||||
* Open a document by identity.
|
||||
* Open a document by identity (non-interactive).
|
||||
* Local: Opens file at path
|
||||
* PLM: Opens document by UUID (downloads if needed)
|
||||
* @param identity Document identity (path or UUID)
|
||||
@@ -176,9 +176,17 @@ public:
|
||||
*/
|
||||
virtual App::Document* openDocument(const std::string& identity) = 0;
|
||||
|
||||
/**
|
||||
* Open a document interactively (shows dialog).
|
||||
* Local: Shows file picker dialog
|
||||
* PLM: Shows search/browse dialog
|
||||
* @return The opened document or nullptr if cancelled/failed
|
||||
*/
|
||||
virtual App::Document* openDocumentInteractive() = 0;
|
||||
|
||||
/**
|
||||
* Save the document.
|
||||
* Local: Saves to disk
|
||||
* Local: Saves to disk (if path known)
|
||||
* PLM: Saves to disk and syncs with external system
|
||||
* @param doc The document to save
|
||||
* @return true if save succeeded
|
||||
@@ -186,14 +194,21 @@ public:
|
||||
virtual bool saveDocument(App::Document* doc) = 0;
|
||||
|
||||
/**
|
||||
* Save document with new identity.
|
||||
* Local: File picker for new path
|
||||
* PLM: Migration or copy workflow
|
||||
* Save document with new identity (non-interactive).
|
||||
* @param doc The document to save
|
||||
* @param newIdentity New identity (path or part number)
|
||||
* @return true if save succeeded
|
||||
*/
|
||||
virtual bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) = 0;
|
||||
|
||||
/**
|
||||
* Save document interactively (shows dialog).
|
||||
* Local: Shows file picker for new path
|
||||
* PLM: Shows migration or copy workflow dialog
|
||||
* @param doc The document to save
|
||||
* @return true if save succeeded
|
||||
*/
|
||||
virtual bool saveDocumentAsInteractive(App::Document* doc) = 0;
|
||||
//@}
|
||||
|
||||
///@name Extended Operations (PLM-specific, default to no-op)
|
||||
@@ -250,8 +265,10 @@ public:
|
||||
// Document operations
|
||||
App::Document* newDocument(const std::string& name = "") override;
|
||||
App::Document* openDocument(const std::string& identity) override;
|
||||
App::Document* openDocumentInteractive() override;
|
||||
bool saveDocument(App::Document* doc) override;
|
||||
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
|
||||
bool saveDocumentAsInteractive(App::Document* doc) override;
|
||||
};
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentPy.h>
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Interpreter.h>
|
||||
#include <Base/PyObjectBase.h>
|
||||
@@ -41,7 +42,7 @@ void FileOriginPython::addOrigin(const Py::Object& obj)
|
||||
{
|
||||
// Check if already registered
|
||||
if (findOrigin(obj)) {
|
||||
Base::Console().Warning("FileOriginPython: Origin already registered\n");
|
||||
Base::Console().warning("FileOriginPython: Origin already registered\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,7 +51,7 @@ void FileOriginPython::addOrigin(const Py::Object& obj)
|
||||
// Cache the ID immediately for registration
|
||||
origin->_cachedId = origin->callStringMethod("id");
|
||||
if (origin->_cachedId.empty()) {
|
||||
Base::Console().Error("FileOriginPython: Origin must have non-empty id()\n");
|
||||
Base::Console().error("FileOriginPython: Origin must have non-empty id()\n");
|
||||
delete origin;
|
||||
return;
|
||||
}
|
||||
@@ -117,7 +118,7 @@ Py::Object FileOriginPython::callMethod(const char* method, const Py::Tuple& arg
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return Py::None();
|
||||
}
|
||||
@@ -139,7 +140,7 @@ bool FileOriginPython::callBoolMethod(const char* method, bool defaultValue) con
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
@@ -158,7 +159,7 @@ std::string FileOriginPython::callStringMethod(const char* method, const std::st
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
@@ -210,7 +211,7 @@ OriginType FileOriginPython::type() const
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return OriginType::Custom;
|
||||
}
|
||||
@@ -265,7 +266,7 @@ ConnectionState FileOriginPython::connectionState() const
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return ConnectionState::Connected;
|
||||
}
|
||||
@@ -297,7 +298,7 @@ std::string FileOriginPython::documentIdentity(App::Document* doc) const
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -318,7 +319,7 @@ std::string FileOriginPython::documentDisplayId(App::Document* doc) const
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return documentIdentity(doc);
|
||||
}
|
||||
@@ -342,7 +343,7 @@ bool FileOriginPython::ownsDocument(App::Document* doc) const
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -366,7 +367,7 @@ bool FileOriginPython::syncProperties(App::Document* doc)
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -391,7 +392,7 @@ App::Document* FileOriginPython::newDocument(const std::string& name)
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -414,7 +415,7 @@ App::Document* FileOriginPython::openDocument(const std::string& identity)
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -438,7 +439,7 @@ bool FileOriginPython::saveDocument(App::Document* doc)
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -463,7 +464,7 @@ bool FileOriginPython::saveDocumentAs(App::Document* doc, const std::string& new
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -488,7 +489,7 @@ bool FileOriginPython::commitDocument(App::Document* doc)
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -512,7 +513,7 @@ bool FileOriginPython::pullDocument(App::Document* doc)
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -536,7 +537,7 @@ bool FileOriginPython::pushDocument(App::Document* doc)
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -554,7 +555,7 @@ void FileOriginPython::showInfo(App::Document* doc)
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,8 +572,53 @@ void FileOriginPython::showBOM(App::Document* doc)
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.ReportException();
|
||||
e.reportException();
|
||||
}
|
||||
}
|
||||
|
||||
App::Document* FileOriginPython::openDocumentInteractive()
|
||||
{
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
if (_inst.hasAttr("openDocumentInteractive")) {
|
||||
Py::Callable func(_inst.getAttr("openDocumentInteractive"));
|
||||
Py::Object result = func.apply(Py::Tuple());
|
||||
if (!result.isNone()) {
|
||||
if (PyObject_TypeCheck(result.ptr(), &(App::DocumentPy::Type))) {
|
||||
return static_cast<App::DocumentPy*>(result.ptr())->getDocumentPtr();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.reportException();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool FileOriginPython::saveDocumentAsInteractive(App::Document* doc)
|
||||
{
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
if (_inst.hasAttr("saveDocumentAsInteractive")) {
|
||||
Py::Callable func(_inst.getAttr("saveDocumentAsInteractive"));
|
||||
Py::Tuple args(1);
|
||||
args.setItem(0, getDocPyObject(doc));
|
||||
Py::Object result = func.apply(args);
|
||||
if (result.isBoolean()) {
|
||||
return Py::Boolean(result);
|
||||
}
|
||||
if (result.isNumeric()) {
|
||||
return Py::Long(result) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e;
|
||||
e.reportException();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
@@ -115,8 +115,10 @@ public:
|
||||
|
||||
App::Document* newDocument(const std::string& name = "") override;
|
||||
App::Document* openDocument(const std::string& identity) override;
|
||||
App::Document* openDocumentInteractive() override;
|
||||
bool saveDocument(App::Document* doc) override;
|
||||
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
|
||||
bool saveDocumentAsInteractive(App::Document* doc) override;
|
||||
|
||||
bool commitDocument(App::Document* doc) override;
|
||||
bool pullDocument(App::Document* doc) override;
|
||||
|
||||
@@ -185,6 +185,11 @@
|
||||
<file>sel-bbox.svg</file>
|
||||
<file>sel-forward.svg</file>
|
||||
<file>sel-instance.svg</file>
|
||||
<file>silo-bom.svg</file>
|
||||
<file>silo-commit.svg</file>
|
||||
<file>silo-info.svg</file>
|
||||
<file>silo-pull.svg</file>
|
||||
<file>silo-push.svg</file>
|
||||
<file>spaceball_button.svg</file>
|
||||
<file>SpNav-PanLR.svg</file>
|
||||
<file>SpNav-PanUD.svg</file>
|
||||
|
||||
12
src/Gui/Icons/silo-bom.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Outer box -->
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" fill="#313244"/>
|
||||
<!-- List lines (BOM rows) -->
|
||||
<line x1="8" y1="8" x2="18" y2="8" stroke="#89dceb" stroke-width="1.5"/>
|
||||
<line x1="8" y1="12" x2="18" y2="12" stroke="#89dceb" stroke-width="1.5"/>
|
||||
<line x1="8" y1="16" x2="18" y2="16" stroke="#89dceb" stroke-width="1.5"/>
|
||||
<!-- Hierarchy dots -->
|
||||
<circle cx="6" cy="8" r="1" fill="#cba6f7"/>
|
||||
<circle cx="6" cy="12" r="1" fill="#cba6f7"/>
|
||||
<circle cx="6" cy="16" r="1" fill="#cba6f7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 680 B |
8
src/Gui/Icons/silo-commit.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Git commit style -->
|
||||
<circle cx="12" cy="12" r="4" fill="#313244" stroke="#a6e3a1"/>
|
||||
<line x1="12" y1="2" x2="12" y2="8" stroke="#cba6f7"/>
|
||||
<line x1="12" y1="16" x2="12" y2="22" stroke="#cba6f7"/>
|
||||
<!-- Checkmark inside -->
|
||||
<polyline points="9.5 12 11 13.5 14.5 10" stroke="#a6e3a1" stroke-width="1.5" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 493 B |
6
src/Gui/Icons/silo-info.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Info circle -->
|
||||
<circle cx="12" cy="12" r="10" fill="#313244"/>
|
||||
<line x1="12" y1="16" x2="12" y2="12" stroke="#89dceb" stroke-width="2"/>
|
||||
<circle cx="12" cy="8" r="0.5" fill="#89dceb" stroke="#89dceb"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 377 B |
7
src/Gui/Icons/silo-pull.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Cloud -->
|
||||
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" fill="#313244"/>
|
||||
<!-- Download arrow -->
|
||||
<path d="M12 13v5m0 0l-2-2m2 2l2-2" stroke="#89b4fa" stroke-width="2"/>
|
||||
<line x1="12" y1="9" x2="12" y2="13" stroke="#89b4fa" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 428 B |
7
src/Gui/Icons/silo-push.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Cloud -->
|
||||
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" fill="#313244"/>
|
||||
<!-- Upload arrow -->
|
||||
<path d="M12 18v-5m0 0l-2 2m2-2l2 2" stroke="#a6e3a1" stroke-width="2"/>
|
||||
<line x1="12" y1="13" x2="12" y2="9" stroke="#a6e3a1" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 427 B |
@@ -44,7 +44,9 @@
|
||||
#include "Application.h"
|
||||
#include "Document.h"
|
||||
#include "FileDialog.h"
|
||||
#include "FileOrigin.h"
|
||||
#include "MainWindow.h"
|
||||
#include "OriginManager.h"
|
||||
#include "ViewProviderDocumentObject.h"
|
||||
|
||||
|
||||
@@ -522,6 +524,13 @@ QString MDIView::buildWindowTitle() const
|
||||
QString windowTitle;
|
||||
if (auto document = getAppDocument()) {
|
||||
windowTitle.append(QString::fromStdString(document->Label.getStrValue()));
|
||||
|
||||
// Append origin suffix for non-local origins
|
||||
FileOrigin* origin = OriginManager::instance()->originForDocument(document);
|
||||
if (origin && origin->type() != OriginType::Local) {
|
||||
windowTitle.append(QStringLiteral(" [%1]")
|
||||
.arg(QString::fromStdString(origin->nickname())));
|
||||
}
|
||||
}
|
||||
|
||||
return windowTitle;
|
||||
|
||||
@@ -111,20 +111,20 @@ bool OriginManager::registerOrigin(FileOrigin* origin)
|
||||
|
||||
std::string originId = origin->id();
|
||||
if (originId.empty()) {
|
||||
Base::Console().Warning("OriginManager: Cannot register origin with empty ID\n");
|
||||
Base::Console().warning("OriginManager: Cannot register origin with empty ID\n");
|
||||
delete origin;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if ID already in use
|
||||
if (_origins.find(originId) != _origins.end()) {
|
||||
Base::Console().Warning("OriginManager: Origin '%s' already registered\n", originId.c_str());
|
||||
Base::Console().warning("OriginManager: Origin '%s' already registered\n", originId.c_str());
|
||||
delete origin;
|
||||
return false;
|
||||
}
|
||||
|
||||
_origins[originId] = std::unique_ptr<FileOrigin>(origin);
|
||||
Base::Console().Log("OriginManager: Registered origin '%s'\n", originId.c_str());
|
||||
Base::Console().log("OriginManager: Registered origin '%s'\n", originId.c_str());
|
||||
|
||||
signalOriginRegistered(originId);
|
||||
return true;
|
||||
@@ -134,7 +134,7 @@ bool OriginManager::unregisterOrigin(const std::string& id)
|
||||
{
|
||||
// Cannot unregister the built-in local origin
|
||||
if (id == LOCAL_ORIGIN_ID) {
|
||||
Base::Console().Warning("OriginManager: Cannot unregister built-in local origin\n");
|
||||
Base::Console().warning("OriginManager: Cannot unregister built-in local origin\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ bool OriginManager::unregisterOrigin(const std::string& id)
|
||||
}
|
||||
|
||||
_origins.erase(it);
|
||||
Base::Console().Log("OriginManager: Unregistered origin '%s'\n", id.c_str());
|
||||
Base::Console().log("OriginManager: Unregistered origin '%s'\n", id.c_str());
|
||||
|
||||
signalOriginUnregistered(id);
|
||||
return true;
|
||||
@@ -231,6 +231,63 @@ FileOrigin* OriginManager::findOwningOrigin(App::Document* doc) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileOrigin* OriginManager::originForDocument(App::Document* doc) const
|
||||
{
|
||||
if (!doc) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check explicit association first
|
||||
auto it = _documentOrigins.find(doc);
|
||||
if (it != _documentOrigins.end()) {
|
||||
FileOrigin* origin = getOrigin(it->second);
|
||||
if (origin) {
|
||||
return origin;
|
||||
}
|
||||
// Origin was unregistered, clear stale association
|
||||
_documentOrigins.erase(it);
|
||||
}
|
||||
|
||||
// Fall back to ownership detection
|
||||
FileOrigin* owner = findOwningOrigin(doc);
|
||||
if (owner) {
|
||||
// Cache the result
|
||||
_documentOrigins[doc] = owner->id();
|
||||
return owner;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void OriginManager::setDocumentOrigin(App::Document* doc, FileOrigin* origin)
|
||||
{
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string originId = origin ? origin->id() : "";
|
||||
|
||||
if (origin) {
|
||||
_documentOrigins[doc] = originId;
|
||||
} else {
|
||||
_documentOrigins.erase(doc);
|
||||
}
|
||||
|
||||
signalDocumentOriginChanged(doc, originId);
|
||||
}
|
||||
|
||||
void OriginManager::clearDocumentOrigin(App::Document* doc)
|
||||
{
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = _documentOrigins.find(doc);
|
||||
if (it != _documentOrigins.end()) {
|
||||
_documentOrigins.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
FileOrigin* OriginManager::originForNewDocument() const
|
||||
{
|
||||
return currentOrigin();
|
||||
|
||||
@@ -121,6 +121,27 @@ public:
|
||||
*/
|
||||
FileOrigin* findOwningOrigin(App::Document* doc) const;
|
||||
|
||||
/**
|
||||
* Get the origin associated with a document.
|
||||
* First checks explicit association, then uses findOwningOrigin().
|
||||
* @param doc The document to check
|
||||
* @return The document's origin or nullptr if unknown
|
||||
*/
|
||||
FileOrigin* originForDocument(App::Document* doc) const;
|
||||
|
||||
/**
|
||||
* Associate a document with an origin.
|
||||
* @param doc The document
|
||||
* @param origin The origin to associate (nullptr to clear)
|
||||
*/
|
||||
void setDocumentOrigin(App::Document* doc, FileOrigin* origin);
|
||||
|
||||
/**
|
||||
* Clear document origin association (called when document closes).
|
||||
* @param doc The document being closed
|
||||
*/
|
||||
void clearDocumentOrigin(App::Document* doc);
|
||||
|
||||
/**
|
||||
* Get the appropriate origin for a new document.
|
||||
* Returns the current origin.
|
||||
@@ -137,6 +158,8 @@ public:
|
||||
fastsignals::signal<void(const std::string&)> signalOriginUnregistered;
|
||||
/** Emitted when current origin changes */
|
||||
fastsignals::signal<void(const std::string&)> signalCurrentOriginChanged;
|
||||
/** Emitted when a document's origin association changes */
|
||||
fastsignals::signal<void(App::Document*, const std::string&)> signalDocumentOriginChanged;
|
||||
//@}
|
||||
|
||||
protected:
|
||||
@@ -151,6 +174,9 @@ private:
|
||||
static OriginManager* _instance;
|
||||
std::map<std::string, std::unique_ptr<FileOrigin>> _origins;
|
||||
std::string _currentOriginId;
|
||||
|
||||
// Document-to-origin associations (doc -> origin ID)
|
||||
mutable std::map<App::Document*, std::string> _documentOrigins;
|
||||
};
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
251
src/Gui/OriginManagerDialog.cpp
Normal file
@@ -0,0 +1,251 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2025 Kindred Systems *
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library 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 Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "OriginManagerDialog.h"
|
||||
#include "OriginManager.h"
|
||||
#include "FileOrigin.h"
|
||||
#include "BitmapFactory.h"
|
||||
|
||||
|
||||
namespace Gui {
|
||||
|
||||
OriginManagerDialog::OriginManagerDialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setupUi();
|
||||
populateOriginList();
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
OriginManagerDialog::~OriginManagerDialog() = default;
|
||||
|
||||
void OriginManagerDialog::setupUi()
|
||||
{
|
||||
setWindowTitle(tr("Manage File Origins"));
|
||||
setMinimumSize(450, 350);
|
||||
|
||||
auto* mainLayout = new QVBoxLayout(this);
|
||||
|
||||
// Description
|
||||
auto* descLabel = new QLabel(tr("Configure file origins for storing and retrieving documents."));
|
||||
descLabel->setWordWrap(true);
|
||||
mainLayout->addWidget(descLabel);
|
||||
|
||||
// Origin list
|
||||
m_originList = new QListWidget(this);
|
||||
m_originList->setIconSize(QSize(24, 24));
|
||||
m_originList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
connect(m_originList, &QListWidget::itemSelectionChanged,
|
||||
this, &OriginManagerDialog::onOriginSelectionChanged);
|
||||
connect(m_originList, &QListWidget::itemDoubleClicked,
|
||||
this, &OriginManagerDialog::onOriginDoubleClicked);
|
||||
mainLayout->addWidget(m_originList);
|
||||
|
||||
// Action buttons
|
||||
auto* actionLayout = new QHBoxLayout();
|
||||
|
||||
m_addButton = new QPushButton(tr("Add Silo..."));
|
||||
m_addButton->setIcon(BitmapFactory().iconFromTheme("list-add"));
|
||||
connect(m_addButton, &QPushButton::clicked, this, &OriginManagerDialog::onAddSilo);
|
||||
actionLayout->addWidget(m_addButton);
|
||||
|
||||
m_editButton = new QPushButton(tr("Edit..."));
|
||||
m_editButton->setIcon(BitmapFactory().iconFromTheme("document-edit"));
|
||||
connect(m_editButton, &QPushButton::clicked, this, &OriginManagerDialog::onEditOrigin);
|
||||
actionLayout->addWidget(m_editButton);
|
||||
|
||||
m_removeButton = new QPushButton(tr("Remove"));
|
||||
m_removeButton->setIcon(BitmapFactory().iconFromTheme("list-remove"));
|
||||
connect(m_removeButton, &QPushButton::clicked, this, &OriginManagerDialog::onRemoveOrigin);
|
||||
actionLayout->addWidget(m_removeButton);
|
||||
|
||||
actionLayout->addStretch();
|
||||
|
||||
m_defaultButton = new QPushButton(tr("Set as Default"));
|
||||
connect(m_defaultButton, &QPushButton::clicked, this, &OriginManagerDialog::onSetDefault);
|
||||
actionLayout->addWidget(m_defaultButton);
|
||||
|
||||
mainLayout->addLayout(actionLayout);
|
||||
|
||||
// Dialog buttons
|
||||
m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
|
||||
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
mainLayout->addWidget(m_buttonBox);
|
||||
}
|
||||
|
||||
void OriginManagerDialog::populateOriginList()
|
||||
{
|
||||
m_originList->clear();
|
||||
|
||||
auto* mgr = OriginManager::instance();
|
||||
std::string currentId = mgr->currentOriginId();
|
||||
|
||||
for (const std::string& originId : mgr->originIds()) {
|
||||
FileOrigin* origin = mgr->getOrigin(originId);
|
||||
if (!origin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* item = new QListWidgetItem(m_originList);
|
||||
item->setIcon(origin->icon());
|
||||
|
||||
QString displayText = QString::fromStdString(origin->name());
|
||||
|
||||
// Add connection status for remote origins
|
||||
if (origin->requiresAuthentication()) {
|
||||
ConnectionState state = origin->connectionState();
|
||||
switch (state) {
|
||||
case ConnectionState::Connected:
|
||||
displayText += tr(" [Connected]");
|
||||
break;
|
||||
case ConnectionState::Connecting:
|
||||
displayText += tr(" [Connecting...]");
|
||||
break;
|
||||
case ConnectionState::Disconnected:
|
||||
displayText += tr(" [Disconnected]");
|
||||
break;
|
||||
case ConnectionState::Error:
|
||||
displayText += tr(" [Error]");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark default origin
|
||||
if (originId == currentId) {
|
||||
displayText += tr(" (Default)");
|
||||
QFont font = item->font();
|
||||
font.setBold(true);
|
||||
item->setFont(font);
|
||||
}
|
||||
|
||||
item->setText(displayText);
|
||||
item->setData(Qt::UserRole, QString::fromStdString(originId));
|
||||
item->setToolTip(QString::fromStdString(origin->name()));
|
||||
}
|
||||
}
|
||||
|
||||
void OriginManagerDialog::updateButtonStates()
|
||||
{
|
||||
FileOrigin* origin = selectedOrigin();
|
||||
bool hasSelection = (origin != nullptr);
|
||||
bool isLocal = hasSelection && (origin->id() == "local");
|
||||
bool isDefault = hasSelection && (origin->id() == OriginManager::instance()->currentOriginId());
|
||||
|
||||
// Can't edit or remove local origin
|
||||
m_editButton->setEnabled(hasSelection && !isLocal);
|
||||
m_removeButton->setEnabled(hasSelection && !isLocal);
|
||||
m_defaultButton->setEnabled(hasSelection && !isDefault);
|
||||
}
|
||||
|
||||
FileOrigin* OriginManagerDialog::selectedOrigin() const
|
||||
{
|
||||
QListWidgetItem* item = m_originList->currentItem();
|
||||
if (!item) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string originId = item->data(Qt::UserRole).toString().toStdString();
|
||||
return OriginManager::instance()->getOrigin(originId);
|
||||
}
|
||||
|
||||
void OriginManagerDialog::onAddSilo()
|
||||
{
|
||||
// TODO: Open SiloConfigDialog for adding new instance
|
||||
QMessageBox::information(this, tr("Add Silo"),
|
||||
tr("Silo configuration dialog not yet implemented.\n\n"
|
||||
"To add a Silo instance, configure it in the Silo workbench preferences."));
|
||||
}
|
||||
|
||||
void OriginManagerDialog::onEditOrigin()
|
||||
{
|
||||
FileOrigin* origin = selectedOrigin();
|
||||
if (!origin || origin->id() == "local") {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Open SiloConfigDialog for editing
|
||||
QMessageBox::information(this, tr("Edit Origin"),
|
||||
tr("Origin editing not yet implemented.\n\n"
|
||||
"To edit this origin, modify settings in the Silo workbench preferences."));
|
||||
}
|
||||
|
||||
void OriginManagerDialog::onRemoveOrigin()
|
||||
{
|
||||
FileOrigin* origin = selectedOrigin();
|
||||
if (!origin || origin->id() == "local") {
|
||||
return;
|
||||
}
|
||||
|
||||
QString name = QString::fromStdString(origin->name());
|
||||
QMessageBox::StandardButton reply = QMessageBox::question(this,
|
||||
tr("Remove Origin"),
|
||||
tr("Are you sure you want to remove '%1'?\n\n"
|
||||
"This will not delete any files, but you will need to reconfigure "
|
||||
"the connection to use this origin again.").arg(name),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::No);
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
std::string originId = origin->id();
|
||||
OriginManager::instance()->unregisterOrigin(originId);
|
||||
populateOriginList();
|
||||
updateButtonStates();
|
||||
}
|
||||
}
|
||||
|
||||
void OriginManagerDialog::onSetDefault()
|
||||
{
|
||||
FileOrigin* origin = selectedOrigin();
|
||||
if (!origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
OriginManager::instance()->setCurrentOrigin(origin->id());
|
||||
populateOriginList();
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
void OriginManagerDialog::onOriginSelectionChanged()
|
||||
{
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
void OriginManagerDialog::onOriginDoubleClicked(QListWidgetItem* item)
|
||||
{
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string originId = item->data(Qt::UserRole).toString().toStdString();
|
||||
if (originId != "local") {
|
||||
onEditOrigin();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Gui
|
||||
74
src/Gui/OriginManagerDialog.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2025 Kindred Systems *
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library 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 Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef GUI_ORIGINMANAGERDIALOG_H
|
||||
#define GUI_ORIGINMANAGERDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QDialogButtonBox>
|
||||
#include <FCGlobal.h>
|
||||
|
||||
namespace Gui {
|
||||
|
||||
class FileOrigin;
|
||||
|
||||
/**
|
||||
* @brief Dialog for managing file origins
|
||||
*
|
||||
* This dialog allows users to view, add, edit, and remove file origins
|
||||
* (Silo instances). The local filesystem origin cannot be removed.
|
||||
*/
|
||||
class GuiExport OriginManagerDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OriginManagerDialog(QWidget* parent = nullptr);
|
||||
~OriginManagerDialog() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onAddSilo();
|
||||
void onEditOrigin();
|
||||
void onRemoveOrigin();
|
||||
void onSetDefault();
|
||||
void onOriginSelectionChanged();
|
||||
void onOriginDoubleClicked(QListWidgetItem* item);
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void populateOriginList();
|
||||
void updateButtonStates();
|
||||
FileOrigin* selectedOrigin() const;
|
||||
|
||||
QListWidget* m_originList;
|
||||
QPushButton* m_addButton;
|
||||
QPushButton* m_editButton;
|
||||
QPushButton* m_removeButton;
|
||||
QPushButton* m_defaultButton;
|
||||
QDialogButtonBox* m_buttonBox;
|
||||
};
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
#endif // GUI_ORIGINMANAGERDIALOG_H
|
||||
270
src/Gui/OriginSelectorWidget.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2025 Kindred Systems *
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library 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 Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "OriginSelectorWidget.h"
|
||||
#include "OriginManager.h"
|
||||
#include "OriginManagerDialog.h"
|
||||
#include "FileOrigin.h"
|
||||
#include "BitmapFactory.h"
|
||||
|
||||
|
||||
namespace Gui {
|
||||
|
||||
OriginSelectorWidget::OriginSelectorWidget(QWidget* parent)
|
||||
: QToolButton(parent)
|
||||
, m_menu(nullptr)
|
||||
, m_originActions(nullptr)
|
||||
, m_manageAction(nullptr)
|
||||
{
|
||||
setupUi();
|
||||
connectSignals();
|
||||
rebuildMenu();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
OriginSelectorWidget::~OriginSelectorWidget()
|
||||
{
|
||||
disconnectSignals();
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::setupUi()
|
||||
{
|
||||
setPopupMode(QToolButton::InstantPopup);
|
||||
setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
setMinimumWidth(70);
|
||||
setMaximumWidth(120);
|
||||
|
||||
// Create menu
|
||||
m_menu = new QMenu(this);
|
||||
setMenu(m_menu);
|
||||
|
||||
// Create action group for exclusive selection
|
||||
m_originActions = new QActionGroup(this);
|
||||
m_originActions->setExclusive(true);
|
||||
|
||||
// Connect action group to selection handler
|
||||
connect(m_originActions, &QActionGroup::triggered,
|
||||
this, &OriginSelectorWidget::onOriginActionTriggered);
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::connectSignals()
|
||||
{
|
||||
auto* mgr = OriginManager::instance();
|
||||
|
||||
// Connect to OriginManager fastsignals
|
||||
m_connRegistered = mgr->signalOriginRegistered.connect(
|
||||
[this](const std::string& id) { onOriginRegistered(id); }
|
||||
);
|
||||
m_connUnregistered = mgr->signalOriginUnregistered.connect(
|
||||
[this](const std::string& id) { onOriginUnregistered(id); }
|
||||
);
|
||||
m_connChanged = mgr->signalCurrentOriginChanged.connect(
|
||||
[this](const std::string& id) { onCurrentOriginChanged(id); }
|
||||
);
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::disconnectSignals()
|
||||
{
|
||||
m_connRegistered.disconnect();
|
||||
m_connUnregistered.disconnect();
|
||||
m_connChanged.disconnect();
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::onOriginRegistered(const std::string& /*originId*/)
|
||||
{
|
||||
// Rebuild menu to include new origin
|
||||
rebuildMenu();
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::onOriginUnregistered(const std::string& /*originId*/)
|
||||
{
|
||||
// Rebuild menu to remove origin
|
||||
rebuildMenu();
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::onCurrentOriginChanged(const std::string& /*originId*/)
|
||||
{
|
||||
// Update display and menu checkmarks
|
||||
updateDisplay();
|
||||
|
||||
// Update checked state in menu
|
||||
auto* mgr = OriginManager::instance();
|
||||
std::string currentId = mgr->currentOriginId();
|
||||
|
||||
for (QAction* action : m_originActions->actions()) {
|
||||
std::string actionId = action->data().toString().toStdString();
|
||||
action->setChecked(actionId == currentId);
|
||||
}
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::onOriginActionTriggered(QAction* action)
|
||||
{
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string originId = action->data().toString().toStdString();
|
||||
auto* mgr = OriginManager::instance();
|
||||
|
||||
// Check if origin requires connection
|
||||
FileOrigin* origin = mgr->getOrigin(originId);
|
||||
if (origin && origin->requiresAuthentication()) {
|
||||
ConnectionState state = origin->connectionState();
|
||||
if (state == ConnectionState::Disconnected || state == ConnectionState::Error) {
|
||||
// Try to connect
|
||||
if (!origin->connect()) {
|
||||
// Connection failed - don't switch
|
||||
// Revert the checkmark to current origin
|
||||
std::string currentId = mgr->currentOriginId();
|
||||
for (QAction* a : m_originActions->actions()) {
|
||||
a->setChecked(a->data().toString().toStdString() == currentId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mgr->setCurrentOrigin(originId);
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::onManageOriginsClicked()
|
||||
{
|
||||
OriginManagerDialog dialog(this);
|
||||
dialog.exec();
|
||||
|
||||
// Refresh the menu in case origins changed
|
||||
rebuildMenu();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::updateDisplay()
|
||||
{
|
||||
FileOrigin* origin = OriginManager::instance()->currentOrigin();
|
||||
if (!origin) {
|
||||
setText(tr("No Origin"));
|
||||
setIcon(QIcon());
|
||||
setToolTip(QString());
|
||||
return;
|
||||
}
|
||||
|
||||
setText(QString::fromStdString(origin->nickname()));
|
||||
setIcon(iconForOrigin(origin));
|
||||
setToolTip(QString::fromStdString(origin->name()));
|
||||
}
|
||||
|
||||
void OriginSelectorWidget::rebuildMenu()
|
||||
{
|
||||
m_menu->clear();
|
||||
|
||||
// Remove old actions from action group
|
||||
for (QAction* action : m_originActions->actions()) {
|
||||
m_originActions->removeAction(action);
|
||||
}
|
||||
|
||||
auto* mgr = OriginManager::instance();
|
||||
std::string currentId = mgr->currentOriginId();
|
||||
|
||||
// Add origin entries
|
||||
for (const std::string& originId : mgr->originIds()) {
|
||||
FileOrigin* origin = mgr->getOrigin(originId);
|
||||
if (!origin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QAction* action = m_menu->addAction(
|
||||
iconForOrigin(origin),
|
||||
QString::fromStdString(origin->nickname())
|
||||
);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(originId == currentId);
|
||||
action->setData(QString::fromStdString(originId));
|
||||
action->setToolTip(QString::fromStdString(origin->name()));
|
||||
|
||||
m_originActions->addAction(action);
|
||||
}
|
||||
|
||||
// Add separator and manage action
|
||||
m_menu->addSeparator();
|
||||
|
||||
m_manageAction = m_menu->addAction(
|
||||
BitmapFactory().iconFromTheme("preferences-system"),
|
||||
tr("Manage Origins...")
|
||||
);
|
||||
connect(m_manageAction, &QAction::triggered,
|
||||
this, &OriginSelectorWidget::onManageOriginsClicked);
|
||||
}
|
||||
|
||||
QIcon OriginSelectorWidget::iconForOrigin(FileOrigin* origin) const
|
||||
{
|
||||
if (!origin) {
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
QIcon baseIcon = origin->icon();
|
||||
|
||||
// For origins that require authentication, overlay connection status
|
||||
if (origin->requiresAuthentication()) {
|
||||
ConnectionState state = origin->connectionState();
|
||||
|
||||
switch (state) {
|
||||
case ConnectionState::Connected:
|
||||
// No overlay needed - use base icon
|
||||
break;
|
||||
|
||||
case ConnectionState::Connecting:
|
||||
// TODO: Animated connecting indicator
|
||||
break;
|
||||
|
||||
case ConnectionState::Disconnected:
|
||||
// Overlay disconnected indicator
|
||||
{
|
||||
QPixmap overlay = BitmapFactory().pixmapFromSvg(
|
||||
"dagViewFail", QSizeF(8, 8));
|
||||
if (!overlay.isNull()) {
|
||||
baseIcon = BitmapFactoryInst::mergePixmap(
|
||||
baseIcon, overlay, BitmapFactoryInst::BottomRight);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ConnectionState::Error:
|
||||
// Overlay error indicator
|
||||
{
|
||||
QPixmap overlay = BitmapFactory().pixmapFromSvg(
|
||||
"Warning", QSizeF(8, 8));
|
||||
if (!overlay.isNull()) {
|
||||
baseIcon = BitmapFactoryInst::mergePixmap(
|
||||
baseIcon, overlay, BitmapFactoryInst::BottomRight);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return baseIcon;
|
||||
}
|
||||
|
||||
} // namespace Gui
|
||||
95
src/Gui/OriginSelectorWidget.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2025 Kindred Systems *
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library 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 Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef GUI_ORIGINSELECTORWIDGET_H
|
||||
#define GUI_ORIGINSELECTORWIDGET_H
|
||||
|
||||
#include <QToolButton>
|
||||
#include <QMenu>
|
||||
#include <QActionGroup>
|
||||
#include <fastsignals/signal.h>
|
||||
#include <FCGlobal.h>
|
||||
|
||||
namespace Gui {
|
||||
|
||||
class FileOrigin;
|
||||
|
||||
/**
|
||||
* @brief Toolbar widget for selecting the current file origin
|
||||
*
|
||||
* OriginSelectorWidget displays the currently selected origin and provides
|
||||
* a dropdown menu to switch between available origins (Local Files, Silo
|
||||
* instances, etc.).
|
||||
*
|
||||
* Visual design:
|
||||
* Collapsed (toolbar state):
|
||||
* ┌──────────────────┐
|
||||
* │ ☁️ Work ▼ │ ~70-100px wide
|
||||
* └──────────────────┘
|
||||
*
|
||||
* Expanded (dropdown open):
|
||||
* ┌──────────────────┐
|
||||
* │ ✓ ☁️ Work │ ← Current selection (checkmark)
|
||||
* │ ☁️ Prod │
|
||||
* │ 📁 Local │
|
||||
* ├──────────────────┤
|
||||
* │ ⚙️ Manage... │ ← Opens config dialog
|
||||
* └──────────────────┘
|
||||
*/
|
||||
class GuiExport OriginSelectorWidget : public QToolButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OriginSelectorWidget(QWidget* parent = nullptr);
|
||||
~OriginSelectorWidget() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onOriginActionTriggered(QAction* action);
|
||||
void onManageOriginsClicked();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void connectSignals();
|
||||
void disconnectSignals();
|
||||
|
||||
void onOriginRegistered(const std::string& originId);
|
||||
void onOriginUnregistered(const std::string& originId);
|
||||
void onCurrentOriginChanged(const std::string& originId);
|
||||
|
||||
void updateDisplay();
|
||||
void rebuildMenu();
|
||||
QIcon iconForOrigin(FileOrigin* origin) const;
|
||||
|
||||
QMenu* m_menu;
|
||||
QActionGroup* m_originActions;
|
||||
QAction* m_manageAction;
|
||||
|
||||
// Signal connections
|
||||
fastsignals::scoped_connection m_connRegistered;
|
||||
fastsignals::scoped_connection m_connUnregistered;
|
||||
fastsignals::scoped_connection m_connChanged;
|
||||
};
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
#endif // GUI_ORIGINSELECTORWIDGET_H
|
||||
@@ -1142,6 +1142,28 @@ Gui--WorkbenchComboBox::drop-down {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
/* Origin Selector */
|
||||
Gui--OriginSelectorWidget {
|
||||
background-color: #313244;
|
||||
color: #cdd6f4;
|
||||
border: 1px solid #45475a;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
min-width: 70px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
Gui--OriginSelectorWidget:hover {
|
||||
border-color: #585b70;
|
||||
background-color: #45475a;
|
||||
}
|
||||
|
||||
Gui--OriginSelectorWidget::menu-indicator {
|
||||
subcontrol-origin: padding;
|
||||
subcontrol-position: center right;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
/* Task Panel */
|
||||
QSint--ActionGroup {
|
||||
background-color: #313244;
|
||||
|
||||
@@ -1163,6 +1163,28 @@ Gui--WorkbenchComboBox::drop-down {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
/* Origin Selector */
|
||||
Gui--OriginSelectorWidget {
|
||||
background-color: #313244;
|
||||
color: #cdd6f4;
|
||||
border: 1px solid #45475a;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
min-width: 70px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
Gui--OriginSelectorWidget:hover {
|
||||
border-color: #585b70;
|
||||
background-color: #45475a;
|
||||
}
|
||||
|
||||
Gui--OriginSelectorWidget::menu-indicator {
|
||||
subcontrol-origin: padding;
|
||||
subcontrol-position: center right;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
/* Task Panel */
|
||||
QSint--ActionGroup {
|
||||
background-color: #313244;
|
||||
|
||||
@@ -682,7 +682,10 @@ MenuItem* StdWorkbench::setupMenuBar() const
|
||||
file->setCommand("&File");
|
||||
*file << "Std_New" << "Std_Open" << "Std_RecentFiles" << "Separator" << "Std_CloseActiveWindow"
|
||||
<< "Std_CloseAllWindows" << "Separator" << "Std_Save" << "Std_SaveAs"
|
||||
<< "Std_SaveCopy" << "Std_SaveAll" << "Std_Revert" << "Separator" << "Std_Import"
|
||||
<< "Std_SaveCopy" << "Std_SaveAll" << "Std_Revert"
|
||||
<< "Separator" << "Origin_Commit" << "Origin_Pull" << "Origin_Push"
|
||||
<< "Origin_Info" << "Origin_BOM"
|
||||
<< "Separator" << "Std_Import"
|
||||
<< "Std_Export" << "Std_MergeProjects" << "Std_ProjectInfo"
|
||||
<< "Separator" << "Std_Print" << "Std_PrintPreview" << "Std_PrintPdf"
|
||||
<< "Separator" << "Std_Quit";
|
||||
@@ -834,7 +837,13 @@ ToolBarItem* StdWorkbench::setupToolBars() const
|
||||
// File
|
||||
auto file = new ToolBarItem(root);
|
||||
file->setCommand("File");
|
||||
*file << "Std_New" << "Std_Open" << "Std_Save";
|
||||
*file << "Std_Origin" << "Std_New" << "Std_Open" << "Std_Save";
|
||||
|
||||
// Origin Tools (PLM operations - commands auto-disable when not applicable)
|
||||
auto originTools = new ToolBarItem(root);
|
||||
originTools->setCommand("Origin Tools");
|
||||
*originTools << "Origin_Commit" << "Origin_Pull" << "Origin_Push"
|
||||
<< "Separator" << "Origin_Info" << "Origin_BOM";
|
||||
|
||||
// Edit
|
||||
auto edit = new ToolBarItem(root);
|
||||
|
||||
@@ -16,7 +16,7 @@ def setup_kindred_addons():
|
||||
# Define built-in addons with their paths relative to mods/
|
||||
addons = [
|
||||
("ztools", "ztools/ztools"), # mods/ztools/ztools/
|
||||
("silo", "silo/pkg/freecad"), # mods/silo/pkg/freecad/
|
||||
("silo", "silo/freecad"), # mods/silo/freecad/
|
||||
]
|
||||
|
||||
for name, subpath in addons:
|
||||
|
||||
@@ -15,7 +15,7 @@ def setup_kindred_workbenches():
|
||||
|
||||
addons = [
|
||||
("ztools", "ztools/ztools"),
|
||||
("silo", "silo/pkg/freecad"),
|
||||
("silo", "silo/freecad"),
|
||||
]
|
||||
|
||||
for name, subpath in addons:
|
||||
|
||||