Roof builder

A forum dedicated to the Draft, Arch and BIM workbenches development.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
rockn
Veteran
Posts: 1791
Joined: Wed Sep 28, 2011 10:39 am
Location: Toulouse, France
Contact:

Roof builder

Post by rockn »

RoofBuilderImage.png
RoofBuilderImage.png (111.52 KiB) Viewed 5306 times
Hello everyone,
I seek to improve the Roof tool. I have make a script to generate a roof with parameters for each side of roof.
I would like to divide with you what I imagine for the interface and unfolding so that one can discuss it.
The principle:
On the basis of closed surface, one assigns with each edges a profile of roof.
This profile will be composed of several parameters:
  • the slope: angle in degrees of the roof with the horizontal one, to put 0° for calculations automatic, 90° to make a pinion
  • measurement in plan: width horizontal enters the bottom of slope and the top of slope, to put 0 for calculations automatic the
  • overflow of roof: exit of roof compared to the edge
  • the height: the height of the ridge sheathing
  • the thickness: the thickness of the cover
  • associated profile: automatic basic profile for calculations.
For each edge one calculations the projection of the connection of roofs.
That gives us wire that one will be able to extrude.
For each edge one draws the profile of roof and one extrudes it.
The side of roof is the intersection of two extrusions.
Hardest is of course to calculate the connections of roof. For example, for the moment script does not manage the connections with different height of ridge sheathing.
The GUI
I imagine a table which recence the edges of the object, automatically filled the parameters and the user adjust them. A table as the system of axis appears to me well.
If not like the parameters of profile of roof risks growing, one can create a specific interface to each edge.

What do you think, what are your expectations of the tools?

Limitations:
The wire one must be carried out while following the anti-clockwise direction and the first line of the wire is the first profil.

Script
This script is a proof of concept. To use it a base should be drawn. Then populated the following lists manually:
line 33 : angles is the slope of the roof

Code: Select all

angles = [30.,45.,30.,30.,0.,0.,30.,30.,90.,]
line 34 : largeurs is the width of the roof pane

Code: Select all

largeurs = [20.,0.,20.,20.,14.14,14.14,20.,0.,10.,]
line 36 :relation is the referenced profil, set the id of the referenced profil (id start at 1)

Code: Select all

relation = [0,1,0,0,4,4,0,1,0,] # profil a partir de 1
line 38 : epaisseur is the height of roofing complex

Code: Select all

epaisseurToit = 1.
Select the base and launch the script
RoofBuilder.FCMacro.zip
Fake ZIP, remove .zip extension before reading
(13.42 KiB) Downloaded 221 times

Code: Select all

 #***************************************************************************
#* *
#* Copyright (c) 2014 *
#* Jonathan Wiedemann <wood.galaxy@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program 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 program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************

import FreeCAD
import FreeCADGui
import DraftGeomUtils
import DraftVecUtils
import Part
import math
import Draft


angles = [30.,45.,30.,30.,0.,0.,30.,30.,90.,]
largeurs = [20.,0.,20.,20.,14.14,14.14,20.,0.,10.,]
debords = [4.,4.,4.,4.,4.,4.,4.,4.,4.,]
relation = [0,1,0,0,4,4,0,1,0,] # profil a partir de 1
hauteurs = []
epaisseurToit = 1.


profilsDico = []
def createProfilDico(id, name, pente, largeur, debord, hauteur):
	profilDico = {}
	profilDico["id"]=id
	profilDico["name"]=name
	profilDico["pente"]=pente
	profilDico["largeur"]=largeur
	profilDico["debord"]=debord
	profilDico["hauteur"]=hauteur
	profilDico["wires"] = []
	profilDico["refEdge"] = []
	profilsDico.append(profilDico)

# Hauteur relatives
def getHeightRelative(id):
	print("getheightRelative id : "+str(id))
	#print("id " + str( id))
	idRel = relation[id]
	idRel = idRel - 1
	print("idRel " +str(idRel))
	print(largeurs[idRel])
	print(angles[idRel])
	print(math.radians(angles[idRel]))
	print(math.tan(math.radians(angles[idRel])))

	htRel = largeurs[idRel]*(math.tan(math.radians(angles[idRel])))
	print("quit getHeightRelative")
	return htRel
