2025-01-28 00:04:13 +01:00
|
|
|
import math
|
|
|
|
|
import FreeCAD
|
|
|
|
|
from Utils.PVPlantUtils import findObjects
|
|
|
|
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
|
|
|
import FreeCADGui
|
2025-03-28 19:39:33 +06:00
|
|
|
from PySide import QtCore, QtGui, QtWidgets
|
2025-01-28 00:04:13 +01:00
|
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
else:
|
|
|
|
|
# \cond
|
|
|
|
|
def translate(ctxt, txt):
|
|
|
|
|
return txt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def QT_TRANSLATE_NOOP(ctxt, txt):
|
|
|
|
|
return txt
|
|
|
|
|
# \endcond
|
|
|
|
|
|
|
|
|
|
__title__ = "PVPlant Export to DXF"
|
|
|
|
|
__author__ = "Javier Braña"
|
|
|
|
|
__url__ = "http://www.sogos-solar.com"
|
|
|
|
|
|
|
|
|
|
import PVPlantResources
|
|
|
|
|
from PVPlantResources import DirIcons as DirIcons
|
|
|
|
|
|
|
|
|
|
field = {"name": "", "width": 0, "heigth": 0}
|
|
|
|
|
|
|
|
|
|
|
2025-03-28 19:39:33 +06:00
|
|
|
class LineTypeDelegate(QtWidgets.QStyledItemDelegate):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.line_types = [
|
|
|
|
|
{"name": "Continuous", "pen": QtCore.Qt.SolidLine},
|
|
|
|
|
{"name": "Dashed", "pen": QtCore.Qt.DashLine},
|
|
|
|
|
{"name": "Dotted", "pen": QtCore.Qt.DotLine},
|
|
|
|
|
{"name": "Dash-Dot", "pen": QtCore.Qt.DashDotLine},
|
|
|
|
|
{"name": "Dash-Dot-Dot", "pen": QtCore.Qt.DashDotDotLine}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def paint(self, painter, option, index):
|
|
|
|
|
# Dibujar el fondo
|
|
|
|
|
painter.save()
|
|
|
|
|
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
|
|
|
|
|
|
|
|
# Configuraciones comunes
|
|
|
|
|
line_color = QtGui.QColor(0, 0, 0) # Color de la línea
|
|
|
|
|
line_width = 2 # Grosor de línea
|
|
|
|
|
|
|
|
|
|
# Dibujar el ítem base
|
|
|
|
|
super().paint(painter, option, index)
|
|
|
|
|
|
|
|
|
|
# Obtener el tipo de línea
|
|
|
|
|
line_type = self.line_types[index.row()]
|
|
|
|
|
|
|
|
|
|
# Configurar el área de dibujo
|
|
|
|
|
text_rect = option.rect.adjusted(5, 0, -100, 0)
|
|
|
|
|
line_rect = option.rect.adjusted(option.rect.width() - 90, 2, -5, -2)
|
|
|
|
|
|
|
|
|
|
# Dibujar el texto
|
|
|
|
|
painter.drawText(text_rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, line_type["name"])
|
|
|
|
|
|
|
|
|
|
# Dibujar la línea de muestra
|
|
|
|
|
pen = QtGui.QPen(line_color, line_width, line_type["pen"])
|
|
|
|
|
painter.setPen(pen)
|
|
|
|
|
|
|
|
|
|
start_y = line_rect.center().y()
|
|
|
|
|
painter.drawLine(line_rect.left(), start_y, line_rect.right(), start_y)
|
|
|
|
|
|
|
|
|
|
painter.restore()
|
|
|
|
|
|
|
|
|
|
def sizeHint(self, option, index):
|
|
|
|
|
size = super().sizeHint(option, index)
|
|
|
|
|
size.setHeight(25) # Altura aumentada para mejor visualización
|
|
|
|
|
return size
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LineTypePreviewDelegate(QtWidgets.QStyledItemDelegate):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
'''self.line_types = [
|
|
|
|
|
("Continuous", QtCore.Qt.SolidLine),
|
|
|
|
|
("Dashed", QtCore.Qt.DashLine),
|
|
|
|
|
("Dotted", QtCore.Qt.DotLine),
|
|
|
|
|
("Dash-Dot", QtCore.Qt.DashDotLine),
|
|
|
|
|
("Dash-Dot-Dot", QtCore.Qt.DashDotDotLine)
|
|
|
|
|
]'''
|
|
|
|
|
|
|
|
|
|
self.line_types = [
|
|
|
|
|
{"name": "Continuous", "style": QtCore.Qt.SolidLine},
|
|
|
|
|
{"name": "Dashed", "style": QtCore.Qt.DashLine},
|
|
|
|
|
{"name": "Dotted", "style": QtCore.Qt.DotLine},
|
|
|
|
|
{"name": "Custom 1 (--0--0--)",
|
|
|
|
|
"style": QtCore.Qt.CustomDashLine,
|
|
|
|
|
"pattern": [8, 4, 2, 4]}, # Patrón personalizado
|
|
|
|
|
{"name": "Custom 2 (- - -)",
|
|
|
|
|
"style": QtCore.Qt.CustomDashLine,
|
|
|
|
|
"pattern": [6, 3]} # Otro patrón
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def paint(self, painter, option, index):
|
|
|
|
|
painter.save()
|
|
|
|
|
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
|
|
|
|
|
|
|
|
if index.row() >= len(self.line_types):
|
|
|
|
|
painter.restore()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
line_data = self.line_types[index.row()]
|
|
|
|
|
line_name = line_data["name"]
|
|
|
|
|
line_style = line_data.get("style", QtCore.Qt.SolidLine)
|
|
|
|
|
dash_pattern = line_data.get("pattern", [])
|
|
|
|
|
|
|
|
|
|
# Fondo para selección
|
|
|
|
|
if option.state & QtGui.QStyle.State_Selected:
|
|
|
|
|
painter.fillRect(option.rect, option.palette.highlight())
|
|
|
|
|
|
|
|
|
|
# Áreas de dibujo
|
|
|
|
|
text_rect = option.rect.adjusted(5, 0, -100, 0)
|
|
|
|
|
preview_rect = option.rect.adjusted(option.rect.width() - 90, 2, -5, -2)
|
|
|
|
|
|
|
|
|
|
# Texto
|
|
|
|
|
painter.setPen(option.palette.color(QtGui.QPalette.Text))
|
|
|
|
|
painter.drawText(text_rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, line_name)
|
|
|
|
|
|
|
|
|
|
# Línea personalizada
|
|
|
|
|
pen = QtGui.QPen(QtGui.QColor(0, 0, 0), 2)
|
|
|
|
|
pen.setStyle(line_style)
|
|
|
|
|
|
|
|
|
|
if line_style == QtCore.Qt.CustomDashLine and dash_pattern:
|
|
|
|
|
pen.setDashPattern(dash_pattern)
|
|
|
|
|
|
|
|
|
|
painter.setPen(pen)
|
|
|
|
|
painter.drawLine(preview_rect.left(), preview_rect.center().y(),
|
|
|
|
|
preview_rect.right(), preview_rect.center().y())
|
|
|
|
|
|
|
|
|
|
painter.restore()
|
|
|
|
|
|
|
|
|
|
def sizeHint(self, option, index):
|
|
|
|
|
return QtCore.QSize(200, 25)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LineTypeComboBox(QtWidgets.QComboBox):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self._delegate = LineTypePreviewDelegate(self)
|
|
|
|
|
self.setItemDelegate(self._delegate)
|
|
|
|
|
self.addItems(["Continuous", "Dashed", "Dotted", "Dash-Dot", "Dash-Dot-Dot"])
|
|
|
|
|
|
|
|
|
|
def paintEvent(self, event):
|
|
|
|
|
painter = QtGui.QPainter(self)
|
|
|
|
|
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
|
|
|
|
|
|
|
|
# Dibujar el fondo del combobox
|
|
|
|
|
option = QtWidgets.QStyleOptionComboBox()
|
|
|
|
|
self.initStyleOption(option)
|
|
|
|
|
self.style().drawComplexControl(QtGui.QStyle.CC_ComboBox, option, painter, self)
|
|
|
|
|
|
|
|
|
|
# Obtener el texto y estilo actual
|
|
|
|
|
current_text = self.currentText()
|
|
|
|
|
line_style = next(
|
|
|
|
|
(style for name, style in self._delegate.line_types if name == current_text),
|
|
|
|
|
QtCore.Qt.SolidLine
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Área de dibujo
|
|
|
|
|
text_rect = self.rect().adjusted(5, 0, -30, 0)
|
|
|
|
|
preview_rect = self.rect().adjusted(self.width() - 70, 2, -5, -2)
|
|
|
|
|
|
|
|
|
|
# Dibujar texto
|
|
|
|
|
painter.setPen(self.palette().color(QtGui.QPalette.Text))
|
|
|
|
|
painter.drawText(text_rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, current_text)
|
|
|
|
|
|
|
|
|
|
# Dibujar línea de previsualización
|
|
|
|
|
pen = QtGui.QPen(QtGui.QColor(0, 0, 0), 2)
|
|
|
|
|
pen.setStyle(line_style)
|
|
|
|
|
painter.setPen(pen)
|
|
|
|
|
|
|
|
|
|
center_y = preview_rect.center().y()
|
|
|
|
|
painter.drawLine(preview_rect.left(), center_y, preview_rect.right(), center_y)
|
|
|
|
|
|
|
|
|
|
|
2025-01-28 00:04:13 +01:00
|
|
|
class exportDXF:
|
|
|
|
|
|
|
|
|
|
def __init__(self, filename):
|
|
|
|
|
''' '''
|
2025-03-28 19:39:33 +06:00
|
|
|
|
2025-01-28 00:04:13 +01:00
|
|
|
self.doc = None
|
|
|
|
|
self.msp = None
|
|
|
|
|
self.filename = filename
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
doc.linetypes.add("GRENZE2",
|
|
|
|
|
# linetype definition in acad.lin:
|
|
|
|
|
# A,.25,-.1,[BOX,ltypeshp.shx,x=-.1,s=.1],-.1,1
|
|
|
|
|
# replacing BOX by shape index 132 (got index from an AutoCAD file),
|
|
|
|
|
# ezdxf can't get shape index from ltypeshp.shx
|
|
|
|
|
pattern="A,.25,-.1,[132,ltypeshp.shx,x=-.1,s=.1],-.1,1",
|
|
|
|
|
description="Grenze eckig ----[]-----[]----[]-----[]----[]--",
|
|
|
|
|
length=1.45, # required for complex line types
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
doc.linetypes.add("GRENZE2",
|
|
|
|
|
# linetype definition in acad.lin:
|
|
|
|
|
# A,.25,-.1,[BOX,ltypeshp.shx,x=-.1,s=.1],-.1,1
|
|
|
|
|
# replacing BOX by shape index 132 (got index from an AutoCAD file),
|
|
|
|
|
# ezdxf can't get shape index from ltypeshp.shx
|
|
|
|
|
pattern = "A,.25,-.1,[132,ltypeshp.shx,x=-.1,s=.1],-.1,1",
|
|
|
|
|
description = "Límite1 ----0-----0----0-----0----0-----0--",
|
|
|
|
|
length = 1.45, # required for complex line types
|
|
|
|
|
})
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
def createFile(self, version='R2018'):
|
|
|
|
|
import ezdxf
|
|
|
|
|
from ezdxf.tools.standards import linetypes
|
|
|
|
|
|
|
|
|
|
# 1. Create a new document
|
|
|
|
|
self.doc = ezdxf.new(version)
|
|
|
|
|
# 2. Setup document:
|
|
|
|
|
self.doc.header["$INSUNITS"] = 6
|
|
|
|
|
self.doc.header['$MEASUREMENT'] = 1
|
|
|
|
|
self.doc.header['$LUNITS'] = 2
|
|
|
|
|
self.doc.header['$AUNITS'] = 0
|
|
|
|
|
|
|
|
|
|
# 3. Add new entities to the modelspace:
|
|
|
|
|
self.msp = self.doc.modelspace()
|
|
|
|
|
|
|
|
|
|
print("linetypes: ", self.doc.linetypes)
|
|
|
|
|
for name, desc, pattern in linetypes():
|
|
|
|
|
if name not in self.doc.linetypes:
|
|
|
|
|
self.doc.linetypes.add(name=name,
|
|
|
|
|
pattern=pattern,
|
|
|
|
|
description=desc,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.doc.linetypes.add(name="FENCELINE1",
|
|
|
|
|
pattern="A,.25,-.1,[132,ltypeshp.shx,x=-.1,s=.1],-.1,1",
|
|
|
|
|
description="Límite1 ----0-----0----0-----0----0-----0--",
|
|
|
|
|
length=1.45) # required for complex line types
|
|
|
|
|
|
|
|
|
|
def save(self):
|
|
|
|
|
from os.path import exists
|
|
|
|
|
file_exists = exists(self.filename)
|
|
|
|
|
self.doc.saveas(self.filename)
|
|
|
|
|
self.doc.save()
|
|
|
|
|
|
|
|
|
|
def createLayer(self, layerName="newLayer", layerColor=(0,0,0), layerLineType='CONTINUOUS'):
|
|
|
|
|
layer = self.doc.layers.add(name=layerName, linetype=layerLineType)
|
|
|
|
|
layer.rgb = layerColor
|
|
|
|
|
return layer
|
|
|
|
|
|
|
|
|
|
def createBlock(self, blockName='newBlock'):
|
|
|
|
|
# Create a block
|
|
|
|
|
block = self.doc.blocks.new(name=blockName)
|
|
|
|
|
return block
|
|
|
|
|
|
|
|
|
|
def insertBlock(self, name, point=(0.0, 0.0), rotation=0.0, xscale=0.0, yscale=0.0):
|
|
|
|
|
if name == "":
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
return self.msp.add_blockref(name, point, dxfattribs={
|
|
|
|
|
'xscale': xscale,
|
|
|
|
|
'yscale': yscale,
|
|
|
|
|
'rotation': rotation
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
def createPolyline(self, wire):
|
2025-06-15 23:10:17 +02:00
|
|
|
try:
|
|
|
|
|
data = getWire(wire.Shape)
|
|
|
|
|
lwp = self.msp.add_lwpolyline(data)
|
|
|
|
|
return lwp
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print("Error creating polyline:", e)
|
|
|
|
|
return None
|
2025-01-28 00:04:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def getWire(wire, nospline=False, width=.0):
|
|
|
|
|
"""Return a list of DXF ready points and bulges from a wire.
|
|
|
|
|
|
|
|
|
|
It builds a list of points from the edges of a `wire`.
|
|
|
|
|
If the edges are circular arcs, the "bulge" of that edge is calculated,
|
|
|
|
|
for other cases, the bulge is considered zero.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
wire : Part::TopoShape ('Wire')
|
|
|
|
|
A shape representing a wire.
|
|
|
|
|
|
|
|
|
|
nospline : bool, optional
|
|
|
|
|
It defaults to `False`.
|
|
|
|
|
If it is `True`, the edges of the wire are not considered as
|
|
|
|
|
being one of `'BSplineCurve'`, `'BezierCurve'`, or `'Ellipse'`,
|
|
|
|
|
and a simple point is added to the list.
|
|
|
|
|
Otherwise, `getSplineSegs(edge)` is used to extract
|
|
|
|
|
the points and add them to the list.
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
list of tuples
|
|
|
|
|
It returns a list of tuples ``[(...), (...), ...]``
|
|
|
|
|
where each tuple indicates a point with additional information
|
|
|
|
|
besides the coordinates.
|
|
|
|
|
Two types of tuples may be returned.
|
|
|
|
|
|
|
|
|
|
[(float, float, float, None, None, float), ...]
|
|
|
|
|
When `lw` is `True` (`'lwpolyline'`)
|
|
|
|
|
the first three values represent the coordinates of the point,
|
|
|
|
|
the next two are `None`, and the last value is the bulge.
|
|
|
|
|
|
|
|
|
|
[((float, float, float), None, [None, None], float), ...]
|
|
|
|
|
When `lw` is `False` (`'polyline'`)
|
|
|
|
|
the first element is a tuple of three values that indicate
|
|
|
|
|
the coordinates of the point, the next element is `None`,
|
|
|
|
|
the next element is a list of two `None` values,
|
|
|
|
|
and the last element is the value of the bulge.
|
|
|
|
|
|
|
|
|
|
See also
|
|
|
|
|
--------
|
|
|
|
|
calcBulge
|
|
|
|
|
"""
|
|
|
|
|
import Part
|
|
|
|
|
import DraftGeomUtils
|
|
|
|
|
import math
|
|
|
|
|
|
|
|
|
|
def fmt(vec, b=0.0):
|
|
|
|
|
return (vec.x * 0.001, vec.y * 0.001, width, width, b)
|
|
|
|
|
|
|
|
|
|
points = []
|
|
|
|
|
edges = Part.__sortEdges__(wire.Edges)
|
|
|
|
|
for edge in edges:
|
|
|
|
|
v1 = edge.Vertexes[0].Point
|
|
|
|
|
if DraftGeomUtils.geomType(edge) == "Circle":
|
|
|
|
|
# polyline bulge -> negative makes the arc go clockwise
|
|
|
|
|
angle = edge.LastParameter - edge.FirstParameter
|
|
|
|
|
bul = math.tan(angle / 4)
|
|
|
|
|
if edge.Curve.Axis.dot(FreeCAD.Vector(0, 0, 1)) < 0:
|
|
|
|
|
bul = -bul
|
|
|
|
|
points.append(fmt(v1, bul))
|
|
|
|
|
elif (DraftGeomUtils.geomType(edge) in ["BSplineCurve",
|
|
|
|
|
"BezierCurve",
|
|
|
|
|
"Ellipse"]) and (not nospline):
|
|
|
|
|
spline = getSplineSegs(edge)
|
|
|
|
|
spline.pop()
|
|
|
|
|
for p in spline:
|
|
|
|
|
points.append(fmt(p))
|
|
|
|
|
else:
|
|
|
|
|
points.append(fmt(v1))
|
|
|
|
|
|
|
|
|
|
v = edges[-1].Vertexes[-1].Point
|
|
|
|
|
points.append(fmt(v))
|
|
|
|
|
|
|
|
|
|
return points
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def getArcData(edge):
|
|
|
|
|
"""Return center, radius, start, and end angles of a circle-based edge.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
edge : Part::TopoShape ('Edge')
|
|
|
|
|
An edge representing a circular arc, either open or closed.
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
(tuple, float, float, float)
|
|
|
|
|
It returns a tuple of four values; the first value is a tuple
|
|
|
|
|
with the coordinates of the center `(x, y, z)`;
|
|
|
|
|
the other three represent the magnitude of the radius,
|
|
|
|
|
and the start and end angles in degrees that define the arc.
|
|
|
|
|
|
|
|
|
|
(tuple, float, 0, 0)
|
|
|
|
|
If the number of vertices in the `edge` is only one, only the center
|
|
|
|
|
point exists, so it's a full circumference; in this case, both
|
|
|
|
|
angles are zero.
|
|
|
|
|
"""
|
|
|
|
|
ce = edge.Curve.Center
|
|
|
|
|
radius = edge.Curve.Radius
|
|
|
|
|
if len(edge.Vertexes) == 1:
|
|
|
|
|
# closed circle
|
|
|
|
|
return DraftVecUtils.tup(ce), radius, 0, 0
|
|
|
|
|
else:
|
|
|
|
|
# new method: recalculate ourselves as we cannot trust edge.Curve.Axis
|
|
|
|
|
# or XAxis
|
|
|
|
|
p1 = edge.Vertexes[0].Point
|
|
|
|
|
p2 = edge.Vertexes[-1].Point
|
|
|
|
|
v1 = p1.sub(ce)
|
|
|
|
|
v2 = p2.sub(ce)
|
|
|
|
|
# print(v1.cross(v2))
|
|
|
|
|
# print(edge.Curve.Axis)
|
|
|
|
|
# print(p1)
|
|
|
|
|
# print(p2)
|
|
|
|
|
# we can use Z check since arcs getting here will ALWAYS be in XY plane
|
|
|
|
|
# Z can be 0 if the arc is 180 deg
|
|
|
|
|
# if (v1.cross(v2).z >= 0) or (edge.Curve.Axis.z > 0):
|
|
|
|
|
# Calculates the angles of the first and last points
|
|
|
|
|
# in the circular arc, with respect to the global X axis.
|
|
|
|
|
if edge.Curve.Axis.z > 0:
|
|
|
|
|
# clockwise
|
|
|
|
|
ang1 = -DraftVecUtils.angle(v1)
|
|
|
|
|
ang2 = -DraftVecUtils.angle(v2)
|
|
|
|
|
else:
|
|
|
|
|
# counterclockwise
|
|
|
|
|
ang2 = -DraftVecUtils.angle(v1)
|
|
|
|
|
ang1 = -DraftVecUtils.angle(v2)
|
|
|
|
|
|
|
|
|
|
# obsolete method - fails a lot
|
|
|
|
|
# if round(edge.Curve.Axis.dot(Vector(0, 0, 1))) == 1:
|
|
|
|
|
# ang1, ang2 = edge.ParameterRange
|
|
|
|
|
# else:
|
|
|
|
|
# ang2, ang1 = edge.ParameterRange
|
|
|
|
|
# if edge.Curve.XAxis != Vector(1, 0, 0):
|
|
|
|
|
# ang1 -= DraftVecUtils.angle(edge.Curve.XAxis)
|
|
|
|
|
# ang2 -= DraftVecUtils.angle(edge.Curve.XAxis)
|
|
|
|
|
|
|
|
|
|
return (DraftVecUtils.tup(ce), radius,
|
|
|
|
|
math.degrees(ang1), math.degrees(ang2))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _PVPlantExportDXF(QtGui.QWidget):
|
|
|
|
|
'''The editmode TaskPanel to select what you want to export'''
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
# super(_PVPlantExportDXF, self).__init__()
|
|
|
|
|
QtGui.QWidget.__init__(self)
|
|
|
|
|
|
|
|
|
|
import os
|
2025-03-28 19:39:33 +06:00
|
|
|
from datetime import datetime
|
2025-01-28 00:04:13 +01:00
|
|
|
# self.form:
|
|
|
|
|
self.form = FreeCADGui.PySideUic.loadUi(
|
|
|
|
|
os.path.join(os.path.dirname(os.path.realpath(__file__)), "exportDXF.ui"))
|
|
|
|
|
# setWindowIcon(QtGui.QIcon(os.path.join(PVPlantResources.DirIcons, "convert.svg")))
|
|
|
|
|
# self.form.buttonTo.clicked.connect(self.addTo)
|
2025-03-28 19:39:33 +06:00
|
|
|
self.form.tableLayers.setItemDelegateForColumn(3, LineTypeDelegate())
|
2025-01-28 00:04:13 +01:00
|
|
|
|
|
|
|
|
self.layout = QtGui.QHBoxLayout(self)
|
|
|
|
|
self.layout.setContentsMargins(4, 4, 4, 4)
|
|
|
|
|
self.layout.addWidget(self.form)
|
|
|
|
|
|
|
|
|
|
self.form.buttonAcept.clicked.connect(self.onAceptClick)
|
|
|
|
|
|
2025-03-28 19:39:33 +06:00
|
|
|
self.add_row("Layer 1", QtGui.QColor(255, 0, 0), "Continua", "1")
|
|
|
|
|
self.add_row("Layer 2", QtGui.QColor(255, 0, 0), "Continua", "1")
|
|
|
|
|
|
2025-01-28 00:04:13 +01:00
|
|
|
path = os.path.join(os.path.dirname(FreeCAD.ActiveDocument.FileName), "outputs", "autocad")
|
|
|
|
|
if not os.path.exists(path):
|
|
|
|
|
os.makedirs(path)
|
2025-03-28 19:39:33 +06:00
|
|
|
name = datetime.now().strftime("%Y%m%d%H%M%S") + "-" + FreeCAD.ActiveDocument.Name
|
|
|
|
|
self.filename = os.path.join(path, name) + ".dxf"
|
|
|
|
|
|
|
|
|
|
def add_row(self, name, color, line_type, thickness):
|
|
|
|
|
row = self.form.tableLayers.rowCount()
|
|
|
|
|
self.form.tableLayers.insertRow(row)
|
|
|
|
|
self.form.tableLayers.setRowHeight(row, 20)
|
|
|
|
|
|
|
|
|
|
# Columna 0: Checkbox
|
|
|
|
|
checkbox = QtWidgets.QCheckBox()
|
|
|
|
|
cell_widget = QtWidgets.QWidget()
|
|
|
|
|
layout = QtWidgets.QHBoxLayout(cell_widget)
|
|
|
|
|
layout.addWidget(checkbox)
|
|
|
|
|
layout.setAlignment(QtCore.Qt.AlignCenter)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
self.form.tableLayers.setCellWidget(row, 0, cell_widget)
|
|
|
|
|
|
|
|
|
|
# Columna 1: Nombre (editable)
|
|
|
|
|
item = QtWidgets.QTableWidgetItem(name)
|
|
|
|
|
item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
|
|
|
|
|
self.form.tableLayers.setItem(row, 1, item)
|
|
|
|
|
|
|
|
|
|
# Columna 2: Selector de color
|
|
|
|
|
color_btn = QtWidgets.QPushButton()
|
|
|
|
|
color_btn.setFixedSize(20, 20) # Tamaño del cuadrado
|
|
|
|
|
color_btn.setStyleSheet(f"""
|
|
|
|
|
QPushButton {{
|
|
|
|
|
background-color: {color.name()};
|
|
|
|
|
border: 1px solid #808080;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
}}
|
|
|
|
|
QPushButton:hover {{
|
|
|
|
|
border: 2px solid #606060;
|
|
|
|
|
}}
|
|
|
|
|
""")
|
|
|
|
|
color_btn.color = color # Almacenar el color como atributo
|
|
|
|
|
color_btn.clicked.connect(lambda: self.change_color(color_btn))
|
|
|
|
|
|
|
|
|
|
# Widget contenedor para centrar el botón
|
|
|
|
|
cell_widget = QtWidgets.QWidget()
|
|
|
|
|
layout = QtWidgets.QHBoxLayout(cell_widget)
|
|
|
|
|
layout.addWidget(color_btn)
|
|
|
|
|
layout.setAlignment(QtCore.Qt.AlignCenter)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
self.form.tableLayers.setCellWidget(row, 2, cell_widget)
|
|
|
|
|
|
|
|
|
|
# Columna 3: Tipo de línea (combobox)
|
|
|
|
|
line_combo = LineTypeComboBox()
|
|
|
|
|
line_combo.addItems(["Continua", "Discontinua", "Punteada", "Mixta"])
|
|
|
|
|
line_combo.setCurrentText(line_type)
|
|
|
|
|
self.form.tableLayers.setCellWidget(row, 3, line_combo)
|
|
|
|
|
|
|
|
|
|
# Columna 4: Grosor de línea (combobox)
|
|
|
|
|
thickness_combo = QtWidgets.QComboBox()
|
|
|
|
|
thickness_combo.addItems(["1", "2", "3", "4"])
|
|
|
|
|
thickness_combo.setCurrentText(thickness)
|
|
|
|
|
self.form.tableLayers.setCellWidget(row, 4, thickness_combo)
|
|
|
|
|
|
|
|
|
|
def change_color(self, button):
|
|
|
|
|
color = QtWidgets.QColorDialog.getColor(button.color, self, "Seleccionar color")
|
|
|
|
|
if color.isValid():
|
|
|
|
|
button.color = color
|
|
|
|
|
button.setStyleSheet(f"background-color: {color.name()}")
|
2025-01-28 00:04:13 +01:00
|
|
|
|
|
|
|
|
def createLayers(self):
|
|
|
|
|
''' '''
|
|
|
|
|
self.exporter.createLayer("Areas_Boundary", layerColor=(0, 125, 125), layerLineType="FENCELINE1")
|
|
|
|
|
self.exporter.createLayer("Areas_Exclusion", layerColor=(255, 0, 0))
|
|
|
|
|
self.exporter.createLayer("Areas_Offsets", layerColor=(128, 128, 255))
|
|
|
|
|
|
|
|
|
|
self.exporter.createLayer("Internal_Roads", layerColor=(128, 128, 128))
|
|
|
|
|
self.exporter.createLayer("Internal_Roads_Axis", layerColor=(255, 255, 255), layerLineType="DASHEDX2")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def writeArea(self):
|
2025-03-28 19:39:33 +06:00
|
|
|
pol = self.exporter.createPolyline(FreeCAD.ActiveDocument.Site.Boundary)
|
2025-06-15 23:10:17 +02:00
|
|
|
if pol:
|
|
|
|
|
pol.dxf.layer = "boundary"
|
2025-03-28 19:39:33 +06:00
|
|
|
|
|
|
|
|
for area in FreeCAD.ActiveDocument.Boundaries.Group:
|
2025-01-28 00:04:13 +01:00
|
|
|
pol = self.exporter.createPolyline(area)
|
|
|
|
|
pol.dxf.layer = "Areas_Boundary"
|
|
|
|
|
|
2025-03-28 19:39:33 +06:00
|
|
|
for area in FreeCAD.ActiveDocument.Exclusions.Group:
|
2025-01-28 00:04:13 +01:00
|
|
|
pol = self.exporter.createPolyline(area)
|
|
|
|
|
pol.dxf.layer = "Areas_Exclusion"
|
|
|
|
|
|
|
|
|
|
for area in FreeCAD.ActiveDocument.Offsets.Group:
|
2025-03-28 19:39:33 +06:00
|
|
|
pol = self.exporter.createPolyline(area)
|
2025-01-28 00:04:13 +01:00
|
|
|
pol.dxf.layer = "Areas_Offsets"
|
|
|
|
|
|
|
|
|
|
def writeFrameSetups(self):
|
|
|
|
|
import Part
|
|
|
|
|
# 1. Profiles:
|
|
|
|
|
profilelist = list()
|
|
|
|
|
for ts in FreeCAD.ActiveDocument.Site.Frames:
|
|
|
|
|
for poletype in ts.PoleType:
|
|
|
|
|
if not (poletype in profilelist):
|
|
|
|
|
profilelist.append(poletype)
|
|
|
|
|
block = self.exporter.createBlock(poletype.Label)
|
|
|
|
|
w = poletype.Base.Shape.Wires[0]
|
|
|
|
|
w.Placement.Base = FreeCAD.Vector(w.Placement.Base).sub(w.BoundBox.Center)
|
|
|
|
|
block.add_lwpolyline(getWire(w))
|
|
|
|
|
|
|
|
|
|
block.add_circle((0, 0), 0.2, dxfattribs={'color': 2})
|
|
|
|
|
p = math.sin(math.radians(45)) * 0.2
|
|
|
|
|
|
|
|
|
|
block.add_line((-p, -p), (p, p), dxfattribs={"layer": "MyLines"})
|
|
|
|
|
block.add_line((-p, p), (p, -p), dxfattribs={"layer": "MyLines"})
|
|
|
|
|
|
|
|
|
|
# 2. Frames
|
|
|
|
|
for ts in FreeCAD.ActiveDocument.Site.Frames:
|
|
|
|
|
w = max(ts.Shape.SubShapes[0].SubShapes[0].SubShapes[0].Faces, key=lambda x: x.Area)
|
|
|
|
|
pts = [w.BoundBox.getPoint(i) for i in range(4)]
|
|
|
|
|
pts.append(FreeCAD.Vector(pts[0]))
|
|
|
|
|
w = Part.makePolygon(pts)
|
|
|
|
|
w.Placement.Base = w.Placement.Base.sub(w.BoundBox.Center)
|
|
|
|
|
mblockname = "Trina_TSM-DEG21C-20-6XXWp Vertex"
|
|
|
|
|
mblock = self.exporter.createBlock(mblockname)
|
|
|
|
|
mblock.add_lwpolyline(getWire(w))
|
|
|
|
|
|
|
|
|
|
rblock = self.exporter.createBlock(ts.Label)
|
|
|
|
|
w = max(ts.Shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda x: x.Placement.Base.z).Wires[0]
|
|
|
|
|
w.Placement.Base = w.Placement.Base.sub(w.BoundBox.Center)
|
|
|
|
|
rblock.add_lwpolyline(getWire(w))
|
|
|
|
|
for module in ts.Shape.SubShapes[0].SubShapes[0].SubShapes:
|
|
|
|
|
point = FreeCAD.Vector(module.BoundBox.Center) * 0.001
|
|
|
|
|
point = point[:2]
|
|
|
|
|
rblock.add_blockref(mblockname, point, dxfattribs={
|
|
|
|
|
'xscale': .0,
|
|
|
|
|
'yscale': .0,
|
|
|
|
|
'rotation': 0})
|
|
|
|
|
for ind in range(int(ts.NumberPole.Value)):
|
|
|
|
|
point = ts.Shape.SubShapes[1].SubShapes[0].SubShapes[ind].Placement.Base * 0.001
|
|
|
|
|
point = point[:2]
|
|
|
|
|
name = ts.PoleType[ts.PoleSequence[ind]].Label
|
|
|
|
|
rblock.add_blockref(name, point, dxfattribs={
|
|
|
|
|
'xscale': .0,
|
|
|
|
|
'yscale': .0,
|
|
|
|
|
'rotation': 0})
|
|
|
|
|
|
|
|
|
|
def writeFrames(self):
|
|
|
|
|
objects = findObjects('Tracker')
|
|
|
|
|
for frame in objects:
|
|
|
|
|
if hasattr(frame, "Setup"):
|
|
|
|
|
point = frame.Placement.Base * 0.001
|
|
|
|
|
point = point[:2]
|
|
|
|
|
self.exporter.insertBlock(frame.Setup.Label, point=point, rotation=frame.AngleZ)
|
|
|
|
|
|
|
|
|
|
def writeRoads(self):
|
|
|
|
|
objects = findObjects("Road")
|
|
|
|
|
#rblock = self.exporter.createBlock("Internal_roads")
|
|
|
|
|
for road in objects:
|
|
|
|
|
base = self.exporter.createPolyline(road.Base)
|
|
|
|
|
base.dxf.const_width = road.Width.Value * 0.001
|
|
|
|
|
base.dxf.layer = "Internal_Roads"
|
|
|
|
|
|
|
|
|
|
axis = self.exporter.createPolyline(road.Base)
|
|
|
|
|
axis.dxf.const_width = .2
|
|
|
|
|
axis.dxf.layer = "Internal_Roads_Axis"
|
|
|
|
|
#my_lines = doc.layers.get('MyLines')
|
|
|
|
|
|
|
|
|
|
def writeTrenches(self):
|
|
|
|
|
objects = findObjects("Trench")
|
|
|
|
|
# rblock = self.exporter.createBlock("Internal_roads")
|
|
|
|
|
for obj in objects:
|
|
|
|
|
base = self.exporter.createPolyline(obj.Base)
|
|
|
|
|
base.dxf.const_width = obj.Width.Value * 0.001
|
|
|
|
|
base.dxf.layer = "Trench"
|
|
|
|
|
|
|
|
|
|
def setup_layout4(self, doc):
|
|
|
|
|
layout2 = doc.layouts.new("scale 1-1")
|
|
|
|
|
# The default paperspace scale is 1:1
|
|
|
|
|
# 1 mm printed is 1 drawing unit in paperspace
|
|
|
|
|
# For most use cases this is the preferred scaling and important fact:
|
|
|
|
|
# the paperspace scaling has no influence on the VIEWPORT scaling - this is
|
|
|
|
|
# a total different topic, see example "viewports_in_paperspace.py"
|
|
|
|
|
|
|
|
|
|
layout2.page_setup(size=(297, 210),
|
|
|
|
|
margins=(10, 10, 10, 10),
|
|
|
|
|
units="mm",
|
|
|
|
|
scale=(1, 1),
|
|
|
|
|
#offset=(50, 50),
|
|
|
|
|
)
|
|
|
|
|
layout2.add_viewport(
|
|
|
|
|
# center of viewport in paperspace units
|
|
|
|
|
center=(100, 100),
|
|
|
|
|
# viewport size in paperspace units
|
|
|
|
|
size=(50, 50),
|
|
|
|
|
# modelspace point to show in center of viewport in WCS
|
|
|
|
|
view_center_point=(60, 40),
|
|
|
|
|
# how much modelspace area to show in viewport in drawing units
|
|
|
|
|
view_height=20,
|
|
|
|
|
#status=2,
|
|
|
|
|
)
|
|
|
|
|
lower_left, upper_right = layout2.get_paper_limits()
|
|
|
|
|
x1, y1 = lower_left
|
|
|
|
|
x2, y2 = upper_right
|
|
|
|
|
center = lower_left.lerp(upper_right)
|
|
|
|
|
|
|
|
|
|
# Add DXF entities to the "Layout1" in paperspace coordinates:
|
|
|
|
|
layout2.add_line((x1, center.y), (x2, center.y)) # horizontal center line
|
|
|
|
|
layout2.add_line((center.x, y1), (center.x, y2)) # vertical center line
|
|
|
|
|
layout2.add_circle((0, 0), radius=5) # plot origin
|
|
|
|
|
|
|
|
|
|
def onAceptClick(self):
|
|
|
|
|
self.exporter = exportDXF(self.filename)
|
|
|
|
|
if self.exporter:
|
|
|
|
|
self.exporter.createFile()
|
|
|
|
|
self.createLayers()
|
|
|
|
|
self.writeArea()
|
|
|
|
|
self.writeFrameSetups()
|
|
|
|
|
self.writeFrames()
|
|
|
|
|
self.writeRoads()
|
|
|
|
|
self.writeTrenches()
|
|
|
|
|
self.setup_layout4(self.exporter.doc)
|
|
|
|
|
|
|
|
|
|
self.exporter.save()
|
|
|
|
|
print(self.filename)
|
|
|
|
|
|
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
|
|
|
2025-03-28 19:39:33 +06:00
|
|
|
class CommandExportDXF:
|
2025-01-28 00:04:13 +01:00
|
|
|
def GetResources(self):
|
|
|
|
|
return {'Pixmap': str(os.path.join(DirIcons, "dxf.svg")),
|
|
|
|
|
'Accel': "E, A",
|
|
|
|
|
'MenuText': "Export to DXF",
|
|
|
|
|
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Export choosed layers to dxf")}
|
|
|
|
|
|
|
|
|
|
def Activated(self):
|
|
|
|
|
taskd = _PVPlantExportDXF()
|
|
|
|
|
taskd.setParent(FreeCADGui.getMainWindow())
|
|
|
|
|
taskd.setWindowFlags(QtCore.Qt.Dialog or QtCore.Qt.Dialog)
|
|
|
|
|
taskd.setWindowModality(QtCore.Qt.WindowModal)
|
|
|
|
|
taskd.show()
|
|
|
|
|
|
|
|
|
|
def IsActive(self):
|
|
|
|
|
if FreeCAD.ActiveDocument:
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
2025-03-28 19:39:33 +06:00
|
|
|
'''if FreeCAD.GuiUp:
|
|
|
|
|
FreeCADGui.addCommand('exportDXF', _CommandExportDXF())'''
|