1021 lines
39 KiB
Python
1021 lines
39 KiB
Python
import math
|
|
import FreeCAD
|
|
from Utils.PVPlantUtils import findObjects
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from PySide import QtCore, QtGui, QtWidgets
|
|
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}
|
|
|
|
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 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))
|
|
|
|
|
|
# =================================================================================
|
|
# CLASE PARA EXPORTACIÓN DXF
|
|
# =================================================================================
|
|
|
|
class exportDXF:
|
|
|
|
def __init__(self, filename):
|
|
|
|
self.doc = None
|
|
self.msp = None
|
|
self.filename = filename
|
|
self.paper_layouts = []
|
|
|
|
'''
|
|
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()
|
|
|
|
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):
|
|
try:
|
|
data = getWire(wire.Shape)
|
|
lwp = self.msp.add_lwpolyline(data)
|
|
return lwp
|
|
except Exception as e:
|
|
print("Error creating polyline:", e)
|
|
return None
|
|
|
|
# =================================================================================
|
|
# INTERFAZ DE USUARIO
|
|
# =================================================================================
|
|
class LineTypeEditorDelegate(QtWidgets.QStyledItemDelegate):
|
|
"""Delegado que muestra el combobox personalizado solo durante la edición"""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.line_styles = {
|
|
"CONTINUOUS": QtCore.Qt.SolidLine,
|
|
"DASHED": QtCore.Qt.DashLine,
|
|
"DOTTED": QtCore.Qt.DotLine,
|
|
"DASH-DOT": QtCore.Qt.DashDotLine,
|
|
"DASH-DOT-DOT": QtCore.Qt.DashDotDotLine,
|
|
"FENCELINE1": QtCore.Qt.CustomDashLine,
|
|
}
|
|
self.custom_patterns = {
|
|
"FENCELINE1": [4, 2, 1, 2]
|
|
}
|
|
|
|
# Tamaño mínimo para mostrar la previsualización
|
|
self.min_preview_width = 80
|
|
|
|
def createEditor(self, parent, option, index):
|
|
# SOLO se crea el editor cuando se inicia la edición
|
|
return LineTypeComboBox(parent)
|
|
|
|
def setEditorData(self, editor, index):
|
|
# Establecer el valor actual en el editor
|
|
value = index.data(QtCore.Qt.DisplayRole)
|
|
if value:
|
|
editor.setCurrentText(value)
|
|
|
|
def setModelData(self, editor, model, index):
|
|
# Guardar el valor seleccionado en el modelo
|
|
model.setData(index, editor.currentText(), QtCore.Qt.EditRole)
|
|
|
|
def paint0(self, painter, option, index):
|
|
"""Pintado normal de la celda (sin editor)"""
|
|
# Configurar el fondo según selección
|
|
if option.state & QtWidgets.QStyle.State_Selected:
|
|
painter.fillRect(option.rect, option.palette.highlight())
|
|
else:
|
|
painter.fillRect(option.rect, option.palette.base())
|
|
|
|
# Dibujar el texto del tipo de línea
|
|
text = index.data(QtCore.Qt.DisplayRole) or ""
|
|
painter.setPen(option.palette.text().color())
|
|
painter.drawText(option.rect.adjusted(5, 0, -5, 0),
|
|
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
|
text)
|
|
|
|
def paint1(self, painter, option, index):
|
|
# Fondo para selección
|
|
if option.state & QtWidgets.QStyle.State_Selected:
|
|
painter.fillRect(option.rect, option.palette.highlight())
|
|
else:
|
|
painter.fillRect(option.rect, option.palette.base())
|
|
|
|
# Obtener el tipo de línea
|
|
line_type = index.data(QtCore.Qt.DisplayRole) or "CONTINUOUS"
|
|
line_type = line_type.upper()
|
|
|
|
# Área de texto
|
|
text_rect = option.rect.adjusted(5, 0, -100, 0)
|
|
painter.setPen(option.palette.color(QtGui.QPalette.Text))
|
|
painter.drawText(text_rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, line_type)
|
|
|
|
# Área de previsualización
|
|
preview_rect = option.rect.adjusted(option.rect.width() - 90, 2, -5, -2)
|
|
|
|
# Configurar el estilo de línea
|
|
pen = QtGui.QPen(QtGui.QColor(0, 0, 0), 2)
|
|
|
|
if line_type in self.line_styles:
|
|
pen.setStyle(self.line_styles[line_type])
|
|
|
|
if line_type in self.custom_patterns:
|
|
pen.setDashPattern(self.custom_patterns[line_type])
|
|
else:
|
|
pen.setStyle(QtCore.Qt.SolidLine)
|
|
|
|
painter.setPen(pen)
|
|
|
|
# Dibujar la línea
|
|
center_y = preview_rect.center().y()
|
|
painter.drawLine(preview_rect.left(), center_y, preview_rect.right(), center_y)
|
|
|
|
def paint(self, painter, option, index):
|
|
# Fondo para selección
|
|
if option.state & QtWidgets.QStyle.State_Selected:
|
|
painter.fillRect(option.rect, option.palette.highlight())
|
|
else:
|
|
painter.fillRect(option.rect, option.palette.base())
|
|
|
|
# Obtener el tipo de línea
|
|
line_type = index.data(QtCore.Qt.DisplayRole) or "CONTINUOUS"
|
|
line_type = line_type.upper()
|
|
|
|
# Calcular el espacio disponible
|
|
total_width = option.rect.width()
|
|
|
|
# Área de texto (ajustable)
|
|
text_rect = option.rect.adjusted(5, 0, -5, 0)
|
|
|
|
# Área de previsualización (derecha, tamaño fijo pero adaptable)
|
|
preview_width = min(self.min_preview_width, total_width - 50) # 50px para texto mínimo
|
|
if preview_width < 20: # No mostrar previsualización si es demasiado pequeña
|
|
preview_width = 0
|
|
|
|
# Ajustar rectángulos
|
|
if preview_width > 0:
|
|
text_rect.setRight(option.rect.right() - preview_width - 10)
|
|
preview_rect = option.rect.adjusted(
|
|
option.rect.width() - preview_width,
|
|
2,
|
|
-5,
|
|
-2
|
|
)
|
|
else:
|
|
# Solo mostrar texto si no hay espacio para previsualización
|
|
preview_rect = None
|
|
|
|
# Dibujar texto del tipo de línea
|
|
painter.setPen(option.palette.color(QtGui.QPalette.Text))
|
|
painter.drawText(text_rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, line_type)
|
|
|
|
# Dibujar previsualización si hay espacio suficiente
|
|
if preview_rect and preview_width > 20:
|
|
# Configurar el estilo de línea
|
|
pen = QtGui.QPen(QtGui.QColor(0, 0, 0), 2)
|
|
if line_type in self.line_styles:
|
|
pen.setStyle(self.line_styles[line_type])
|
|
if line_type in self.custom_patterns:
|
|
pen.setDashPattern(self.custom_patterns[line_type])
|
|
else:
|
|
pen.setStyle(QtCore.Qt.SolidLine)
|
|
|
|
painter.setPen(pen)
|
|
|
|
# Dibujar la línea centrada verticalmente
|
|
center_y = preview_rect.center().y()
|
|
painter.drawLine(preview_rect.left(), center_y, preview_rect.right(), center_y)
|
|
|
|
def sizeHint(self, option, index):
|
|
return QtCore.QSize(200, 25)
|
|
|
|
|
|
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 = [
|
|
{"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):
|
|
|
|
# SOLUCIÓN AL PROBLEMA: Usar un enfoque más simple sin painter.save/restore
|
|
try:
|
|
if index.row() >= len(self.line_types):
|
|
return
|
|
|
|
# Configurar el fondo para selección
|
|
if option.state & QtGui.QStyle.State_Selected:
|
|
painter.fillRect(option.rect, option.palette.highlight())
|
|
|
|
# Determinar el tipo de línea
|
|
line_data = self.line_types[index.row()]
|
|
line_name = line_data["name"]
|
|
line_style = line_data["style"]
|
|
|
|
# Área de texto
|
|
text_rect = option.rect.adjusted(5, 0, -100, 0)
|
|
painter.setPen(option.palette.color(QtGui.QPalette.Text))
|
|
painter.drawText(text_rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, line_name)
|
|
|
|
# Área de previsualización
|
|
preview_rect = option.rect.adjusted(option.rect.width() - 90, 2, -5, -2)
|
|
|
|
# Dibujar la línea de muestra
|
|
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)
|
|
|
|
except Exception as e:
|
|
print("Error painting line type:", e)
|
|
|
|
'''
|
|
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):
|
|
"""Pintado simplificado sin QPainter complejo"""
|
|
painter = QtGui.QPainter(self)
|
|
try:
|
|
# Dibujar fondo
|
|
option = QtWidgets.QStyleOptionComboBox()
|
|
self.initStyleOption(option)
|
|
self.style().drawComplexControl(QtGui.QStyle.CC_ComboBox, option, painter, self)
|
|
|
|
# Dibujar texto
|
|
text_rect = self.rect().adjusted(5, 0, -30, 0)
|
|
painter.setPen(self.palette().color(QtGui.QPalette.Text))
|
|
painter.drawText(text_rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, self.currentText())
|
|
finally:
|
|
painter.end() # Asegurar que el painter se cierre correctamente
|
|
|
|
|
|
class _PVPlantExportDXF(QtGui.QWidget):
|
|
'''The editmode TaskPanel to select what you want to export'''
|
|
|
|
def __init__(self):
|
|
import os
|
|
QtGui.QWidget.__init__(self)
|
|
# 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)
|
|
|
|
self.layout = QtGui.QHBoxLayout(self)
|
|
self.layout.setContentsMargins(4, 4, 4, 4)
|
|
self.layout.addWidget(self.form)
|
|
|
|
self.form.buttonAcept.clicked.connect(self.onAceptClick)
|
|
self.form.buttonCancel.clicked.connect(self.onCancelClick)
|
|
|
|
self.delegate = LineTypeEditorDelegate()
|
|
self.form.tableLayers.setItemDelegateForColumn(3, self.delegate)
|
|
|
|
from datetime import datetime
|
|
import os
|
|
output_dir = os.path.join(os.path.dirname(FreeCAD.ActiveDocument.FileName), "outputs", "autocad")
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
self.filename = os.path.join(output_dir, f"{timestamp}-{FreeCAD.ActiveDocument.Name}.dxf")
|
|
|
|
# Limpiar la tabla
|
|
for row in range(self.form.tableLayers.rowCount() - 1, -1, -1):
|
|
self.form.tableLayers.removeRow(row)
|
|
|
|
# Configuración de las capas por defecto
|
|
self.add_row("Areas_Boundary", QtGui.QColor(0, 125, 125), "FENCELINE1", "1", True)
|
|
self.add_row("Areas_Exclusion", QtGui.QColor(255, 0, 0), "CONTINUOUS", "1", True)
|
|
self.add_row("Internal_Roads", QtGui.QColor(128, 128, 128), "CONTINUOUS", "1", True)
|
|
self.add_row("Frames", QtGui.QColor(0, 255, 0), "CONTINUOUS", "1", True)
|
|
|
|
def save_project_settings(self):
|
|
"""Guarda la configuración actual en el proyecto de FreeCAD"""
|
|
try:
|
|
# Guardar estado de la tabla
|
|
table_data = []
|
|
for row in range(self.form.tableLayers.rowCount()):
|
|
# Estado del checkbox
|
|
checked = self.form.tableLayers.cellWidget(row, 0).findChild(QtWidgets.QCheckBox).isChecked()
|
|
|
|
# Nombre de capa
|
|
name = self.form.tableLayers.item(row, 1).text()
|
|
|
|
# Color
|
|
color_btn = self.form.tableLayers.cellWidget(row, 2).findChild(QtWidgets.QPushButton)
|
|
color = color_btn.color
|
|
color_str = f"{color.red()},{color.green()},{color.blue()}"
|
|
|
|
# Tipo de línea
|
|
line_type = self.form.tableLayers.item(row, 3).text()
|
|
|
|
# Grosor
|
|
thickness_combo = self.form.tableLayers.cellWidget(row, 4)
|
|
thickness = thickness_combo.currentText()
|
|
|
|
table_data.append({
|
|
"name": name,
|
|
"color": color_str,
|
|
"line_type": line_type,
|
|
"thickness": thickness,
|
|
"checked": checked
|
|
})
|
|
|
|
# Crear o actualizar el objeto de configuración en el proyecto
|
|
config_obj = None
|
|
for obj in FreeCAD.ActiveDocument.Objects:
|
|
if obj.Name == "DXF_Export_Config":
|
|
config_obj = obj
|
|
break
|
|
|
|
if not config_obj:
|
|
config_obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", "DXF_Export_Config")
|
|
|
|
# Guardar como propiedad del objeto
|
|
config_obj.addProperty("App::PropertyString", "TableSettings", "DXF Export", "Table configuration")
|
|
config_obj.TableSettings = json.dumps(table_data)
|
|
|
|
print("Configuración guardada en el proyecto")
|
|
except Exception as e:
|
|
print("Error guardando configuración en el proyecto:", e)
|
|
|
|
def load_project_settings(self):
|
|
"""Carga la configuración guardada desde el proyecto de FreeCAD"""
|
|
try:
|
|
# Buscar objeto de configuración en el proyecto
|
|
config_obj = None
|
|
for obj in FreeCAD.ActiveDocument.Objects:
|
|
if obj.Name == "DXF_Export_Config":
|
|
config_obj = obj
|
|
break
|
|
|
|
if config_obj and hasattr(config_obj, "TableSettings"):
|
|
table_json = config_obj.TableSettings
|
|
if table_json:
|
|
table_data = json.loads(table_json)
|
|
self.load_table_data(table_data)
|
|
return
|
|
|
|
# Si no hay configuración guardada para este proyecto, cargar configuración por defecto
|
|
self.add_default_rows()
|
|
except Exception as e:
|
|
print("Error cargando configuración del proyecto:", e)
|
|
self.add_default_rows()
|
|
|
|
def add_default_rows(self):
|
|
"""Añade filas por defecto cuando no hay configuración guardada"""
|
|
self.add_row("Areas_Boundary", QtGui.QColor(0, 125, 125), "FENCELINE1", "1", True)
|
|
self.add_row("Areas_Exclusion", QtGui.QColor(255, 0, 0), "DASHED", "1", True)
|
|
self.add_row("Internal_Roads", QtGui.QColor(128, 128, 128), "CONTINUOUS", "1", True)
|
|
self.add_row("Frames", QtGui.QColor(0, 255, 0), "CONTINUOUS", "1", True)
|
|
|
|
def load_table_data(self, table_data):
|
|
"""Carga los datos en la tabla desde una estructura de datos"""
|
|
for layer_data in table_data:
|
|
# Parsear color
|
|
color_parts = layer_data["color"].split(",")
|
|
if len(color_parts) == 3:
|
|
color = QtGui.QColor(int(color_parts[0]), int(color_parts[1]), int(color_parts[2]))
|
|
else:
|
|
color = QtGui.QColor(0, 0, 0) # Color por defecto si hay error
|
|
|
|
# Añadir fila
|
|
self.add_row(
|
|
layer_data["name"],
|
|
color,
|
|
layer_data["line_type"],
|
|
layer_data["thickness"],
|
|
layer_data["checked"]
|
|
)
|
|
|
|
def add_row(self, name, color, line_type, thickness, checked=True):
|
|
row = self.form.tableLayers.rowCount()
|
|
self.form.tableLayers.insertRow(row)
|
|
self.form.tableLayers.setRowHeight(row, 25)
|
|
|
|
# Checkbox
|
|
checkbox = QtWidgets.QCheckBox()
|
|
checkbox.setChecked(checked)
|
|
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)
|
|
|
|
# Nombre de capa
|
|
item = QtWidgets.QTableWidgetItem(name)
|
|
item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
|
|
self.form.tableLayers.setItem(row, 1, item)
|
|
|
|
# Selector de color
|
|
color_btn = QtWidgets.QPushButton()
|
|
color_btn.setFixedSize(20, 20)
|
|
color_btn.setStyleSheet(f"background-color: {color.name()}; border: 1px solid #808080; border-radius: 3px;")
|
|
color_btn.color = color
|
|
color_btn.clicked.connect(lambda: self.change_color(color_btn))
|
|
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)
|
|
|
|
# Tipo de línea
|
|
item = QtWidgets.QTableWidgetItem(line_type)
|
|
self.form.tableLayers.setItem(row, 3, item)
|
|
|
|
# Grosor de línea
|
|
thickness_combo = QtWidgets.QComboBox()
|
|
thickness_combo.addItems(["0.1", "0.2", "0.3", "0.5", "1.0", "2.0"])
|
|
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()}; border: 1px solid #808080; border-radius: 3px;")
|
|
|
|
def createLayers(self, exporter):
|
|
for row in range(self.form.tableLayers.rowCount()):
|
|
if self.form.tableLayers.cellWidget(row, 0).findChild(QtWidgets.QCheckBox).isChecked():
|
|
layer_name = self.form.tableLayers.item(row, 1).text()
|
|
color = self.form.tableLayers.cellWidget(row, 2).findChild(QtWidgets.QPushButton).color
|
|
#line_type = self.form.tableLayers.cellWidget(row, 3).currentText()
|
|
line_type = self.form.tableLayers.item(row, 3).text()
|
|
|
|
exporter.createLayer(
|
|
layerName=layer_name,
|
|
layerColor=(color.red(), color.green(), color.blue()),
|
|
layerLineType=line_type.upper()
|
|
)
|
|
|
|
def writeArea(self, exporter):
|
|
exporter.createPolyline(FreeCAD.ActiveDocument.Site.Boundary, "boundary")
|
|
|
|
areas_types = ["Boundaries", "Exclusions", "Offsets"]
|
|
for area_type in areas_types:
|
|
if hasattr(FreeCAD.ActiveDocument, area_type):
|
|
for area in FreeCAD.ActiveDocument.Boundaries.Group:
|
|
exporter.createPolyline(area, "Areas_Boundary")
|
|
|
|
'''for area in FreeCAD.ActiveDocument.Boundaries.Group:
|
|
pol = exporter.createPolyline(area)
|
|
pol.dxf.layer = "Areas_Boundary"
|
|
|
|
for area in FreeCAD.ActiveDocument.Exclusions.Group:
|
|
pol = exporter.createPolyline(area)
|
|
pol.dxf.layer = "Areas_Exclusion"
|
|
|
|
for area in FreeCAD.ActiveDocument.Offsets.Group:
|
|
pol = exporter.createPolyline(area)
|
|
pol.dxf.layer = "Areas_Offsets"'''
|
|
|
|
def writeFrameSetups(self, exporter):
|
|
if not hasattr(FreeCAD.ActiveDocument, "Site"):
|
|
return
|
|
|
|
# 2. Postes
|
|
for ts in FreeCAD.ActiveDocument.Site.Frames:
|
|
for poletype in ts.PoleType:
|
|
block = exporter.createBlock(poletype.Label)
|
|
w = poletype.Base.Shape.Wires[0]
|
|
center = w.BoundBox.Center
|
|
w.Placement.Base = w.Placement.Base.sub(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 = exporter.createBlock(mblockname)
|
|
mblock.add_lwpolyline(getWire(w))
|
|
|
|
rblock = 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, exporter):
|
|
objects = findObjects('Tracker')
|
|
for frame in objects:
|
|
if hasattr(frame, "Setup"):
|
|
point = frame.Placement.Base * 0.001
|
|
point = point[:2]
|
|
exporter.insertBlock(frame.Setup.Label, point=point, rotation=frame.AngleZ)
|
|
|
|
def writeRoads(self, exporter):
|
|
objects = findObjects("Road")
|
|
#rblock = exporter.createBlock("Internal_roads")
|
|
for road in objects:
|
|
base = exporter.createPolyline(road.Base, "Internal_Roads")
|
|
base.dxf.const_width = road.Width.Value * 0.001
|
|
|
|
axis = exporter.createPolyline(road.Base, "Internal_Roads_Axis")
|
|
axis.dxf.const_width = .2
|
|
|
|
if FreeCAD.ActiveDocument.Transport:
|
|
for road in FreeCAD.ActiveDocument.Transport.Group:
|
|
base = exporter.createPolyline(road, "External_Roads")
|
|
base.dxf.const_width = road.Width
|
|
|
|
axis = exporter.createPolyline(road.Base, "External_Roads_Axis")
|
|
axis.dxf.const_width = .2
|
|
|
|
def writeTrenches(self, exporter):
|
|
objects = findObjects("Trench")
|
|
# rblock = exporter.createBlock("Internal_roads")
|
|
for obj in objects:
|
|
base = exporter.createPolyline(obj.Base, "Trench")
|
|
base.dxf.const_width = obj.Width.Value * 0.001
|
|
|
|
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 createPaperSpaces(self, exporter):
|
|
# Datos para el cajetín
|
|
title_block_data = {
|
|
'width': 190,
|
|
'height': 50,
|
|
'fields': [
|
|
{'name': 'Proyecto', 'value': FreeCAD.ActiveDocument.Label},
|
|
{'name': 'Escala', 'value': '1:100'},
|
|
{'name': 'Fecha', 'value': QtCore.QDate.currentDate().toString("dd/MM/yyyy")},
|
|
{'name': 'Dibujado por', 'value': os.getlogin()}
|
|
]
|
|
}
|
|
|
|
# Datos para la tabla de información
|
|
table_data = {
|
|
'Potencia pico': '50 MWp',
|
|
'Número de trackers': str(len(findObjects('Tracker'))),
|
|
'Superficie total': f"{FreeCAD.ActiveDocument.Site.Boundary.Shape.Area / 10000:.2f} ha"
|
|
}
|
|
|
|
# Datos para el viewport
|
|
viewport_data = {
|
|
'center': (150, 100),
|
|
'size': (250, 180),
|
|
'view_center': (0, 0),
|
|
'view_height': 200
|
|
}
|
|
|
|
# Crear diferentes layouts para diferentes tamaños de papel
|
|
exporter.createPaperSpaceLayout(
|
|
"Layout_A4",
|
|
(297, 210), # A4 landscape
|
|
title_block_data,
|
|
table_data,
|
|
viewport_data
|
|
)
|
|
|
|
exporter.createPaperSpaceLayout(
|
|
"Layout_A3",
|
|
(420, 297), # A3 landscape
|
|
title_block_data,
|
|
table_data,
|
|
viewport_data
|
|
)
|
|
|
|
def onAceptClick(self):
|
|
exporter = exportDXF(self.filename)
|
|
exporter.createFile()
|
|
|
|
# Crear capas configuradas
|
|
self.createLayers(exporter)
|
|
|
|
# Exportar objetos
|
|
self.writeArea(exporter)
|
|
self.writeFrameSetups(exporter)
|
|
self.writeFrames(exporter)
|
|
self.writeRoads(exporter)
|
|
self.writeTrenches(exporter)
|
|
|
|
# Crear espacios de papel
|
|
self.setup_layout4(exporter.doc)
|
|
self.createPaperSpaces(exporter)
|
|
|
|
# Guardar archivo
|
|
exporter.save()
|
|
|
|
# Mostrar mensaje de éxito
|
|
QtWidgets.QMessageBox.information(
|
|
self,
|
|
"Exportación completada",
|
|
f"Archivo DXF guardado en:\n{self.filename}"
|
|
)
|
|
|
|
self.close()
|
|
|
|
def onCancelClick(self):
|
|
# Cerrar el diálogo sin hacer nada
|
|
self.close()
|
|
|
|
class CommandExportDXF:
|
|
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
|
|
|
|
|
|
'''if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand('exportDXF', _CommandExportDXF())'''
|