def getRightPaneHeight(id):
	if relation[id-1] == 0:
		rhtRel = largeurs[id-1]
	else :
		rhtRel=getHeightRelative((id-1))
	print("rhtRel " + str(rhtRel))
	return rhtRel
def getLeftPaneHeight(id):
	if relation[id+1] == 0:
		lhtRel = largeurs[id+1]
		print("lht")
	else:
		lhtRel=getHeightRelative(id+1)
		
		print("lht")
	print("lhtRel " + str(lhtRel))
	return lhtRel
# Largeur relative
def getWidthRelative(id):
	htRel = getHeightRelative(id)
	#idRel = relation[id]
	#idRel = idRel - 1
	lgRel = htRel/(math.tan(math.radians(angles[id])))
	return lgRel
def getRightPaneWidth(id):
	rlgRel=getWidthRelative(id-1)
	return rlgRel
def getLeftPaneWidth(id):
	llgRel=getWidthRelative(id+1)
	return llgRel

#valeur
def getAngle(id):
	print("getAngle"+str(id))
	#print(getHeightRelative(id))
	#print(largeurs[id])
	a = math.degrees(math.atan(getHeightRelative(id)/largeurs[id]))
	print("quit getAngle")
	return a
def getHeight(id):
	ht = largeurs[id]*(math.tan(math.radians(angles[id])))
	return ht

l = len(angles)
#traitement liste :
for i in range(l):
	if angles[i] == 90.:
		largeurs[i] = 0.
largeurs.append(largeurs[0])
relation.append(relation[0])
angles.append(angles[0])
debords.append(debords[0])
print("Pour chaque i dans range(l) l = 9")
for i in range(l):
	print("Indent "+str(i))
	a = angles[i]
	lg = largeurs[i]
	db = debords[i]
	if a == 0.0 or lg ==0.0 :
		print("Si a = 0 ou lg = 0")
		if relation[i] == 0 and a !=90.:
			"Si relation[i] = 0 et est different de 90 alors"
			print("Impossible de calculer sans profil relatif")
		else :
			if lg == 0:
				print("Si lg auto = 0")
				if a == 90. :
					print("Si a = 90 alors Pignon")
					name = "Pignon"+str(i)
					print(getRightPaneHeight(i))
					print(getLeftPaneHeight(i))
					#htRel = max(getRightPaneHeight(i),getLeftPaneHeight(i))
					htRel = getHeightRelative(i)
					createProfilDico(i,name, 90.,0.,db,htRel)
				else :
					print("Si a != 90 alors calculs de la largeur par rapport a la hauteur des aux autre pans")
					#htRel = max(getRightPaneHeight(i),getLeftPaneHeight(i))
					htRel = getHeightRelative(i)
					print("htRel " + str(htRel))
					lgRel = getWidthRelative(i)
					name = "Pan"+str(i)
					createProfilDico(i,name,a,lgRel,db,htRel)
			elif a == 0. :
				print("Si a = 0 alors Angle automatique par rapport a hauteur autres pans")
				a = getAngle(i)
				print(a)
				#htRel = max(getRightPaneHeight(i),getLeftPaneHeight(i))
				htRel = getHeightRelative(i)
				name = "Pan"+str(i)
				createProfilDico(i,name,a,lg,db,htRel)
	else :
		print("Tout est normal")
		ht = getHeight(i)
		name = "Pan"+str(i)
		createProfilDico(i,name,a,lg,db,ht)


