Improved SweetHome 3D importer (#17165)

* Fixed access to Addon::Metadat::Url attributes

* Fixed invalid vector in distance calculation

* SH3D importer initial version

* Cleaned up and added baseboard

* Make sure notificationWidth is properly enabled/disabled

* Added furnitureGroup, color prefs, light weight mesh object

* Allow to join walls

* Prepare to join wall, improved status feedback

* Removing trailing white space

* SH3D importer initial version

* Cleaned up and added baseboard

* Make sure notificationWidth is properly enabled/disabled

* Added furnitureGroup, color prefs, light weight mesh object

* Allow to join walls

* Prepare to join wall, improved status feedback

* Removing trailing white space

* fixing tipo, 80 charlines, etc

* Adding a basic import test

* Work in local but fails on pipeline. Commenting out.

* Adding testcase and join wall path

* Use ruled surface to fix failed sweep

* Fixed faces order when joining walls

* Fixed missing sample importer file

* Allow to change pref just before import

* Fixed excessive debug output

* Allow to import from string. Test use embedded string

* Fixed tipo in comment

Co-authored-by: João Matos <joao@tritao.eu>

* Improved door import

Also added coloring for wall section's edges when debuging

* Moved debug init script to FreeCAD-Docker repo

---------

Co-authored-by: João Matos <joao@tritao.eu>
Co-authored-by: Yorik van Havre <yorik@uncreated.net>
This commit is contained in:
JULIEN MASNADA
2024-12-20 09:46:39 +01:00
committed by GitHub
parent 0609fa2335
commit bcdfcce95c
14 changed files with 2247 additions and 149 deletions

View File

@@ -59,12 +59,14 @@ SET(importers_SRCS
importers/importWebGL.py
importers/importJSON.py
importers/importSH3D.py
importers/importSH3DHelper.py
importers/import3DS.py
importers/importSHP.py
importers/importGBXML.py
importers/exportIFCStructuralTools.py
importers/exportIFC.py
importers/exportIFCHelper.py
importers/samples/Sample.sh3d
)
SET(Dice3DS_SRCS
@@ -198,6 +200,10 @@ SET(BIMGuiIcon_SVG
Resources/icons/BIMWorkbench.svg
)
SET(ImportersSample_Files
importers/samples/Sample.sh3d
)
ADD_CUSTOM_TARGET(BIM ALL
SOURCES ${Arch_SRCS}
${Arch_QRC_SRCS}
@@ -210,6 +216,10 @@ ADD_CUSTOM_TARGET(BIM ALL
${BIMGuiIcon_SVG}
)
ADD_CUSTOM_TARGET(ImporterPythonTestData ALL
SOURCES ${ImportersSample_Files}
)
fc_copy_sources(BIM "${CMAKE_BINARY_DIR}/Mod/BIM" ${Arch_SRCS})
fc_copy_sources(BIM "${CMAKE_BINARY_DIR}/Mod/BIM" ${Dice3DS_SRCS})
fc_copy_sources(BIM "${CMAKE_BINARY_DIR}/Mod/BIM" ${importers_SRCS})
@@ -223,6 +233,12 @@ fc_target_copy_resource(BIM
${Arch_presets}
)
fc_target_copy_resource(ImporterPythonTestData
${CMAKE_SOURCE_DIR}/src/Mod/BIM
${CMAKE_BINARY_DIR}/Mod/BIM
${ImportersSample_Files})
IF (BUILD_GUI)
fc_target_copy_resource(BIM
${CMAKE_CURRENT_BINARY_DIR}

View File

@@ -33,5 +33,5 @@ FreeCAD.addExportType("JavaScript Object Notation (*.json)","importers.importJSO
FreeCAD.addImportType("Collada (*.dae *.DAE)","importers.importDAE")
FreeCAD.addExportType("Collada (*.dae)","importers.importDAE")
FreeCAD.addImportType("3D Studio mesh (*.3ds *.3DS)","importers.import3DS")
FreeCAD.addImportType("SweetHome3D XML export (*.zip *.ZIP)","importers.importSH3D")
FreeCAD.addImportType("SweetHome3D (*.sh3d)","importers.importSH3D")
FreeCAD.addImportType("Shapefile (*.shp *.SHP)","importers.importSHP")

View File

@@ -677,6 +677,7 @@ t = QT_TRANSLATE_NOOP("QObject", "Import-Export")
FreeCADGui.addPreferencePage(":/ui/preferences-ifc.ui", t)
FreeCADGui.addPreferencePage(":/ui/preferences-ifc-export.ui", t)
FreeCADGui.addPreferencePage(":/ui/preferences-dae.ui", t)
FreeCADGui.addPreferencePage(":/ui/preferences-sh3d-import.ui", t)
# Add unit tests
FreeCAD.__unit_test__ += ["TestArch"]

View File

@@ -268,6 +268,7 @@
<file>ui/preferences-dae.ui</file>
<file>ui/preferences-ifc-export.ui</file>
<file>ui/preferences-ifc.ui</file>
<file>ui/preferences-sh3d-import.ui</file>
<file>ui/preferencesNativeIFC.ui</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,337 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui::Dialog::DlgSettingsArch</class>
<widget class="QWidget" name="Gui::Dialog::DlgSettingsArch">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>555</width>
<height>729</height>
</rect>
</property>
<property name="windowTitle">
<string>SH3D import</string>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>9</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>General options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_1">
<property name="text">
<string>Show this dialog when importing</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dShowDialog</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_2">
<property name="toolTip">
<string>Shows verbose debug messages during import of SH3D files in the Report
view panel. Log level message must be allowed for this setting to have an effect.</string>
</property>
<property name="text">
<string>Show debug messages</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dDebug</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Import options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_3">
<property name="toolTip">
<string>Whether to import the model's doors and windows</string>
</property>
<property name="text">
<string>Doors and Windows</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dImportDoorsAndWindows</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_4">
<property name="toolTip">
<string>Whether to import the model's furnitures</string>
</property>
<property name="text">
<string>Furnitures</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dImportFurnitures</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_5">
<property name="toolTip">
<string>Whether to create Arch::Equipment for each furniture defined in the model (NOTE: this can negatively impact the import process speed)</string>
</property>
<property name="text">
<string>Create Arch::Equipment</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dCreateArchEquipment</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_6">
<property name="toolTip">
<string>Whether to join the different Arch::Wall together</string>
</property>
<property name="text">
<string>Join Arch::Wall</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dJoinArchWall</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_7">
<property name="toolTip">
<string>Whether to import the model's lights. Note that you also need to import
the model's furnitures.</string>
</property>
<property name="text">
<string>Lights (requires Render)</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dImportLights</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_8">
<property name="toolTip">
<string>Whether to import the model's cameras</string>
</property>
<property name="text">
<string>Cameras (requires Render)</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dImportCameras</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_9">
<property name="toolTip">
<string>Merge imported element with existing FC object</string>
</property>
<property name="text">
<string>Merge into existing document</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dMerge</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" columnstretch="2,1,0">
<item row="0" column="0">
<widget class="QLabel" name="label1">
<property name="text">
<string>Default Floor Color</string>
</property>
<property name="buddy">
<cstring>sh3dDefaultFloorColor</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::PrefColorButton" name="ArchSH3DDefaultFloorColor">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>This color might be used when a room does not define its own color.</string>
</property>
<property name="color" stdset="0">
<color>
<red>150</red>
<green>169</green>
<blue>186</blue>
</color>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dDefaultFloorColor</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" columnstretch="2,1,0">
<item row="0" column="0">
<widget class="QLabel" name="label1">
<property name="text">
<string>Default Ceiling Color</string>
</property>
<property name="buddy">
<cstring>sh3dDefaultCeilingColor</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::PrefColorButton" name="ArchSH3DDefaultCeilingColor">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>This color might be used when a room does not define its own color.</string>
</property>
<property name="color" stdset="0">
<color>
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dDefaultCeilingColor</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_10">
<property name="toolTip">
<string>Create a default Render project with the newly Site</string>
</property>
<property name="text">
<string>Create Render Project (requires Render)</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dCreateRenderProject</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_11">
<property name="toolTip">
<string>Fit view while importing.</string>
</property>
<property name="text">
<string>Fit view while importing</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>sh3dFitView</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11" />
<pixmapfunction>qPixmapFromMimeSource</pixmapfunction>
<customwidgets>
<customwidget>
<class>Gui::PrefSpinBox</class>
<extends>QSpinBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefCheckBox</class>
<extends>QCheckBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefComboBox</class>
<extends>QComboBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefLineEdit</class>
<extends>QLineEdit</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
</customwidgets>
<resources />
<connections />
</ui>

View File

@@ -759,6 +759,20 @@ class ArchTest(unittest.TestCase):
App.ActiveDocument.recompute()
assert wall.Visibility
def testImportSH3D(self):
"""Import a SweetHome 3D file
"""
operation = "importers.importSH3D"
_msg(" Test '{}'".format(operation))
import BIM.importers.importSH3DHelper
importer = BIM.importers.importSH3DHelper.SH3DImporter(None)
importer.import_sh3d_from_string(SH3D_HOME)
assert App.ActiveDocument.Project
assert App.ActiveDocument.Site
assert App.ActiveDocument.BuildingPart.Label == "Building"
assert App.ActiveDocument.BuildingPart001.Label == "Level"
assert App.ActiveDocument.Wall
def testViewGeneration(self):
"""Tests the whole TD view generation workflow"""
@@ -804,8 +818,77 @@ class ArchTest(unittest.TestCase):
view.Y = "15cm"
App.ActiveDocument.recompute()
assert True
def tearDown(self):
App.closeDocument("ArchTest")
pass
SH3D_HOME = """<?xml version='1.0'?>
<home version='7200' name='0-JoinWall.sh3d' camera='topCamera' wallHeight='250.0'>
<property name='com.eteks.sweethome3d.SweetHome3D.CatalogPaneDividerLocation' value='327'/>
<property name='com.eteks.sweethome3d.SweetHome3D.ColumnWidths' value='100,84,82,85,84'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameHeight' value='576'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameMaximized' value='true'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameWidth' value='1092'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameX' value='50'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameY' value='87'/>
<property name='com.eteks.sweethome3d.SweetHome3D.MainPaneDividerLocation' value='441'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanPaneDividerLocation' value='263'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanScale' value='0.21343713'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanViewportX' value='0'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanViewportY' value='0'/>
<property name='com.eteks.sweethome3d.SweetHome3D.ScreenHeight' value='720'/>
<property name='com.eteks.sweethome3d.SweetHome3D.ScreenWidth' value='1366'/>
<furnitureVisibleProperty name='NAME'/>
<furnitureVisibleProperty name='WIDTH'/>
<furnitureVisibleProperty name='DEPTH'/>
<furnitureVisibleProperty name='HEIGHT'/>
<furnitureVisibleProperty name='VISIBLE'/>
<environment groundColor='FFB78744' skyColor='00CCE4FC' lightColor='00D0D0D0' ceillingLightColor='00D0D0D0' photoWidth='400' photoHeight='300' photoAspectRatio='VIEW_3D_RATIO' photoQuality='0' videoWidth='320' videoAspectRatio='RATIO_4_3' videoQuality='0' videoFrameRate='25'>
<texture attribute='skyTexture' name='Cloudy' creator='eTeks' catalogId='eTeks#cloudy' width='100.0' height='27.6' image='0'/>
</environment>
<compass x='-100.0' y='50.0' diameter='100.0' northDirection='0.0' longitude='-0.06428629' latitude='0.70511305' timeZone='Europe/Madrid'/>
<observerCamera attribute='observerCamera' lens='PINHOLE' x='50.0' y='50.0' z='170.0' yaw='5.4977875' pitch='0.19634955' fieldOfView='1.0995575' time='1729080000000'/>
<camera attribute='topCamera' lens='PINHOLE' x='1304.082' y='1936.5889' z='1526.6199' yaw='8.98363' pitch='0.7049999' fieldOfView='1.0995575' time='1729080000000'/>
<wall id='wall0' wallAtEnd='wall1' xStart='0.0' yStart='0.0' xEnd='100.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall1' wallAtStart='wall0' wallAtEnd='wall2' xStart='100.0' yStart='0.0' xEnd='200.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall2' wallAtStart='wall1' xStart='200.0' yStart='0.0' xEnd='300.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall3' wallAtEnd='wall4' xStart='0.0' yStart='50.0' xEnd='100.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall4' wallAtStart='wall3' wallAtEnd='wall5' xStart='100.0' yStart='100.0' xEnd='200.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall5' wallAtStart='wall4' xStart='200.0' yStart='100.0' xEnd='300.0' yEnd='50.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall6' wallAtEnd='wall7' xStart='0.0' yStart='200.0' xEnd='100.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall7' wallAtStart='wall6' wallAtEnd='wall8' xStart='100.0' yStart='300.0' xEnd='200.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall8' wallAtStart='wall7' xStart='200.0' yStart='300.0' xEnd='300.0' yEnd='200.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall9' wallAtEnd='wall10' xStart='100.0' yStart='400.0' xEnd='100.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall10' wallAtStart='wall9' wallAtEnd='wall11' xStart='100.0' yStart='500.0' xEnd='200.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall11' wallAtStart='wall10' xStart='200.0' yStart='500.0' xEnd='200.0' yEnd='400.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall12' wallAtStart='wall14' wallAtEnd='wall13' xStart='150.0' yStart='600.0' xEnd='100.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall13' wallAtStart='wall12' wallAtEnd='wall14' xStart='100.0' yStart='700.0' xEnd='200.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall14' wallAtStart='wall13' wallAtEnd='wall12' xStart='200.0' yStart='700.0' xEnd='150.0' yEnd='600.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall15' wallAtEnd='wall16' xStart='400.0' yStart='150.0' xEnd='500.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall16' wallAtStart='wall15' wallAtEnd='wall17' xStart='500.0' yStart='100.0' xEnd='600.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall17' wallAtStart='wall16' xStart='600.0' yStart='100.0' xEnd='700.0' yEnd='50.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall18' wallAtEnd='wall19' xStart='400.0' yStart='400.0' xEnd='500.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall19' wallAtStart='wall18' wallAtEnd='wall20' xStart='500.0' yStart='300.0' xEnd='600.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall20' wallAtStart='wall19' xStart='600.0' yStart='300.0' xEnd='700.0' yEnd='200.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall21' wallAtEnd='wall22' xStart='400.0' yStart='600.0' xEnd='400.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall22' wallAtStart='wall21' wallAtEnd='wall23' xStart='400.0' yStart='500.0' xEnd='600.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall23' wallAtStart='wall22' xStart='600.0' yStart='500.0' xEnd='600.0' yEnd='400.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall24' wallAtEnd='wall25' xStart='600.0' yStart='800.0' xEnd='500.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall25' wallAtStart='wall24' wallAtEnd='wall26' xStart='500.0' yStart='700.0' xEnd='600.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall26' wallAtStart='wall25' xStart='600.0' yStart='700.0' xEnd='500.0' yEnd='600.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall27' wallAtStart='wall30' wallAtEnd='wall28' xStart='800.0' yStart='0.0' xEnd='1000.0' yEnd='0.0' height='250.0' thickness='10.0' arcExtent='1.0471976' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall28' wallAtStart='wall27' wallAtEnd='wall29' xStart='1000.0' yStart='0.0' xEnd='1000.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall29' wallAtStart='wall28' wallAtEnd='wall30' xStart='1000.0' yStart='100.0' xEnd='800.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall30' wallAtStart='wall29' wallAtEnd='wall27' xStart='800.0' yStart='100.0' xEnd='800.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall31' wallAtEnd='wall32' xStart='800.0' yStart='400.0' xEnd='1000.0' yEnd='200.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall32' wallAtStart='wall31' wallAtEnd='wall33' xStart='1000.0' yStart='200.0' xEnd='1200.0' yEnd='400.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall33' wallAtStart='wall32' wallAtEnd='wall34' xStart='1200.0' yStart='400.0' xEnd='1000.0' yEnd='600.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall34' wallAtStart='wall33' xStart='1000.0' yStart='600.0' xEnd='800.0' yEnd='400.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall35' wallAtEnd='wall36' xStart='800.0' yStart='800.0' xEnd='900.0' yEnd='900.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall36' wallAtStart='wall35' wallAtEnd='wall37' xStart='900.0' yStart='900.0' xEnd='1000.0' yEnd='800.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall37' wallAtStart='wall36' wallAtEnd='wall38' xStart='1000.0' yStart='800.0' xEnd='900.0' yEnd='700.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall38' wallAtStart='wall37' xStart='900.0' yStart='700.0' xEnd='800.0' yEnd='800.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
</home>
"""

View File

@@ -23,18 +23,13 @@ __title__ = "FreeCAD SweetHome3D Importer"
__author__ = "Yorik van Havre"
__url__ = "https://www.freecad.org"
import math
import os
import tempfile
import xml.sax
import zipfile
from builtins import open as pyopen
import FreeCAD
import Arch
import Draft
import Mesh
import Part
from FreeCAD import Base
## @package importSH3D
# \ingroup ARCH
@@ -69,140 +64,16 @@ def insert(filename,docname):
def read(filename):
"reads the file and creates objects in the active document"
z = zipfile.ZipFile(filename)
homexml = z.read("Home.xml")
handler = SH3DHandler(z)
xml.sax.parseString(homexml,handler)
import BIM.importers.importSH3DHelper
if DEBUG:
from importlib import reload
reload(BIM.importers.importSH3DHelper)
pi = Base.ProgressIndicator()
try:
importer = BIM.importers.importSH3DHelper.SH3DImporter(pi)
importer.import_sh3d_from_filename(filename)
finally:
pi.stop()
FreeCAD.ActiveDocument.recompute()
if not handler.makeIndividualWalls:
delete = []
walls = []
for k,lines in handler.lines.items():
sk = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject","Walls_trace")
for l in lines:
for edge in l.Shape.Edges:
sk.addGeometry(edge.Curve)
delete.append(l.Name)
FreeCAD.ActiveDocument.recompute()
k = k.split(";")
walls.append(Arch.makeWall(baseobj=sk,width=float(k[0]),height=float(k[1])))
for d in delete:
FreeCAD.ActiveDocument.removeObject(d)
w = walls.pop()
w.Additions = walls
w.Subtractions = handler.windows
g = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup","Furniture")
g.Group = handler.furniture
FreeCAD.ActiveDocument.recompute()
class SH3DHandler(xml.sax.ContentHandler):
def __init__(self,z):
super().__init__()
self.makeIndividualWalls = False
self.z = z
self.windows = []
self.furniture = []
self.lines = {}
def startElement(self, tag, attributes):
if tag == "wall":
name = attributes["id"]
p1 = FreeCAD.Vector(float(attributes["xStart"])*10,float(attributes["yStart"])*10,0)
p2 = FreeCAD.Vector(float(attributes["xEnd"])*10,float(attributes["yEnd"])*10,0)
height = float(attributes["height"])*10
thickness = float(attributes["thickness"])*10
if DEBUG: print("Creating wall: ",name)
line = Draft.makeLine(p1,p2)
if self.makeIndividualWalls:
wall = Arch.makeWall(baseobj=line,width=thickness,height=height,name=name)
wall.Label = name
else:
self.lines.setdefault(str(thickness)+";"+str(height),[]).append(line)
elif tag == "pieceOfFurniture":
name = attributes["name"]
data = self.z.read(attributes["model"])
th,tf = tempfile.mkstemp(suffix=".obj")
f = pyopen(tf,"wb")
f.write(data)
f.close()
os.close(th)
m = Mesh.read(tf)
fx = (float(attributes["width"])/100)/m.BoundBox.XLength
fy = (float(attributes["height"])/100)/m.BoundBox.YLength
fz = (float(attributes["depth"])/100)/m.BoundBox.ZLength
mat = FreeCAD.Matrix()
mat.scale(1000*fx,1000*fy,1000*fz)
mat.rotateX(math.pi/2)
mat.rotateZ(math.pi)
if DEBUG: print("Creating furniture: ",name)
if "angle" in attributes:
mat.rotateZ(float(attributes["angle"]))
m.transform(mat)
os.remove(tf)
p = m.BoundBox.Center.negative()
p = p.add(FreeCAD.Vector(float(attributes["x"])*10,float(attributes["y"])*10,0))
p = p.add(FreeCAD.Vector(0,0,m.BoundBox.Center.z-m.BoundBox.ZMin))
m.Placement.Base = p
obj = FreeCAD.ActiveDocument.addObject("Mesh::Feature",name)
obj.Mesh = m
self.furniture.append(obj)
elif tag == "doorOrWindow":
name = attributes["name"]
data = self.z.read(attributes["model"])
th,tf = tempfile.mkstemp(suffix=".obj")
f = pyopen(tf,"wb")
f.write(data)
f.close()
os.close(th)
m = Mesh.read(tf)
fx = (float(attributes["width"])/100)/m.BoundBox.XLength
fy = (float(attributes["height"])/100)/m.BoundBox.YLength
fz = (float(attributes["depth"])/100)/m.BoundBox.ZLength
mat = FreeCAD.Matrix()
mat.scale(1000*fx,1000*fy,1000*fz)
mat.rotateX(math.pi/2)
m.transform(mat)
b = m.BoundBox
v1 = FreeCAD.Vector(b.XMin,b.YMin-500,b.ZMin)
v2 = FreeCAD.Vector(b.XMax,b.YMin-500,b.ZMin)
v3 = FreeCAD.Vector(b.XMax,b.YMax+500,b.ZMin)
v4 = FreeCAD.Vector(b.XMin,b.YMax+500,b.ZMin)
sub = Part.makePolygon([v1,v2,v3,v4,v1])
sub = Part.Face(sub)
sub = sub.extrude(FreeCAD.Vector(0,0,b.ZLength))
os.remove(tf)
shape = Arch.getShapeFromMesh(m)
if not shape:
shape=Part.Shape()
shape.makeShapeFromMesh(m.Topology,0.100000)
shape = shape.removeSplitter()
if shape:
if DEBUG: print("Creating window: ",name)
if "angle" in attributes:
shape.rotate(shape.BoundBox.Center,FreeCAD.Vector(0,0,1),math.degrees(float(attributes["angle"])))
sub.rotate(shape.BoundBox.Center,FreeCAD.Vector(0,0,1),math.degrees(float(attributes["angle"])))
p = shape.BoundBox.Center.negative()
p = p.add(FreeCAD.Vector(float(attributes["x"])*10,float(attributes["y"])*10,0))
p = p.add(FreeCAD.Vector(0,0,shape.BoundBox.Center.z-shape.BoundBox.ZMin))
if "elevation" in attributes:
p = p.add(FreeCAD.Vector(0,0,float(attributes["elevation"])*10))
shape.translate(p)
sub.translate(p)
obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
obj.Shape = shape
subobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_sub")
subobj.Shape = sub
if FreeCAD.GuiUp:
subobj.ViewObject.hide()
win = Arch.makeWindow(baseobj=obj,name=name)
win.Label = name
win.Subvolume = subobj
self.windows.append(win)
else:
print("importSH3D: Error creating shape for door/window "+name)

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -43,7 +43,23 @@ Part = lz.LazyLoader("Part", globals(), "Part")
def isClockwise(edge, ref=None):
"""Return True if a circle-based edge has a clockwise direction."""
"""Return True if a circle-based edge has a clockwise direction.
Parameters
----------
edge :
The edge to be analyzed.
ref : Vector, optional
The normal around which the direction of the edge is to be determined.
Defaults to the Z normal vector.
Returns
-------
bool
Returns True if the edge is clockwise oriented around the ref Vector
or not.
"""
if not geomType(edge) == "Circle":
return True

View File

@@ -242,7 +242,18 @@ def hasOnlyWires(shape):
def geomType(edge):
"""Return the type of geometry this edge is based on."""
"""Return the type of geometry this edge is based on.
Parameters
----------
edge: the edge whose `Curve` attribute is to be checked.
Returns
-------
str
Return the type of the edge's Curve attribute or "Unknown",
if the parameter is missing.
"""
try:
if isinstance(edge.Curve, (Part.LineSegment, Part.Line)):
return "Line"

View File

@@ -49,8 +49,43 @@ def findIntersection(edge1, edge2,
You can also feed 4 points instead of `edge1` and `edge2`.
If `dts` is used, `Shape.section()` is used.
"""
Parameters
----------
edge1
Part.Edge, Circle, Line -> the first edge.
Base::Vector3 -> the starting point of the first line. In which case
`infinite1` must also be a point.
edge2
Part.Edge, Circle, Line -> the second edge.
Base::Vector3 -> the ending point of the second line. In which case
`infinite2` must also be a point.
the second edge. In case of a point, `infinite2` must also be a point.
infinite1
bool, optional -> whether `edge1` should be continued to infinity.
Default to `False`.
Base::Vector3 -> if `edge1` is a point, must also be a point.
infinite2
bool, optional -> whether `edge2` should be continued to infinity.
Default to `False`.
Base::Vector3 -> if `edge2` is a point, must also be a point.
ex1: bool, optional
In case `edge1` is a point, indicate whether the line should be
continued to infinity. Default to `False`
ex2: bool, optional
In case `edge2` is a point, indicate whether the line should be
continued to infinity. Default to `False`
dts: bool, optional
NOT_DOCUMENTED. Default to `True`
findAll: bool, optional
In case either `edge1` or `edge2` is a circle, indicates whether
to find all intersection points. Default to `False`
Returns
-------
list
A list of intersection points
"""
def getLineIntersections(pt1, pt2, pt3, pt4, infinite1, infinite2):
if pt1:
# first check if we don't already have coincident endpoints

View File

@@ -135,6 +135,21 @@ def offset(edge, vector, trim=False):
and a complete circle will be returned.
None if there is a problem.
Parameters
----------
edge: Part.Shape
the edge to offset
vector: Base::Vector3
the vector by which the edge is to be offset
trim: bool, optional
If `edge` is an arc and `trim` is `True`, the resulting
arc will be trimmed to the proper angle
Returns
-------
Part.Shape
The offset shape
"""
if (not isinstance(edge, Part.Shape)
or not isinstance(vector, App.Vector)):

View File

@@ -538,7 +538,8 @@ def _get_param_dictionary():
":/ui/preferences-archdefaults.ui",
":/ui/preferences-dae.ui",
":/ui/preferences-ifc.ui",
":/ui/preferences-ifc-export.ui"):
":/ui/preferences-ifc-export.ui",
":/ui/preferences-sh3d-import.ui",):
# https://stackoverflow.com/questions/14750997/load-txt-file-from-resources-in-python
fd = QtCore.QFile(fnm)