def getPerpendicular(vec, angleEdge,l):
	#print(angleEdge)
	#print(l)
	norm = FreeCAD.Vector(0,0,1)
	perpendicular = vec.cross(norm)
	#perpendicular.scale(l,l,l)
	#print("perpendicularI"+ str(perpendicular))
	if  -180. <= angleEdge < -90.:
		perpendicular[0] = abs(perpendicular[0])*-1
		perpendicular[1] = abs(perpendicular[1])*-1
	elif   -90. <= angleEdge <= 0.:
		perpendicular[0] = abs(perpendicular[0])*-1
		perpendicular[1] = abs(perpendicular[1])
	elif 0. < angleEdge <= 90.:
		perpendicular[0] = abs(perpendicular[0])
		perpendicular[1] = abs(perpendicular[1])
	elif 90. < angleEdge <= 180.:
		perpendicular[0] = abs(perpendicular[0])
		perpendicular[1] = abs(perpendicular[1])*-1
	else:
		print("Angle inconnue")
	perpendicular[2] = abs(perpendicular[2])
	#print("perpendicularII"+ str(perpendicular))
		# ramener a longueur 1
	perpendicular.normalize()
	#print("perpendicularIII"+ str(perpendicular))
	#perpendicular.scale(l,l,l)
	perpendicular = perpendicular.multiply(l)
	#print("perpendicular"+ str(perpendicular))
	return perpendicular

sel = FreeCADGui.Selection.getSelectionEx()[0].Object
#points=[FreeCAD.Vector(0.0,0.0,0.0),FreeCAD.Vector(100.0,0.0,0.0),FreeCAD.Vector(100.0,40.0,0.0),FreeCAD.Vector(70.0,40.0,0.0),FreeCAD.Vector(70.0,60.0,0.0),FreeCAD.Vector(50.0,80.0,0.0),FreeCAD.Vector(30.0,60.0,0.0),FreeCAD.Vector(30.0,40.0,0.0),FreeCAD.Vector(0,40.0,0.0)]
#base = Draft.makeWire(points,closed=True,face=True,support=None)
base = sel.Shape.Wires[0]
edges = DraftGeomUtils.sortEdges(base.Edges)
l = len(edges)
print("l = " + str(l))
edgesForward = edges[:]
edgesForward.append(edges[0])
#print(edgesForward[0])
#edgesForward.append(edges[0])
edgesBack = edges[:]
edgesBack.insert(0,edges[-1])
print edges
print edgesForward
print edgesBack
#edges.append(edges[0])

#edges.insert(0,edges[-1])
#edges.append(edges[0])
wireList = []
for i in range(l):
	print(i)
	#points = [FreeCAD.Vector(0.0,0.0,0.0),]
	points=[]
	#if i == 0:
	profil0 = profilsDico[i-1]
	#else :
	#	profil0 = profilsDico[i-1]
	profil1 = profilsDico[i]
	if i == l-1:
		profil2 = profilsDico[0]
	else:
		profil2 = profilsDico[i+1]
	vec0 = edges[i-1].Vertexes[-1].Point.sub(edges[i-1].Vertexes[0].Point)
	vec1 = edges[i].Vertexes[-1].Point.sub(edges[i].Vertexes[0].Point)
	#if i == l-1:
	vec2 = edgesForward[i+1].Vertexes[-1].Point.sub(edgesForward[i+1].Vertexes[0].Point)
	#angleEdge0 = math.degrees(DraftVecUtils.angle(vec1,vec0))
	angleEdge0 = math.degrees(DraftVecUtils.angle(vec0))
	angleEdge1 = math.degrees(DraftVecUtils.angle(vec1))
	#angleEdge2 = math.degrees(DraftVecUtils.angle(vec1,vec2))
	angleEdge2 = math.degrees(DraftVecUtils.angle(vec2))
	long = vec1.Length
	#points.append(FreeCAD.Vector(long,0.0,0.0))
	points=[edges[i].Vertexes[0].Point,edges[i].Vertexes[-1].Point]
	if profil1["pente"] != 90.:
		lg = profil1["largeur"]
		print(lg)
		faitage = DraftGeomUtils.offset(edges[i],getPerpendicular(vec1,angleEdge1,lg))
		midpoint = DraftGeomUtils.findMidpoint(edges[i])
		print("perpendicular"+ str(getPerpendicular(vec1,angleEdge1,lg)))
		print("angleEdge" + str(angleEdge1))
		if profil2["pente"] == 90. :
			edge = DraftGeomUtils.offset(edgesForward[i+1],FreeCAD.Vector(0,0,0))
			point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			points.append(FreeCAD.Vector(point[0]))
			print(points)
		elif profil2["hauteur"] == profil1["hauteur"]:
			edge = DraftGeomUtils.offset(edgesForward[i+1],getPerpendicular(vec2,angleEdge2,profil2["largeur"]))
			point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			points.append(FreeCAD.Vector(point[0]))
		elif profil1["hauteur"] > profil2["hauteur"]:
			edge = DraftGeomUtils.offset(edgesForward[i+1],getPerpendicular(vec2,angleEdge2,profil2["largeur"]))
			point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			points.append(FreeCAD.Vector(point[0]))
			#edge2 = DraftGeomUtils.offset(edges[i+1],getPerpendicular(vec2,angleEdge2,profil2["largeur"]))
			#dec = profil2["hauteur"]/math.tan(math.degrees(profil1["pente"]))
			#dec = dec*-1
			#edge1 = DraftGeomUtils.offset(edges[i],getPerpendicular(vec1,angleEdge1,dec))
			#point = DraftGeomUtils.findIntersection(edge1,edge2,infinite1=True,infinite2=True,)
			#points.append(FreeCAD.Vector(point[0]))
			#print("Point pointe",point)
			#point = DraftGeomUtils.findPerpendicular(point,[faitage])
			#print("Projection sur faitage",point)
			#points.append(FreeCAD.Vector(point[0]))
		elif profil1["hauteur"] < profil2["hauteur"]:
			edge = DraftGeomUtils.offset(edges[i+1],getPerpendicular(vec2,angleEdge2,profil2["largeur"]))
			point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			points.append(FreeCAD.Vector(point[0]))
			"""
			edge2 = DraftGeomUtils.offset(edges[i+1],getPerpendicular(vec2,angleEdge2,profil2["largeur"]))
			print("Edge2",edge2.Vertexes[-1].Point,edge2.Vertexes[0].Point)
			dec = profil2["hauteur"]/math.tan(math.degrees(profil1["pente"]))
			print("dec",dec)
			edge1 = DraftGeomUtils.offset(edges[i],getPerpendicular(vec1,angleEdge1,dec))
			print("Edge1",edge1.Vertexes[-1].Point,edge1.Vertexes[0].Point)
			print("Faitage",edge.Vertexes[-1].Point,edge.Vertexes[0].Point)
			#point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			point = DraftGeomUtils.findIntersection(edge1,edge2,infinite1=True,infinite2=True,)
			points.append(FreeCAD.Vector(point[0]))
			#point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			point = DraftGeomUtils.findPerpendicular(point, [faitage,],)
			print("Projection sur faitage",point)
			points.append(FreeCAD.Vector(point[0]))
			"""
			#pass
		else:
			print("Cas de figure non pris en charge")
		if profil0["pente"] == 90. :
			edge = edgesBack[i]
			point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			points.append(FreeCAD.Vector(point[0]))
		elif profil0["hauteur"] == profil1["hauteur"]:
			edge = DraftGeomUtils.offset(edgesBack[i],getPerpendicular(vec0,angleEdge0,profil0["largeur"]))
			point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			points.append(FreeCAD.Vector(point[0]))
		elif profil1["hauteur"] > profil0["hauteur"]:
			edge = DraftGeomUtils.offset(edgesBack[i],getPerpendicular(vec0,angleEdge0,profil0["largeur"]))
			point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			points.append(FreeCAD.Vector(point[0]))
		elif profil1["hauteur"] < profil0["hauteur"]:
			edge = DraftGeomUtils.offset(edgesBack[i],getPerpendicular(vec0,angleEdge0,profil0["largeur"]))
			point = DraftGeomUtils.findIntersection(faitage,edge,infinite1=True,infinite2=True,)
			points.append(FreeCAD.Vector(point[0]))
			#edge2 = DraftGeomUtils.offset(edges[i-1],getPerpendicular(vec0,angleEdge0,profil0["largeur"]))
			#dec = profil2["hauteur"]/math.tan(math.degrees(profil1["pente"]))
			#dec = dec*-1
			#edge1 = DraftGeomUtils.offset(edges[i],getPerpendicular(vec1,angleEdge1,dec))
			#point = DraftGeomUtils.findIntersection(edge1,edge2,infinite1=True,infinite2=True,)
			#points.append(FreeCAD.Vector(point[0]))
		else:
			print("Cas de figure non pris en charge")
	else:
		#Points en Z directement pour construire pignon
		#if profil0["hauteur"] == profil1["hauteur"] == profil2["hauteur"]:
		pass
	points = DraftVecUtils.removeDoubles(points)	
	print("points a ferme",points)
	profilsDico[i]["wires"] = points
	wireList.append(points)
#print("len profilsDico : "+str(len(profilsDico)))
#for d in profilsDico:
#	print(d)
#n = 0
#for p in profilsDico:
#	print(n)
	#print(p["pente"])
	#print("WireListe"+str(n),p)
	#pDi = profilsDico[i] 
	f = Draft.makeWire(profil1["wires"],closed=True,face=True,support=None)
	f.Label = profil1["name"]
	d = f.Shape.Wires[0].BoundBox.DiagonalLength
	if profil1["pente"] != 90.:
		epaisseurVToit = epaisseurToit/(math.cos(math.radians(profil1["pente"])))
		f = Part.Face(f.Shape.Wires[0])
		f = f.extrude(FreeCAD.Vector(0,0,profil1["hauteur"]+2*epaisseurVToit))
		#Part.show(f)
		points=[FreeCAD.Vector(0.0,-epaisseurVToit,0.0),FreeCAD.Vector(profil1["largeur"],profil1["hauteur"]-epaisseurVToit,0.0),FreeCAD.Vector(profil1["largeur"],profil1["hauteur"],0.0),FreeCAD.Vector(0.0,0.0,0.0)]
		profilCouv = Draft.makeWire(points,closed=True,face=True,support=None,)
		profilCouv.Label = "Profil-"+str(profil1["name"])
		Draft.move(profilCouv,midpoint,copy=False)
		Draft.rotate(profilCouv,90+angleEdge1*-1,midpoint,axis=FreeCAD.Vector(0.0,0.0,1.0),copy=False)
		perp = getPerpendicular(vec1,angleEdge1,profil1["largeur"])
		Draft.rotate(profilCouv,90*1,midpoint,axis=perp,copy=False)
		vecT = vec1.normalize()
		#vecT.negative()
		vecT.multiply(d)
		Draft.move(profilCouv,vecT,copy=False)
		vecE = vecT.multiply(-2)
		
		profilVol = profilCouv.Shape.extrude(vecE)
		#Part.show(profilVol)
		panVol = f.common(profilVol)
		Part.show(panVol)
#	n=n+1
detail.png
detail.png (24.82 KiB) Viewed 5306 times
Formations - Assistance - Développement : https://freecad-france.com
User avatar
rockn
Veteran
Posts: 1791
Joined: Wed Sep 28, 2011 10:39 am
Location: Toulouse, France
Contact:

Re: Roof builder

Post by rockn »

So I made some progress :
If you want to test this new roof builder :
Git : https://github.com/wood-galaxy/FreeCAD_sf_master
Branche : arch-roof

Check this video to see how it could work.
http://youtu.be/4Urwru71dVk

The Overhang parameters is not implemented.
Work with wire and sketch >= 4 edges.

Tests and comments are welcome.
Formations - Assistance - Développement : https://freecad-france.com
User avatar
yorik
Founder
Posts: 13640
Joined: Tue Feb 17, 2009 9:16 pm
Location: Brussels
Contact:

Re: Roof builder

Post by yorik »

Awesome! This is becoming very good!
User avatar
bernd
Veteran
Posts: 12849
Joined: Sun Sep 08, 2013 8:07 pm
Location: Zürich, Switzerland
Contact:

Re: Roof builder

Post by bernd »

Wow. Modelling that roof in just over 2 minutes !!! Very cool tool rockn.
User avatar
rockn
Veteran
Posts: 1791
Joined: Wed Sep 28, 2011 10:39 am
Location: Toulouse, France
Contact:

Re: Roof builder

Post by rockn »

Thanks you !

@Bernd : I try to make it as simple as possible even the quantity of parameters needed to. Glad you found it fast :)

I am going to implement the overhang, that 's a tricky part. After that we could have a great tool for modeling roof ;)
Of course there is to implement some tricky stuff like sloping ridge and plate.

@Yorik : how can I/we do to cut the wall along the roof ? Is the function getSubVolume make for that ?
Formations - Assistance - Développement : https://freecad-france.com
User avatar
yorik
Founder
Posts: 13640
Joined: Tue Feb 17, 2009 9:16 pm
Location: Brussels
Contact:

Re: Roof builder

Post by yorik »

rockn wrote:@Yorik : how can I/we do to cut the wall along the roof ? Is the function getSubVolume make for that ?
Yes. I had started to implement something like the windows, that is, implement a getSubVolume function. If you look into the Arch Components processSubShapes functions, if the subtracted object is a roof, it uses a shape given by its getSubvolume() function, instead of the object's shape.

So you could use that function to produce a special shape, that should be subtracted from walls, structures, etc.
User avatar
rockn
Veteran
Posts: 1791
Joined: Wed Sep 28, 2011 10:39 am
Location: Toulouse, France
Contact:

Re: Roof builder

Post by rockn »

Hi !
I implemented the overflow of roof.
overhang==overflow ?
RoofOverhang.png
RoofOverhang.png (60.43 KiB) Viewed 5096 times
The Roof tool does not manage badly now. Of course one cannot make roofs like that yet:
Image
:lol:
The current tool either (almost).

In this moment each contour of side of roof is visible, the Roof object is a compound. That can become again a fusion but the idea was to see whether one could select a side of particular roof to change his parameters.
Do you know if there is a method to find the solid inside a compound to which the selected face belongs?

I also wonder how to recover SubVolume starting from the object and of its base without all to recompute…Any ideas with Boolean operations simple?

I would like returns, mainly on the interface and the interaction users and also on the bugs. Do Yorik, think you that it is mergeable in the master ? Or I make a "Mod workbench" ...

A+
Formations - Assistance - Développement : https://freecad-france.com
User avatar
yorik
Founder
Posts: 13640
Joined: Tue Feb 17, 2009 9:16 pm
Location: Brussels
Contact:

Re: Roof builder

Post by yorik »

Great progress rockn! This is becoming very good!
I think overhang is the correct word https://www.google.com.br/search?q=over ... 20&bih=954

About finding which solid "owns" a face, you must compare their hash code:

Code: Select all

hash1 = myFace.hashCode()
for sol in myshape.Solids:
    for face in sol.Faces:
        if face.hashCode() == hash1:
            print "owner is ",sol
About the subvolume, maybe you could create it at the same time when you build the shape, and store it in a variable? Then, inside the getSubVolume() function, if that variable is present, you use it, if not (for example the object has just been loaded from a file), you just recalculate the shape...

II would be more than happy to merge this in master, of course! It replaces the current Arch Roof tool, right? When you think it is ready, just give me the branch URL and I do it. Since the current roof tool is really very basic (probably you were the only one using it :) ), I think there is no harm in using yours, even if it contains bugs. So indeed people can go testing...
jmaustpc
Veteran
Posts: 11207
Joined: Tue Jul 26, 2011 6:28 am
Location: Australia

Re: Roof builder

Post by jmaustpc »

yorik wrote:Since the current roof tool is really very basic (probably you were the only one using it :) ),
I have used "roof" in the past when I made all those house designs back when you were first creating Arch WB....but not for a long time. :)

For me, it would not matter if those files had problems and I lost backwards compatibility, but I posted many of those file on this forum, I don't know how many had Roof or how much that would matter.
User avatar
rockn
Veteran
Posts: 1791
Joined: Wed Sep 28, 2011 10:39 am
Location: Toulouse, France
Contact:

Re: Roof builder

Post by rockn »

Hi !
Ok now you can merge the branch https://github.com/wood-galaxy/FreeCAD_ ... /arch-roof
Indeed it is going to replace the current Roof tool.

I'm going to write the wiki page.

Thanks :D
Formations - Assistance - Développement : https://freecad-france.com
Post Reply